Skip to content

Commit 2af3e37

Browse files
maxholmanclaude
andcommitted
feat!: support response headers, type-safe parameter styles, and multi-content-type responses
Implement the Header construct for OpenAPI 3.1 response headers with components/headers registration and $ref support. Enforce lowercase header names at the type level for HTTP/2 compliance (RFC 7540 §8.1.2). Constrain parameter style values per location (path/query/header/cookie) matching the OAS 3.1 style table, and add the missing explode field. Widen MediaType contentType to accept any MIME string while preserving autocomplete for common types. Allow Response and RequestBody to accept arrays of content types for endpoints serving multiple representations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 25b9d27 commit 2af3e37

12 files changed

Lines changed: 522 additions & 38 deletions

File tree

__tests__/__snapshots__/json-schema.test.ts.snap

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ exports[`Example > JSON Schema snapshot 1`] = `
1919
],
2020
"type": "object",
2121
},
22+
"Binary": {
23+
"format": "binary",
24+
"type": "string",
25+
},
2226
"CreateUserRequest": {
2327
"additionalProperties": false,
2428
"properties": {
@@ -52,6 +56,11 @@ exports[`Example > JSON Schema snapshot 1`] = `
5256
"Id": {
5357
"type": "string",
5458
},
59+
"RateLimit": {
60+
"format": "int32",
61+
"minimum": 0,
62+
"type": "integer",
63+
},
5564
"UpdateUserRequest": {
5665
"additionalProperties": false,
5766
"minProperties": 1,

__tests__/__snapshots__/openapi-schema.test.ts.snap

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,21 @@
33
exports[`Example > OpenAPI 1`] = `
44
{
55
"components": {
6+
"headers": {
7+
"x-rate-limit": {
8+
"description": "Number of requests allowed per hour",
9+
"required": true,
10+
"schema": {
11+
"$ref": "#/components/schemas/RateLimit",
12+
},
13+
},
14+
"x-rate-limit-remaining": {
15+
"description": "Number of requests remaining in the current window",
16+
"schema": {
17+
"$ref": "#/components/schemas/RateLimit",
18+
},
19+
},
20+
},
621
"parameters": {
722
"UserId": {
823
"in": "path",
@@ -29,6 +44,10 @@ exports[`Example > OpenAPI 1`] = `
2944
],
3045
"type": "object",
3146
},
47+
"Binary": {
48+
"format": "binary",
49+
"type": "string",
50+
},
3251
"CreateUserRequest": {
3352
"additionalProperties": false,
3453
"properties": {
@@ -62,6 +81,11 @@ exports[`Example > OpenAPI 1`] = `
6281
"Id": {
6382
"type": "string",
6483
},
84+
"RateLimit": {
85+
"format": "int32",
86+
"minimum": 0,
87+
"type": "integer",
88+
},
6589
"UpdateUserRequest": {
6690
"additionalProperties": false,
6791
"minProperties": 1,
@@ -142,6 +166,21 @@ exports[`Example > OpenAPI 1`] = `
142166
},
143167
},
144168
"description": "User 200 response",
169+
"headers": {
170+
"x-rate-limit": {
171+
"description": "Number of requests allowed per hour",
172+
"required": true,
173+
"schema": {
174+
"$ref": "#/components/schemas/RateLimit",
175+
},
176+
},
177+
"x-rate-limit-remaining": {
178+
"description": "Number of requests remaining in the current window",
179+
"schema": {
180+
"$ref": "#/components/schemas/RateLimit",
181+
},
182+
},
183+
},
145184
},
146185
},
147186
"tags": [
@@ -250,6 +289,63 @@ exports[`Example > OpenAPI 1`] = `
250289
"tags": [],
251290
},
252291
},
292+
"/users/{userId}/avatar": {
293+
"get": {
294+
"description": "Download user avatar as JSON metadata or raw binary",
295+
"operationId": "getUserAvatarCommand",
296+
"responses": {
297+
"200": {
298+
"content": {
299+
"application/json": {
300+
"schema": {
301+
"$ref": "#/components/schemas/User",
302+
},
303+
},
304+
"application/octet-stream": {
305+
"schema": {
306+
"$ref": "#/components/schemas/Binary",
307+
},
308+
},
309+
},
310+
"description": "Avatar response",
311+
},
312+
},
313+
"tags": [],
314+
},
315+
"parameters": [
316+
{
317+
"$ref": "#/components/parameters/UserId",
318+
},
319+
],
320+
"put": {
321+
"description": "Upload user avatar as binary",
322+
"operationId": "uploadUserAvatarCommand",
323+
"requestBody": {
324+
"content": {
325+
"application/octet-stream": {
326+
"schema": {
327+
"$ref": "#/components/schemas/Binary",
328+
},
329+
},
330+
},
331+
"description": "",
332+
"required": true,
333+
},
334+
"responses": {
335+
"200": {
336+
"content": {
337+
"application/json": {
338+
"schema": {
339+
"$ref": "#/components/schemas/User",
340+
},
341+
},
342+
},
343+
"description": "Successful response",
344+
},
345+
},
346+
"tags": [],
347+
},
348+
},
253349
},
254350
"security": [
255351
{
@@ -281,6 +377,25 @@ exports[`Example > OpenAPI 1`] = `
281377
exports[`Example > Swagger Parser validate 1`] = `
282378
{
283379
"components": {
380+
"headers": {
381+
"x-rate-limit": {
382+
"description": "Number of requests allowed per hour",
383+
"required": true,
384+
"schema": {
385+
"format": "int32",
386+
"minimum": 0,
387+
"type": "integer",
388+
},
389+
},
390+
"x-rate-limit-remaining": {
391+
"description": "Number of requests remaining in the current window",
392+
"schema": {
393+
"format": "int32",
394+
"minimum": 0,
395+
"type": "integer",
396+
},
397+
},
398+
},
284399
"parameters": {
285400
"UserId": {
286401
"in": "path",
@@ -307,6 +422,10 @@ exports[`Example > Swagger Parser validate 1`] = `
307422
],
308423
"type": "object",
309424
},
425+
"Binary": {
426+
"format": "binary",
427+
"type": "string",
428+
},
310429
"CreateUserRequest": {
311430
"additionalProperties": false,
312431
"properties": {
@@ -352,6 +471,11 @@ exports[`Example > Swagger Parser validate 1`] = `
352471
"Id": {
353472
"type": "string",
354473
},
474+
"RateLimit": {
475+
"format": "int32",
476+
"minimum": 0,
477+
"type": "integer",
478+
},
355479
"UpdateUserRequest": {
356480
"additionalProperties": false,
357481
"minProperties": 1,
@@ -538,6 +662,25 @@ exports[`Example > Swagger Parser validate 1`] = `
538662
},
539663
},
540664
"description": "User 200 response",
665+
"headers": {
666+
"x-rate-limit": {
667+
"description": "Number of requests allowed per hour",
668+
"required": true,
669+
"schema": {
670+
"format": "int32",
671+
"minimum": 0,
672+
"type": "integer",
673+
},
674+
},
675+
"x-rate-limit-remaining": {
676+
"description": "Number of requests remaining in the current window",
677+
"schema": {
678+
"format": "int32",
679+
"minimum": 0,
680+
"type": "integer",
681+
},
682+
},
683+
},
541684
},
542685
},
543686
"tags": [
@@ -835,6 +978,148 @@ exports[`Example > Swagger Parser validate 1`] = `
835978
"tags": [],
836979
},
837980
},
981+
"/users/{userId}/avatar": {
982+
"get": {
983+
"description": "Download user avatar as JSON metadata or raw binary",
984+
"operationId": "getUserAvatarCommand",
985+
"responses": {
986+
"200": {
987+
"content": {
988+
"application/json": {
989+
"schema": {
990+
"additionalProperties": false,
991+
"properties": {
992+
"address": {
993+
"additionalProperties": false,
994+
"properties": {
995+
"postcode": {
996+
"format": "int32",
997+
"maximum": 9999,
998+
"minimum": 1000,
999+
"type": "integer",
1000+
},
1001+
},
1002+
"required": [
1003+
"postcode",
1004+
],
1005+
"type": "object",
1006+
},
1007+
"age": {
1008+
"anyOf": [
1009+
{
1010+
"format": "int32",
1011+
"minimum": 0,
1012+
"type": "integer",
1013+
},
1014+
{
1015+
"type": "null",
1016+
},
1017+
],
1018+
},
1019+
"name": {
1020+
"type": "string",
1021+
},
1022+
"userId": {
1023+
"type": "string",
1024+
},
1025+
},
1026+
"required": [
1027+
"name",
1028+
],
1029+
"type": "object",
1030+
},
1031+
},
1032+
"application/octet-stream": {
1033+
"schema": {
1034+
"format": "binary",
1035+
"type": "string",
1036+
},
1037+
},
1038+
},
1039+
"description": "Avatar response",
1040+
},
1041+
},
1042+
"tags": [],
1043+
},
1044+
"parameters": [
1045+
{
1046+
"in": "path",
1047+
"name": "userId",
1048+
"required": true,
1049+
"schema": {
1050+
"type": "string",
1051+
},
1052+
},
1053+
],
1054+
"put": {
1055+
"description": "Upload user avatar as binary",
1056+
"operationId": "uploadUserAvatarCommand",
1057+
"requestBody": {
1058+
"content": {
1059+
"application/octet-stream": {
1060+
"schema": {
1061+
"format": "binary",
1062+
"type": "string",
1063+
},
1064+
},
1065+
},
1066+
"description": "",
1067+
"required": true,
1068+
},
1069+
"responses": {
1070+
"200": {
1071+
"content": {
1072+
"application/json": {
1073+
"schema": {
1074+
"additionalProperties": false,
1075+
"properties": {
1076+
"address": {
1077+
"additionalProperties": false,
1078+
"properties": {
1079+
"postcode": {
1080+
"format": "int32",
1081+
"maximum": 9999,
1082+
"minimum": 1000,
1083+
"type": "integer",
1084+
},
1085+
},
1086+
"required": [
1087+
"postcode",
1088+
],
1089+
"type": "object",
1090+
},
1091+
"age": {
1092+
"anyOf": [
1093+
{
1094+
"format": "int32",
1095+
"minimum": 0,
1096+
"type": "integer",
1097+
},
1098+
{
1099+
"type": "null",
1100+
},
1101+
],
1102+
},
1103+
"name": {
1104+
"type": "string",
1105+
},
1106+
"userId": {
1107+
"type": "string",
1108+
},
1109+
},
1110+
"required": [
1111+
"name",
1112+
],
1113+
"type": "object",
1114+
},
1115+
},
1116+
},
1117+
"description": "Successful response",
1118+
},
1119+
},
1120+
"tags": [],
1121+
},
1122+
},
8381123
},
8391124
"security": [
8401125
{
@@ -866,6 +1151,7 @@ exports[`Example > Swagger Parser validate 1`] = `
8661151
exports[`Note Taking > OpenAPI 1`] = `
8671152
{
8681153
"components": {
1154+
"headers": {},
8691155
"parameters": {
8701156
"NoteId": {
8711157
"in": "path",
@@ -1353,6 +1639,7 @@ exports[`Note Taking > OpenAPI 1`] = `
13531639
exports[`Note Taking > Swagger Parser validate 1`] = `
13541640
{
13551641
"components": {
1642+
"headers": {},
13561643
"parameters": {
13571644
"NoteId": {
13581645
"in": "path",

0 commit comments

Comments
 (0)