@polymorphism-tech/morph-spec 4.12.0 → 4.13.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/README.md +21 -29
- package/bin/morph-spec.js +41 -24
- package/bin/task-manager.js +962 -1341
- package/bin/validate.js +1 -1
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +10 -11
- package/docs/QUICKSTART.md +15 -15
- package/framework/CLAUDE.md +5 -2
- package/framework/agents/README.md +3 -4
- package/framework/agents/ai-agents/ai-system-architect.md +6 -10
- package/framework/agents/architecture/seo-growth-hacker.md +166 -326
- package/framework/agents/architecture/standards-architect.md +21 -31
- package/framework/agents/backend/dotnet-senior.md +160 -86
- package/framework/agents/backend/ef-modeler.md +2 -6
- package/framework/agents/backend/hangfire-orchestrator.md +8 -9
- package/framework/agents/backend/ms-agent-expert.md +1 -1
- package/framework/agents/frontend/nextjs-expert.md +198 -133
- package/framework/agents/frontend/ui-ux-designer.md +226 -267
- package/framework/agents/infrastructure/infra-architect.md +191 -27
- package/framework/agents/integrations/clerk-auth.md +19 -26
- package/framework/agents/integrations/hangfire-integration.md +1 -4
- package/framework/agents.json +215 -1453
- package/framework/commands/morph-apply.md +4 -4
- package/framework/commands/morph-preflight.md +4 -26
- package/framework/commands/morph-proposal.md +19 -19
- package/framework/commands/morph-status.md +2 -2
- package/framework/commands/morph-troubleshoot.md +6 -13
- package/framework/hooks/README.md +1 -6
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +4 -6
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +12 -7
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +2 -2
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +2 -2
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +5 -10
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +63 -30
- package/framework/hooks/claude-code/session-start/resume-context.js +77 -0
- package/framework/hooks/claude-code/statusline.py +4 -5
- package/framework/hooks/claude-code/stop/validate-completion.js +4 -4
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +8 -8
- package/framework/hooks/shared/compact-restore.js +28 -5
- package/framework/hooks/shared/phase-utils.js +4 -8
- package/framework/hooks/shared/state-reader.js +34 -3
- package/framework/phases.json +7 -45
- package/framework/rules/ai-agents-standards.md +15 -0
- package/framework/rules/asaas-standards.md +13 -0
- package/framework/rules/clerk-standards.md +13 -0
- package/framework/rules/csharp-standards.md +8 -4
- package/framework/rules/data-standards.md +18 -0
- package/framework/rules/dotnet-templates.md +87 -0
- package/framework/rules/frontend-standards.md +13 -13
- package/framework/rules/hangfire-standards.md +13 -0
- package/framework/rules/infrastructure-standards.md +27 -16
- package/framework/rules/morph-workflow.md +73 -13
- package/framework/rules/nextjs-standards.md +1 -0
- package/framework/rules/nextjs-templates.md +40 -0
- package/framework/rules/resend-standards.md +12 -0
- package/framework/rules/supabase-standards.md +29 -0
- package/framework/skills/README.md +6 -8
- package/framework/skills/level-0-meta/mcp-registry.json +21 -99
- package/framework/{standards/ai-agents/team-orchestration.md → skills/level-0-meta/morph-agent-teams/SKILL.md} +472 -479
- package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +28 -1
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
- package/framework/skills/level-0-meta/morph-code-review/SKILL.md +1 -5
- package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +1 -1
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +23 -41
- package/framework/skills/level-0-meta/morph-init/SKILL.md +18 -14
- package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +225 -0
- package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +20 -0
- package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +4 -8
- package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +15 -59
- package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +7 -19
- package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +8 -8
- package/framework/skills/level-1-workflows/README.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +49 -50
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +58 -124
- package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +5 -50
- package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +127 -159
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +4 -3
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +2 -2
- package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +17 -13
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +22 -40
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -2
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +5 -5
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +57 -77
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
- package/framework/squad-templates/backend-only.json +6 -6
- package/framework/squad-templates/frontend-only.json +17 -8
- package/framework/squad-templates/full-stack.json +29 -24
- package/framework/standards/STANDARDS.json +1021 -2369
- package/framework/standards/backend/dotnet/fluent-validation-vsa.md +200 -0
- package/framework/standards/backend/dotnet/result-pattern.md +213 -0
- package/framework/standards/backend/dotnet/scalar-ui.md +172 -0
- package/framework/standards/backend/dotnet/scrutor.md +158 -0
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +274 -0
- package/framework/standards/frontend/nextjs/motion-patterns.md +1 -1
- package/framework/standards/infrastructure/supabase/mcp-setup.md +83 -193
- package/framework/standards/integration/mcp/mcp-tools.md +91 -77
- package/framework/templates/IDE-SNIPPETS.md +2 -6
- package/framework/templates/README.md +709 -814
- package/framework/templates/REGISTRY.json +223 -762
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -0
- package/framework/templates/code/dotnet/abstractions/error.cs +60 -0
- package/framework/templates/code/dotnet/abstractions/handler.cs +12 -0
- package/framework/templates/code/dotnet/abstractions/result.cs +74 -0
- package/framework/templates/code/dotnet/backend/repository.cs +148 -141
- package/framework/templates/code/dotnet/constants/api-tags.cs +6 -0
- package/framework/templates/code/dotnet/exceptions/exception-handler.cs +32 -0
- package/framework/templates/code/dotnet/extensions/database-extensions.cs +20 -0
- package/framework/templates/code/dotnet/extensions/handler-registration.cs +38 -0
- package/framework/templates/code/dotnet/extensions/health-checks.cs +24 -0
- package/framework/templates/code/dotnet/extensions/map-endpoints.cs +35 -0
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +29 -0
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -0
- package/framework/templates/code/dotnet/feature/entity.cs.hbs +8 -0
- package/framework/templates/code/dotnet/feature/errors.cs.hbs +8 -0
- package/framework/templates/code/dotnet/feature/handler.cs.hbs +24 -0
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -0
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -0
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -0
- package/framework/templates/code/dotnet/jobs/job.cs +21 -17
- package/framework/templates/code/dotnet/pipelines/logging-decorator.cs +31 -0
- package/framework/templates/code/dotnet/pipelines/validation-decorator.cs +97 -0
- package/framework/templates/code/dotnet/test.cs +192 -106
- package/framework/templates/context/ARCHITECTURE.md +89 -0
- package/framework/templates/context/CONTEXT-FEATURE.md +2 -2
- package/framework/templates/context/CONTEXT.md +180 -181
- package/framework/templates/context/design-system.md +1 -5
- package/framework/templates/docs/onboarding.md +2 -3
- package/framework/templates/docs/proposal.md +171 -182
- package/framework/templates/docs/spec.md +1 -3
- package/framework/templates/docs/ui-components.md +0 -72
- package/framework/templates/examples/design-system-examples.md +4 -4
- package/framework/templates/feature/decisions.md +0 -66
- package/framework/templates/feature/recap.md +1 -2
- package/framework/templates/infrastructure/github/README.md +56 -593
- package/framework/templates/integrations/clerk-config.cs +0 -41
- package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +2 -2
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -2
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +83 -126
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +2 -2
- package/framework/templates/project/validate.js +2 -4
- package/framework/templates/ui/design-system.css +1 -1
- package/framework/workflows/README.md +8 -8
- package/framework/workflows/configs/fast-track.json +51 -0
- package/framework/workflows/configs/full.json +68 -0
- package/framework/workflows/configs/standard.json +7 -3
- package/framework/workflows/docs/STORY-DRIVEN-DEVELOPMENT.md +1 -1
- package/framework/workflows/docs/enforcement-pipeline.md +18 -19
- package/framework/workflows/docs/full-morph.md +69 -76
- package/framework/workflows/docs/standard.md +49 -44
- package/framework/workflows/docs/ui-refresh.md +1 -1
- package/package.json +3 -3
- package/src/commands/agents/dispatch-agents.js +55 -116
- package/src/commands/context/context-refresh.js +79 -0
- package/src/commands/phase/phase-reset.js +1 -1
- package/src/commands/project/doctor.js +10 -32
- package/src/commands/project/init.js +12 -0
- package/src/commands/project/status.js +6 -12
- package/src/commands/scaffold/scaffold.js +131 -0
- package/src/commands/scope/escalate.js +1 -1
- package/src/commands/state/advance-phase.js +15 -29
- package/src/commands/state/state.js +20 -5
- package/src/commands/tasks/task.js +0 -30
- package/src/commands/validation/validate-feature.js +3 -10
- package/src/core/paths/output-schema.js +12 -13
- package/src/core/state/phase-state-machine.js +211 -217
- package/src/core/state/state-manager.js +9 -9
- package/src/lib/agents/agents-manager.js +2 -2
- package/src/lib/agents/output-collector.js +1 -2
- package/src/lib/detectors/design-system-detector.js +4 -4
- package/src/lib/detectors/index.js +1 -11
- package/src/lib/generators/architecture-scanner.js +640 -0
- package/src/lib/generators/context-generator.js +3 -3
- package/src/lib/generators/design-system-generator.js +508 -587
- package/src/lib/generators/index.js +12 -10
- package/src/lib/generators/metadata-extractor.js +386 -387
- package/src/lib/generators/recap-generator.js +4 -4
- package/src/lib/generators/scaffold-generator.js +317 -0
- package/src/lib/generators/settings-generator.js +0 -1
- package/src/lib/phase-chain/phase-validator.js +64 -42
- package/src/lib/stack/stack-profile.js +2 -2
- package/src/lib/standards/digest-builder.js +72 -285
- package/src/lib/tasks/task-parser.js +11 -5
- package/src/lib/validators/architecture/architecture-validator.js +259 -387
- package/src/lib/validators/content/content-validator.js +287 -287
- package/src/lib/validators/css/css-validator.js +340 -352
- package/src/lib/validators/packages/package-validator.js +347 -360
- package/src/lib/validators/shared/issue-counter.js +14 -18
- package/src/lib/validators/validation-runner.js +3 -19
- package/src/scripts/setup-infra.js +1 -1
- package/src/utils/agents-installer.js +24 -3
- package/src/utils/file-copier.js +1 -0
- package/src/utils/hooks-installer.js +11 -19
- package/src/utils/skills-installer.js +178 -178
- package/framework/agents/architecture/prompt-engineer.md +0 -195
- package/framework/agents/backend/api-designer.md +0 -106
- package/framework/agents/frontend/blazor-builder.md +0 -229
- package/framework/agents/frontend/nextjs-ui-specialist.md +0 -218
- package/framework/agents/infrastructure/azure-architect.md +0 -150
- package/framework/agents/infrastructure/azure-deploy-specialist.md +0 -707
- package/framework/agents/infrastructure/bicep-architect.md +0 -132
- package/framework/agents/infrastructure/container-specialist.md +0 -137
- package/framework/agents/infrastructure/devops-engineer.md +0 -126
- package/framework/agents/integrations/azure-identity.md +0 -148
- package/framework/agents/quality/code-analyzer.md +0 -239
- package/framework/agents/quality/testing-specialist.md +0 -130
- package/framework/hooks/claude-code/notification/approval-reminder.js +0 -57
- package/framework/hooks/claude-code/post-tool-use/context-refresh.js +0 -109
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +0 -78
- package/framework/index/troubleshooting-index.json +0 -184
- package/framework/rules/testing-standards.md +0 -14
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +0 -230
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +0 -70
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +0 -256
- package/framework/skills/level-1-workflows/morph-phase-clarify/references/clarifications-example.md +0 -117
- package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +0 -269
- package/framework/standards/ai-agents/blazor-ui.md +0 -364
- package/framework/standards/architecture/ddd/aggregates.md +0 -120
- package/framework/standards/architecture/ddd/bounded-contexts.md +0 -105
- package/framework/standards/architecture/ddd/complexity-levels.md +0 -108
- package/framework/standards/architecture/ddd/entities.md +0 -99
- package/framework/standards/architecture/ddd/ubiquitous-language.md +0 -58
- package/framework/standards/architecture/ddd/value-objects.md +0 -124
- package/framework/standards/backend/api/minimal-api.md +0 -494
- package/framework/standards/backend/api/rest.md +0 -492
- package/framework/standards/backend/authentication/passkeys.md +0 -428
- package/framework/standards/backend/database/ef-core.md +0 -199
- package/framework/standards/backend/database/migrations.md +0 -393
- package/framework/standards/backend/database/repository-patterns.md +0 -528
- package/framework/standards/backend/dotnet/async.md +0 -366
- package/framework/standards/backend/dotnet/core.md +0 -117
- package/framework/standards/backend/dotnet/di.md +0 -439
- package/framework/standards/backend/dotnet/program-cs-checklist.md +0 -92
- package/framework/standards/context/analytics.md +0 -96
- package/framework/standards/context/bundles.md +0 -110
- package/framework/standards/context/priming.md +0 -78
- package/framework/standards/core/architecture.md +0 -185
- package/framework/standards/core/coding.md +0 -214
- package/framework/standards/core/git-branching-strategy.md +0 -403
- package/framework/standards/core/git.md +0 -185
- package/framework/standards/core/testing.md +0 -295
- package/framework/standards/data/nosql/blob-storage.md +0 -102
- package/framework/standards/data/vector-search/azure-ai-search.md +0 -121
- package/framework/standards/frontend/blazor/design-checklist.md +0 -222
- package/framework/standards/frontend/blazor/fluent-ui-setup.md +0 -595
- package/framework/standards/frontend/blazor/fluent-ui.md +0 -137
- package/framework/standards/frontend/blazor/html-conversion.md +0 -184
- package/framework/standards/frontend/blazor/lifecycle.md +0 -195
- package/framework/standards/frontend/blazor/pitfalls.md +0 -198
- package/framework/standards/frontend/blazor/state.md +0 -191
- package/framework/standards/frontend/design-system/aesthetic-direction.md +0 -266
- package/framework/standards/frontend/design-system/design-trends.md +0 -171
- package/framework/standards/infrastructure/azure/azure.md +0 -624
- package/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +0 -422
- package/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +0 -516
- package/framework/standards/infrastructure/azure/devops/local-development.md +0 -520
- package/framework/standards/infrastructure/azure/services/functions.md +0 -486
- package/framework/standards/infrastructure/azure/services/service-bus.md +0 -459
- package/framework/standards/infrastructure/azure/services/storage.md +0 -407
- package/framework/standards/infrastructure/vercel/vercel-database.md +0 -106
- package/framework/standards/integration/api/graphql.md +0 -91
- package/framework/standards/integration/api/grpc.md +0 -114
- package/framework/standards/integration/api/rest-design.md +0 -95
- package/framework/standards/integration/event-driven/cqrs.md +0 -101
- package/framework/standards/integration/event-driven/event-sourcing.md +0 -124
- package/framework/standards/integration/event-driven/service-bus.md +0 -95
- package/framework/standards/observability/logging.md +0 -131
- package/framework/standards/observability/metrics.md +0 -121
- package/framework/standards/observability/monitoring.md +0 -114
- package/framework/standards/observability/tracing.md +0 -132
- package/framework/standards/workflows/parallel-execution.md +0 -112
- package/framework/standards/workflows/thread-management.md +0 -113
- package/framework/templates/code/dotnet/backend/service.cs +0 -139
- package/framework/templates/code/dotnet/contracts/Commands.cs +0 -74
- package/framework/templates/code/dotnet/contracts/Entities.cs +0 -25
- package/framework/templates/code/dotnet/contracts/Queries.cs +0 -74
- package/framework/templates/code/dotnet/contracts/api-contracts.cs +0 -173
- package/framework/templates/code/dotnet/contracts/contracts-level1.cs +0 -69
- package/framework/templates/code/dotnet/contracts/contracts-level2.cs +0 -86
- package/framework/templates/code/dotnet/contracts/contracts-level3.cs +0 -41
- package/framework/templates/code/dotnet/frontend/component.razor +0 -239
- package/framework/templates/infrastructure/azure/Dockerfile.example +0 -82
- package/framework/templates/infrastructure/azure/README.md +0 -286
- package/framework/templates/infrastructure/azure/app-insights.bicep +0 -63
- package/framework/templates/infrastructure/azure/app-service.bicep +0 -164
- package/framework/templates/infrastructure/azure/container-app-env.bicep +0 -49
- package/framework/templates/infrastructure/azure/container-app.bicep +0 -156
- package/framework/templates/infrastructure/azure/deploy-checklist.md +0 -426
- package/framework/templates/infrastructure/azure/deploy.ps1 +0 -229
- package/framework/templates/infrastructure/azure/deploy.sh +0 -208
- package/framework/templates/infrastructure/azure/key-vault.bicep +0 -91
- package/framework/templates/infrastructure/azure/main.bicep +0 -189
- package/framework/templates/infrastructure/azure/parameters.dev.json +0 -29
- package/framework/templates/infrastructure/azure/parameters.prod.json +0 -29
- package/framework/templates/infrastructure/azure/parameters.staging.json +0 -29
- package/framework/templates/infrastructure/azure/sql-database.bicep +0 -103
- package/framework/templates/infrastructure/azure/storage.bicep +0 -106
- package/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +0 -22
- package/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +0 -61
- package/framework/templates/integrations/azure-identity-config.cs +0 -288
- package/framework/templates/project-structure/dotnet-ddd.md +0 -70
- package/framework/templates/saas/subscription.cs +0 -347
- package/framework/templates/saas/tenant.cs +0 -338
- package/framework/templates/ui/FluentDesignTheme.cs +0 -149
- package/framework/templates/ui/MudTheme.cs +0 -281
- package/framework/workflows/configs/nodejs-cli.json +0 -40
- package/src/commands/standards/context-budget.js +0 -89
- package/src/lib/standards/index.js +0 -7
- package/src/lib/standards/standards-context-injector.js +0 -302
- package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +0 -316
- package/src/lib/validators/blazor/blazor-state-validator.js +0 -279
- package/src/lib/validators/blazor/blazor-validator.js +0 -369
- package/src/lib/validators/blazor/index.js +0 -9
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# FluentValidation in VSA Pipelines
|
|
2
|
+
|
|
3
|
+
> **Scope:** dotnet-vsa
|
|
4
|
+
> **Layer:** 0 (always load)
|
|
5
|
+
> **Keywords:** fluent-validation, validation, decorator, pipeline, vsa
|
|
6
|
+
> **Load When:** dotnet VSA projects
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
FluentValidation integrates into VSA via the decorator pattern. Validators are auto-discovered per assembly, and the `ValidationDecorator<TRequest, TResponse>` runs all matching validators before the handler executes. Failed validation short-circuits to `Result.Failure` without touching the handler.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Validator Structure
|
|
15
|
+
|
|
16
|
+
One validator per request DTO. Lives in the same feature folder as the handler:
|
|
17
|
+
|
|
18
|
+
```csharp
|
|
19
|
+
// Features/BookFeature/CreateBook/CreateBookValidator.cs
|
|
20
|
+
public class CreateBookValidator : AbstractValidator<CreateBookRequest>
|
|
21
|
+
{
|
|
22
|
+
public CreateBookValidator()
|
|
23
|
+
{
|
|
24
|
+
RuleFor(c => c.Title)
|
|
25
|
+
.NotEmpty().WithMessage("Title is required")
|
|
26
|
+
.MaximumLength(200).WithMessage("Title must not exceed 200 characters");
|
|
27
|
+
|
|
28
|
+
RuleFor(c => c.Author)
|
|
29
|
+
.NotEmpty().WithMessage("Author is required")
|
|
30
|
+
.MaximumLength(100).WithMessage("Author must not exceed 100 characters");
|
|
31
|
+
|
|
32
|
+
RuleFor(c => c.ISBN)
|
|
33
|
+
.NotEmpty().WithMessage("ISBN is required");
|
|
34
|
+
|
|
35
|
+
RuleFor(c => c.Price)
|
|
36
|
+
.GreaterThan(0).WithMessage("Price must be greater than 0");
|
|
37
|
+
|
|
38
|
+
RuleFor(c => c.PublishedYear)
|
|
39
|
+
.GreaterThan(1000).WithMessage("Published year must be a valid year")
|
|
40
|
+
.LessThanOrEqualTo(DateTime.UtcNow.Year)
|
|
41
|
+
.WithMessage("Published year cannot be in the future");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Auto-Discovery
|
|
49
|
+
|
|
50
|
+
Register all validators from the assembly in `Program.cs`. This must happen **before** handler registration so the `ValidationDecorator` can resolve them:
|
|
51
|
+
|
|
52
|
+
```csharp
|
|
53
|
+
// Program.cs
|
|
54
|
+
builder.Services.AddValidatorsFromAssembly(typeof(CreateBookValidator).Assembly);
|
|
55
|
+
builder.Services.AddHandlersFromAssembly(typeof(Program).Assembly);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## ValidationDecorator Pattern
|
|
61
|
+
|
|
62
|
+
The decorator wraps every `IHandler<TRequest, TResponse>` via Scrutor. It collects all `IValidator<TRequest>` from DI, runs them in parallel, and short-circuits on failure:
|
|
63
|
+
|
|
64
|
+
```csharp
|
|
65
|
+
public sealed class ValidationDecorator<TRequest, TResponse>(
|
|
66
|
+
IEnumerable<IValidator<TRequest>> validators,
|
|
67
|
+
IHandler<TRequest, TResponse> innerHandler) : IHandler<TRequest, TResponse>
|
|
68
|
+
{
|
|
69
|
+
public async Task<TResponse> HandleAsync(
|
|
70
|
+
TRequest command, CancellationToken cancellationToken)
|
|
71
|
+
{
|
|
72
|
+
// No validators registered for this request — pass through
|
|
73
|
+
if (!validators.Any())
|
|
74
|
+
{
|
|
75
|
+
return await innerHandler.HandleAsync(command, cancellationToken);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
var context = new ValidationContext<TRequest>(command);
|
|
79
|
+
|
|
80
|
+
// Run all validators in parallel
|
|
81
|
+
ValidationFailure[] failures = (await Task.WhenAll(
|
|
82
|
+
validators.Select(v => v.ValidateAsync(context, cancellationToken))))
|
|
83
|
+
.SelectMany(r => r.Errors)
|
|
84
|
+
.Where(f => f is not null)
|
|
85
|
+
.ToArray();
|
|
86
|
+
|
|
87
|
+
if (failures.Length == 0)
|
|
88
|
+
{
|
|
89
|
+
return await innerHandler.HandleAsync(command, cancellationToken);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Short-circuit: convert to Result failure
|
|
93
|
+
return CreateFailureResponse(failures);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## FluentValidation to ValidationError Conversion
|
|
101
|
+
|
|
102
|
+
`ValidationFailure[]` from FluentValidation is converted to the Result pattern's `ValidationError`:
|
|
103
|
+
|
|
104
|
+
```csharp
|
|
105
|
+
private static ValidationError CreateValidationError(ValidationFailure[] validationFailures) =>
|
|
106
|
+
new(validationFailures
|
|
107
|
+
.Select(f => Error.Validation(f.ErrorCode, f.ErrorMessage))
|
|
108
|
+
.ToArray());
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This produces a `ValidationError` containing an array of typed `Error` records, each with `ErrorType.Validation`.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Reflection for Typed Failure Responses
|
|
116
|
+
|
|
117
|
+
The decorator supports both `Result` and `Result<T>` via reflection. This is necessary because the generic `TResponse` type is not known at compile time:
|
|
118
|
+
|
|
119
|
+
```csharp
|
|
120
|
+
private static TResponse CreateFailureResponse(ValidationFailure[] failures)
|
|
121
|
+
{
|
|
122
|
+
// Non-generic Result
|
|
123
|
+
if (typeof(TResponse) == typeof(Result))
|
|
124
|
+
{
|
|
125
|
+
return (TResponse)(object)Result.Failure(CreateValidationError(failures));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Generic Result<T> — use reflection to call Result.Failure<T>()
|
|
129
|
+
if (typeof(TResponse).IsGenericType &&
|
|
130
|
+
typeof(TResponse).GetGenericTypeDefinition() == typeof(Result<>))
|
|
131
|
+
{
|
|
132
|
+
var valueType = typeof(TResponse).GetGenericArguments()[0];
|
|
133
|
+
var failureMethod = typeof(Result)
|
|
134
|
+
.GetMethods()
|
|
135
|
+
.First(m =>
|
|
136
|
+
m.Name == nameof(Result.Failure) &&
|
|
137
|
+
m.IsGenericMethodDefinition &&
|
|
138
|
+
m.GetParameters().Length == 1);
|
|
139
|
+
|
|
140
|
+
var typedFailure = failureMethod
|
|
141
|
+
.MakeGenericMethod(valueType)
|
|
142
|
+
.Invoke(null, [CreateValidationError(failures)]);
|
|
143
|
+
|
|
144
|
+
return (TResponse)typedFailure!;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
throw new InvalidOperationException(
|
|
148
|
+
$"ValidationDecorator supports only Result and Result<T> responses.");
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## RuleFor Chain Reference
|
|
155
|
+
|
|
156
|
+
Common FluentValidation rules used in VSA:
|
|
157
|
+
|
|
158
|
+
| Rule | Purpose |
|
|
159
|
+
|------|---------|
|
|
160
|
+
| `.NotEmpty()` | Non-null and non-whitespace for strings |
|
|
161
|
+
| `.MaximumLength(n)` | String max length |
|
|
162
|
+
| `.GreaterThan(n)` | Numeric minimum (exclusive) |
|
|
163
|
+
| `.LessThanOrEqualTo(n)` | Numeric maximum (inclusive) |
|
|
164
|
+
| `.Must(predicate)` | Custom inline validation |
|
|
165
|
+
| `.WithMessage("...")` | Human-readable error message |
|
|
166
|
+
| `.WithErrorCode("...")` | Machine-readable error code |
|
|
167
|
+
| `.When(condition)` | Conditional rule application |
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## File Placement
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
Features/
|
|
175
|
+
BookFeature/
|
|
176
|
+
CreateBook/
|
|
177
|
+
CreateBookRequest.cs (or inline in handler)
|
|
178
|
+
CreateBookResponse.cs (or inline in handler)
|
|
179
|
+
CreateBookHandler.cs
|
|
180
|
+
CreateBookValidator.cs <-- one validator per request
|
|
181
|
+
CreateBookEndpoint.cs
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Anti-Patterns
|
|
187
|
+
|
|
188
|
+
| Do NOT | Do Instead |
|
|
189
|
+
|--------|------------|
|
|
190
|
+
| Validate inside the handler | Use `AbstractValidator<T>` + decorator |
|
|
191
|
+
| Throw `ValidationException` | Return `Result.Failure(ValidationError)` |
|
|
192
|
+
| Register validators after handlers | Register validators first: `AddValidatorsFromAssembly()` before `AddHandlersFromAssembly()` |
|
|
193
|
+
| Put validation logic in endpoints | Endpoints only do `Match()` — validation is automatic |
|
|
194
|
+
| Use `IValidateOptions<T>` | Use `AbstractValidator<T>` (FluentValidation, not Microsoft) |
|
|
195
|
+
| Skip `.WithMessage()` | Always provide user-facing messages |
|
|
196
|
+
| Use `RuleSet` grouping | One validator per request DTO — no rulesets needed in VSA |
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Result Pattern — Railway-Oriented Programming
|
|
2
|
+
|
|
3
|
+
> **Scope:** dotnet-vsa
|
|
4
|
+
> **Layer:** 0 (always load)
|
|
5
|
+
> **Keywords:** result, error-handling, railway, match, monadic
|
|
6
|
+
> **Load When:** dotnet VSA projects
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
The Result pattern replaces exceptions for expected failures. Every handler returns `Result` or `Result<T>`, and endpoints use `Match()` to map success/failure to HTTP responses. No try-catch in business logic.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Result Anatomy
|
|
15
|
+
|
|
16
|
+
```csharp
|
|
17
|
+
public class Result
|
|
18
|
+
{
|
|
19
|
+
protected internal Result(bool isSuccess, Error error) { ... }
|
|
20
|
+
|
|
21
|
+
public bool IsSuccess { get; }
|
|
22
|
+
public bool IsFailure => !IsSuccess;
|
|
23
|
+
public Error Error { get; }
|
|
24
|
+
|
|
25
|
+
// Factory methods
|
|
26
|
+
public static Result Success() => new(true, Error.None);
|
|
27
|
+
public static Result<TValue> Success<TValue>(TValue value) => new(value, true, Error.None);
|
|
28
|
+
public static Result Failure(Error error) => new(false, error);
|
|
29
|
+
public static Result<TValue> Failure<TValue>(Error error) => new(default, false, error);
|
|
30
|
+
public static Result<TValue> Create<TValue>(TValue? value) =>
|
|
31
|
+
value is not null ? Success(value) : Failure<TValue>(Error.Null);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Result\<T\> with Implicit Conversions
|
|
38
|
+
|
|
39
|
+
```csharp
|
|
40
|
+
public class Result<TValue> : Result
|
|
41
|
+
{
|
|
42
|
+
public TValue Value => IsSuccess
|
|
43
|
+
? _value!
|
|
44
|
+
: throw new InvalidOperationException(
|
|
45
|
+
"The value of a failure result can not be accessed.");
|
|
46
|
+
|
|
47
|
+
// Implicit: value -> Result<T> (success)
|
|
48
|
+
public static implicit operator Result<TValue>(TValue? value) => Create(value);
|
|
49
|
+
|
|
50
|
+
// Implicit: Error -> Result<T> (failure)
|
|
51
|
+
public static implicit operator Result<TValue>(Error error) => Failure<TValue>(error);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This allows handlers to return values or errors directly:
|
|
56
|
+
|
|
57
|
+
```csharp
|
|
58
|
+
// Both are valid return statements in a handler returning Result<BookResponse>
|
|
59
|
+
return new BookResponse(book.Id, book.Title); // implicit success
|
|
60
|
+
return BookErrors.NotFound(bookId); // implicit failure
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Error Types (7 + Custom)
|
|
66
|
+
|
|
67
|
+
```csharp
|
|
68
|
+
public record Error(string Code, string? Description = default, ErrorType Type = ErrorType.Failure)
|
|
69
|
+
{
|
|
70
|
+
public static readonly Error None = new(string.Empty);
|
|
71
|
+
public static readonly Error Null = new("Error.NullValue", "The specified result value is null.");
|
|
72
|
+
|
|
73
|
+
// Factory methods
|
|
74
|
+
public static Error Failure(string code, string description) =>
|
|
75
|
+
new(code, description, ErrorType.Failure);
|
|
76
|
+
public static Error Unexpected(string code, string description) =>
|
|
77
|
+
new(code, description, ErrorType.Unexpected);
|
|
78
|
+
public static Error Validation(string code, string description) =>
|
|
79
|
+
new(code, description, ErrorType.Validation);
|
|
80
|
+
public static Error Conflict(string code, string description) =>
|
|
81
|
+
new(code, description, ErrorType.Conflict);
|
|
82
|
+
public static Error NotFound(string code, string description) =>
|
|
83
|
+
new(code, description, ErrorType.NotFound);
|
|
84
|
+
public static Error Unauthorized(string code, string description) =>
|
|
85
|
+
new(code, description, ErrorType.Unauthorized);
|
|
86
|
+
public static Error Forbidden(string code, string description) =>
|
|
87
|
+
new(code, description, ErrorType.Forbidden);
|
|
88
|
+
|
|
89
|
+
// Implicit: Error -> Result (failure)
|
|
90
|
+
public static implicit operator Result(Error error) => Result.Failure(error);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public enum ErrorType
|
|
94
|
+
{
|
|
95
|
+
Failure, // General business rule violation
|
|
96
|
+
Unexpected, // Unrecoverable system error
|
|
97
|
+
Validation, // Input validation failure (often aggregated)
|
|
98
|
+
Conflict, // Duplicate / concurrency conflict
|
|
99
|
+
NotFound, // Entity does not exist
|
|
100
|
+
Unauthorized, // Missing credentials
|
|
101
|
+
Forbidden, // Insufficient permissions
|
|
102
|
+
Custom // Domain-specific extensions
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### When to Use Which Error Type
|
|
107
|
+
|
|
108
|
+
| Error Type | HTTP Status | Use Case |
|
|
109
|
+
|------------|-------------|----------|
|
|
110
|
+
| `Failure` | 400 | Business rule violation (e.g., insufficient balance) |
|
|
111
|
+
| `Unexpected` | 500 | System error (e.g., external service down) |
|
|
112
|
+
| `Validation` | 400 | Input validation (FluentValidation aggregated) |
|
|
113
|
+
| `Conflict` | 409 | Duplicate key, concurrency conflict |
|
|
114
|
+
| `NotFound` | 404 | Entity lookup returned null |
|
|
115
|
+
| `Unauthorized` | 401 | Missing or invalid auth token |
|
|
116
|
+
| `Forbidden` | 403 | Authenticated but lacks permission |
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## ValidationError (Aggregated)
|
|
121
|
+
|
|
122
|
+
Wraps multiple `Error` instances from FluentValidation:
|
|
123
|
+
|
|
124
|
+
```csharp
|
|
125
|
+
public sealed record ValidationError : Error
|
|
126
|
+
{
|
|
127
|
+
public ValidationError(Error[] errors)
|
|
128
|
+
: base("Validation.General", "One or more validation errors occurred", ErrorType.Validation)
|
|
129
|
+
{
|
|
130
|
+
Errors = errors;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public Error[] Errors { get; }
|
|
134
|
+
|
|
135
|
+
public static ValidationError FromResults(IEnumerable<Result> results) =>
|
|
136
|
+
new(results.Where(r => r.IsFailure).Select(r => r.Error).ToArray());
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Match() Pattern
|
|
143
|
+
|
|
144
|
+
Endpoints use `Match()` to convert `Result<T>` into HTTP responses — never access `.Value` directly:
|
|
145
|
+
|
|
146
|
+
```csharp
|
|
147
|
+
public static class ResultExtensions
|
|
148
|
+
{
|
|
149
|
+
// For Result (no value)
|
|
150
|
+
public static TOut Match<TOut>(
|
|
151
|
+
this Result result,
|
|
152
|
+
Func<TOut> onSuccess,
|
|
153
|
+
Func<Error, TOut> onFailure)
|
|
154
|
+
{
|
|
155
|
+
return result.IsSuccess ? onSuccess() : onFailure(result.Error);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// For Result<T> (with value)
|
|
159
|
+
public static TOut Match<TIn, TOut>(
|
|
160
|
+
this Result<TIn> result,
|
|
161
|
+
Func<TIn, TOut> onSuccess,
|
|
162
|
+
Func<Error, TOut> onFailure)
|
|
163
|
+
{
|
|
164
|
+
return result.IsSuccess ? onSuccess(result.Value) : onFailure(result.Error);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Usage in an endpoint:
|
|
170
|
+
|
|
171
|
+
```csharp
|
|
172
|
+
var result = await handler.HandleAsync(command, cancellationToken);
|
|
173
|
+
return result.Match(
|
|
174
|
+
onSuccess: () => Results.Ok(result.Value),
|
|
175
|
+
onFailure: error => Results.BadRequest(error));
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Defining Feature Errors
|
|
181
|
+
|
|
182
|
+
Each feature defines its own static error factory class:
|
|
183
|
+
|
|
184
|
+
```csharp
|
|
185
|
+
public static class BookErrors
|
|
186
|
+
{
|
|
187
|
+
public static Error NotFound(Guid bookId) =>
|
|
188
|
+
Error.NotFound("Book.NotFound", $"Book with ID '{bookId}' was not found.");
|
|
189
|
+
|
|
190
|
+
public static Error DuplicateIsbn(string isbn) =>
|
|
191
|
+
Error.Conflict("Book.DuplicateIsbn", $"A book with ISBN '{isbn}' already exists.");
|
|
192
|
+
|
|
193
|
+
public static Error InvalidPrice =>
|
|
194
|
+
Error.Failure("Book.InvalidPrice", "Book price must be greater than zero.");
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Anti-Patterns
|
|
201
|
+
|
|
202
|
+
| Do NOT | Do Instead |
|
|
203
|
+
|--------|------------|
|
|
204
|
+
| Throw exceptions for expected failures | Return `Result.Failure(error)` |
|
|
205
|
+
| Access `.Value` without checking `IsSuccess` | Use `Match()` |
|
|
206
|
+
| Use string error messages without codes | Use `Error.NotFound("Code", "Description")` |
|
|
207
|
+
| Catch exceptions in handlers | Let unexpected exceptions bubble to `CustomExceptionHandler` |
|
|
208
|
+
| Return `null` from handlers | Return `Result.Failure<T>(error)` |
|
|
209
|
+
| Use generic `Error.Failure` for everything | Choose the correct `ErrorType` for proper HTTP mapping |
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Scalar.AspNetCore — API Documentation UI
|
|
2
|
+
|
|
3
|
+
> **Scope:** dotnet-vsa
|
|
4
|
+
> **Layer:** 0 (always load)
|
|
5
|
+
> **Keywords:** scalar, openapi, swagger, api-docs, documentation
|
|
6
|
+
> **Load When:** dotnet VSA projects
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Scalar replaces Swagger UI as the API documentation frontend. It provides a modern UI with built-in request testing, client code generation, and dark themes. Works with ASP.NET Core's native OpenAPI support (`Microsoft.AspNetCore.OpenApi`).
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Package Setup
|
|
15
|
+
|
|
16
|
+
```xml
|
|
17
|
+
<!-- .csproj -->
|
|
18
|
+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
|
|
19
|
+
<PackageReference Include="Scalar.AspNetCore" Version="2.12.40" />
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
No Swashbuckle needed. ASP.NET Core 9+ has built-in OpenAPI document generation.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Program.cs Configuration
|
|
27
|
+
|
|
28
|
+
```csharp
|
|
29
|
+
using Scalar.AspNetCore;
|
|
30
|
+
|
|
31
|
+
var builder = WebApplication.CreateBuilder(args);
|
|
32
|
+
|
|
33
|
+
// Enable OpenAPI document generation
|
|
34
|
+
builder.Services.AddOpenApi();
|
|
35
|
+
|
|
36
|
+
var app = builder.Build();
|
|
37
|
+
|
|
38
|
+
// Map the OpenAPI JSON endpoint (dev only)
|
|
39
|
+
if (app.Environment.IsDevelopment())
|
|
40
|
+
{
|
|
41
|
+
app.MapOpenApi();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Map Scalar UI at /scalar/v1
|
|
45
|
+
app.MapScalarApiReference(options =>
|
|
46
|
+
{
|
|
47
|
+
options.WithTheme(ScalarTheme.DeepSpace);
|
|
48
|
+
|
|
49
|
+
// Enable Developer Tools (request + client code panel)
|
|
50
|
+
options.WithDefaultHttpClient(
|
|
51
|
+
ScalarTarget.CSharp,
|
|
52
|
+
ScalarClient.HttpClient);
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Access URL:** `https://localhost:{port}/scalar/v1`
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Available Themes
|
|
61
|
+
|
|
62
|
+
| Theme | Description |
|
|
63
|
+
|-------|-------------|
|
|
64
|
+
| `ScalarTheme.Default` | Light theme |
|
|
65
|
+
| `ScalarTheme.DeepSpace` | Dark blue (recommended) |
|
|
66
|
+
| `ScalarTheme.Purple` | Purple accent |
|
|
67
|
+
| `ScalarTheme.Moon` | Dark grey |
|
|
68
|
+
| `ScalarTheme.Saturn` | Warm dark |
|
|
69
|
+
| `ScalarTheme.Mars` | Red accent |
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## ApiTags Organization
|
|
74
|
+
|
|
75
|
+
Group endpoints by feature using a constants class and `.WithTags()`:
|
|
76
|
+
|
|
77
|
+
```csharp
|
|
78
|
+
// Constants/ApiTags.cs
|
|
79
|
+
public static class ApiTags
|
|
80
|
+
{
|
|
81
|
+
public const string Books = "books";
|
|
82
|
+
public const string Authors = "authors";
|
|
83
|
+
public const string Orders = "orders";
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Apply tags in endpoints:
|
|
88
|
+
|
|
89
|
+
```csharp
|
|
90
|
+
internal sealed class CreateBookEndpoint : IApiEndpoint
|
|
91
|
+
{
|
|
92
|
+
public void MapEndpoint(WebApplication app)
|
|
93
|
+
{
|
|
94
|
+
app.MapPost("books", async (
|
|
95
|
+
IHandler<CreateBookRequest, Result<CreateBookResponse>> handler,
|
|
96
|
+
CreateBookRequest command,
|
|
97
|
+
CancellationToken cancellationToken) =>
|
|
98
|
+
{
|
|
99
|
+
var result = await handler.HandleAsync(command, cancellationToken);
|
|
100
|
+
return result.Match(
|
|
101
|
+
onSuccess: () => Results.Ok(result.Value),
|
|
102
|
+
onFailure: error => Results.BadRequest(error));
|
|
103
|
+
})
|
|
104
|
+
.WithTags(ApiTags.Books)
|
|
105
|
+
.Produces<CreateBookResponse>(StatusCodes.Status200OK)
|
|
106
|
+
.Produces(StatusCodes.Status400BadRequest);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Endpoint Metadata
|
|
114
|
+
|
|
115
|
+
Use `.Produces<T>()` to document response types for OpenAPI:
|
|
116
|
+
|
|
117
|
+
```csharp
|
|
118
|
+
app.MapGet("books/{id:guid}", async (...) => { ... })
|
|
119
|
+
.WithTags(ApiTags.Books)
|
|
120
|
+
.WithName("GetBookById")
|
|
121
|
+
.WithSummary("Get a book by its unique identifier")
|
|
122
|
+
.Produces<GetBookResponse>(StatusCodes.Status200OK)
|
|
123
|
+
.Produces(StatusCodes.Status404NotFound);
|
|
124
|
+
|
|
125
|
+
app.MapDelete("books/{id:guid}", async (...) => { ... })
|
|
126
|
+
.WithTags(ApiTags.Books)
|
|
127
|
+
.Produces(StatusCodes.Status204NoContent)
|
|
128
|
+
.Produces(StatusCodes.Status404NotFound);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Common Metadata Methods
|
|
132
|
+
|
|
133
|
+
| Method | Purpose |
|
|
134
|
+
|--------|---------|
|
|
135
|
+
| `.WithTags("tag")` | Group in Scalar sidebar |
|
|
136
|
+
| `.WithName("OperationId")` | Unique operation identifier |
|
|
137
|
+
| `.WithSummary("...")` | Short description in endpoint list |
|
|
138
|
+
| `.WithDescription("...")` | Long description in detail panel |
|
|
139
|
+
| `.Produces<T>(statusCode)` | Document success response type |
|
|
140
|
+
| `.Produces(statusCode)` | Document error response (no body) |
|
|
141
|
+
| `.ProducesProblem(statusCode)` | Document ProblemDetails response |
|
|
142
|
+
| `.RequireAuthorization()` | Marks as requiring auth in docs |
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Client Code Generation
|
|
147
|
+
|
|
148
|
+
Scalar auto-generates client code for API calls. The `WithDefaultHttpClient` option selects the default language:
|
|
149
|
+
|
|
150
|
+
| Target | Client | Description |
|
|
151
|
+
|--------|--------|-------------|
|
|
152
|
+
| `ScalarTarget.CSharp` | `ScalarClient.HttpClient` | .NET HttpClient |
|
|
153
|
+
| `ScalarTarget.JavaScript` | `ScalarClient.Fetch` | Browser fetch() |
|
|
154
|
+
| `ScalarTarget.Python` | `ScalarClient.Requests` | Python requests |
|
|
155
|
+
| `ScalarTarget.Shell` | `ScalarClient.Curl` | cURL command |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Anti-Patterns
|
|
160
|
+
|
|
161
|
+
| Do NOT | Do Instead |
|
|
162
|
+
|--------|------------|
|
|
163
|
+
| Install Swashbuckle | Use `Microsoft.AspNetCore.OpenApi` + `Scalar.AspNetCore` |
|
|
164
|
+
| Expose OpenAPI in production | Guard with `IsDevelopment()` check |
|
|
165
|
+
| Skip `.WithTags()` on endpoints | Every endpoint needs a tag for organized docs |
|
|
166
|
+
| Skip `.Produces<T>()` | Always document response types for accurate API docs |
|
|
167
|
+
| Use magic strings for tags | Define tags in `Constants/ApiTags.cs` |
|
|
168
|
+
| Map Scalar before `MapOpenApi()` | `MapOpenApi()` first, then `MapScalarApiReference()` |
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
*MORPH-SPEC by Polymorphism Tech*
|