Skip to content

Commit 7d9d3f7

Browse files
authored
Merge pull request #5 from codeforamerica/application-schema-restructure
Application schema restructure
2 parents 681b3d2 + 1f367b9 commit 7d9d3f7

File tree

27 files changed

+4710
-4324
lines changed

27 files changed

+4710
-4324
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@ Visit `http://localhost:3000` for interactive API docs.
3737
| `npm run validate` | Validate base specs |
3838
| `npm run validate:state` | Validate specs for current STATE |
3939
| `npm run validate:all-states` | Validate all states |
40-
| `npm run clients:generate` | Generate TypeScript clients |
40+
| `npm run clients:generate` | Generate Zodios TypeScript clients |
41+
| `npm run clients:validate` | Type-check generated clients |
4142
| `npm run postman:generate` | Generate Postman collection |
4243
| `npm run mock:reset` | Reset database to example data |
4344
| `npm test` | Run unit tests |
45+
| `npm run test:integration` | Run integration tests (includes Postman/newman) |
4446

4547
[Full command reference →](./docs/reference/commands.md)
4648

docs/architecture-decisions/multi-state-overlays.md

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# ADR: Multi-State Support Using OpenAPI Overlays
22

3-
**Status:** Proposed
3+
**Status:** Accepted
44

5-
**Date:** 2025-12-18
5+
**Date:** 2026-01-06
66

77
**Deciders:** Development Team
88

@@ -41,7 +41,7 @@ We chose **OpenAPI Overlay Specification (1.0.0)** with a configuration-driven s
4141
### How It Works
4242

4343
1. **Base schemas** in `openapi/` define the universal structure
44-
2. **Overlay files** in `openapi/overlays/{state}.overlay.yaml` declare state-specific modifications
44+
2. **Overlay files** in `openapi/overlays/{state}/modifications.yaml` declare state-specific modifications
4545
3. **Resolve script** merges base + overlay → `openapi/resolved/` at build time
4646
4. **All tooling** operates on resolved specs, unaware of the overlay system
4747

@@ -64,8 +64,13 @@ openapi/
6464
│ ├── person.yaml # Base schema
6565
│ └── application.yaml # Base schema
6666
├── overlays/
67-
│ ├── california.overlay.yaml
68-
│ └── colorado.overlay.yaml
67+
│ ├── california/
68+
│ │ ├── modifications.yaml # Overlay actions
69+
│ │ └── replacements/ # Complete schema replacements
70+
│ │ └── expenses.yaml
71+
│ └── colorado/
72+
│ ├── modifications.yaml
73+
│ └── replacements/
6974
└── resolved/ # .gitignored, generated at build time
7075
├── persons.yaml
7176
└── components/
@@ -75,6 +80,7 @@ openapi/
7580
### Overlay File Format
7681

7782
```yaml
83+
# overlays/california/modifications.yaml
7884
overlay: 1.0.0
7985
info:
8086
title: California State Overlay
@@ -106,20 +112,29 @@ actions:
106112
- target: $.Person.properties.federalProgramId
107113
description: Use California-specific name
108114
rename: calworksId
115+
116+
# Replace entire schema with state-specific structure (custom extension)
117+
- target: $.PersonExpenses
118+
description: California expense tracking structure
119+
replace:
120+
$ref: "./replacements/expenses.yaml#/CaliforniaExpenses"
109121
```
110122
111123
### Custom Extensions
112124
113-
We extend the OpenAPI Overlay spec with a `rename` action for renaming properties:
125+
We extend the OpenAPI Overlay spec with custom actions:
114126
115127
| Action | Standard | Description |
116128
|--------|----------|-------------|
117129
| `update` | Yes | Merge/replace values at target path |
118130
| `remove` | Yes | Delete value at target path |
119131
| `rename` | **No** (custom) | Rename property, preserving full definition |
132+
| `replace` | **No** (custom) | Complete replacement of target (no merging) |
120133

121134
The `rename` action copies the entire property definition to a new key and removes the old key. This is useful when states use different terminology for the same concept without having to duplicate the property definition.
122135

136+
The `replace` action completely replaces the target value (unlike `update` which merges objects). It supports `$ref` to load replacement schemas from separate files in the `replacements/` directory. This is useful when a state needs a fundamentally different structure that can't be achieved through property updates.
137+
123138
---
124139

125140
## Options Considered
@@ -260,12 +275,12 @@ openapi/
260275
261276
| File | Change |
262277
|------|--------|
263-
| `src/overlay/overlay-resolver.js` | Core overlay resolution logic with custom `rename` action support |
278+
| `src/overlay/overlay-resolver.js` | Core overlay resolution logic with `rename` and `replace` action support |
264279
| `scripts/resolve-overlay.js` | CLI script to apply overlays with target validation |
265280
| `scripts/validate-state.js` | New script to resolve and validate state specs |
266281
| `scripts/validate-patterns.js` | Updated to support `--dir` argument |
267-
| `openapi/overlays/california.overlay.yaml` | California state overlay |
268-
| `openapi/overlays/colorado.overlay.yaml` | Colorado state overlay |
282+
| `openapi/overlays/{state}/modifications.yaml` | State-specific overlay actions |
283+
| `openapi/overlays/{state}/replacements/` | State-specific schema replacements |
269284
| `openapi/resolved/` | Generated directory (.gitignored) |
270285
| `package.json` | Added overlay and state validation scripts |
271286
| `.gitignore` | Added `openapi/resolved/` |

docs/guides/state-overlays.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ State overlays allow you to customize API specifications for different states wi
55
## How It Works
66

77
1. **Base schemas** in `openapi/` define the universal structure
8-
2. **Overlay files** in `openapi/overlays/{state}.overlay.yaml` declare modifications
8+
2. **Overlay files** in `openapi/overlays/{state}/modifications.yaml` declare modifications
99
3. **Resolve script** merges base + overlay into `openapi/resolved/`
1010
4. **All tooling** operates on resolved specs
1111

@@ -33,7 +33,7 @@ npm run overlay:resolve
3333
Overlays use the [OpenAPI Overlay Specification 1.0.0](https://github.com/OAI/Overlay-Specification):
3434

3535
```yaml
36-
# openapi/overlays/california.overlay.yaml
36+
# openapi/overlays/california/modifications.yaml
3737
overlay: 1.0.0
3838
info:
3939
title: California State Overlay
@@ -133,11 +133,12 @@ Targets use JSONPath-like syntax:
133133

134134
## Creating a New State Overlay
135135

136-
### 1. Create the Overlay File
136+
### 1. Create the Overlay Directory and File
137137

138138
```bash
139-
# Copy an existing overlay as a template
140-
cp openapi/overlays/california.overlay.yaml openapi/overlays/newstate.overlay.yaml
139+
# Create state directory and copy an existing overlay as a template
140+
mkdir openapi/overlays/newstate
141+
cp openapi/overlays/california/modifications.yaml openapi/overlays/newstate/modifications.yaml
141142
```
142143

143144
### 2. Update the Metadata

docs/reference/commands.md

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ All available npm scripts in the Safety Net OpenAPI toolkit.
1010
| `npm run validate` | Validate base specs |
1111
| `npm run validate:state` | Validate specs for current STATE |
1212
| `npm run validate:all-states` | Validate all states |
13-
| `npm run clients:generate` | Generate TypeScript clients |
13+
| `npm run clients:generate` | Generate Zodios TypeScript clients |
14+
| `npm run clients:validate` | Type-check generated clients |
1415
| `npm run postman:generate` | Generate Postman collection |
1516
| `npm run mock:start` | Start mock server only |
1617
| `npm run mock:reset` | Reset database to example data |
1718
| `npm test` | Run unit tests |
19+
| `npm run test:integration` | Run integration tests (includes Postman/newman) |
1820

1921
## Validation Commands
2022

@@ -117,20 +119,30 @@ Creates:
117119
Generates TypeScript/Zodios clients from specs.
118120

119121
```bash
120-
STATE=california npm run clients:generate
122+
npm run clients:generate
121123
```
122124

123-
Output: `generated/clients/zodios/*.ts`
125+
Output: `packages/clients/generated/clients/zodios/*.ts`
126+
127+
### `npm run clients:validate`
128+
129+
Type-checks the generated Zodios clients using TypeScript.
130+
131+
```bash
132+
npm run clients:validate
133+
```
134+
135+
Runs `tsc --noEmit` to verify all generated clients compile without errors.
124136

125137
### `npm run postman:generate`
126138

127139
Generates a Postman collection from specs.
128140

129141
```bash
130-
STATE=california npm run postman:generate
142+
npm run postman:generate
131143
```
132144

133-
Output: `generated/postman-collection.json`
145+
Output: `packages/clients/generated/postman-collection.json`
134146

135147
## Server Commands
136148

@@ -205,12 +217,17 @@ Alias for `npm test`.
205217

206218
### `npm run test:integration`
207219

208-
Runs integration tests (requires mock server).
220+
Runs integration tests against the mock server. Automatically starts the server if not running.
209221

210222
```bash
211223
npm run test:integration
212224
```
213225

226+
Includes:
227+
- CRUD operation tests for all discovered APIs
228+
- Cross-API accessibility tests
229+
- Postman collection execution via Newman
230+
214231
### `npm run test:all`
215232

216233
Runs both unit and integration tests.
@@ -239,9 +256,9 @@ npm run validate && npm run validate:all-states
239256
# Reset and start
240257
npm run mock:reset && npm start
241258

242-
# Generate all artifacts
243-
npm run clients:generate && npm run postman:generate
259+
# Generate and validate all artifacts
260+
npm run clients:generate && npm run clients:validate && npm run postman:generate
244261

245-
# Validate and test
246-
npm run validate:state && npm test
262+
# Full test suite
263+
npm run validate && npm test && npm run test:integration
247264
```

docs/reference/project-structure.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ safety-net-openapi/
2525
│ │ │ ├── patterns/ # API design patterns
2626
│ │ │ │ └── api-patterns.yaml
2727
│ │ │ ├── overlays/ # State-specific variations
28-
│ │ │ │ ├── california.overlay.yaml
29-
│ │ │ │ └── colorado.overlay.yaml
28+
│ │ │ │ ├── california/
29+
│ │ │ │ │ └── modifications.yaml
30+
│ │ │ │ └── colorado/
31+
│ │ │ │ └── modifications.yaml
3032
│ │ │ └── resolved/ # Generated state specs (gitignored)
3133
│ │ ├── src/
3234
│ │ │ ├── overlay/ # Overlay resolution logic
@@ -95,7 +97,7 @@ npm install -w @safety-net/schemas -w @safety-net/mock-server
9597
| API specs | kebab-case | `case-workers.yaml` |
9698
| Component schemas | kebab-case | `case-worker.yaml` |
9799
| Example files | kebab-case | `case-workers.yaml` |
98-
| Overlay files | kebab-case + `.overlay` | `california.overlay.yaml` |
100+
| Overlay files | `{state}/modifications.yaml` | `california/modifications.yaml` |
99101
| Scripts | kebab-case | `generate-clients.js` |
100102
| Tests | kebab-case + `.test` | `overlay-resolver.test.js` |
101103

@@ -127,7 +129,7 @@ npm install -w @safety-net/schemas -w @safety-net/mock-server
127129
| `packages/schemas/openapi/*.yaml` | Main API specifications |
128130
| `packages/schemas/openapi/components/*.yaml` | Reusable schemas |
129131
| `packages/schemas/openapi/examples/*.yaml` | Example data |
130-
| `packages/schemas/openapi/overlays/*.overlay.yaml` | State variations |
132+
| `packages/schemas/openapi/overlays/*/modifications.yaml` | State variations |
131133

132134
### Generated (Gitignored)
133135

@@ -154,7 +156,7 @@ npm run api:new -- --name "benefits" --resource "Benefit"
154156

155157
## Adding State Overlays
156158

157-
1. Create overlay: `packages/schemas/openapi/overlays/{state}.overlay.yaml`
159+
1. Create overlay directory and file: `packages/schemas/openapi/overlays/{state}/modifications.yaml`
158160
2. Define actions for state-specific changes
159161
3. Validate: `STATE={state} npm run validate:state`
160162

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"overlay:resolve": "npm run overlay:resolve -w @safety-net/schemas",
2222
"overlay:list": "npm run overlay:list -w @safety-net/schemas",
2323
"clients:generate": "npm run generate -w @safety-net/clients",
24+
"clients:validate": "npm run validate -w @safety-net/clients",
2425
"postman:generate": "npm run postman -w @safety-net/clients",
2526
"mock:start": "npm start -w @safety-net/mock-server",
2627
"mock:setup": "npm run setup -w @safety-net/mock-server",

packages/clients/package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@
55
"type": "module",
66
"scripts": {
77
"generate": "node scripts/generate-zodios.js",
8-
"postman": "node scripts/generate-postman.js"
8+
"postman": "node scripts/generate-postman.js",
9+
"validate": "tsc --noEmit -p tsconfig.validate.json"
910
},
1011
"dependencies": {
1112
"@apidevtools/json-schema-ref-parser": "^11.7.2",
1213
"@safety-net/schemas": "*",
13-
"js-yaml": "^4.1.0"
14+
"@zodios/core": "^10.9.6",
15+
"js-yaml": "^4.1.0",
16+
"zod": "^3.23.8"
1417
},
1518
"devDependencies": {
16-
"openapi-zod-client": "^1.18.2"
19+
"openapi-zod-client": "^1.18.2",
20+
"typescript": "^5.3.3"
1721
}
1822
}

packages/clients/scripts/generate-zodios.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { validateSpec } from '@safety-net/schemas/validation';
1010
const __filename = fileURLToPath(import.meta.url);
1111
const __dirname = dirname(__filename);
1212
const workspaceRoot = join(__dirname, '..');
13+
const schemasRoot = join(__dirname, '..', '..', 'schemas');
1314

1415
/**
1516
* Executes a command and returns a promise
@@ -128,8 +129,8 @@ async function generateClient(specPath) {
128129
* Main function to generate all clients
129130
*/
130131
async function main() {
131-
const openAPIDir = join(workspaceRoot, 'openapi');
132-
132+
const openAPIDir = join(schemasRoot, 'openapi');
133+
133134
console.log('🚀 Starting Zodios API client generation...');
134135
console.log(`📂 Searching for OpenAPI specs in: ${openAPIDir}\n`);
135136

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"module": "ESNext",
5+
"moduleResolution": "bundler",
6+
"strict": true,
7+
"skipLibCheck": true,
8+
"noEmit": true
9+
},
10+
"include": [
11+
"generated/clients/zodios/**/*.ts"
12+
]
13+
}

packages/mock-server/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,8 @@
2020
"express": "^5.1.0",
2121
"js-yaml": "^4.1.0",
2222
"swagger-ui-express": "^5.0.1"
23+
},
24+
"devDependencies": {
25+
"newman": "^6.2.1"
2326
}
2427
}

0 commit comments

Comments
 (0)