Project 8: api-forge (API Schema to CLI)
Build a CLI that converts an OpenAPI schema into a usable command tree with safe auth and output.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 3 (Advanced) |
| Time Estimate | 2 weeks |
| Main Programming Language | Rust (Alternatives: Go, TypeScript) |
| Alternative Programming Languages | Go, TypeScript |
| Coolness Level | Level 4: Impressive |
| Business Potential | 3: Micro-SaaS |
| Prerequisites | CLI grammar, HTTP APIs, code generation |
| Key Topics | schema parsing, codegen, auth, output formatting |
1. Learning Objectives
By completing this project, you will:
- Parse OpenAPI schemas and map them to CLI commands.
- Generate a command tree that is discoverable and ergonomic.
- Handle authentication and config safely.
- Produce consistent JSON output and error shapes.
- Create a code generation pipeline with templates and tests.
2. All Theory Needed (Per-Concept Breakdown)
2.1 Mapping REST APIs to CLI Grammar
Fundamentals
REST APIs are organized around resources and HTTP methods. A CLI must translate those into a command tree that humans can discover. A naive 1:1 mapping of paths to commands often produces unreadable commands. A better approach groups endpoints by tag or resource, then maps HTTP methods to verbs like list, get, create, update, and delete. This makes the CLI feel natural while still reflecting the API structure.
Deep Dive into the concept
OpenAPI schemas describe endpoints with paths, methods, parameters, and tags. You must decide how to convert these into CLI commands. Tag grouping is usually the best choice because it aligns with the API’s conceptual modules. For example, endpoints tagged users become api-forge users list, users get, users create. Path parameters become positional arguments: /users/{id} -> users get <id>. Query parameters become flags: --limit, --page. Request bodies become JSON files or --data flags. You should avoid requiring the user to type large JSON strings in the command line; instead, support --body @file.json and --data flags for quick tests.
The grammar must also handle nested resources. For example, /users/{id}/projects can map to users projects list <id>, or users <id> projects list. The latter is more intuitive because the resource ID comes before the sub-resource. Define a consistent rule and apply it across all endpoints. Consistency beats perfection.
Another issue is method naming. GET /users can be list or get-all. POST /users can be create. PATCH /users/{id} can be update. The mapping should be documented and predictable. For edge cases like POST /users/{id}/reset-password, you may need to map it to users reset-password <id> or users reset-password --id. Use operationId if available to preserve meaning.
Finally, help output must be clear. Each generated command should include a short summary from the OpenAPI description and examples if possible. This is a major usability improvement and reduces friction for API users.
How this fit on projects
This concept defines how api-forge generates the command tree and how users discover endpoints.
Definitions & key terms
- Resource: A noun-like API entity (users, projects).
- OperationId: Unique identifier for an API endpoint.
- Tag: OpenAPI grouping label.
- Verb mapping: Translating HTTP methods to CLI verbs.
Mental model diagram (ASCII)
OpenAPI tags -> command groups -> methods -> verbs -> flags
How it works (step-by-step)
- Parse OpenAPI schema and collect operations.
- Group by tag or resource.
- Map HTTP methods to CLI verbs.
- Map path params to positionals and query params to flags.
- Generate help text and usage.
Minimal concrete example
GET /users/{id} -> api-forge users get <id>
POST /users -> api-forge users create --body @user.json
Common misconceptions
- “All endpoints map directly to commands.” -> The grammar must be curated.
- “Path params should be flags.” -> Positionals are more ergonomic.
- “Generated help is optional.” -> It is critical for usability.
Check-your-understanding questions
- Why group endpoints by tag instead of path?
- How should query parameters be represented?
- What is the role of operationId in command naming?
Check-your-understanding answers
- Tags align with conceptual modules and improve discoverability.
- As flags, so they are explicit and optional.
- It can provide a stable command name for edge cases.
Real-world applications
awsandgcloudCLIs map API resources into command trees.kubectlmaps REST resources to verbs.
Where you will apply it
- See §3.1 What You Will Build and §4.1 High-Level Design.
- Also used in: Project 2: task-nexus and Project 9: plug-master.
References
- OpenAPI Specification
- Command Line Interface Guidelines
Key insights
A generated CLI must be opinionated about grammar to be usable.
Summary
Mapping REST to CLI is about turning endpoints into a consistent, human-friendly command tree.
Homework/Exercises to practice the concept
- Map
GET /users/{id}andPOST /usersto CLI commands. - Decide how to represent query parameters as flags.
- Design a rule for nested resources.
Solutions to the homework/exercises
users get <id>andusers create.- Use
--limit,--page,--filterflags. - Use
users <id> projects list.
2.2 Code Generation and Template Stability
Fundamentals
Code generation turns a schema into executable CLI commands. This requires templates, deterministic output, and a stable generator. If the generator outputs different code for the same input, it becomes impossible to version or test. Template stability means that given the same schema and config, the generated code and command list are always identical.
Deep Dive into the concept
A code generator is essentially a compiler from schema to CLI. It has a parser, a model, and a renderer. The parser reads the OpenAPI schema and builds an internal representation. The model normalizes and enriches it (grouping, naming, defaults). The renderer uses templates to output code or command definitions. Each step must be deterministic. This means stable ordering of operations, stable naming rules, and stable default values. For example, if you sort operations by path or tag name, you must define a tie-breaker so the output order does not depend on hash map iteration.
Templates should be small and composable. Instead of a single huge template, use partials for command definitions, option rendering, and help output. This makes it easier to change the generator without breaking everything. You should also create a golden test suite: given a sample schema, the generated output must match a committed snapshot. If the snapshot changes, you can detect regression and decide whether it is intentional.
Code generation also introduces output locations. You should generate into a dedicated generated/ folder and avoid overwriting user files. Provide a --out flag and refuse to overwrite unless --force is set. This mirrors the safety patterns from the init-wizard project. Determinism extends to timestamps: avoid writing generation timestamps into files unless you allow a fixed time value. This keeps diffs clean.
Finally, you should design the generator to be idempotent. Running it multiple times should produce the same output without accumulating changes. This is essential for CI workflows where generation happens during builds.
How this fit on projects
This concept defines how api-forge generates command definitions and ensures reproducible output and tests.
Definitions & key terms
- Generator pipeline: parse -> model -> render.
- Template partials: reusable pieces of templates.
- Golden tests: snapshot tests for generated output.
- Idempotent: repeated runs produce identical output.
Mental model diagram (ASCII)
schema -> parser -> model -> templates -> generated CLI
How it works (step-by-step)
- Parse OpenAPI schema into a structured model.
- Normalize and sort operations deterministically.
- Render templates into output files.
- Compare output to golden snapshots in tests.
Minimal concrete example
operations sorted by: tag, path, method
Common misconceptions
- “Generation order does not matter.” -> It affects diffs and tests.
- “Timestamps are harmless.” -> They make outputs non-deterministic.
- “Overwriting is safe.” -> It can destroy user changes.
Check-your-understanding questions
- Why are golden tests important for code generation?
- How do you enforce deterministic ordering?
- Why should generation be idempotent?
Check-your-understanding answers
- They catch unintended changes in output.
- Sort operations with explicit rules and tie-breakers.
- To ensure reproducible builds and clean diffs.
Real-world applications
protocand OpenAPI generators use deterministic pipelines.- Terraform providers generate SDKs with snapshot tests.
Where you will apply it
- See §3.2 Functional Requirements and §5.10 Implementation Phases.
- Also used in: Project 3: init-wizard and Project 10: distro-flow.
References
- OpenAPI Generator documentation
- Snapshot testing guides
Key insights
Deterministic generation is what makes codegen trustworthy and maintainable.
Summary
A generator must be deterministic, safe, and tested with golden snapshots.
Homework/Exercises to practice the concept
- Define a sorting rule for OpenAPI operations.
- Create a tiny template and render it with data.
- Build a snapshot test for generated output.
Solutions to the homework/exercises
- Sort by tag, then path, then method.
- Use a template engine and render a command stub.
- Compare rendered output to a committed fixture.
2.3 Authentication, Config, and Output Formats
Fundamentals
API CLIs must handle authentication safely. Tokens should never be passed as flags. Config and environment variables are safer. Output formats must support both humans and scripts. JSON output is usually the default because APIs already return structured data. Error responses should be normalized into a consistent shape, even if the API errors differ.
Deep Dive into the concept
Authentication strategies vary: API keys, bearer tokens, OAuth. Your CLI should support at least one method and allow configuration via env vars and config files. For example, API_FORGE_TOKEN can store a token and a config file can store the base URL. Avoid printing tokens in logs. If a token is missing, provide an error that explains how to set it. Also, consider token precedence: flags should override env, env should override config. The same precedence rules from task-nexus apply here.
Output formats should be stable. For a CLI that wraps APIs, JSON is natural, but you might also offer a table view for common list endpoints. If you do, ensure table output is optional and that JSON output includes all fields. Use a --json flag or --output json|table. For errors, define a unified error shape:
{"error":{"code":"UNAUTHORIZED","message":"missing token","status":401}}
This allows scripts to parse errors reliably. You should also propagate HTTP status codes into exit codes, but map them carefully. For example, exit code 2 for client errors (4xx), exit code 3 for server errors (5xx). Document this mapping and keep it stable.
Auth also interacts with request building. For GET endpoints, you can map query parameters to flags. For POST/PUT, you can support --body @file.json and --data key=value flags. The CLI must validate JSON input before sending to the API. This prevents the API from returning unclear errors.
Finally, API CLIs should avoid leaking secrets in logs. Redact Authorization headers in debug output. Provide a --debug flag that prints request metadata but not sensitive values. This is the same redaction discipline as env-vault.
How this fit on projects
This concept defines how api-forge handles authentication, configuration, and output formats for generated commands.
Definitions & key terms
- Bearer token: Token passed in Authorization header.
- Unified error shape: Standard JSON error response.
- Output mode: json or table formatting.
- Exit code mapping: Convert HTTP status to exit codes.
Mental model diagram (ASCII)
config/env -> auth -> request -> response -> format -> output
How it works (step-by-step)
- Load config and auth token from env or file.
- Build request with headers and body.
- Send HTTP request and parse response.
- Output JSON or table.
- Map HTTP status to exit code.
Minimal concrete example
$ api-forge users list --limit 5 --json
Common misconceptions
- “Tokens are fine in flags.” -> They leak into history.
- “Raw API errors are enough.” -> Scripts need consistent error shapes.
- “Table output is always better.” -> JSON is safer for automation.
Check-your-understanding questions
- Why should tokens be stored in env/config instead of flags?
- How do you design a unified error shape?
- What exit code mapping is reasonable for 4xx and 5xx?
Check-your-understanding answers
- Flags are exposed in history and process listings.
- Define
{error:{code,message,status}}and use it consistently. - Map 4xx to exit code 2, 5xx to exit code 3.
Real-world applications
awsandgcloudCLIs use env/config for auth.- Many API CLIs expose JSON output by default.
Where you will apply it
- See §3.5 Data Formats and §3.7 Real World Outcome.
- Also used in: Project 5: env-vault and Project 2: task-nexus.
References
- OpenAPI auth schemes
- OWASP API Security guidance
Key insights
Authentication and output formats are the trust layer of API CLIs.
Summary
Use env/config for auth, expose JSON output, and normalize errors for scripts.
Homework/Exercises to practice the concept
- Define an error shape for a fake API response.
- Design a config file schema for base URL and token.
- Map HTTP codes to exit codes.
Solutions to the homework/exercises
{error:{code,message,status}}with explicit fields.- Use keys
base_urlandtoken. - 4xx -> 2, 5xx -> 3.
3. Project Specification
3.1 What You Will Build
A CLI named api-forge that reads an OpenAPI schema and generates a command tree. It supports auth via config/env, JSON output by default, and deterministic code generation. It includes an init command that reads a schema and outputs command definitions.
3.2 Functional Requirements
- Schema parsing: OpenAPI v3 JSON/YAML.
- Command generation: group by tag/resource.
- Auth config: env and config file.
- Output modes: JSON default, table optional.
- Error normalization: unified error shape.
- Deterministic codegen: stable ordering and outputs.
3.3 Non-Functional Requirements
- Performance: generate 100 commands in under 2 seconds.
- Reliability: deterministic outputs for same schema.
- Usability: clear help output for generated commands.
3.4 Example Usage / Output
$ api-forge init ./openapi.json
Generated 42 commands
$ api-forge users list --limit 5 --json
[{"id":1,"name":"Alice"}, {"id":2,"name":"Bob"}]
3.5 Data Formats / Schemas / Protocols
{
"config": {"base_url":"https://api.example.com","token":"ENV:API_FORGE_TOKEN"},
"errors": {"shape": {"error": {"code":"UNAUTHORIZED","message":"missing token","status":401}}}
}
3.6 Edge Cases
- Schema missing tags -> group by path prefix.
- Auth token missing -> exit code 2 with instructions.
- Unsupported schema version -> exit code 3.
3.7 Real World Outcome
3.7.1 How to Run (Copy/Paste)
# Build
cargo build --release
# Generate
./target/release/api-forge init ./openapi.json
3.7.2 Golden Path Demo (Deterministic)
$ API_FORGE_TOKEN=demo ./target/release/api-forge users list --limit 2 --json
[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]
$ echo $?
0
3.7.3 Failure Demo (Deterministic)
$ ./target/release/api-forge users list --limit 2
api-forge: missing API token (set API_FORGE_TOKEN)
$ echo $?
2
3.7.4 Exit Codes
0: Success.2: Missing auth or invalid input.3: Schema or generation error.
4. Solution Architecture
4.1 High-Level Design
+------------------+
| Schema Parser |
+------------------+
|
v
+------------------+ +------------------+
| Command Model | --> | Code Generator |
+------------------+ +------------------+
| |
v v
+------------------+ +------------------+
| HTTP Client | --> | Output Formatter |
+------------------+ +------------------+
4.2 Key Components
| Component | Responsibility | Key Decisions |
|---|---|---|
| Parser | OpenAPI parsing | support JSON/YAML |
| Model | normalize ops | deterministic ordering |
| Generator | emit commands | template-based |
| Client | execute requests | auth from env/config |
| Formatter | output | JSON default |
4.3 Data Structures (No Full Code)
struct Operation {
tag: String,
method: String,
path: String,
params: Vec<Param>,
}
4.4 Algorithm Overview
Key Algorithm: Operation Mapping
- Parse operations from schema.
- Group by tag.
- Map HTTP methods to verbs.
- Generate command definitions.
Complexity Analysis:
- Time: O(O) operations.
- Space: O(O).
5. Implementation Guide
5.1 Development Environment Setup
cargo new api-forge
cd api-forge
5.2 Project Structure
src/
main.rs
parser.rs
model.rs
generate.rs
client.rs
5.3 The Core Question You’re Answering
“How do I design a command grammar that is generated from a schema without becoming unusable?”
5.4 Concepts You Must Understand First
- Mapping REST to CLI grammar.
- Deterministic code generation.
- Auth handling and output modes.
5.5 Questions to Guide Your Design
- Should commands be grouped by tag or path?
- How do you handle request bodies?
- What is the default output format?
5.6 Thinking Exercise
Map GET /users/{id} and POST /users into CLI commands and flags.
5.7 The Interview Questions They’ll Ask
- “How do you map REST endpoints to CLI commands?”
- “How do you avoid leaking API keys?”
- “How do you test code generation?”
5.8 Hints in Layers
Hint 1: Start with one tag Generate commands for one tag to validate grammar.
Hint 2: Add deterministic ordering Sort operations by tag/path/method.
Hint 3: Add auth config Support env var token.
Hint 4: Add JSON output Make JSON the default for scripts.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Architecture | Clean Architecture | Ch. 2 |
| Error handling | Effective C | Ch. 5 |
5.10 Implementation Phases
Phase 1: Schema Parsing (3-4 days)
Goals: parse OpenAPI and build model.
Phase 2: Generation (4-5 days)
Goals: templates and deterministic output.
Phase 3: Runtime CLI (3-4 days)
Goals: HTTP client, auth, output.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| Grouping | tag vs path | tag | better UX. |
| Output | json vs table | json default | scriptability. |
| Auth | env vs flags | env/config | avoid leaks. |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| Unit Tests | parsing | schema to model |
| Snapshot Tests | codegen | golden output |
| Integration Tests | HTTP client | mock server |
6.2 Critical Test Cases
- Same schema yields identical output.
- Missing token returns exit code 2.
- Error responses map to unified error shape.
6.3 Test Data
fixtures/openapi.json
7. Common Pitfalls and Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution |
|---|---|---|
| Direct path mapping | confusing commands | group by tag |
| Non-deterministic output | unstable diffs | sort operations |
| Token leakage | secrets in logs | redact headers |
7.2 Debugging Strategies
- Use mock servers for deterministic responses.
- Compare generated output against snapshots.
- Add verbose logs with redacted auth.
7.3 Performance Traps
- Generating code for each run instead of caching results.
8. Extensions and Challenges
8.1 Beginner Extensions
- Add
--dry-runto show request without sending. - Add
--curloutput mode.
8.2 Intermediate Extensions
- Add pagination helpers for list endpoints.
- Add environment profiles.
8.3 Advanced Extensions
- Generate SDKs in addition to CLI commands.
- Add interactive API explorer mode.
9. Real-World Connections
9.1 Industry Applications
- API CLIs for cloud services and internal platforms.
9.2 Related Open Source Projects
openapi-generatorandswagger-codegen.
9.3 Interview Relevance
- Code generation and API design are common backend topics.
10. Resources
10.1 Essential Reading
- OpenAPI Specification
- Clean Architecture, Ch. 2
10.2 Video Resources
- “OpenAPI Codegen” talks
10.3 Tools and Documentation
openapi-generatordocsserdeor JSON parsing docs
10.4 Related Projects in This Series
11. Self-Assessment Checklist
11.1 Understanding
- I can explain how REST endpoints map to CLI commands.
- I can explain deterministic code generation.
- I can explain auth precedence and output modes.
11.2 Implementation
- Generated commands are usable and documented.
- Auth works without leaking secrets.
- Output schema is stable.
11.3 Growth
- I can add new schema features without breaking output.
- I can demo both JSON and table output.
- I can explain codegen testing strategy.
12. Submission / Completion Criteria
Minimum Viable Completion:
- Parse schema and generate basic commands.
- Auth via env/config works.
Full Completion:
- Deterministic codegen with snapshot tests.
- Unified error shape.
Excellence (Going Above and Beyond):
- Interactive explorer or SDK generation.