Adopt the rules-swift pin-and-codify strategy in snapshot mode (third_party/rules-swift @97dc70f + sha256), add codified gates, a conformance audit ledger, and .github/workflows/rules-swift.yml (corpus-pin integrity required, full gate report mode). Remove the docs/rules mirror it supersedes (repo-specific files kept under docs/). Style and leak gates exclude the vendored corpus.
196 lines
5.2 KiB
Markdown
196 lines
5.2 KiB
Markdown
---
|
|
description: Guardrails for OpenAPI-generated code and spec files (Swift projects)
|
|
globs: []
|
|
alwaysApply: true
|
|
---
|
|
# OpenAPI Generated Code and Spec Guardrails
|
|
|
|
Never hand-edit OpenAPI-generated Swift sources or OpenAPI specs. The spec is the
|
|
source of truth: generated code is a build artifact derived from it, and any hand
|
|
edit is lost on the next generation. When a change seems necessary, change the
|
|
spec, regenerate, and put your own code in hand-written modules, or propose a spec
|
|
change without touching the generated artifacts.
|
|
|
|
## Core rules
|
|
|
|
### Rule 1: Do-not-touch zones
|
|
|
|
Do not edit files that are:
|
|
|
|
- Generated by the OpenAPI generator.
|
|
- Located under typical generation paths:
|
|
- `Sources/**/Generated/**`
|
|
- `.build/**`
|
|
- `DerivedData/**`
|
|
- `**/OpenAPI/Generated/**`
|
|
- The OpenAPI spec(s): `openapi.yaml`, `openapi.yml`, `**/openapi/*.yaml`.
|
|
|
|
### Rule 2: Analysis-only
|
|
|
|
Analyse, do not change. If asked to "analyse only", do not offer diffs for the
|
|
spec or the generated files. Provide findings plus actionable recommendations for
|
|
where to apply changes safely.
|
|
|
|
### Rule 3: Safe change targets
|
|
|
|
Direct code changes to:
|
|
|
|
- Hand-written modules (the API client, the API server, the shared model module).
|
|
- Adapters, mappers, and extensions in non-generated targets.
|
|
- Dependency-injected services and repositories.
|
|
- A separate notes or proposals file for spec-change suggestions.
|
|
|
|
### Rule 4: Change requests to specs
|
|
|
|
If a spec change is unavoidable:
|
|
|
|
- Produce a proposal file (for example
|
|
`Docs/OpenAPI-Proposals/YYYY-MM-DD-what-why.md`).
|
|
- Include the motivation, example payloads, and migration steps.
|
|
- Do **not** modify the spec inline as part of that proposal.
|
|
|
|
## Decision tree
|
|
|
|
```
|
|
Need to modify API behaviour?
|
|
├─ Is it in generated code?
|
|
│ ├─ YES → Do NOT modify
|
|
│ │ ├─ Create an extension in a hand-written module
|
|
│ │ ├─ Add an adapter / mapper layer
|
|
│ │ └─ Use dependency injection
|
|
│ └─ NO → Safe to modify (the API client, the API server, and so on)
|
|
├─ Is it the OpenAPI spec?
|
|
│ ├─ YES → Do NOT modify inline
|
|
│ │ ├─ Create a proposal under Docs/OpenAPI-Proposals/
|
|
│ │ ├─ Include motivation and examples
|
|
│ │ └─ Wait for approval, then change the spec and regenerate
|
|
│ └─ NO → Safe to modify
|
|
└─ Is it a DTO mapping?
|
|
├─ Generated DTO → Create a domain model plus a mapper
|
|
└─ Domain model → Safe to modify
|
|
```
|
|
|
|
## Implementation patterns
|
|
|
|
### Pattern 1: Extension on generated types
|
|
|
|
```swift
|
|
// CORRECT: extend in a hand-written module
|
|
// File: ApiClient/Extensions/User+Domain.swift
|
|
import ApiShared
|
|
|
|
extension Components.Schemas.UserDTO {
|
|
func toDomain() -> User {
|
|
User(
|
|
id: UUID(uuidString: self.id) ?? UUID(),
|
|
name: self.name,
|
|
email: self.email
|
|
)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Pattern 2: Adapter layer
|
|
|
|
```swift
|
|
// CORRECT: adapter in the API client package
|
|
// File: ApiClient/Adapters/UserAdapter.swift
|
|
import ApiShared
|
|
import SharedModels
|
|
|
|
public struct UserAdapter {
|
|
public static func toDomain(_ dto: Components.Schemas.UserDTO) -> User {
|
|
User(
|
|
id: UUID(uuidString: dto.id) ?? UUID(),
|
|
name: dto.name,
|
|
email: dto.email
|
|
)
|
|
}
|
|
|
|
public static func toDTO(_ user: User) -> Components.Schemas.UserDTO {
|
|
Components.Schemas.UserDTO(
|
|
id: user.id.uuidString,
|
|
name: user.name,
|
|
email: user.email
|
|
)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Pattern 3: Spec change proposal
|
|
|
|
````markdown
|
|
// File: Docs/OpenAPI-Proposals/2025-01-15-add-user-avatar.md
|
|
|
|
# Proposal: add a user avatar field
|
|
|
|
## Motivation
|
|
Users need profile pictures for the social features.
|
|
|
|
## Proposed change
|
|
Add an `avatarUrl` field to the UserDTO schema.
|
|
|
|
## Example payload
|
|
```yaml
|
|
UserDTO:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
name:
|
|
type: string
|
|
avatarUrl: # NEW
|
|
type: string
|
|
format: uri
|
|
nullable: true
|
|
```
|
|
|
|
## Migration steps
|
|
1. Update the OpenAPI spec.
|
|
2. Regenerate the Swift code.
|
|
3. Update the domain model.
|
|
4. Add a migration for existing users.
|
|
````
|
|
|
|
## Anti-patterns
|
|
|
|
### Do NOT modify generated files
|
|
|
|
```swift
|
|
// WRONG: editing Generated/Types.swift
|
|
// This file is overwritten on the next generation.
|
|
extension Components.Schemas.UserDTO {
|
|
var fullName: String { ... } // Lost on regenerate.
|
|
}
|
|
```
|
|
|
|
### Do NOT edit the spec directly without a proposal
|
|
|
|
```yaml
|
|
# WRONG: a direct, undocumented edit to the spec
|
|
UserDTO:
|
|
properties:
|
|
newField: # Added without a proposal
|
|
type: string
|
|
```
|
|
|
|
## Checklist
|
|
|
|
Before modifying API-related code:
|
|
|
|
- [ ] Verified the target file is NOT in a `Generated/` folder.
|
|
- [ ] Verified the target file is NOT the OpenAPI spec.
|
|
- [ ] Changes go to hand-written modules (the API client, the API server).
|
|
- [ ] Extensions or adapters created for generated-type customisation.
|
|
- [ ] Spec changes documented in a proposal file first.
|
|
- [ ] Domain models separated from DTOs.
|
|
|
|
## Verification
|
|
|
|
When working with OpenAPI:
|
|
|
|
1. Check the file path before any edit.
|
|
2. Never modify `.build/`, `DerivedData/`, or `Generated/` paths.
|
|
3. Create adapters for DTO-to-domain mapping.
|
|
4. Document spec changes in proposals first, then change the spec and regenerate.
|