Skip to content

Commit c0fa38b

Browse files
authored
input validation
1 parent 8c644cf commit c0fa38b

27 files changed

Lines changed: 7171 additions & 0 deletions

config/api-versioning.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* API Versioning configuration and lifecycle.
3+
* Supports multi-version API with backward compatibility, deprecation, and sunset policies.
4+
*/
5+
6+
export type VersionStatus = 'stable' | 'deprecated' | 'sunset';
7+
8+
export interface VersionLifecycle {
9+
version: string;
10+
status: VersionStatus;
11+
/** RFC 3339 date when this version was released */
12+
releasedAt: string;
13+
/** RFC 3339 date when deprecation was announced (if status is deprecated or sunset) */
14+
deprecatedAt?: string;
15+
/** RFC 3339 date after which the version will be removed (sunset) */
16+
sunsetAt?: string;
17+
/** URL to migration guide or changelog */
18+
migrationGuideUrl?: string;
19+
/** Human-readable deprecation message */
20+
deprecationMessage?: string;
21+
}
22+
23+
export interface ApiVersioningConfig {
24+
/** Currently supported API versions (e.g. ['v1', 'v2']) */
25+
supportedVersions: string[];
26+
/** Default version when client does not specify one */
27+
defaultVersion: string;
28+
/** Latest version (used for /api without version prefix) */
29+
latestVersion: string;
30+
/** Version lifecycle and deprecation metadata */
31+
lifecycle: Record<string, VersionLifecycle>;
32+
/** Base URL for versioned docs (e.g. https://docs.nepa.example.com/api) */
33+
docsBaseUrl?: string;
34+
}
35+
36+
const LIFECYCLE: Record<string, VersionLifecycle> = {
37+
v1: {
38+
version: 'v1',
39+
status: 'stable',
40+
releasedAt: '2024-01-01T00:00:00Z',
41+
migrationGuideUrl: '/docs/api-versioning#migrating-v1-to-v2',
42+
},
43+
v2: {
44+
version: 'v2',
45+
status: 'stable',
46+
releasedAt: '2024-06-01T00:00:00Z',
47+
migrationGuideUrl: '/docs/api-versioning',
48+
},
49+
};
50+
51+
export const apiVersioningConfig: ApiVersioningConfig = {
52+
supportedVersions: ['v1', 'v2'],
53+
defaultVersion: 'v2',
54+
latestVersion: 'v2',
55+
lifecycle: LIFECYCLE,
56+
docsBaseUrl: process.env.API_DOCS_BASE_URL || '/api/docs',
57+
};
58+
59+
/**
60+
* Get lifecycle for a version. Returns undefined if version is unknown.
61+
*/
62+
export function getVersionLifecycle(version: string): VersionLifecycle | undefined {
63+
const normalized = version.startsWith('v') ? version : `v${version}`;
64+
return apiVersioningConfig.lifecycle[normalized];
65+
}
66+
67+
/**
68+
* Check if a version is supported (exists and not sunset).
69+
*/
70+
export function isVersionSupported(version: string): boolean {
71+
const normalized = version.startsWith('v') ? version : `v${version}`;
72+
if (!apiVersioningConfig.supportedVersions.includes(normalized)) return false;
73+
const lifecycle = getVersionLifecycle(normalized);
74+
return lifecycle ? lifecycle.status !== 'sunset' : false;
75+
}
76+
77+
/**
78+
* Get HTTP headers for deprecation/sunset (RFC 8594-style).
79+
*/
80+
export function getDeprecationHeaders(version: string): Record<string, string> {
81+
const lifecycle = getVersionLifecycle(version);
82+
if (!lifecycle || lifecycle.status === 'stable') return {};
83+
84+
const headers: Record<string, string> = {};
85+
if (lifecycle.status === 'deprecated' || lifecycle.status === 'sunset') {
86+
headers['Deprecation'] = 'true';
87+
if (lifecycle.sunsetAt) {
88+
headers['Sunset'] = lifecycle.sunsetAt;
89+
}
90+
if (lifecycle.migrationGuideUrl) {
91+
headers['Link'] = `<${lifecycle.migrationGuideUrl}>; rel="deprecation"; type="text/html"`;
92+
}
93+
}
94+
return headers;
95+
}

0 commit comments

Comments
 (0)