Skip to content

Commit 7bd193a

Browse files
kevinccbsgclaude
andcommitted
chore: add CI/publish workflows and improve README
- Add GitHub Actions CI workflow (Node 20.x, 22.x, 24.x) - Add publish workflow (triggered by GitHub releases, supports beta tags) - Rewrite README with proper markdown, full API docs, warnings table - Add lcov coverage reporter for CI tools - Add files field to package.json for clean npm publishes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e42ce0d commit 7bd193a

5 files changed

Lines changed: 194 additions & 19 deletions

File tree

.github/workflows/ci.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
node-version: [20.x, 22.x, 24.x]
15+
steps:
16+
- name: Checkout repo
17+
uses: actions/checkout@v5
18+
19+
- name: Setup Node.js
20+
uses: actions/setup-node@v5
21+
with:
22+
node-version: ${{ matrix.node-version }}
23+
cache: npm
24+
25+
- name: Install dependencies
26+
run: npm ci
27+
28+
- name: Run Vitest
29+
run: npm run test:ci

.github/workflows/publish.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Publish
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
publish:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- name: Checkout repo
13+
uses: actions/checkout@v5
14+
15+
- name: Setup Node.js
16+
uses: actions/setup-node@v5
17+
with:
18+
node-version: 24
19+
registry-url: https://registry.npmjs.org/
20+
cache: npm
21+
22+
- name: Install dependencies
23+
run: npm ci
24+
25+
- name: Build package
26+
run: npm run build
27+
28+
- name: Publish to npm
29+
run: |
30+
if [ "${{ github.event.release.prerelease }}" = "true" ]; then
31+
npm publish --access public --tag beta
32+
else
33+
npm publish --access public
34+
fi
35+
env:
36+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

README.md

Lines changed: 125 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,52 +2,159 @@
22

33
Validate JSON payloads against OpenAPI 3.0/3.1 specs. Catch mock drift before it hits production.
44

5-
## Purpose
5+
## Why
66

77
Frontend teams write mock responses in tests that drift from reality over time. Fields get renamed, removed, or added in the API but mocks stay frozen. Tests pass, code ships, and the app breaks in production.
88

99
This package validates mock payloads against the OpenAPI spec — the source of truth. No YAML parsing, no URL fetching — consumers handle I/O, this package handles validation.
1010

1111
## Install
1212

13+
```bash
1314
npm install openapi-mock-validator
15+
```
1416

15-
## Usage
17+
## Quick Start
1618

19+
```typescript
1720
import { OpenAPIMockValidator } from 'openapi-mock-validator';
21+
import fs from 'node:fs';
1822

19-
// Consumers load the spec themselves (fetch, readFile, etc.)
23+
// Load the spec yourself (fetch, readFile, etc.)
2024
const spec = JSON.parse(fs.readFileSync('./openapi.json', 'utf-8'));
2125

2226
const validator = new OpenAPIMockValidator(spec);
2327
await validator.init();
2428

2529
// Match a mock URL to a spec path
2630
const match = validator.matchPath('/v1/orders/abc-123/status', 'GET');
27-
// { path: '/v1/orders/{id}/status', params: { id: 'abc-123' } }
31+
// { path: '/v1/orders/{id}/status', params: { id: 'abc-123' } }
2832

29-
// Validate mock response against the spec
30-
const result = validator.validateResponse(match.path, 'GET', 200, mockPayload);
31-
// { valid: false, errors: [...], warnings: [...] }
33+
if (match) {
34+
// Validate the mock response against the spec
35+
const result = validator.validateResponse(match.path, 'GET', 200, mockPayload);
36+
// → { valid: false, errors: [...], warnings: [...] }
37+
}
38+
```
3239

33-
// Validate request body
34-
const reqResult = validator.validateRequest('/v1/orders', 'POST', requestBody);
40+
## API
3541

36-
## Options
42+
### `new OpenAPIMockValidator(spec, options?)`
3743

38-
const validator = new OpenAPIMockValidator(spec, { strict: false });
39-
// strict (default: true) — reject additional properties not in spec
44+
Creates a validator instance. The spec must be a parsed OpenAPI 3.x JSON object.
4045

41-
// Can also override per call:
46+
```typescript
47+
const validator = new OpenAPIMockValidator(spec, {
48+
strict: true, // default: true — reject additional properties not in spec
49+
});
50+
```
51+
52+
### `validator.init()`
53+
54+
Dereferences all `$ref`s, normalizes OpenAPI 3.0 schemas to 3.1 format, and compiles path matchers. Must be called before any validation.
55+
56+
```typescript
57+
await validator.init();
58+
```
59+
60+
### `validator.matchPath(url, method)`
61+
62+
Matches a URL against the spec's paths. Returns the matched spec path and extracted parameters, or `null`.
63+
64+
```typescript
65+
const match = validator.matchPath('/v1/pets/abc-123', 'GET');
66+
// → { path: '/v1/pets/{petId}', params: { petId: 'abc-123' } }
67+
// → null if no match
68+
```
69+
70+
- Strips trailing slashes and query strings automatically
71+
- Prefers literal path segments over parameterized ones (`/orders/pending` beats `/orders/{id}`)
72+
73+
### `validator.validateResponse(path, method, status, payload, options?)`
74+
75+
Validates a response payload against the schema defined in the spec.
76+
77+
```typescript
78+
const result = validator.validateResponse('/v1/pets/{petId}', 'GET', 200, {
79+
id: 1,
80+
name: 'Fido',
81+
});
82+
```
83+
84+
Returns:
85+
86+
```typescript
87+
{
88+
valid: boolean;
89+
errors: ValidationError[]; // field-level mismatches
90+
warnings: ValidationWarning[]; // undocumented status codes, missing schemas
91+
}
92+
```
93+
94+
### `validator.validateRequest(path, method, payload, options?)`
95+
96+
Validates a request body payload against the spec's `requestBody` schema.
97+
98+
```typescript
99+
const result = validator.validateRequest('/v1/pets', 'POST', {
100+
name: 'Fido',
101+
tag: 'dog',
102+
});
103+
```
104+
105+
### Per-call options
106+
107+
Override the constructor's `strict` option per call:
108+
109+
```typescript
42110
validator.validateResponse(path, method, status, payload, { strict: false });
111+
```
112+
113+
## Errors and Warnings
114+
115+
### Errors
116+
117+
Returned when the payload doesn't match the schema:
118+
119+
```typescript
120+
{
121+
path: '/id', // JSON pointer to the field
122+
message: 'must be integer', // human-readable
123+
keyword: 'type', // AJV keyword
124+
expected: 'integer', // what the spec says
125+
received: 'string', // what the payload has
126+
}
127+
```
128+
129+
### Warnings
130+
131+
Returned when the validator can't fully validate — the payload isn't wrong, but it's not contract-tested either:
132+
133+
| Type | When |
134+
|------|------|
135+
| `UNMATCHED_STATUS` | Status code not documented in the spec |
136+
| `MISSING_SCHEMA` | No schema defined for this path/method/status |
137+
| `EMPTY_SPEC_RESPONSE` | Response exists but has no `content` (e.g., 204) |
43138

44139
## OpenAPI Support
45140

46-
- OpenAPI 3.0 — nullable fields normalized automatically
47-
- OpenAPI 3.1 — native JSON Schema Draft 2020-12
48-
- Full $ref resolution (nested, circular)
49-
- oneOf / anyOf / allOf composition
50-
- discriminator support
141+
- **OpenAPI 3.0**`nullable` fields normalized to 3.1 format automatically
142+
- **OpenAPI 3.1** — native JSON Schema Draft 2020-12
143+
- **$ref resolution** — nested, deeply nested, components referencing components
144+
- **Composition**`oneOf`, `anyOf`, `allOf` with full validation
145+
- **Discriminator**`discriminator.propertyName` support
146+
- **Strict mode**`additionalProperties: false` enforced by default
147+
148+
### Known Limitation
149+
150+
Strict mode (`additionalProperties: false`) can conflict with `allOf` schemas. When `allOf` branches define different properties, each branch rejects the other's properties as "additional." Use `{ strict: false }` for endpoints that use `allOf` composition, or define `additionalProperties` explicitly in your spec.
151+
152+
## Design Decisions
153+
154+
- **JSON only** — no YAML parsing, no URL fetching. Consumers handle I/O.
155+
- **Strict by default** — if the spec is the source of truth, mocks should match it exactly.
156+
- **Warnings, not silence** — undocumented status codes and missing schemas are surfaced, never silently skipped.
157+
- **Parse once, validate many** — the `init()` step is expensive (dereferencing, normalization, path compilation). Validation calls are fast.
51158

52159
## License
53160

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
"types": "./dist/index.d.ts"
1212
}
1313
},
14+
"files": [
15+
"dist"
16+
],
1417
"scripts": {
1518
"build": "tsc",
1619
"test": "vitest",

vitest.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default defineConfig({
66
coverage: {
77
provider: 'v8',
88
include: ['src/**/*.ts'],
9-
reporter: ['text', 'json', 'html'],
9+
reporter: ['text', 'json', 'html', 'lcov'],
1010
},
1111
},
1212
});

0 commit comments

Comments
 (0)