14 KiB
API Contract Sync Manager - Technical Reference
This document provides technical details for working with OpenAPI and GraphQL specifications, including structure, breaking change patterns, and validation strategies.
OpenAPI Specification Structure
OpenAPI 3.0/3.1 Schema
openapi: 3.0.0
info:
title: API Name
version: 1.0.0
description: API description
servers:
- url: https://api.example.com/v1
paths:
/users:
get:
summary: List users
operationId: listUsers
parameters:
- name: limit
in: query
schema:
type: integer
responses:
'200':
description: Success
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
required:
- id
- email
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
Key OpenAPI Fields
info: Metadata about the API
title: API nameversion: Semantic versiondescription: Overview of API purpose
servers: Base URLs for API
url: Full base URLdescription: Environment name (optional)
paths: API endpoints
- Key is the path (e.g.,
/users,/users/{id}) - Operations:
get,post,put,patch,delete
components: Reusable definitions
schemas: Data modelsparameters: Reusable parametersresponses: Reusable responsessecuritySchemes: Auth methods
OpenAPI Data Types
| Type | Format | Description | Example |
|---|---|---|---|
| string | - | Text | "hello" |
| string | date | ISO 8601 date | "2025-10-16" |
| string | date-time | ISO 8601 timestamp | "2025-10-16T10:30:00Z" |
| string | Email address | "user@example.com" | |
| string | uuid | UUID v4 | "123e4567-e89b..." |
| integer | int32 | 32-bit integer | 42 |
| integer | int64 | 64-bit integer | 9007199254740991 |
| number | float | Floating point | 3.14 |
| number | double | Double precision | 3.141592653589793 |
| boolean | - | True/false | true |
| array | - | List of items | [1, 2, 3] |
| object | - | Key-value pairs | {"key": "value"} |
OpenAPI References
Use $ref to avoid duplication:
# Define once
components:
schemas:
User:
type: object
properties:
id:
type: string
# Reference multiple times
paths:
/users/{id}:
get:
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/User'
/users:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
GraphQL Schema Structure
GraphQL SDL (Schema Definition Language)
# Scalar types
scalar DateTime
scalar Email
# Enum types
enum UserRole {
ADMIN
MEMBER
GUEST
}
# Object types
type User {
id: ID!
email: Email!
name: String
role: UserRole!
createdAt: DateTime!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
published: Boolean!
}
# Input types (for mutations)
input CreateUserInput {
email: Email!
name: String
role: UserRole = MEMBER
}
# Query operations
type Query {
user(id: ID!): User
users(limit: Int = 10, offset: Int = 0): [User!]!
posts(authorId: ID): [Post!]!
}
# Mutation operations
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}
# Subscription operations
type Subscription {
userCreated: User!
postPublished: Post!
}
GraphQL Type Modifiers
| Modifier | Meaning | Example |
|---|---|---|
Type |
Nullable | Can be User or null |
Type! |
Non-null | Must be User, never null |
[Type] |
Nullable list of nullable items | [User], null, or [User, null] |
[Type]! |
Non-null list of nullable items | [User] or [User, null] but never null |
[Type!] |
Nullable list of non-null items | [User] or null, never [User, null] |
[Type!]! |
Non-null list of non-null items | [User], never null or [User, null] |
GraphQL Directives
type User {
id: ID!
email: String!
oldEmail: String @deprecated(reason: "Use 'email' field instead")
internalId: String @internal
}
type Query {
users: [User!]! @auth(requires: ADMIN)
}
Common directives:
@deprecated: Mark field as deprecated@auth,@requires: Custom authorization@skip,@include: Conditional inclusion (query-side)
Breaking Change Patterns
OpenAPI Breaking Changes
1. Removed Endpoints
# Before
paths:
/users/{id}:
delete:
# ...
# After
paths:
# /users/{id} delete operation removed
Impact: Clients calling DELETE /users/{id} will get 404
2. Required Parameters Added
# Before
parameters:
- name: email
in: query
required: false
# After
parameters:
- name: email
in: query
required: true # Changed to required
Impact: Existing calls without email parameter will fail validation
3. Parameter Type Changed
# Before
parameters:
- name: limit
in: query
schema:
type: string
# After
parameters:
- name: limit
in: query
schema:
type: integer # Changed from string
Impact: Clients sending "10" as string may fail validation
4. Required Property Added to Request
# Before
CreateUserRequest:
type: object
required:
- email
properties:
email:
type: string
# After
CreateUserRequest:
type: object
required:
- email
- role # New required field
properties:
email:
type: string
role:
type: string
Impact: Requests without role field will be rejected
5. Response Property Removed
# Before
User:
properties:
id:
type: string
email:
type: string
phone:
type: string
# After
User:
properties:
id:
type: string
email:
type: string
# phone removed
Impact: Clients accessing user.phone will get undefined/null
6. Property Type Changed
# Before
User:
properties:
id:
type: string
# After
User:
properties:
id:
type: integer # Changed type
Impact: Type mismatches in strongly-typed clients
7. Enum Values Removed
# Before
UserRole:
type: string
enum:
- admin
- member
- guest
# After
UserRole:
type: string
enum:
- admin
- member
# guest removed
Impact: Requests with role: "guest" will fail validation
GraphQL Breaking Changes
1. Field Removed from Type
# Before
type User {
id: ID!
email: String!
phone: String
}
# After
type User {
id: ID!
email: String!
# phone removed
}
Impact: Queries requesting phone field will fail
2. Argument Added to Field
# Before
type Query {
users: [User!]!
}
# After
type Query {
users(role: UserRole!): [User!]! # New required argument
}
Impact: Existing queries without role argument will fail
3. Non-Null Modifier Added
# Before
type User {
email: String
}
# After
type User {
email: String! # Now non-null
}
Impact: Clients expecting nullable email may fail
4. Argument Type Changed
# Before
type Query {
user(id: String!): User
}
# After
type Query {
user(id: ID!): User # Changed from String to ID
}
Impact: May cause validation issues depending on implementation
5. Field Type Changed
# Before
type User {
createdAt: String
}
# After
type User {
createdAt: DateTime! # Changed scalar type
}
Impact: Clients expecting string format may break
Non-Breaking Changes (Safe)
OpenAPI Non-Breaking
- ✓ Add new endpoint
- ✓ Add optional parameter
- ✓ Add optional request body property
- ✓ Add response property
- ✓ Make required field optional
- ✓ Add new enum value
- ✓ Expand validation (e.g., increase maxLength)
- ✓ Add default values
GraphQL Non-Breaking
- ✓ Add new field to type
- ✓ Add new type
- ✓ Add new query/mutation
- ✓ Add optional argument to field
- ✓ Remove non-null modifier (make nullable)
- ✓ Add value to enum
- ✓ Deprecate field (with @deprecated)
Validation Strategies
OpenAPI Validation
Structural Validation
- Schema adherence: Validate against OpenAPI 3.0/3.1 spec
- Reference integrity: Ensure all
$refpoint to valid schemas - Required fields: Check all required fields exist
- Type consistency: Verify types match throughout
Content Validation
- Descriptions: All operations and schemas should have descriptions
- Examples: Include request/response examples
- Error responses: Document 4xx and 5xx responses
- Security: All protected endpoints have security requirements
- Tags: Operations grouped with consistent tags
Common Issues to Check
# ❌ Bad: Missing description
/users:
get:
responses:
'200':
description: Success
# ✓ Good: Descriptive
/users:
get:
summary: List all users
description: Returns a paginated list of users with optional filtering
responses:
'200':
description: Successfully retrieved user list
GraphQL Validation
Schema Validation
- Type definitions: All types properly defined
- Field types: All fields reference valid types
- Arguments: Argument types are valid scalars, enums, or input types
- Resolver coverage: All fields have resolvers (implementation check)
- Circular references: Detect and handle circular dependencies
Naming Conventions
- Types: PascalCase (e.g.,
User,BlogPost) - Fields: camelCase (e.g.,
firstName,createdAt) - Enums: UPPER_SNAKE_CASE (e.g.,
USER_ROLE,POST_STATUS) - Arguments: camelCase (e.g.,
userId,includeDeleted)
Common Issues
# ❌ Bad: Query returns nullable when it shouldn't
type Query {
user(id: ID!): User # Could return null
}
# ✓ Good: Clear nullability
type Query {
user(id: ID!): User # Can return null if not found
requireUser(id: ID!): User! # Always returns user or throws error
}
Type Generation Strategies
OpenAPI to TypeScript
Basic Type Mapping
// OpenAPI schema
{
"type": "object",
"required": ["id", "email"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"email": { "type": "string", "format": "email" },
"name": { "type": "string" },
"age": { "type": "integer" }
}
}
// Generated TypeScript
interface User {
id: string; // uuid format → string
email: string; // email format → string
name?: string; // optional → ?
age?: number; // integer → number
}
Union Types from oneOf
# OpenAPI
PaymentMethod:
oneOf:
- $ref: '#/components/schemas/CreditCard'
- $ref: '#/components/schemas/BankAccount'
// TypeScript
type PaymentMethod = CreditCard | BankAccount;
Enum Mapping
# OpenAPI
UserRole:
type: string
enum:
- admin
- member
- guest
// TypeScript
enum UserRole {
Admin = 'admin',
Member = 'member',
Guest = 'guest'
}
// or
type UserRole = 'admin' | 'member' | 'guest';
GraphQL to TypeScript
# GraphQL
type User {
id: ID!
email: String!
name: String
posts: [Post!]!
}
// TypeScript
interface User {
id: string; // ID! → string
email: string; // String! → string
name: string | null; // String → string | null
posts: Post[]; // [Post!]! → Post[]
}
Validation Tools Integration
Spectral (OpenAPI)
# Install
npm install -g @stoplight/spectral-cli
# Validate
spectral lint openapi.yaml
# Custom ruleset
spectral lint openapi.yaml --ruleset .spectral.yaml
GraphQL Inspector
# Install
npm install -g @graphql-inspector/cli
# Validate schema
graphql-inspector validate schema.graphql
# Compare schemas
graphql-inspector diff old.graphql new.graphql
# Coverage check
graphql-inspector coverage schema.graphql documents/*.graphql
OpenAPI Diff
# Install
npm install -g openapi-diff
# Compare versions
openapi-diff old.yaml new.yaml
# Output markdown report
openapi-diff old.yaml new.yaml --format markdown > changes.md
Security Considerations
OpenAPI Security
- Authentication: Always define security schemes
- Authorization: Document which roles can access endpoints
- Sensitive data: Mark PII fields in schemas
- Rate limiting: Document rate limit headers
- HTTPS only: Use https:// in server URLs
GraphQL Security
- Query depth limiting: Prevent deeply nested queries
- Query cost analysis: Limit expensive operations
- Field-level auth: Use directives like @auth
- Input validation: Validate all arguments
- Disable introspection: In production environments
Best Practices Summary
OpenAPI
- Use semantic versioning in
info.version - Group related operations with tags
- Always include request/response examples
- Document all error responses (4xx, 5xx)
- Use
$refto keep specs DRY - Validate specs in CI/CD pipeline
GraphQL
- Use meaningful type and field names
- Add descriptions to all types and fields
- Use custom scalars for validated types (Email, URL, etc.)
- Implement pagination for list fields
- Use @deprecated instead of removing fields
- Version schema with additive changes only
Both
- Automate validation in CI/CD
- Generate client code from specs
- Keep specs in version control
- Review API changes in pull requests
- Maintain changelog of API versions
- Test generated client code