Skip to content

Commit 4d296d6

Browse files
Copilothotlong
andcommitted
Deduplicate HTTP-related Zod schemas into shared/http.zod.ts
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 1de712e commit 4d296d6

6 files changed

Lines changed: 276 additions & 35 deletions

File tree

SCHEMA_DEDUPLICATION_SUMMARY.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Schema Deduplication - Changes Summary
2+
3+
## Issue
4+
@hotlong requested evaluation of whether the new HTTP server and REST API Zod schemas duplicate existing definitions in `spec/api`.
5+
6+
## Analysis Results
7+
8+
### Identified Duplications
9+
10+
1. **CORS Configuration**
11+
- Found in: `api/router.zod.ts` and `system/http-server.zod.ts`
12+
- Issue: Similar structure with minor differences (field names: "origin" vs "origins", "dir" vs "directory")
13+
14+
2. **Rate Limiting**
15+
- Found in: `api/endpoint.zod.ts` and `system/http-server.zod.ts`
16+
- Issue: Nearly identical schema, one exported, one inline
17+
18+
3. **Static File Serving**
19+
- Found in: `api/router.zod.ts` and `system/http-server.zod.ts`
20+
- Issue: Same structure with field name differences
21+
22+
### Resolution
23+
24+
Created a new shared schema file to consolidate duplicated HTTP-related schemas:
25+
26+
**New File:** `packages/spec/src/shared/http.zod.ts`
27+
28+
This file now contains:
29+
- `CorsConfigSchema` - Unified CORS configuration
30+
- `RateLimitConfigSchema` - Unified rate limiting configuration
31+
- `StaticMountSchema` - Unified static file serving configuration
32+
33+
### Files Modified
34+
35+
1. **`packages/spec/src/shared/http.zod.ts`** (NEW)
36+
- Created new shared HTTP schemas
37+
- Comprehensive JSDoc documentation
38+
- Examples for each schema
39+
40+
2. **`packages/spec/src/shared/index.ts`**
41+
- Added export for `http.zod.ts`
42+
43+
3. **`packages/spec/src/system/http-server.zod.ts`**
44+
- Removed inline CORS, rate limit, and static schemas
45+
- Now imports from `shared/http.zod.ts`
46+
- Standardized field names: "origins" and "directory"
47+
48+
4. **`packages/spec/src/api/router.zod.ts`**
49+
- Removed inline CORS and static mount schemas
50+
- Now imports from `shared/http.zod.ts`
51+
- Standardized field names to match shared schemas
52+
53+
5. **`packages/spec/src/api/endpoint.zod.ts`**
54+
- Deprecated local `RateLimitSchema`
55+
- Now re-exports from `shared/http.zod.ts` for backward compatibility
56+
57+
## Benefits
58+
59+
1.**Eliminated Duplication** - Single source of truth for common HTTP schemas
60+
2.**Consistency** - Standardized field names across all uses
61+
3.**Maintainability** - Changes to HTTP schemas now only need to be made in one place
62+
4.**Documentation** - Centralized documentation for shared schemas
63+
5.**Backward Compatibility** - Old `RateLimitSchema` still available (deprecated)
64+
65+
## Breaking Changes
66+
67+
### Field Name Changes (Minor)
68+
69+
**CORS Configuration:**
70+
- `origin``origins` (more semantically correct for multiple origins)
71+
72+
**Static Mounts:**
73+
- `dir``directory` (more explicit and clear)
74+
75+
These changes standardize the naming convention and improve clarity. If there are existing implementations depending on the old field names, they should be updated to use the new names.
76+
77+
## Migration Guide
78+
79+
### For CORS Configuration
80+
```typescript
81+
// Before (router.zod.ts)
82+
cors: {
83+
origin: 'http://localhost:3000'
84+
}
85+
86+
// After (shared/http.zod.ts)
87+
cors: {
88+
origins: 'http://localhost:3000' // Note: 'origins' instead of 'origin'
89+
}
90+
```
91+
92+
### For Static Mounts
93+
```typescript
94+
// Before (router.zod.ts)
95+
staticMounts: [{
96+
path: '/static',
97+
dir: './public'
98+
}]
99+
100+
// After (shared/http.zod.ts)
101+
staticMounts: [{
102+
path: '/static',
103+
directory: './public' // Note: 'directory' instead of 'dir'
104+
}]
105+
```
106+
107+
### For Rate Limiting
108+
```typescript
109+
// Before (endpoint.zod.ts or http-server.zod.ts)
110+
import { RateLimitSchema } from './endpoint.zod';
111+
112+
// After (shared/http.zod.ts)
113+
import { RateLimitConfigSchema } from '../shared/http.zod';
114+
// Or continue using RateLimitSchema (deprecated) from endpoint.zod.ts
115+
```
116+
117+
## Next Steps
118+
119+
1. Update any existing code that uses `origin` to use `origins`
120+
2. Update any existing code that uses `dir` to use `directory`
121+
3. Consider adding migration guide to CHANGELOG.md
122+
4. Run tests to ensure no breaking changes in actual usage
123+
124+
## Notes
125+
126+
- The `HttpMethod` enum was already being imported from `router.zod.ts` in the new files (no duplication)
127+
- All changes follow the Zod-first architecture pattern
128+
- Maintained backward compatibility where possible

packages/spec/src/api/endpoint.zod.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import { z } from 'zod';
22
import { HttpMethod } from './router.zod';
3+
import { RateLimitConfigSchema } from '../shared/http.zod';
34

45
/**
56
* Rate Limit Strategy
7+
* @deprecated Use RateLimitConfigSchema from shared/http.zod.ts instead
68
*/
7-
export const RateLimitSchema = z.object({
8-
enabled: z.boolean().default(false),
9-
windowMs: z.number().default(60000).describe('Time window in milliseconds'),
10-
maxRequests: z.number().default(100).describe('Max requests per window'),
11-
});
9+
export const RateLimitSchema = RateLimitConfigSchema;
1210

1311
/**
1412
* API Mapping Schema

packages/spec/src/api/router.zod.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { z } from 'zod';
2+
import { CorsConfigSchema, StaticMountSchema } from '../shared/http.zod';
23

34
/**
45
* HTTP Method Enum
@@ -102,20 +103,12 @@ export const RouterConfigSchema = z.object({
102103
/**
103104
* Cross-Origin Resource Sharing
104105
*/
105-
cors: z.object({
106-
enabled: z.boolean().default(true),
107-
origin: z.union([z.string(), z.array(z.string())]).default('*'),
108-
methods: z.array(HttpMethod).optional(),
109-
}).optional(),
106+
cors: CorsConfigSchema.optional(),
110107

111108
/**
112109
* Static asset mounts
113110
*/
114-
staticMounts: z.array(z.object({
115-
path: z.string().describe('URL mount path'),
116-
dir: z.string().describe('Physical directory path'),
117-
cacheControl: z.string().optional()
118-
})).optional(),
111+
staticMounts: z.array(StaticMountSchema).optional(),
119112
});
120113

121114
export type RouterConfig = z.infer<typeof RouterConfigSchema>;
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { z } from 'zod';
2+
import { HttpMethod } from '../api/router.zod';
3+
4+
/**
5+
* Shared HTTP Schemas
6+
*
7+
* Common HTTP-related schemas used across API and System protocols.
8+
* These schemas ensure consistency across different parts of the stack.
9+
*/
10+
11+
// ==========================================
12+
// CORS Configuration
13+
// ==========================================
14+
15+
/**
16+
* CORS Configuration Schema
17+
* Cross-Origin Resource Sharing configuration
18+
*
19+
* Used by:
20+
* - api/router.zod.ts (RouterConfigSchema)
21+
* - system/http-server.zod.ts (HttpServerConfigSchema)
22+
*
23+
* @example
24+
* {
25+
* "enabled": true,
26+
* "origins": ["http://localhost:3000", "https://app.example.com"],
27+
* "methods": ["GET", "POST", "PUT", "DELETE"],
28+
* "credentials": true,
29+
* "maxAge": 86400
30+
* }
31+
*/
32+
export const CorsConfigSchema = z.object({
33+
/**
34+
* Enable CORS
35+
*/
36+
enabled: z.boolean().default(true).describe('Enable CORS'),
37+
38+
/**
39+
* Allowed origins (* for all)
40+
*/
41+
origins: z.union([
42+
z.string(),
43+
z.array(z.string())
44+
]).default('*').describe('Allowed origins (* for all)'),
45+
46+
/**
47+
* Allowed HTTP methods
48+
*/
49+
methods: z.array(HttpMethod).optional().describe('Allowed HTTP methods'),
50+
51+
/**
52+
* Allow credentials (cookies, authorization headers)
53+
*/
54+
credentials: z.boolean().default(false).describe('Allow credentials (cookies, authorization headers)'),
55+
56+
/**
57+
* Preflight cache duration in seconds
58+
*/
59+
maxAge: z.number().int().optional().describe('Preflight cache duration in seconds'),
60+
});
61+
62+
export type CorsConfig = z.infer<typeof CorsConfigSchema>;
63+
64+
// ==========================================
65+
// Rate Limiting
66+
// ==========================================
67+
68+
/**
69+
* Rate Limit Configuration Schema
70+
*
71+
* Used by:
72+
* - api/endpoint.zod.ts (ApiEndpointSchema)
73+
* - system/http-server.zod.ts (HttpServerConfigSchema)
74+
*
75+
* @example
76+
* {
77+
* "enabled": true,
78+
* "windowMs": 60000,
79+
* "maxRequests": 100
80+
* }
81+
*/
82+
export const RateLimitConfigSchema = z.object({
83+
/**
84+
* Enable rate limiting
85+
*/
86+
enabled: z.boolean().default(false).describe('Enable rate limiting'),
87+
88+
/**
89+
* Time window in milliseconds
90+
*/
91+
windowMs: z.number().int().default(60000).describe('Time window in milliseconds'),
92+
93+
/**
94+
* Max requests per window
95+
*/
96+
maxRequests: z.number().int().default(100).describe('Max requests per window'),
97+
});
98+
99+
export type RateLimitConfig = z.infer<typeof RateLimitConfigSchema>;
100+
101+
// ==========================================
102+
// Static File Serving
103+
// ==========================================
104+
105+
/**
106+
* Static Mount Configuration Schema
107+
* Configuration for serving static files
108+
*
109+
* Used by:
110+
* - api/router.zod.ts (RouterConfigSchema)
111+
* - system/http-server.zod.ts (HttpServerConfigSchema)
112+
*
113+
* @example
114+
* {
115+
* "path": "/static",
116+
* "directory": "./public",
117+
* "cacheControl": "public, max-age=31536000"
118+
* }
119+
*/
120+
export const StaticMountSchema = z.object({
121+
/**
122+
* URL path to serve from
123+
*/
124+
path: z.string().describe('URL path to serve from'),
125+
126+
/**
127+
* Physical directory to serve
128+
*/
129+
directory: z.string().describe('Physical directory to serve'),
130+
131+
/**
132+
* Cache-Control header value
133+
*/
134+
cacheControl: z.string().optional().describe('Cache-Control header value'),
135+
});
136+
137+
export type StaticMount = z.infer<typeof StaticMountSchema>;

packages/spec/src/shared/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55

66
export * from './identifiers.zod';
77
export * from './mapping.zod';
8+
export * from './http.zod';

packages/spec/src/system/http-server.zod.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { z } from 'zod';
22
import { HttpMethod } from '../api/router.zod';
3+
import { CorsConfigSchema, RateLimitConfigSchema, StaticMountSchema } from '../shared/http.zod';
34

45
/**
56
* HTTP Server Protocol
@@ -47,16 +48,7 @@ export const HttpServerConfigSchema = z.object({
4748
/**
4849
* CORS configuration
4950
*/
50-
cors: z.object({
51-
enabled: z.boolean().default(true).describe('Enable CORS'),
52-
origins: z.union([
53-
z.string(),
54-
z.array(z.string())
55-
]).default('*').describe('Allowed origins (* for all)'),
56-
methods: z.array(HttpMethod).optional().describe('Allowed HTTP methods'),
57-
credentials: z.boolean().default(false).describe('Allow credentials (cookies, authorization headers)'),
58-
maxAge: z.number().int().optional().describe('Preflight cache duration in seconds'),
59-
}).optional().describe('CORS configuration'),
51+
cors: CorsConfigSchema.optional().describe('CORS configuration'),
6052

6153
/**
6254
* Request handling options
@@ -74,21 +66,13 @@ export const HttpServerConfigSchema = z.object({
7466
*/
7567
security: z.object({
7668
helmet: z.boolean().default(true).describe('Enable security headers via helmet'),
77-
rateLimit: z.object({
78-
enabled: z.boolean().default(true).describe('Enable rate limiting'),
79-
windowMs: z.number().int().default(60000).describe('Time window in milliseconds'),
80-
maxRequests: z.number().int().default(100).describe('Max requests per window per IP'),
81-
}).optional(),
69+
rateLimit: RateLimitConfigSchema.optional().describe('Global rate limiting configuration'),
8270
}).optional().describe('Security configuration'),
8371

8472
/**
8573
* Static file serving
8674
*/
87-
static: z.array(z.object({
88-
path: z.string().describe('URL path to serve from'),
89-
directory: z.string().describe('Physical directory to serve'),
90-
cacheControl: z.string().optional().describe('Cache-Control header value'),
91-
})).optional().describe('Static file serving configuration'),
75+
static: z.array(StaticMountSchema).optional().describe('Static file serving configuration'),
9276

9377
/**
9478
* Trust proxy settings

0 commit comments

Comments
 (0)