docs: add SDK generation design (S3.4)
Design document for OpenAPI SDK generation feature: - Hybrid generators (native + openapi-generator fallback) - Core 4 languages: TypeScript, Python, Go, PHP - Auto-detection: config → common paths → Laravel Scramble - Breaking change detection with oasdiff - Monorepo publish support Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4ae16dc09e
commit
53b3d2613d
1 changed files with 291 additions and 0 deletions
291
docs/plans/2026-01-29-sdk-generation-design.md
Normal file
291
docs/plans/2026-01-29-sdk-generation-design.md
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
# SDK Generation Design
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Generate typed API clients from OpenAPI specs for TypeScript, Python, Go, and PHP. Includes breaking change detection via semantic diff.
|
||||||
|
|
||||||
|
## Design Decisions
|
||||||
|
|
||||||
|
- **Generator approach**: Hybrid - native generators where available, openapi-generator fallback
|
||||||
|
- **Languages**: TypeScript, Python, Go, PHP (Core 4)
|
||||||
|
- **Detection**: Config → common paths → Laravel Scramble
|
||||||
|
- **Output**: Local `sdk/` + optional monorepo publish
|
||||||
|
- **Diff**: Semantic with oasdiff, CI-friendly exit codes
|
||||||
|
- **Priority**: DX (developer experience)
|
||||||
|
|
||||||
|
## Package Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
pkg/sdk/
|
||||||
|
├── sdk.go # Main SDK type, orchestration
|
||||||
|
├── detect.go # OpenAPI spec detection
|
||||||
|
├── diff.go # Breaking change detection (oasdiff)
|
||||||
|
├── generators/
|
||||||
|
│ ├── generator.go # Generator interface
|
||||||
|
│ ├── typescript.go # openapi-typescript-codegen
|
||||||
|
│ ├── python.go # openapi-python-client
|
||||||
|
│ ├── go.go # oapi-codegen
|
||||||
|
│ └── php.go # openapi-generator (Docker)
|
||||||
|
└── templates/ # Package scaffolding templates
|
||||||
|
├── typescript/
|
||||||
|
│ └── package.json.tmpl
|
||||||
|
├── python/
|
||||||
|
│ └── setup.py.tmpl
|
||||||
|
├── go/
|
||||||
|
│ └── go.mod.tmpl
|
||||||
|
└── php/
|
||||||
|
└── composer.json.tmpl
|
||||||
|
```
|
||||||
|
|
||||||
|
## OpenAPI Detection Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Check config: sdk.spec in .core/release.yaml
|
||||||
|
↓ not found
|
||||||
|
2. Check common paths:
|
||||||
|
- api/openapi.yaml
|
||||||
|
- api/openapi.json
|
||||||
|
- openapi.yaml
|
||||||
|
- openapi.json
|
||||||
|
- docs/api.yaml
|
||||||
|
- swagger.yaml
|
||||||
|
↓ not found
|
||||||
|
3. Laravel Scramble detection:
|
||||||
|
- Check for scramble/scramble in composer.json
|
||||||
|
- Run: php artisan scramble:export --path=api/openapi.json
|
||||||
|
- Use generated spec
|
||||||
|
↓ not found
|
||||||
|
4. Error: No OpenAPI spec found
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generator Interface
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Generator interface {
|
||||||
|
// Language returns the generator's target language
|
||||||
|
Language() string
|
||||||
|
|
||||||
|
// Generate creates SDK from OpenAPI spec
|
||||||
|
Generate(ctx context.Context, opts GenerateOptions) error
|
||||||
|
|
||||||
|
// Available checks if generator dependencies are installed
|
||||||
|
Available() bool
|
||||||
|
|
||||||
|
// Install provides installation instructions
|
||||||
|
Install() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenerateOptions struct {
|
||||||
|
SpecPath string // OpenAPI spec file
|
||||||
|
OutputDir string // Where to write SDK
|
||||||
|
PackageName string // Package/module name
|
||||||
|
Version string // SDK version
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Native Generators
|
||||||
|
|
||||||
|
| Language | Tool | Install |
|
||||||
|
|------------|----------------------------|--------------------------------|
|
||||||
|
| TypeScript | openapi-typescript-codegen | `npm i -g openapi-typescript-codegen` |
|
||||||
|
| Python | openapi-python-client | `pip install openapi-python-client` |
|
||||||
|
| Go | oapi-codegen | `go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@latest` |
|
||||||
|
| PHP | openapi-generator (Docker) | Requires Docker |
|
||||||
|
|
||||||
|
### Fallback Strategy
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (g *TypeScriptGenerator) Generate(ctx context.Context, opts GenerateOptions) error {
|
||||||
|
if g.Available() {
|
||||||
|
return g.generateNative(ctx, opts)
|
||||||
|
}
|
||||||
|
return g.generateDocker(ctx, opts) // openapi-generator in Docker
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Breaking Change Detection
|
||||||
|
|
||||||
|
Using [oasdiff](https://github.com/Tufin/oasdiff) for semantic OpenAPI comparison:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/tufin/oasdiff/diff"
|
||||||
|
import "github.com/tufin/oasdiff/checker"
|
||||||
|
|
||||||
|
func (s *SDK) Diff(base, revision string) (*DiffResult, error) {
|
||||||
|
// Load specs
|
||||||
|
baseSpec, _ := load.From(loader, base)
|
||||||
|
revSpec, _ := load.From(loader, revision)
|
||||||
|
|
||||||
|
// Compute diff
|
||||||
|
d, _ := diff.Get(diff.NewConfig(), baseSpec, revSpec)
|
||||||
|
|
||||||
|
// Check for breaking changes
|
||||||
|
breaks := checker.CheckBackwardCompatibility(
|
||||||
|
checker.GetDefaultChecks(),
|
||||||
|
d,
|
||||||
|
baseSpec,
|
||||||
|
revSpec,
|
||||||
|
)
|
||||||
|
|
||||||
|
return &DiffResult{
|
||||||
|
Breaking: len(breaks) > 0,
|
||||||
|
Changes: breaks,
|
||||||
|
Summary: formatSummary(d),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exit Codes for CI
|
||||||
|
|
||||||
|
| Exit Code | Meaning |
|
||||||
|
|-----------|---------|
|
||||||
|
| 0 | No breaking changes |
|
||||||
|
| 1 | Breaking changes detected |
|
||||||
|
| 2 | Error (invalid spec, etc.) |
|
||||||
|
|
||||||
|
### Breaking Change Categories
|
||||||
|
|
||||||
|
- Removed endpoints
|
||||||
|
- Changed required parameters
|
||||||
|
- Modified response schemas
|
||||||
|
- Changed authentication requirements
|
||||||
|
|
||||||
|
## CLI Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate SDKs from OpenAPI spec
|
||||||
|
core sdk generate # Uses .core/release.yaml config
|
||||||
|
core sdk generate --spec api.yaml # Explicit spec file
|
||||||
|
core sdk generate --lang typescript # Single language
|
||||||
|
|
||||||
|
# Check for breaking changes
|
||||||
|
core sdk diff # Compare current vs last release
|
||||||
|
core sdk diff --spec api.yaml --base v1.0.0
|
||||||
|
|
||||||
|
# Validate spec before generation
|
||||||
|
core sdk validate
|
||||||
|
core sdk validate --spec api.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Config Schema
|
||||||
|
|
||||||
|
In `.core/release.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sdk:
|
||||||
|
# OpenAPI spec source (auto-detected if omitted)
|
||||||
|
spec: api/openapi.yaml
|
||||||
|
|
||||||
|
# Languages to generate
|
||||||
|
languages:
|
||||||
|
- typescript
|
||||||
|
- python
|
||||||
|
- go
|
||||||
|
- php
|
||||||
|
|
||||||
|
# Output directory (default: sdk/)
|
||||||
|
output: sdk/
|
||||||
|
|
||||||
|
# Package naming
|
||||||
|
package:
|
||||||
|
name: myapi # Base name
|
||||||
|
version: "{{.Version}}"
|
||||||
|
|
||||||
|
# Breaking change detection
|
||||||
|
diff:
|
||||||
|
enabled: true
|
||||||
|
fail_on_breaking: true # CI fails on breaking changes
|
||||||
|
|
||||||
|
# Optional: publish to monorepo
|
||||||
|
publish:
|
||||||
|
repo: myorg/sdks
|
||||||
|
path: packages/myapi
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Structure
|
||||||
|
|
||||||
|
Each generator outputs to `sdk/{lang}/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
sdk/
|
||||||
|
├── typescript/
|
||||||
|
│ ├── package.json
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── index.ts
|
||||||
|
│ │ ├── client.ts
|
||||||
|
│ │ └── models/
|
||||||
|
│ └── tsconfig.json
|
||||||
|
├── python/
|
||||||
|
│ ├── setup.py
|
||||||
|
│ ├── myapi/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── client.py
|
||||||
|
│ │ └── models/
|
||||||
|
│ └── requirements.txt
|
||||||
|
├── go/
|
||||||
|
│ ├── go.mod
|
||||||
|
│ ├── client.go
|
||||||
|
│ └── models.go
|
||||||
|
└── php/
|
||||||
|
├── composer.json
|
||||||
|
├── src/
|
||||||
|
│ ├── Client.php
|
||||||
|
│ └── Models/
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Publishing Workflow
|
||||||
|
|
||||||
|
SDK publishing integrates with the existing release pipeline:
|
||||||
|
|
||||||
|
```
|
||||||
|
core release
|
||||||
|
→ build artifacts
|
||||||
|
→ generate SDKs (if sdk: configured)
|
||||||
|
→ run diff check (warns or fails on breaking)
|
||||||
|
→ publish to GitHub release
|
||||||
|
→ publish SDKs (optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Monorepo Publishing
|
||||||
|
|
||||||
|
For projects using a shared SDK monorepo:
|
||||||
|
|
||||||
|
1. Clone target repo (shallow)
|
||||||
|
2. Update `packages/{name}/{lang}/`
|
||||||
|
3. Commit with version tag
|
||||||
|
4. Push (triggers downstream CI)
|
||||||
|
|
||||||
|
The SDK tarball is also attached to GitHub releases for direct download.
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
1. Create `pkg/sdk/` package structure
|
||||||
|
2. Implement OpenAPI detection (`detect.go`)
|
||||||
|
3. Define Generator interface (`generators/generator.go`)
|
||||||
|
4. Implement TypeScript generator (native + fallback)
|
||||||
|
5. Implement Python generator (native + fallback)
|
||||||
|
6. Implement Go generator (native)
|
||||||
|
7. Implement PHP generator (Docker-based)
|
||||||
|
8. Add package templates (`templates/`)
|
||||||
|
9. Implement diff with oasdiff (`diff.go`)
|
||||||
|
10. Add CLI commands (`cmd/core/sdk.go`)
|
||||||
|
11. Integrate with release pipeline
|
||||||
|
12. Add monorepo publish support
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
```go
|
||||||
|
// go.mod additions
|
||||||
|
require (
|
||||||
|
github.com/tufin/oasdiff v1.x.x
|
||||||
|
github.com/getkin/kin-openapi v0.x.x
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- Unit tests for each generator
|
||||||
|
- Integration tests with sample OpenAPI specs
|
||||||
|
- Diff tests with known breaking/non-breaking changes
|
||||||
|
- E2E test generating SDKs for a real API
|
||||||
Loading…
Add table
Reference in a new issue