Skip to content

Commit f9d9ac8

Browse files
author
Ansh Chaturvedi
committed
feat: support for @example and @pattern tags in schema properties
1 parent b93dfb0 commit f9d9ac8

File tree

2 files changed

+165
-10
lines changed

2 files changed

+165
-10
lines changed

packages/openapi-generator/src/openapi.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@ function schemaToOpenAPI(
1515
schema: Schema,
1616
): OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined => {
1717
const description = schema.comment?.description;
18+
const example = getTagName(schema, 'example');
19+
const pattern = getTagName(schema, 'pattern');
1820

19-
const defaultObject = {
21+
const defaultOpenAPIObject = {
2022
...(description ? { description } : {}),
23+
...(example ? { example } : {}),
24+
...(pattern ? { pattern } : {}),
2125
};
2226

2327
switch (schema.type) {
@@ -27,13 +31,13 @@ function schemaToOpenAPI(
2731
return {
2832
type: schema.type,
2933
...(schema.enum ? { enum: schema.enum } : {}),
30-
...defaultObject,
34+
...defaultOpenAPIObject,
3135
};
3236
case 'integer':
3337
return {
3438
type: 'number',
3539
...(schema.enum ? { enum: schema.enum } : {}),
36-
...defaultObject,
40+
...defaultOpenAPIObject,
3741
};
3842
case 'null':
3943
// TODO: OpenAPI v3 does not have an explicit null type, is there a better way to represent this?
@@ -46,11 +50,11 @@ function schemaToOpenAPI(
4650
if (innerSchema === undefined) {
4751
return undefined;
4852
}
49-
return { type: 'array', items: innerSchema, ...defaultObject };
53+
return { type: 'array', items: innerSchema, ...defaultOpenAPIObject };
5054
case 'object':
5155
return {
5256
type: 'object',
53-
...defaultObject,
57+
...defaultOpenAPIObject,
5458
properties: Object.entries(schema.properties).reduce(
5559
(acc, [name, prop]) => {
5660
const innerSchema = schemaToOpenAPI(prop);
@@ -73,7 +77,7 @@ function schemaToOpenAPI(
7377
}
7478
return [innerSchema];
7579
}),
76-
...defaultObject,
80+
...defaultOpenAPIObject,
7781
};
7882
case 'union':
7983
let nullable = false;
@@ -100,12 +104,16 @@ function schemaToOpenAPI(
100104
return {
101105
...(nullable ? { nullable } : {}),
102106
allOf: oneOf,
103-
...defaultObject,
107+
...defaultOpenAPIObject,
104108
};
105109
else
106-
return { ...(nullable ? { nullable } : {}), ...oneOf[0], ...defaultObject };
110+
return {
111+
...(nullable ? { nullable } : {}),
112+
...oneOf[0],
113+
...defaultOpenAPIObject,
114+
};
107115
} else {
108-
return { ...(nullable ? { nullable } : {}), oneOf, ...defaultObject };
116+
return { ...(nullable ? { nullable } : {}), oneOf, ...defaultOpenAPIObject };
109117
}
110118
case 'record':
111119
const additionalProperties = schemaToOpenAPI(schema.codomain);
@@ -115,7 +123,7 @@ function schemaToOpenAPI(
115123
return {
116124
type: 'object',
117125
additionalProperties,
118-
...defaultObject,
126+
...defaultOpenAPIObject,
119127
};
120128
case 'undefined':
121129
return undefined;
@@ -140,6 +148,10 @@ function schemaToOpenAPI(
140148
return openAPIObject;
141149
}
142150

151+
function getTagName(schema: Schema, tagName: String): string | undefined {
152+
return schema.comment?.tags.find((t) => t.tag === tagName)?.name;
153+
}
154+
143155
function routeToOpenAPI(route: Route): [string, string, OpenAPIV3.OperationObject] {
144156
const jsdoc = route.comment !== undefined ? parseCommentBlock(route.comment) : {};
145157
const operationId = jsdoc.tags?.operationId;

packages/openapi-generator/test/openapi.test.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2036,3 +2036,146 @@ testCase('route with record types and descriptions', ROUTE_WITH_RECORD_TYPES_AND
20362036
schemas: {}
20372037
}
20382038
});
2039+
2040+
const ROUTE_WITH_DESCRIPTIONS_PATTERNS_EXAMPLES = `
2041+
import * as t from 'io-ts';
2042+
import * as h from '@api-ts/io-ts-http';
2043+
2044+
/**
2045+
* A simple route with type descriptions
2046+
*
2047+
* @operationId api.v1.test
2048+
* @tag Test Routes
2049+
*/
2050+
export const route = h.httpRoute({
2051+
path: '/foo',
2052+
method: 'GET',
2053+
request: h.httpRequest({
2054+
query: {
2055+
/**
2056+
* This is a bar param.
2057+
* @example { "foo": "bar" }
2058+
*/
2059+
bar: t.record(t.string, t.string),
2060+
},
2061+
body: {
2062+
/**
2063+
* foo description
2064+
* @pattern ^[1-9][0-9]{4}$
2065+
* @example 12345
2066+
*/
2067+
foo: t.number,
2068+
child: {
2069+
/**
2070+
* child description
2071+
*/
2072+
child: t.array(t.union([t.string, t.number])),
2073+
}
2074+
},
2075+
}),
2076+
response: {
2077+
200: {
2078+
test: t.string
2079+
}
2080+
},
2081+
});
2082+
`;
2083+
2084+
testCase('route with descriptions, patterns, and examples', ROUTE_WITH_DESCRIPTIONS_PATTERNS_EXAMPLES, {
2085+
openapi: '3.0.3',
2086+
info: {
2087+
title: 'Test',
2088+
version: '1.0.0'
2089+
},
2090+
paths: {
2091+
'/foo': {
2092+
get: {
2093+
summary: 'A simple route with type descriptions',
2094+
operationId: 'api.v1.test',
2095+
tags: [
2096+
'Test Routes'
2097+
],
2098+
parameters: [
2099+
{
2100+
name: 'bar',
2101+
description: 'This is a bar param.',
2102+
in: 'query',
2103+
required: true,
2104+
schema: {
2105+
type: 'object',
2106+
additionalProperties: {
2107+
type: 'string'
2108+
}
2109+
}
2110+
}
2111+
],
2112+
requestBody: {
2113+
content: {
2114+
'application/json': {
2115+
schema: {
2116+
type: 'object',
2117+
properties: {
2118+
foo: {
2119+
type: 'number',
2120+
description: 'foo description',
2121+
example: '12345',
2122+
pattern: '^[1-9][0-9]{4}$'
2123+
},
2124+
child: {
2125+
type: 'object',
2126+
properties: {
2127+
child: {
2128+
type: 'array',
2129+
items: {
2130+
oneOf: [
2131+
{
2132+
type: 'string'
2133+
},
2134+
{
2135+
type: 'number'
2136+
}
2137+
]
2138+
},
2139+
description: 'child description'
2140+
}
2141+
},
2142+
required: [
2143+
'child'
2144+
]
2145+
}
2146+
},
2147+
required: [
2148+
'foo',
2149+
'child'
2150+
]
2151+
}
2152+
}
2153+
}
2154+
},
2155+
responses: {
2156+
'200': {
2157+
description: 'OK',
2158+
content: {
2159+
'application/json': {
2160+
schema: {
2161+
type: 'object',
2162+
properties: {
2163+
test: {
2164+
type: 'string'
2165+
}
2166+
},
2167+
required: [
2168+
'test'
2169+
]
2170+
}
2171+
}
2172+
}
2173+
}
2174+
}
2175+
}
2176+
}
2177+
},
2178+
components: {
2179+
schemas: {}
2180+
}
2181+
});

0 commit comments

Comments
 (0)