@ds9-solutions/mcp 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/agents/code-reviewer.agent.md +52 -22
- package/content/agents/db-analyser.agent.md +121 -0
- package/content/agents/security-reviewer.agent.md +119 -37
- package/content/agents/test-writer.agent.md +84 -35
- package/content/skills/dart/SKILL.md +139 -0
- package/content/skills/dart/examples/entity.dart +37 -0
- package/content/skills/dart/examples/error_handling.dart +36 -0
- package/content/skills/dart/examples/order_test.dart +34 -0
- package/content/skills/dart/examples/repository.dart +57 -0
- package/content/skills/dart/references/checklist.md +12 -0
- package/content/skills/dart/references/documentation.md +26 -0
- package/content/skills/dart/references/naming.md +27 -0
- package/content/skills/dart-frog/SKILL.md +208 -0
- package/content/skills/dart-frog/examples/auth_middleware.dart +50 -0
- package/content/skills/dart-frog/examples/controller.dart +75 -0
- package/content/skills/dart-frog/examples/dto.dart +41 -0
- package/content/skills/dart-frog/examples/error_response.dart +35 -0
- package/content/skills/dart-frog/examples/exceptions.dart +60 -0
- package/content/skills/dart-frog/examples/middleware_composition.dart +34 -0
- package/content/skills/dart-frog/examples/model.dart +20 -0
- package/content/skills/dart-frog/examples/port.dart +17 -0
- package/content/skills/dart-frog/examples/repository.dart +56 -0
- package/content/skills/dart-frog/examples/route_collection.dart +25 -0
- package/content/skills/dart-frog/examples/route_item.dart +24 -0
- package/content/skills/dart-frog/examples/route_test.dart +33 -0
- package/content/skills/dart-frog/examples/service.dart +51 -0
- package/content/skills/dart-frog/examples/service_test.dart +55 -0
- package/content/skills/dart-frog/references/checklist.md +31 -0
- package/content/skills/dart-frog/references/error-handling.md +48 -0
- package/content/skills/dart-frog/references/fpdart-taskeither.md +53 -0
- package/content/skills/dart-frog/references/layers.md +43 -0
- package/content/skills/dart-frog/references/middlewares-di.md +45 -0
- package/content/skills/dart-frog/references/project-structure.md +67 -0
- package/content/skills/dart-frog/references/routing.md +53 -0
- package/content/skills/dart-frog/references/testing.md +24 -0
- package/content/skills/docker/SKILL.md +3 -3
- package/content/skills/driftorm/SKILL.md +249 -0
- package/content/skills/flutter/SKILL.md +419 -0
- package/content/skills/hex-pattern-dart/SKILL.md +83 -0
- package/content/skills/hex-pattern-dart/examples/adapter-in.md +24 -0
- package/content/skills/hex-pattern-dart/examples/adapter-out.md +20 -0
- package/content/skills/hex-pattern-dart/examples/errors.md +25 -0
- package/content/skills/hex-pattern-dart/examples/port.md +11 -0
- package/content/skills/hex-pattern-dart/examples/service.md +20 -0
- package/content/skills/hex-pattern-dart/examples/structure-tree.md +28 -0
- package/content/skills/hex-pattern-dart/references/anti-patterns.md +16 -0
- package/content/skills/hex-pattern-dart/references/checklist.md +34 -0
- package/content/skills/hex-pattern-dart/references/dependency-injection.md +32 -0
- package/content/skills/hex-pattern-dart/references/error-layers.md +27 -0
- package/content/skills/hex-pattern-dart/references/layers-and-dependency-rule.md +29 -0
- package/content/skills/hex-pattern-dart/references/persistence.md +23 -0
- package/content/skills/hex-pattern-dart/references/ports-and-adapters.md +33 -0
- package/content/skills/hex-pattern-dart/references/testing.md +30 -0
- package/content/skills/mysql/SKILL.md +3 -3
- package/content/skills/typescript/SKILL.md +4 -4
- package/dist/src/content.js +10 -2
- package/dist/src/init.js +1 -0
- package/dist/src/search.js +20 -12
- package/dist/src/server.js +60 -4
- package/dist/src/skill-files.js +125 -0
- package/package.json +2 -2
- package/content/agents/db-specialist.agent.md +0 -78
- package/content/agents/nestjs-api-designer.agent.md +0 -61
- package/content/skills/hexagonal/SKILL.md +0 -156
- package/content/skills/jest-testing/SKILL.md +0 -88
- package/content/skills/nestjs/SKILL.md +0 -100
- package/content/skills/typeorm/SKILL.md +0 -119
- package/dist/test/mcp.test.js +0 -112
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dart
|
|
3
|
+
description: Convenções da linguagem Dart da DS9 — orientação a objetos como abordagem padrão, nomenclatura oficial do Dart (Effective Dart), documentação com `///`, null safety, tratamento de erros, análise estática, formatação e testes. Use ao escrever ou revisar qualquer código Dart (CLI, servidor, pacote ou app Flutter), definir classes/tipos, nomear arquivos e identificadores, documentar código, ou quando o usuário pedir "boas práticas Dart", "padrão Dart", "nomenclatura Dart" ou "documentar com ///".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Dart — Diretrizes DS9
|
|
7
|
+
|
|
8
|
+
Convenções da **linguagem Dart**, válidas para qualquer projeto (CLI, servidor, pacote ou aplicativo). Regras específicas de Flutter (widgets, estado, UI) não fazem parte desta skill. Base: guia oficial *Effective Dart*, documentação do Dart e as skills Dart do projeto `lazy_forge` (`.agents/skills`). Em caso de dúvida sobre versão da linguagem, confirme a versão do SDK no `pubspec.yaml` antes de usar recursos novos.
|
|
9
|
+
|
|
10
|
+
## 1. Orientação a objetos como padrão
|
|
11
|
+
|
|
12
|
+
O código Dart da DS9 é **orientado a objetos**. Modele o domínio com classes e deixe o comportamento junto dos dados.
|
|
13
|
+
|
|
14
|
+
- **Comportamento em métodos, não em funções soltas:** lógica de negócio vive em classes (entidades, serviços, casos de uso). Funções de nível superior ficam restritas a `main()` e a pequenos utilitários verdadeiramente genéricos e sem estado; mesmo estes, prefira como métodos estáticos de uma classe coesa ou como `extension`.
|
|
15
|
+
- **Encapsulamento:** campos privados (`_campo`) com acesso por getters/métodos; exponha o mínimo necessário. Estado interno nunca é alterado de fora da classe.
|
|
16
|
+
- **Imutabilidade por padrão:** campos `final`, construtores `const` quando possível, e novas instâncias (`copyWith`/métodos que retornam um novo objeto) em vez de mutação onde o domínio pedir.
|
|
17
|
+
- **Composição sobre herança:** injete dependências pelo construtor (como `abstract interface class` ou `abstract class`) e componha objetos; use herança só para relação "é um" genuína.
|
|
18
|
+
- **Polimorfismo no lugar de `if`/`switch` por tipo:** variação de comportamento vai em subclasses/implementações, não em cadeias de `is`/`switch` espalhadas. Use `sealed class` para famílias fechadas de tipos; `switch` exaustivo sobre elas é aceitável quando o comportamento não pertence ao tipo (ex.: decodificar dados externos).
|
|
19
|
+
- **Contratos explícitos:** dependências externas (banco, rede, relógio) atrás de uma interface (`abstract interface class`), para troca e teste.
|
|
20
|
+
|
|
21
|
+
### Evite o estilo funcional como abordagem principal
|
|
22
|
+
|
|
23
|
+
Dart é multiparadigma, mas aqui o padrão é objetos. Isso **não** proíbe os recursos idiomáticos da linguagem; proíbe construir a arquitetura em torno de funções.
|
|
24
|
+
|
|
25
|
+
- Evite pipelines longos e encadeados (`map().where().fold().expand()...`) que escondem regra de negócio; se passar de 2 ou 3 etapas, extraia para um método nomeado ou use um laço `for` claro.
|
|
26
|
+
- Evite passar funções/closures como mecanismo principal de extensão (callbacks em cascata, funções de ordem superior como "estratégia"); prefira um objeto com método bem nomeado.
|
|
27
|
+
- Evite funções que retornam funções, currying e composição de funções; evite tipos como `Function` solto (use `typedef` ou interface quando um callback for inevitável).
|
|
28
|
+
- Evite `Either`/`Result` genéricos e estilos monádicos criados à mão quando exceção tipada ou um tipo de resultado `sealed` do domínio resolve de forma mais legível. **Exceção:** em projetos que seguem as skills `hex-pattern-dart`, `dart-frog` ou `flutter`, o `TaskEither` do `fpdart` (com `TaskEither.tryCatch`, `map`, `flatMap`, `mapLeft`) é o padrão obrigatório nos contratos entre camadas; ele não se estende ao domínio e aos models, que continuam orientados a objetos.
|
|
29
|
+
- Aceitável e idiomático: `for` com `collection-if`/`collection-for` em literais, `map`/`where`/`toList` simples (1 a 2 etapas), `async`/`await`, callbacks exigidos por APIs de terceiros e a composição de `TaskEither` conforme essas skills.
|
|
30
|
+
|
|
31
|
+
## 2. Nomenclatura (Effective Dart)
|
|
32
|
+
|
|
33
|
+
| Elemento | Convenção | Exemplo |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| Classes, enums, mixins, extensions, typedefs, parâmetros de tipo | `UpperCamelCase` | `OrderService`, `PaymentStatus`, `T` |
|
|
36
|
+
| Variáveis, parâmetros, campos, métodos, funções, getters | `lowerCamelCase` | `totalPrice`, `findById()` |
|
|
37
|
+
| **Constantes** (incl. `const`/`static const`/valores de enum) | `lowerCamelCase` (**não** `SCREAMING_CAPS`) | `defaultTimeout`, `maxRetries` |
|
|
38
|
+
| Pacotes, diretórios, arquivos, prefixos de import | `lowercase_with_underscores` | `order_service.dart`, `import '...' as http_client;` |
|
|
39
|
+
| Membros e tipos privados da biblioteca | prefixo `_` | `_cache`, `_OrderMapper` |
|
|
40
|
+
|
|
41
|
+
Regras adicionais:
|
|
42
|
+
|
|
43
|
+
- **Arquivo = classe principal:** `UserRepository` fica em `user_repository.dart`. Uma classe pública principal por arquivo; testes em `<nome>_test.dart`.
|
|
44
|
+
- **Siglas:** com mais de duas letras, capitalize como palavra (`HttpClient`, `JsonParser`, `UserId`); com duas letras, mantenha maiúsculas (`IOSink`).
|
|
45
|
+
- **Sem prefixos/sufixos de tipo:** não use `I` para interfaces (`IUserRepository`), notação húngara, nem `k` em constantes (`kTimeout`).
|
|
46
|
+
- **Booleanos** com nome afirmativo: `isEmpty`, `hasPermission`, `canRetry`; evite negações (`isNotValid`).
|
|
47
|
+
- **Métodos** começam com verbo (`calculateTotal`, `save`); **getters e propriedades** são substantivos, sem prefixo `get` (`total`, não `getTotal`).
|
|
48
|
+
- **Nomes descritivos:** sem abreviações obscuras nem nomes de uma letra fora de laços/lambdas curtos; o nome expressa intenção, não implementação.
|
|
49
|
+
- **Variáveis não usadas:** use `_` (wildcard) em vez de nomes sem uso.
|
|
50
|
+
- **Ordem em arquivos:** `dart:` → `package:` → relativos, cada grupo em ordem alfabética; prefira imports relativos dentro de `lib/` do próprio pacote, exceto quando a skill do framework definir outro padrão (a `flutter` usa `package:`).
|
|
51
|
+
|
|
52
|
+
## 3. Documentação e comentários (`///`)
|
|
53
|
+
|
|
54
|
+
**Regra de inclusão:** toda declaração **pública** (biblioteca, classe, enum, mixin, extension, construtor nomeado relevante, método, função, getter/setter, campo e constante) **deve** ter comentário de documentação com `///`. Declarações privadas só precisam quando a lógica não for óbvia. Código novo sem `///` nas declarações públicas é pendência de review.
|
|
55
|
+
|
|
56
|
+
- Use **sempre `///`**, em linhas consecutivas; **nunca** `/** ... */`. Use `//` apenas para comentários de implementação internos, explicando o **porquê**, nunca o óbvio.
|
|
57
|
+
- **Primeira linha:** uma frase única e resumida, terminada em ponto, seguida de uma linha `///` vazia antes do restante. Uma linha totalmente em branco (sem `///`) encerra o comentário.
|
|
58
|
+
- **Abertura por tipo de membro:** propriedades/getters com sintagma nominal (`/// O raio da esfera.`); booleanos com "Se"/"Whether" no idioma do projeto (`/// Se a conexão está ativa.`); métodos com verbo na 3ª pessoa (`/// Inicializa o banco.`).
|
|
59
|
+
- **Sem redundância:** não repita a assinatura nem o nome ("Esta classe é...", "O método foo faz...").
|
|
60
|
+
- **Sem tags estilo Javadoc** (`@param`, `@return`, `@throws`): descreva parâmetros, retorno e exceções em prosa, referenciando parâmetros com `[nome]`.
|
|
61
|
+
- **Links:** use colchetes para símbolos no escopo (`[Order]`, `[Order.total]`, sem parênteses); construtor sem nome como `[Order.new]`; crases para palavras-chave e literais (`` `null` ``, `` `true` ``). Blocos de código com a linguagem (` ```dart `).
|
|
62
|
+
- **Posição:** o comentário `///` vem **antes** das anotações (`@override` etc.).
|
|
63
|
+
- **Herança e pares getter/setter:** não duplique documentação em `@override` sem mudança de comportamento; em par getter/setter, documente só o getter.
|
|
64
|
+
- **Idioma:** siga o idioma já usado no projeto, de forma consistente.
|
|
65
|
+
- Valide com `dart analyze` (referências entre colchetes resolvidas) e, opcionalmente, `dart doc`.
|
|
66
|
+
|
|
67
|
+
```dart
|
|
68
|
+
// Evitar
|
|
69
|
+
/**
|
|
70
|
+
* Busca o pedido.
|
|
71
|
+
* @param id identificador
|
|
72
|
+
* @return o pedido
|
|
73
|
+
*/
|
|
74
|
+
Order find(String id) { ... }
|
|
75
|
+
|
|
76
|
+
// Preferir
|
|
77
|
+
/// Busca o pedido pelo [id].
|
|
78
|
+
///
|
|
79
|
+
/// Lança [OrderNotFoundException] se não existir pedido com esse identificador.
|
|
80
|
+
Order findById(String id) { ... }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 4. Construtores e classes
|
|
84
|
+
|
|
85
|
+
- Prefira **parâmetros iniciais** (`this.campo`) e `super.parametro` a atribuições manuais; use `required` em parâmetros nomeados obrigatórios.
|
|
86
|
+
- Construtores `const` em classes imutáveis; `factory` para validação/cache e para criar a partir de dados externos (`fromJson`).
|
|
87
|
+
- **Invariantes no construtor/factory:** estado inválido não pode existir; valide e lance exceção de domínio.
|
|
88
|
+
- **Primary constructors** (declarar campos no cabeçalho da classe): disponíveis só a partir do Dart 3.13 (experimental no 3.12, via flag). Use **somente se o SDK do projeto suportar**; caso contrário, use construtores tradicionais. Não migre código existente apenas por isso.
|
|
89
|
+
- Use `sealed`, `final`, `base` e `interface` para controlar quem pode estender ou implementar uma classe, quando a API for pública.
|
|
90
|
+
|
|
91
|
+
## 5. Null safety e tipos
|
|
92
|
+
|
|
93
|
+
- Nada de `dynamic` em código novo; tipos explícitos em APIs públicas, em genéricos (`List<Order>`) e em retornos.
|
|
94
|
+
- Evite `!` (asserção de nulo); prefira promoção de tipo, `?.`, `??` ou tratar o nulo. `late` só quando a inicialização estiver garantida antes do uso.
|
|
95
|
+
- Prefira `final` a `var` quando o valor não muda; evite `as` sem garantia do tipo em tempo de execução.
|
|
96
|
+
- **Pattern matching e records** (Dart 3): use para decodificar dados externos (JSON) e `switch` exaustivo em `sealed`; não use para o que um `is` simples, um acesso a propriedade ou uma comparação booleana resolvem. Dados malformados devem falhar com erro explícito (`FormatException`), nunca ser descartados em silêncio.
|
|
97
|
+
|
|
98
|
+
## 6. Tratamento de erros
|
|
99
|
+
|
|
100
|
+
- Falhas recuperáveis: **`Exception` tipada do domínio** (`class OrderNotFoundException implements Exception`), nunca `throw 'texto'` nem lançar `Exception` genérica. Em projetos `hex-pattern-dart`/`dart-frog`, as falhas trafegam como `Left` de `TaskEither` com as exceções por camada definidas em `hex-pattern-dart` (em Flutter, siga a skill `flutter`) (`BusinessException`, `ClientException`, `MiddlewareException`).
|
|
101
|
+
- **Nunca capture `Error`** (nem `TypeError`, `ArgumentError`, etc.): indicam bugs de programação. Capture tipos específicos; não use `catch (e)` amplo sem relançar.
|
|
102
|
+
- Use `rethrow` para propagar preservando o stack trace; não engula exceções em silêncio nem registre dados sensíveis.
|
|
103
|
+
- Mensagens de erro expostas a usuários não vazam detalhes internos.
|
|
104
|
+
|
|
105
|
+
## 7. Assincronismo
|
|
106
|
+
|
|
107
|
+
- `async`/`await` em vez de encadear `.then()`; sempre aguarde (`await`) ou trate explicitamente os `Future`s (sem "fire-and-forget" acidental).
|
|
108
|
+
- Libere recursos (`close`/`cancel`/`dispose`) em `try/finally`; cancele subscriptions e controllers de `Stream`.
|
|
109
|
+
- Não bloqueie o isolate principal com trabalho pesado síncrono; use isolates quando necessário.
|
|
110
|
+
|
|
111
|
+
## 8. Qualidade, formatação e testes
|
|
112
|
+
|
|
113
|
+
- **Análise estática:** `analysis_options.yaml` na raiz do pacote com um conjunto base (`package:lints/recommended.yaml` ou `package:flutter_lints/flutter.yaml`) e `strict-casts`, `strict-inference` e `strict-raw-types` ativos. Exclua só código gerado (`**/*.g.dart`).
|
|
114
|
+
- Rode `dart analyze` e corrija os avisos; use `dart fix --apply` para correções mecânicas (revise com `--dry-run`) e `dart format .` antes de commitar. Suprima diagnósticos (`// ignore:`) só com justificativa e no menor escopo.
|
|
115
|
+
- **Testes:** `package:test` (ou `flutter_test` em Flutter), arquivos em `test/` espelhando `lib/` com sufixo `_test.dart`; `group()` por funcionalidade, `setUp()`/`tearDown()` para preparo, `expect()` com matchers; dependências externas isoladas por interface/mocks. Para criar e executar testes, use o agente `test-writer`.
|
|
116
|
+
|
|
117
|
+
## 9. Checklist de review
|
|
118
|
+
|
|
119
|
+
- [ ] Regra de negócio está em classes, sem funções soltas nem cadeias funcionais longas?
|
|
120
|
+
- [ ] Toda declaração pública tem `///` no formato da seção 3?
|
|
121
|
+
- [ ] Nomes seguem a tabela da seção 2 (inclusive constantes em `lowerCamelCase` e arquivos em `snake_case`)?
|
|
122
|
+
- [ ] Sem `dynamic`, `!` desnecessário ou `catch` de `Error`?
|
|
123
|
+
- [ ] Campos `final`, encapsulamento e construtores `const` onde cabem?
|
|
124
|
+
- [ ] `dart analyze` limpo, `dart format` aplicado e testes cobrindo o comportamento novo?
|
|
125
|
+
|
|
126
|
+
## 10. Arquivos complementares
|
|
127
|
+
|
|
128
|
+
Em caso de dúvida, consulte (via `list_skill_files` / `get_skill_file` com `skill="dart"`; ou direto na pasta `dart/`, se o host não tiver as tools):
|
|
129
|
+
|
|
130
|
+
- `examples/` — código de referência: `entity.dart`, `repository.dart`, `error_handling.dart`, `order_test.dart`. Use quando precisar de um exemplo de entidade, contrato/caso de uso, erros ou teste.
|
|
131
|
+
- `references/` — `naming.md` (nomenclatura), `documentation.md` (`///`), `checklist.md` (review detalhado).
|
|
132
|
+
|
|
133
|
+
## Não fazer
|
|
134
|
+
|
|
135
|
+
- Não usar `/** ... */` nem tags `@param`/`@return`/`@throws`.
|
|
136
|
+
- Não usar `SCREAMING_CAPS`, prefixo `I`, `k` ou notação húngara.
|
|
137
|
+
- Não criar funções de nível superior com regra de negócio nem pipelines funcionais extensos (exceto a composição de `TaskEither` exigida pelas skills `hex-pattern-dart`, `dart-frog` e `flutter`).
|
|
138
|
+
- Não declarar `dynamic`, `Function` solto ou `throw` de strings.
|
|
139
|
+
- Não duplicar regras de outras skills; para testes, ver o agente `test-writer`.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/// Exemplo: entidade imutável, encapsulada e documentada com `///`.
|
|
2
|
+
library;
|
|
3
|
+
|
|
4
|
+
/// Status possíveis de um [Order].
|
|
5
|
+
enum OrderStatus {
|
|
6
|
+
/// Pedido criado e ainda não pago.
|
|
7
|
+
pending,
|
|
8
|
+
|
|
9
|
+
/// Pedido pago.
|
|
10
|
+
paid,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/// Pedido do domínio, com invariantes validadas na criação.
|
|
14
|
+
class Order {
|
|
15
|
+
/// Cria um pedido.
|
|
16
|
+
///
|
|
17
|
+
/// Lança [ArgumentError] se [id] estiver vazio ou [total] for negativo.
|
|
18
|
+
Order({required this.id, required this.total, this.status = OrderStatus.pending}) {
|
|
19
|
+
if (id.isEmpty) throw ArgumentError.value(id, 'id', 'não pode ser vazio');
|
|
20
|
+
if (total < 0) throw ArgumentError.value(total, 'total', 'não pode ser negativo');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Identificador único do pedido.
|
|
24
|
+
final String id;
|
|
25
|
+
|
|
26
|
+
/// Valor total do pedido.
|
|
27
|
+
final double total;
|
|
28
|
+
|
|
29
|
+
/// Situação atual do pedido.
|
|
30
|
+
final OrderStatus status;
|
|
31
|
+
|
|
32
|
+
/// Se o pedido já foi pago.
|
|
33
|
+
bool get isPaid => status == OrderStatus.paid;
|
|
34
|
+
|
|
35
|
+
/// Retorna uma cópia deste pedido já marcada como paga.
|
|
36
|
+
Order markAsPaid() => Order(id: id, total: total, status: OrderStatus.paid);
|
|
37
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/// Exemplo: exceções tipadas e tratamento sem engolir erros.
|
|
2
|
+
library;
|
|
3
|
+
|
|
4
|
+
/// Falha ao decodificar dados externos.
|
|
5
|
+
class InvalidPayloadException implements Exception {
|
|
6
|
+
/// Cria a exceção com a [message] explicando o problema.
|
|
7
|
+
const InvalidPayloadException(this.message);
|
|
8
|
+
|
|
9
|
+
/// Descrição do problema, segura para exibir.
|
|
10
|
+
final String message;
|
|
11
|
+
|
|
12
|
+
@override
|
|
13
|
+
String toString() => 'InvalidPayloadException: $message';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// Lê um valor numérico de um payload externo.
|
|
17
|
+
class PayloadReader {
|
|
18
|
+
/// Lê o campo `amount` de [json].
|
|
19
|
+
///
|
|
20
|
+
/// Lança [InvalidPayloadException] se ele não existir ou não for numérico.
|
|
21
|
+
double readAmount(Map<String, Object?> json) {
|
|
22
|
+
return switch (json['amount']) {
|
|
23
|
+
final num amount => amount.toDouble(),
|
|
24
|
+
_ => throw const InvalidPayloadException('campo "amount" ausente ou inválido'),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Lê o valor e propaga falhas de domínio preservando o stack trace.
|
|
29
|
+
double readOrRethrow(Map<String, Object?> json) {
|
|
30
|
+
try {
|
|
31
|
+
return readAmount(json);
|
|
32
|
+
} on InvalidPayloadException {
|
|
33
|
+
rethrow; // nunca `catch (e)` amplo nem captura de `Error`
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/// Exemplo: teste unitário com `group`, `setUp` e `expect`.
|
|
2
|
+
///
|
|
3
|
+
/// Em um projeto real o arquivo fica em `test/`, espelhando `lib/`.
|
|
4
|
+
library;
|
|
5
|
+
|
|
6
|
+
import 'package:test/test.dart';
|
|
7
|
+
|
|
8
|
+
import 'entity.dart';
|
|
9
|
+
import 'repository.dart';
|
|
10
|
+
|
|
11
|
+
void main() {
|
|
12
|
+
group('PayOrder', () {
|
|
13
|
+
late InMemoryOrderRepository repository;
|
|
14
|
+
late PayOrder useCase;
|
|
15
|
+
|
|
16
|
+
setUp(() {
|
|
17
|
+
repository = InMemoryOrderRepository();
|
|
18
|
+
useCase = PayOrder(repository);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('marca o pedido como pago e persiste', () async {
|
|
22
|
+
await repository.save(Order(id: '1', total: 10));
|
|
23
|
+
|
|
24
|
+
final paid = await useCase.execute('1');
|
|
25
|
+
|
|
26
|
+
expect(paid.isPaid, isTrue);
|
|
27
|
+
expect((await repository.findById('1'))!.isPaid, isTrue);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('lança OrderNotFoundException quando o pedido não existe', () {
|
|
31
|
+
expect(() => useCase.execute('x'), throwsA(isA<OrderNotFoundException>()));
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/// Exemplo: contrato (porta) e implementação, com dependência injetada.
|
|
2
|
+
library;
|
|
3
|
+
|
|
4
|
+
import 'entity.dart';
|
|
5
|
+
|
|
6
|
+
/// Contrato de persistência de pedidos.
|
|
7
|
+
abstract interface class OrderRepository {
|
|
8
|
+
/// Busca o pedido pelo [id]; retorna `null` se não existir.
|
|
9
|
+
Future<Order?> findById(String id);
|
|
10
|
+
|
|
11
|
+
/// Salva o [order].
|
|
12
|
+
Future<void> save(Order order);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// Implementação em memória, útil em testes.
|
|
16
|
+
class InMemoryOrderRepository implements OrderRepository {
|
|
17
|
+
final Map<String, Order> _orders = {};
|
|
18
|
+
|
|
19
|
+
@override
|
|
20
|
+
Future<Order?> findById(String id) async => _orders[id];
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
Future<void> save(Order order) async {
|
|
24
|
+
_orders[order.id] = order;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Caso de uso: paga um pedido existente.
|
|
29
|
+
class PayOrder {
|
|
30
|
+
/// Cria o caso de uso com o [repository] injetado.
|
|
31
|
+
PayOrder(this._repository);
|
|
32
|
+
|
|
33
|
+
final OrderRepository _repository;
|
|
34
|
+
|
|
35
|
+
/// Marca como pago o pedido [id].
|
|
36
|
+
///
|
|
37
|
+
/// Lança [OrderNotFoundException] se o pedido não existir.
|
|
38
|
+
Future<Order> execute(String id) async {
|
|
39
|
+
final order = await _repository.findById(id);
|
|
40
|
+
if (order == null) throw OrderNotFoundException(id);
|
|
41
|
+
final paid = order.markAsPaid();
|
|
42
|
+
await _repository.save(paid);
|
|
43
|
+
return paid;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Erro de domínio: pedido não encontrado.
|
|
48
|
+
class OrderNotFoundException implements Exception {
|
|
49
|
+
/// Cria a exceção para o pedido [id].
|
|
50
|
+
const OrderNotFoundException(this.id);
|
|
51
|
+
|
|
52
|
+
/// Identificador procurado.
|
|
53
|
+
final String id;
|
|
54
|
+
|
|
55
|
+
@override
|
|
56
|
+
String toString() => 'Pedido não encontrado: $id';
|
|
57
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Dart — Checklist de review detalhado
|
|
2
|
+
|
|
3
|
+
- [ ] Regra de negócio em classes; sem funções soltas nem pipelines longos.
|
|
4
|
+
- [ ] Dependências externas atrás de `abstract interface class`, injetadas no construtor.
|
|
5
|
+
- [ ] Campos `final`, construtores `const` onde couber, estado privado encapsulado.
|
|
6
|
+
- [ ] Toda declaração pública com `///` (sem `/** */`, sem `@param`).
|
|
7
|
+
- [ ] Nomes: `UpperCamelCase` (tipos), `lowerCamelCase` (membros e constantes), `snake_case` (arquivos).
|
|
8
|
+
- [ ] Sem `dynamic`, `!` desnecessário, `Function` solto ou `throw` de string.
|
|
9
|
+
- [ ] Sem `catch` de `Error`; `rethrow` preserva o stack trace.
|
|
10
|
+
- [ ] `Future`s aguardados; recursos liberados em `try/finally`.
|
|
11
|
+
- [ ] `dart analyze` limpo, `dart format` aplicado.
|
|
12
|
+
- [ ] Testes em `test/` espelhando `lib/`, com `group`/`setUp`/`expect`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Dart — Documentação com `///` (referência detalhada)
|
|
2
|
+
|
|
3
|
+
Complementa a seção 3 do `SKILL.md`.
|
|
4
|
+
|
|
5
|
+
## Modelo
|
|
6
|
+
|
|
7
|
+
```dart
|
|
8
|
+
/// Calcula o total do pedido.
|
|
9
|
+
///
|
|
10
|
+
/// Soma os itens de [items] e aplica [discount] (entre 0 e 1).
|
|
11
|
+
/// Lança [ArgumentError] se [discount] estiver fora do intervalo.
|
|
12
|
+
double calculateTotal(List<Item> items, {double discount = 0}) { ... }
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Regras rápidas
|
|
16
|
+
|
|
17
|
+
- Primeira linha: frase única, terminada em ponto; depois linha `///` vazia.
|
|
18
|
+
- Propriedade: sintagma nominal. Booleano: "Se ...". Método: verbo na 3ª pessoa.
|
|
19
|
+
- Sem `@param`/`@return`; use prosa e `[nome]`.
|
|
20
|
+
- `///` antes de `@override` e outras anotações.
|
|
21
|
+
- Par getter/setter: documente só o getter.
|
|
22
|
+
- Não documente `@override` sem mudança de comportamento.
|
|
23
|
+
|
|
24
|
+
## Validação
|
|
25
|
+
|
|
26
|
+
`dart analyze` resolve referências entre colchetes; `dart doc` gera a documentação.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Dart — Nomenclatura (referência detalhada)
|
|
2
|
+
|
|
3
|
+
Complementa a seção 2 do `SKILL.md`.
|
|
4
|
+
|
|
5
|
+
## Certo x errado
|
|
6
|
+
|
|
7
|
+
| Evitar | Preferir | Motivo |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `MAX_RETRIES`, `kMaxRetries` | `maxRetries` | Constantes usam `lowerCamelCase` |
|
|
10
|
+
| `IUserRepository` | `UserRepository` | Sem prefixo `I` |
|
|
11
|
+
| `UserRepository.dart` | `user_repository.dart` | Arquivos em `snake_case` |
|
|
12
|
+
| `getTotal()` | `total` (getter) | Propriedade é substantivo, sem `get` |
|
|
13
|
+
| `isNotValid` | `isValid` | Booleano afirmativo |
|
|
14
|
+
| `HTTPClient` | `HttpClient` | Siglas com mais de 2 letras viram palavra |
|
|
15
|
+
| `data`, `tmp`, `x` | `orderTotals` | Nome expressa intenção |
|
|
16
|
+
|
|
17
|
+
## Ordem de imports
|
|
18
|
+
|
|
19
|
+
1. `dart:`
|
|
20
|
+
2. `package:`
|
|
21
|
+
3. relativos
|
|
22
|
+
|
|
23
|
+
Cada grupo em ordem alfabética, separado por linha em branco.
|
|
24
|
+
|
|
25
|
+
## Privacidade
|
|
26
|
+
|
|
27
|
+
O prefixo `_` torna o membro privado à **biblioteca** (arquivo), não à classe.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dart-frog
|
|
3
|
+
description: Diretrizes DS9 para backends com Dart Frog em arquitetura hexagonal (domain/adapter por contexto) — rotas finas com 405 obrigatório, injeção de dependência exclusivamente por middleware, `fpdart` com `TaskEither`/`TaskEither.tryCatch` como retorno entre camadas e exceções por camada (BusinessException, ClientException, MiddlewareException). Persistência é agnóstica (use a skill de persistência do projeto). Use ao criar, editar, revisar ou testar rotas, `_middleware.dart`, controllers, services, ports, repositories ou DTOs em um projeto Dart Frog (pasta `routes/`, comandos `dart_frog dev/build`), ou quando o usuário pedir "backend Dart", "API Dart Frog" ou "arquitetura hexagonal em Dart".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Dart Frog — Diretrizes DS9
|
|
7
|
+
|
|
8
|
+
Padrão DS9 para APIs em **Dart Frog** com **arquitetura hexagonal** (ports & adapters), organizada por contexto de negócio. Complementa as skills `dart` (nomenclatura, `///`, OOP, null safety) e `hex-pattern-dart` (camadas, ports/adapters, erros): siga as três. A persistência é **agnóstica** aqui; para o banco/ORM do projeto, consulte a skill de persistência correspondente (`list_skills` / `recommend_for_task`).
|
|
9
|
+
|
|
10
|
+
## 0. Pré-requisitos (verifique antes de gerar código)
|
|
11
|
+
|
|
12
|
+
1. Confirme no `pubspec.yaml` do projeto as dependências `dart_frog` e **`fpdart`**.
|
|
13
|
+
2. **Se `fpdart` não estiver instalado, informe ao usuário que ele é obrigatório para seguir o padrão DS9** e peça autorização para instalar (`dart pub add fpdart`). Não gere código fora do padrão para contornar a ausência.
|
|
14
|
+
3. Use as versões já fixadas no `pubspec.yaml`; não atualize dependências sem pedido explícito.
|
|
15
|
+
4. Preserve a estrutura existente em projetos pré-existentes; aplique o padrão integralmente em código novo e no que for explicitamente solicitado, sem refatorar o restante.
|
|
16
|
+
|
|
17
|
+
## 1. Regras inegociáveis
|
|
18
|
+
|
|
19
|
+
1. **`fpdart` + `TaskEither`:** todo contrato de camada (port, service, repository, controller) que pode falhar **retorna `TaskEither<Falha, T>`**, e a operação assíncrona é envolvida com **`TaskEither.tryCatch`**. Nunca lance exceção para fora de uma camada nem converta falha em sucesso vazio.
|
|
20
|
+
2. **Injeção só por middleware:** dependências são compostas em `_middleware.dart` com `provider<T>` e lidas com `context.read<T>()`. Nada de instanciar service/repository/controller no handler, sem service locator, sem singleton global fora do middleware.
|
|
21
|
+
3. **405 sempre:** todo handler faz `switch` em `context.request.method` e termina com `case _: return Response(statusCode: HttpStatus.methodNotAllowed);`.
|
|
22
|
+
4. **Camadas de erro fixas:** `BusinessException` (service/repository), `ClientException` (controller), `MiddlewareException` (middleware). Nomes e responsabilidades não mudam entre projetos.
|
|
23
|
+
5. **Persistência agnóstica:** o domínio só conhece **ports**; o acesso a dados fica em `adapter/repository`, sem citar ORM/driver nesta skill.
|
|
24
|
+
|
|
25
|
+
## 2. Estrutura
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
<projeto>/
|
|
29
|
+
├── routes/ # roteador (arquivos = rotas); só despacho HTTP
|
|
30
|
+
│ ├── _middleware.dart # raiz: CORS, infraestrutura compartilhada
|
|
31
|
+
│ ├── api/_middleware.dart # autenticação do grupo
|
|
32
|
+
│ └── api/orders/
|
|
33
|
+
│ ├── _middleware.dart # composição do contexto (providers)
|
|
34
|
+
│ ├── index.dart # /api/orders
|
|
35
|
+
│ └── [id].dart # /api/orders/:id
|
|
36
|
+
├── lib/
|
|
37
|
+
│ ├── <contexto>/
|
|
38
|
+
│ │ ├── domain/{models,ports,services}/
|
|
39
|
+
│ │ └── adapter/{controllers,dtos,repository}/
|
|
40
|
+
│ └── common/{exception,http,middleware,environment,logger,models}/
|
|
41
|
+
├── test/ # espelha lib/ e routes/
|
|
42
|
+
└── main.dart # só init() e run()
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Estrutura completa e variações (`application/use_case`, adapters de integração): `references/project-structure.md`.
|
|
46
|
+
|
|
47
|
+
## 3. Camadas e regra de dependência
|
|
48
|
+
|
|
49
|
+
| Camada | Responsabilidade | Retorno | Falha |
|
|
50
|
+
|---|---|---|---|
|
|
51
|
+
| `routes/**` | despachar método HTTP, ler `controller` do contexto, converter resultado em `Response` | `Future<Response>` | converte `Left` em JSON de erro |
|
|
52
|
+
| `adapter/controllers` | ler body/query/path, validar formato (DTO), chamar service, converter modelo → DTO | `TaskEither<ClientException, T>` | `ClientException` |
|
|
53
|
+
| `domain/services` | regras de negócio, autorização de domínio, composição de ports | `TaskEither<BusinessException, T>` | `BusinessException` |
|
|
54
|
+
| `domain/ports` | contratos (`abstract interface class`) | `TaskEither<BusinessException, T>` | `BusinessException` |
|
|
55
|
+
| `adapter/repository` | implementa o port e acessa a persistência | `TaskEither<BusinessException, T>` | `BusinessException` |
|
|
56
|
+
| `middlewares` | autenticar, compor providers, encerrar com erro | `Handler` | `MiddlewareException` |
|
|
57
|
+
|
|
58
|
+
Dependências apontam para dentro: `routes → controller → service → port ← repository`.
|
|
59
|
+
|
|
60
|
+
- `domain/` **nunca** importa `dart_frog`, SDK, driver/ORM, `adapter/`, `RequestContext` nem `Response`. Pode importar `fpdart`, `equatable`, `json_annotation` e `common/exception`.
|
|
61
|
+
- Contextos se comunicam por **port ou service público**, nunca pelo repository concreto de outro contexto, sem dependência circular.
|
|
62
|
+
- Mapeamento: linha de banco → modelo no repository; modelo → DTO no controller/DTO. Modelos de domínio não conhecem DTOs nem classes geradas.
|
|
63
|
+
|
|
64
|
+
Detalhes e responsabilidades por camada: `references/layers.md`.
|
|
65
|
+
|
|
66
|
+
## 4. `TaskEither` e `tryCatch`
|
|
67
|
+
|
|
68
|
+
```dart
|
|
69
|
+
// repository: toda chamada que pode falhar entra em tryCatch
|
|
70
|
+
TaskEither<BusinessException, Order?> findById(String id) {
|
|
71
|
+
return TaskEither.tryCatch(
|
|
72
|
+
() => _dataSource.findById(id),
|
|
73
|
+
(error, stackTrace) => BusinessException(
|
|
74
|
+
domainName: 'order',
|
|
75
|
+
statusCode: HttpStatus.internalServerError,
|
|
76
|
+
message: 'Falha ao consultar o pedido.',
|
|
77
|
+
error: error,
|
|
78
|
+
stackTrace: stackTrace,
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// service: compõe com flatMap/map/mapLeft; regra inválida vira TaskEither.left
|
|
84
|
+
TaskEither<BusinessException, Order> getById(String id) =>
|
|
85
|
+
_repository.findById(id).flatMap(
|
|
86
|
+
(order) => order == null
|
|
87
|
+
? TaskEither.left(_notFound(id))
|
|
88
|
+
: TaskEither.right(order),
|
|
89
|
+
);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- Prefira `TaskEither.tryCatch`, `map`, `flatMap`, `mapLeft`; execute (`.run()`) **só na rota**.
|
|
93
|
+
- Não use `Exception` genérica como falha; não capture `Error`; não use `catch (_)` amplo.
|
|
94
|
+
- Encadeamentos de `TaskEither` são a exceção funcional aceita da skill `dart`; fora desse contrato, continue em OOP.
|
|
95
|
+
|
|
96
|
+
Guia completo: `references/fpdart-taskeither.md`.
|
|
97
|
+
|
|
98
|
+
## 5. Erros por camada
|
|
99
|
+
|
|
100
|
+
| Tipo | Camada | Campos |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| `BusinessException` | service, repository | `domainName`, `statusCode`, `message` (seguro para o cliente), `error` (causa original, nunca exposta), `stackTrace` |
|
|
103
|
+
| `ClientException` | controller | `statusCode`, `message` |
|
|
104
|
+
| `MiddlewareException` | middleware | `statusCode`, `message`, `stackTrace?` |
|
|
105
|
+
|
|
106
|
+
- O controller converte `BusinessException` em `ClientException` preservando o `statusCode`; para `5xx`, use mensagem genérica.
|
|
107
|
+
- Formato único de erro: `{"error": {"code": "...", "message": "..."}}`.
|
|
108
|
+
- **Nunca** envie stack trace, SQL, causa original (`error`), caminho de arquivo ou detalhe de SDK ao cliente; registre no log sem dados sensíveis.
|
|
109
|
+
|
|
110
|
+
Status HTTP, formato e exemplos: `references/error-handling.md`.
|
|
111
|
+
|
|
112
|
+
## 6. Rotas (sempre 405)
|
|
113
|
+
|
|
114
|
+
```dart
|
|
115
|
+
Future<Response> onRequest(RequestContext context) async {
|
|
116
|
+
final controller = context.read<OrderController>();
|
|
117
|
+
|
|
118
|
+
switch (context.request.method) {
|
|
119
|
+
case HttpMethod.post:
|
|
120
|
+
final result = await controller.create(context).run();
|
|
121
|
+
return result.match(
|
|
122
|
+
ErrorResponse.fromClient,
|
|
123
|
+
(order) => Response.json(statusCode: HttpStatus.created, body: order.toJson()),
|
|
124
|
+
);
|
|
125
|
+
case _:
|
|
126
|
+
return Response(statusCode: HttpStatus.methodNotAllowed);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
- Rota fina: sem regra de negócio, query, SDK ou instanciação de dependência.
|
|
132
|
+
- `index.dart` para o recurso, `[id].dart` para parâmetro (`onRequest(RequestContext context, String id)`); sem conflito entre `api.dart` e `api/index.dart`.
|
|
133
|
+
- Use `HttpStatus` de `dart:io` em vez de números mágicos.
|
|
134
|
+
|
|
135
|
+
Convenções do roteador, CLI e catálogo de grupos: `references/routing.md`.
|
|
136
|
+
|
|
137
|
+
## 7. Injeção de dependência por middleware
|
|
138
|
+
|
|
139
|
+
```dart
|
|
140
|
+
// routes/api/orders/_middleware.dart
|
|
141
|
+
OrderRepositoryPort? _repository;
|
|
142
|
+
OrderService? _service;
|
|
143
|
+
OrderController? _controller;
|
|
144
|
+
|
|
145
|
+
Handler middleware(Handler handler) {
|
|
146
|
+
return handler
|
|
147
|
+
.use(provider<OrderController>((c) => _controller ??= OrderController(c.read<OrderService>())))
|
|
148
|
+
.use(provider<OrderService>((c) => _service ??= OrderService(c.read<OrderRepositoryPort>())))
|
|
149
|
+
.use(provider<OrderRepositoryPort>((c) => _repository ??= OrderRepository(c.read<OrderDataSource>())));
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- Construtores recebem **ports/interfaces**; campos `final`.
|
|
154
|
+
- A resolução é de baixo para cima: se `B` depende de `A`, declare `.use(B).use(A)`.
|
|
155
|
+
- `provider` recria o valor a cada leitura; para recursos caros, faça cache em variável privada (`??=`).
|
|
156
|
+
- Infraestrutura compartilhada (conexão, configuração, rate limit) é provida no middleware raiz.
|
|
157
|
+
- Middleware **não** contém regra de negócio nem lê parâmetros de caminho por posição; autorização de recurso fica no service.
|
|
158
|
+
- Nunca `context.read` em models, ports, services ou repositories.
|
|
159
|
+
|
|
160
|
+
Composição por grupo, autenticação e ordem: `references/middlewares-di.md`.
|
|
161
|
+
|
|
162
|
+
## 8. Configuração, segurança e geração de código
|
|
163
|
+
|
|
164
|
+
- Variáveis de ambiente lidas em um ponto único (`common/environment`); nunca hardcodar segredos nem logar tokens ou dados pessoais. `String.fromEnvironment` não funciona no Dart Frog (use `Platform.environment`).
|
|
165
|
+
- Validação de formato no controller/DTO; de regra de negócio no service; falhas de persistência no repository.
|
|
166
|
+
- Nunca confie em `uid`, papel ou perfil enviados pelo cliente sem validar no servidor.
|
|
167
|
+
- Arquivos gerados (`*.g.dart`) não são editados à mão; regenere com `dart run build_runner build --delete-conflicting-outputs`.
|
|
168
|
+
- `main.dart` só define `init()`/`run()`; não compõe services.
|
|
169
|
+
|
|
170
|
+
## 9. Testes
|
|
171
|
+
|
|
172
|
+
- Service: fake/mock do port, sem banco real. Controller: entrada válida, inválida, `Right` e `Left` com status correto. Rota: `RequestContext` mockado, cobrindo os métodos suportados **e o 405**. Para criar/executar testes, use o agente `test-writer`.
|
|
173
|
+
- Estrutura em `test/` espelhando `lib/` e `routes/`. Detalhes: `references/testing.md`.
|
|
174
|
+
|
|
175
|
+
## 10. Checklist de review
|
|
176
|
+
|
|
177
|
+
- [ ] `fpdart` instalado; contratos retornam `TaskEither` com `tryCatch` nas operações que falham?
|
|
178
|
+
- [ ] Rota fina, com `case _` retornando 405?
|
|
179
|
+
- [ ] Dependências compostas só em `_middleware.dart`, nada instanciado no handler?
|
|
180
|
+
- [ ] `BusinessException` / `ClientException` / `MiddlewareException` nas camadas certas, sem vazar causa interna?
|
|
181
|
+
- [ ] `domain/` sem Dart Frog, SDK ou persistência concreta?
|
|
182
|
+
- [ ] Segredos e dados pessoais fora do código e dos logs; testes cobrindo regra nova e o 405?
|
|
183
|
+
|
|
184
|
+
Checklist detalhado: `references/checklist.md`.
|
|
185
|
+
|
|
186
|
+
## Não fazer
|
|
187
|
+
|
|
188
|
+
- Não instanciar service/repository/controller na rota ou usar service locator.
|
|
189
|
+
- Não retornar `Future` simples, `throw` ou `Exception` genérica nos contratos das camadas.
|
|
190
|
+
- Não colocar regra de negócio em rota, controller ou middleware.
|
|
191
|
+
- Não omitir o 405; não expor stack trace, SQL ou mensagens de SDK.
|
|
192
|
+
- Não referenciar ORM/driver no domínio nem nesta skill.
|
|
193
|
+
|
|
194
|
+
## 11. Arquivos complementares (leitura sob demanda)
|
|
195
|
+
|
|
196
|
+
Leia só o necessário (`list_skill_files` / `get_skill_file` com `skill="dart-frog"`; ou direto em `dart-frog/` sem as tools).
|
|
197
|
+
|
|
198
|
+
| Dúvida | Arquivo |
|
|
199
|
+
|---|---|
|
|
200
|
+
| Árvore de pastas, variações | `references/project-structure.md` |
|
|
201
|
+
| O que cada camada faz e não faz | `references/layers.md` |
|
|
202
|
+
| `TaskEither`, `tryCatch`, instalação do `fpdart` | `references/fpdart-taskeither.md` |
|
|
203
|
+
| Exceções, status e formato de erro | `references/error-handling.md` |
|
|
204
|
+
| Rotas, 405, CLI, grupos | `references/routing.md` |
|
|
205
|
+
| Middlewares, providers e autenticação | `references/middlewares-di.md` |
|
|
206
|
+
| Testes | `references/testing.md` |
|
|
207
|
+
| Checklist de review | `references/checklist.md` |
|
|
208
|
+
| Exemplos de código (exceções, model, port, service, repository, DTO, controller, middlewares, rotas, testes) | `examples/` |
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Caminho: lib/common/middleware/auth_middleware.dart
|
|
2
|
+
// Autentica a requisição e provê o usuário; falhas são MiddlewareException.
|
|
3
|
+
import 'dart:io';
|
|
4
|
+
|
|
5
|
+
import 'package:dart_frog/dart_frog.dart';
|
|
6
|
+
|
|
7
|
+
import '../exception/exceptions.dart';
|
|
8
|
+
import '../http/error_response.dart';
|
|
9
|
+
|
|
10
|
+
/// Usuário autenticado disponibilizado aos handlers.
|
|
11
|
+
class AuthenticatedUser {
|
|
12
|
+
/// Cria o usuário autenticado.
|
|
13
|
+
const AuthenticatedUser({required this.id});
|
|
14
|
+
|
|
15
|
+
/// Identificador do usuário.
|
|
16
|
+
final String id;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// Port de verificação de credenciais (a implementação é um adapter).
|
|
20
|
+
abstract interface class TokenVerifierPort {
|
|
21
|
+
/// Retorna o usuário do [token] ou `null` se inválido.
|
|
22
|
+
Future<AuthenticatedUser?> verify(String token);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// Exige `Authorization: Bearer <token>` e provê [AuthenticatedUser].
|
|
26
|
+
Middleware authMiddleware() {
|
|
27
|
+
return (handler) {
|
|
28
|
+
return (context) async {
|
|
29
|
+
final header = context.request.headers[HttpHeaders.authorizationHeader];
|
|
30
|
+
if (header == null || !header.startsWith('Bearer ')) {
|
|
31
|
+
return ErrorResponse.fromMiddleware(const MiddlewareException(
|
|
32
|
+
statusCode: HttpStatus.unauthorized,
|
|
33
|
+
message: 'Credenciais ausentes.',
|
|
34
|
+
));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
final user = await context
|
|
38
|
+
.read<TokenVerifierPort>()
|
|
39
|
+
.verify(header.substring('Bearer '.length));
|
|
40
|
+
if (user == null) {
|
|
41
|
+
return ErrorResponse.fromMiddleware(const MiddlewareException(
|
|
42
|
+
statusCode: HttpStatus.unauthorized,
|
|
43
|
+
message: 'Credenciais inválidas.',
|
|
44
|
+
));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return handler(context.provide<AuthenticatedUser>(() => user));
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
}
|