Add OpenSpec system specification

This commit is contained in:
Sergey Penkovsky
2026-02-27 14:33:30 +03:00
parent e6b3017384
commit 3c550db8cd
16 changed files with 1663 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
## ADDED Requirements
### Requirement: Сущности аннотаций
Пакет аннотаций MUST предоставлять аннотации `@module`, `@provide`, `@instance`, `@singleton`, `@named`, `@params`, `@inject`, `@injectable`, `@scope`.
#### Scenario: Наличие аннотаций
- **WHEN** разработчик импортирует пакет аннотаций
- **THEN** все перечисленные аннотации доступны для использования
### Requirement: Семантика module/provide/instance/singleton
Генератор MUST трактовать аннотации `@module`, `@provide`, `@instance`, `@singleton` как источники bindings и жизненного цикла.
#### Scenario: Генерация bindings для @module
- **WHEN** класс помечен `@module` и содержит методы с `@provide`
- **THEN** сгенерированный модуль регистрирует эти методы как DIbindings
#### Scenario: Singleton vs Instance
- **WHEN** метод помечен `@singleton`
- **THEN** binding создается как singleton
- **WHEN** метод помечен `@instance` или не имеет lifecycleаннотации
- **THEN** binding создается как factory/instance
#### Scenario: Обязательность provide/instance
- **WHEN** публичный метод в `@module` не помечен `@instance` или `@provide`
- **THEN** генератор завершает сборку с ошибкой валидации
### Requirement: Инъекция полей
Генератор MUST создавать миксин для класса с `@injectable` и инъектировать все поля, помеченные `@inject`.
#### Scenario: Инъекция полей без квалификаторов
- **WHEN** поле помечено `@inject`
- **THEN** миксин вызывает resolve для типа поля
#### Scenario: Инъекция с @named и @scope
- **WHEN** поле помечено `@inject` и `@named` или `@scope`
- **THEN** миксин использует соответствующий named/scope при резолве
### Requirement: Параметры выполнения
`@params` MUST обозначать runtimeпараметры, которые передаются при резолве.
#### Scenario: Параметризованный provider
- **WHEN** providerметод имеет параметр с `@params`
- **THEN** генерируется binding с параметрами и соответствующий API резолва
#### Scenario: Запрет @params с @instance
- **WHEN** `@params` используется вместе с `@instance`
- **THEN** генератор завершает сборку с ошибкой валидации
### Requirement: Поддержка async
Генератор MUST корректно обрабатывать `Future<T>` и асинхронные зависимости.
#### Scenario: Async provider
- **WHEN** provider возвращает `Future<T>`
- **THEN** генерируется asyncbinding и используется `resolveAsync`
### Requirement: Обработка ошибок и валидация
Генератор MUST валидировать корректность применения аннотаций и сообщать об ошибках на стадии build.
#### Scenario: Некорректная цель аннотации
- **WHEN** аннотация применена к неподдерживаемому элементу
- **THEN** генератор завершает сборку с понятной ошибкой
#### Scenario: Взаимоисключающие аннотации
- **WHEN** метод помечен одновременно `@instance` и `@provide`
- **THEN** генератор завершает сборку с ошибкой валидации
#### Scenario: Требования к @named
- **WHEN** `@named` использует пустую строку или некорректный идентификатор
- **THEN** генератор завершает сборку с ошибкой валидации
#### Scenario: Валидность @module
- **WHEN** класс с `@module` не имеет публичных методов
- **THEN** генератор завершает сборку с ошибкой валидации
#### Scenario: Валидность @injectable полей
- **WHEN** поле с `@inject` не является `final`
- **THEN** генератор завершает сборку с ошибкой валидации
### Requirement: Точки расширения
Система MUST позволять расширять DIконтракт через новые модули/классы без изменения генератора, используя стандартные аннотации.
#### Scenario: Новый модуль
- **WHEN** разработчик добавляет новый класс `@module`
- **THEN** генератор автоматически включает его в DIрегистрации

View File

@@ -0,0 +1,137 @@
## ADDED Requirements
### Requirement: Сущности ядра DI
Ядро DI MUST определять сущности `Scope`, `Module`, `Binding`, `BindingResolver`, `Disposable`, `CherryPickObserver` и их роли.
#### Scenario: Декларация базовых сущностей
- **WHEN** разработчик использует публичный API ядра
- **THEN** сущности `Scope`, `Module`, `Binding`, `Disposable`, `CherryPickObserver` доступны и описывают контракт DI
### Requirement: Жизненный цикл Scope
`Scope` MUST поддерживать жизненный цикл открытия, использования и корректного закрытия с освобождением ресурсов.
#### Scenario: Открытие root scope
- **WHEN** вызывается открытие root scope
- **THEN** создается корневой контейнер для регистраций и резолва
#### Scenario: Открытие named subscope
- **WHEN** открывается именованный subscope
- **THEN** subscope наследует bindings родителя и может переопределять зависимости
#### Scenario: Закрытие subscope
- **WHEN** вызывается закрытие subscope
- **THEN** subscope удаляется из дерева, а связанные ресурсы освобождаются
#### Scenario: Путь scope и разделитель
- **WHEN** scope открывается по иерархическому пути с разделителем
- **THEN** создается цепочка subscopes по каждому сегменту пути
#### Scenario: Пустой scopeName
- **WHEN** scope открывается с пустым именем
- **THEN** возвращается root scope
### Requirement: Установка и удаление модулей
`Scope` MUST поддерживать установку и удаление `Module`, а их bindings MUST становиться доступными сразу после установки.
#### Scenario: Установка модуля
- **WHEN** модуль установлен в scope
- **THEN** его bindings доступны для resolve/tryResolve
#### Scenario: Сброс модулей
- **WHEN** модули сброшены
- **THEN** bindings модулей не доступны для резолва в этом scope
### Requirement: Типы bindings
`Binding` MUST поддерживать прямые инстансы, синхронные провайдеры, асинхронные провайдеры и провайдеры с параметрами, а также именованные bindings.
#### Scenario: Именованные bindings
- **WHEN** зарегистрированы два bindings одного типа с разными именами
- **THEN** resolve с указанным именем возвращает соответствующую реализацию
#### Scenario: Параметризованный provider
- **WHEN** binding зарегистрирован с провайдером, принимающим параметры
- **THEN** resolve с параметрами использует переданные аргументы для создания экземпляра
### Requirement: Семантика резолва и fallback
Резолв MUST искать зависимость в текущем scope и, при отсутствии, подниматься к родительским scope.
#### Scenario: Fallback к родителю
- **WHEN** зависимость отсутствует в текущем scope, но есть в родительском
- **THEN** резолв выполняется через родительский binding
### Requirement: Синхронный и асинхронный резолв
`Scope` MUST предоставлять синхронный и асинхронный резолв и их nullableварианты.
#### Scenario: Resolve (sync)
- **WHEN** вызывается `resolve<T>()` для существующей зависимости
- **THEN** возвращается экземпляр или выбрасывается ошибка при отсутствии
#### Scenario: TryResolve (sync)
- **WHEN** вызывается `tryResolve<T>()` для отсутствующей зависимости
- **THEN** возвращается `null` без исключения
#### Scenario: Resolve (async)
- **WHEN** вызывается `resolveAsync<T>()` для зависимости с asyncпровайдером
- **THEN** возвращается `Future<T>` с экземпляром
#### Scenario: TryResolve (async)
- **WHEN** вызывается `tryResolveAsync<T>()` для отсутствующей зависимости
- **THEN** возвращается `null` без исключения
### Requirement: Ошибки несоответствия sync/async
Резолв MUST выбрасывать ошибки при несоответствии sync/async режима.
#### Scenario: ResolveSync для asyncинстанса
- **WHEN** binding зарегистрирован как asyncинстанс или asyncprovider, а вызывается `resolveSync`
- **THEN** выбрасывается ошибка с указанием использовать asyncрезолв
### Requirement: Управление Disposable
`Scope` MUST отслеживать экземпляры, реализующие `Disposable`, и вызывать `dispose()` при закрытии scope.
#### Scenario: Автоматическое освобождение
- **WHEN** scope закрывается
- **THEN** все `Disposable` в scope и дочерних scope освобождаются
### Requirement: Детекция циклов зависимостей
Ядро DI MUST поддерживать локальную и глобальную детекцию циклов.
#### Scenario: Локальный цикл
- **WHEN** локальная детекция включена и резолв обнаруживает цикл
- **THEN** резолв завершается ошибкой цикла
#### Scenario: Глобальный цикл
- **WHEN** глобальная детекция включена и цикл пересекает scope
- **THEN** резолв завершается ошибкой цикла
### Requirement: Наблюдатель событий (Observer)
Ядро DI MUST предоставлять наблюдателя, получающего события регистрации, резолва, ошибок, lifecycle и диагностики.
#### Scenario: События lifecycle
- **WHEN** scope открывается, модули устанавливаются и зависимости резолвятся
- **THEN** observer получает соответствующие уведомления
### Requirement: Ошибки и сообщения об ошибках
При критических сбоях резолва ядро MUST выбрасывать ошибку с понятным сообщением, а для tryResolve MUST не бросать исключения.
#### Scenario: Ошибка отсутствующей зависимости
- **WHEN** вызывается `resolve<T>()` для незарегистрированной зависимости
- **THEN** выбрасывается ошибка с сообщением о невозможности резолва
#### Scenario: Ошибка цикла
- **WHEN** обнаружен цикл зависимостей
- **THEN** выбрасывается ошибка с указанием цепочки
#### Scenario: Отсутствующие параметры для параметризованного binding
- **WHEN** binding зарегистрирован с `toProvideWithParams`, но резолв вызывается без `params`
- **THEN** выбрасывается ошибка о необходимости передать параметры
### Requirement: Точки расширения
Ядро DI MUST предоставлять точки расширения через пользовательский `Module` и `CherryPickObserver`.
#### Scenario: Пользовательский Module
- **WHEN** разработчик создает свой `Module`
- **THEN** он может зарегистрировать bindings и контролировать регистрацию
#### Scenario: Пользовательский Observer
- **WHEN** разработчик передает кастомный observer
- **THEN** он получает все DIсобытия и может интегрировать внешние логеры/метрики

View File

@@ -0,0 +1,58 @@
## ADDED Requirements
### Requirement: Сущности Flutterинтеграции
Flutterинтеграция MUST предоставлять `CherryPickProvider` как `InheritedWidget` для доступа к DIscope в дереве виджетов.
#### Scenario: Публичная сущность провайдера
- **WHEN** разработчик импортирует Flutterпакет
- **THEN** `CherryPickProvider` доступен и может быть помещен в дерево виджетов
### Requirement: Жизненный цикл провайдера
`CherryPickProvider` MUST быть статическим проводником к scope и не владеть их жизненным циклом.
#### Scenario: Stateless поведение
- **WHEN** `CherryPickProvider` пересоздается с тем же child
- **THEN** он не инициирует изменения DIсостояния и не уведомляет зависимые виджеты
### Requirement: Доступ к root scope
Провайдер MUST предоставлять доступ к root scope через `openRootScope`.
#### Scenario: Открытие root scope
- **WHEN** вызывается `openRootScope`
- **THEN** возвращается root scope DIконтейнера
### Requirement: Доступ к subscope
Провайдер MUST предоставлять доступ к subscope через `openSubScope` с именем и разделителем.
#### Scenario: Открытие named subscope
- **WHEN** вызывается `openSubScope` с именем
- **THEN** возвращается subscope с указанным именем
#### Scenario: Пустое имя scope
- **WHEN** вызывается `openSubScope` без имени
- **THEN** возвращается root scope
### Requirement: Доступ через BuildContext
`CherryPickProvider.of(context)` MUST возвращать провайдер из ближайшего ancestor.
#### Scenario: Успешный lookup
- **WHEN** вызов происходит внутри поддерева провайдера
- **THEN** возвращается экземпляр провайдера
#### Scenario: Ошибка lookup
- **WHEN** вызов происходит вне поддерева провайдера
- **THEN** происходит assertionошибка
### Requirement: Ошибки и сообщения
При отсутствии провайдера в дереве MUST быть диагностируемая ошибка.
#### Scenario: Диагностика отсутствия провайдера
- **WHEN** `CherryPickProvider.of(context)` не находит провайдер
- **THEN** сообщение об ошибке указывает на отсутствие провайдера
### Requirement: Точки расширения
Flutterинтеграция MUST позволять использовать собственные DIscope стратегии поверх `CherryPickProvider`.
#### Scenario: Кастомная организация scope
- **WHEN** приложение использует собственные правила создания subscope
- **THEN** провайдер остается совместимым и не ограничивает стратегию

View File

@@ -0,0 +1,47 @@
## ADDED Requirements
### Requirement: Сущность адаптера логирования
Адаптер MUST предоставлять `TalkerCherryPickObserver`, реализующий `CherryPickObserver`.
#### Scenario: Доступность адаптера
- **WHEN** разработчик импортирует пакет адаптера
- **THEN** `TalkerCherryPickObserver` доступен для использования
### Requirement: Жизненный цикл наблюдателя
Наблюдатель MUST подключаться при создании scope и работать в течение жизненного цикла этого scope.
#### Scenario: Подключение наблюдателя
- **WHEN** observer передан при открытии scope
- **THEN** все DIсобытия в scope направляются в Talker
### Requirement: Маппинг уровней логирования
Адаптер MUST маршрутизировать DIсобытия в Talker с корректными уровнями (info/warning/verbose/handle).
#### Scenario: Диагностика
- **WHEN** DIядро генерирует диагностическое событие
- **THEN** адаптер логирует его как verbose
#### Scenario: Ошибка резолва
- **WHEN** DIядро генерирует ошибку
- **THEN** адаптер логирует ее через `handle` и включает stack trace при наличии
### Requirement: Ненавязчивость поведения
Адаптер MUST быть наблюдателем и не изменять DIповедение, жизненный цикл или кэширование.
#### Scenario: Разрешение с адаптером
- **WHEN** адаптер подключен
- **THEN** резолв и поведение DI не изменяются, кроме появления логов
### Requirement: Ошибки логирования
Адаптер MUST передавать ошибки логирования как есть и не содержит собственной обработки исключений.
#### Scenario: Исключение в Talker
- **WHEN** Talker выбрасывает исключение при логировании
- **THEN** исключение пробрасывается вызывающему коду
### Requirement: Точки расширения
Адаптер MUST позволять использовать пользовательские настройки Talker без изменения адаптера.
#### Scenario: Кастомная конфигурация Talker
- **WHEN** пользователь передает кастомный Talker
- **THEN** адаптер использует переданную конфигурацию для всех DIсобытий