OpenAPI Validator
@univ-lehavre/atlas-openapi-validator is a CLI tool and library for validating that an OpenAPI spec matches the real API.
Purpose
Produce a formal and structured report of variations between a spec and the real API, consumable programmatically by source packages to fix their specs.
Installation
bash
pnpm add -D @univ-lehavre/atlas-openapi-validatorStructure
packages/openapi-validator/
├── src/
│ ├── types/
│ │ ├── report.ts # Validation report types
│ │ ├── deviation.ts # Detected deviation types
│ │ └── index.ts
│ ├── validator/
│ │ ├── endpoint-validator.ts # Endpoint validation
│ │ ├── parameter-validator.ts # Parameter validation
│ │ ├── schema-validator.ts # Response schema validation
│ │ ├── type-validator.ts # Field type validation
│ │ └── index.ts
│ ├── reporter/
│ │ ├── json-reporter.ts # Structured JSON export
│ │ ├── console-reporter.ts # Console output
│ │ └── index.ts
│ ├── cli/
│ │ └── index.ts
│ └── index.tsDeviation Types
Deviations are classified by level:
Levels
typescript
type DeviationLevel = 'endpoint' | 'parameter' | 'field' | 'type';Severities
typescript
type DeviationSeverity = 'error' | 'warning' | 'info';Deviation Types
typescript
type DeviationType =
| 'missing_in_spec' // Present in API, absent from spec
| 'missing_in_api' // Present in spec, absent from API
| 'type_mismatch' // Different type
| 'format_mismatch' // Different format (date, uri, etc.)
| 'nullable_mismatch' // Different nullability
| 'enum_mismatch' // Different enum values
| 'required_mismatch' // Different required/optional status
| 'deprecated_missing' // Deprecated field not marked
| 'array_item_mismatch'; // Different array item typeReport Structure
typescript
interface ValidationReport {
meta: {
specPath: string;
specVersion: string;
baseUrl: string;
validatedAt: Date;
duration: number; // ms
requestCount: number;
};
summary: {
total: number;
byLevel: Record<DeviationLevel, number>;
bySeverity: Record<DeviationSeverity, number>;
byType: Record<DeviationType, number>;
};
endpoints: EndpointReport[];
deviations: Deviation[];
suggestions: SpecSuggestion[];
}
interface SpecSuggestion {
deviation: Deviation;
action: 'add' | 'remove' | 'modify';
target: 'endpoint' | 'parameter' | 'schema' | 'property';
yamlPath: string; // Path in the YAML file
currentValue?: unknown;
suggestedValue?: unknown;
confidence: number; // 0-1
}CLI
Validate a spec
bash
# Complete validation with JSON report
atlas-openapi-validator validate specs/openalex.yaml \
--base-url https://api.openalex.org \
--output report.json \
--format json
# Filter by deviation level
atlas-openapi-validator validate specs/openalex.yaml \
--base-url https://api.openalex.org \
--level field,type \
--severity error,warningApply fixes
bash
# Apply suggestions automatically
atlas-openapi-validator fix specs/openalex.yaml \
--report report.json \
--confidence 0.9 \
--dry-run
# Without dry-run to actually apply
atlas-openapi-validator fix specs/openalex.yaml \
--report report.json \
--confidence 0.9Compare specs
bash
# Detect regressions between versions
atlas-openapi-validator diff specs/v1.yaml specs/v2.yaml \
--output diff-report.jsonFetch an existing spec
bash
# From official Swagger
atlas-openapi-validator fetch https://api.crossref.org/swagger.json \
--output specs/alpha/crossref-v1-2025-01.yaml \
--set-stage alphaPromote a spec
bash
# From beta to stable
atlas-openapi-validator promote specs/beta/openalex-2025-01-24.yaml \
--to stable \
--set-currentProgrammatic API
Validate and get the report
typescript
import { validate, applySuggestions } from '@univ-lehavre/atlas-openapi-validator';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
// Validate
const report = yield* validate({
specPath: 'specs/openalex.yaml',
baseUrl: 'https://api.openalex.org',
sampleSize: 10,
respectRateLimits: true,
});
// Filter deviations
const errors = report.deviations.filter(d => d.severity === 'error');
const fieldDeviations = report.deviations.filter(d => d.level === 'field');
// Apply high confidence suggestions
if (report.suggestions.length > 0) {
yield* applySuggestions(
'specs/openalex.yaml',
report.suggestions.filter(s => s.confidence > 0.9)
);
}
return report;
});Integration in a source package
typescript
// packages/openalex/scripts/validate-spec.ts
import { validate } from '@univ-lehavre/atlas-openapi-validator';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const report = yield* validate({
specPath: 'specs/current.yaml',
baseUrl: 'https://api.openalex.org',
sampleSize: 5,
respectRateLimits: true,
});
const errors = report.deviations.filter(d => d.severity === 'error');
if (errors.length > 0) {
console.error(`${errors.length} errors detected:`);
errors.forEach(e =>
console.error(` - [${e.level}] ${e.path}: ${e.message}`)
);
process.exit(1);
}
const warnings = report.deviations.filter(d => d.severity === 'warning');
if (warnings.length > 0) {
console.warn(`${warnings.length} warnings:`);
warnings.forEach(w =>
console.warn(` - [${w.level}] ${w.path}: ${w.message}`)
);
}
console.log('✓ Spec validated');
});
Effect.runPromise(program);Deviation Examples
Endpoint missing in spec
json
{
"level": "endpoint",
"severity": "warning",
"type": "missing_in_spec",
"path": "/works/random",
"method": "GET",
"message": "Endpoint exists in API but not documented in spec"
}Incorrect field type
json
{
"level": "type",
"severity": "error",
"type": "type_mismatch",
"endpoint": "/works",
"method": "GET",
"schemaPath": "#/components/schemas/Work",
"fieldName": "cited_by_count",
"expectedType": "string",
"actualType": "integer",
"message": "Field 'cited_by_count' is integer in API but string in spec"
}Field nullable not marked
json
{
"level": "field",
"severity": "warning",
"type": "nullable_mismatch",
"endpoint": "/works/{id}",
"method": "GET",
"schemaPath": "#/components/schemas/Work",
"fieldName": "abstract",
"message": "Field 'abstract' can be null in API but not marked nullable in spec"
}