Skip to content

Commit 8ccc1a1

Browse files
authored
Merge pull request #856 from BitGo/DX-613
feat: support private fields in the openapi generator
2 parents 87de81f + 377b331 commit 8ccc1a1

File tree

2 files changed

+165
-12
lines changed

2 files changed

+165
-12
lines changed

packages/openapi-generator/src/openapi.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { Route } from './route';
77
import type { Schema } from './ir';
88
import { Block } from 'comment-parser';
99

10-
function schemaToOpenAPI(
10+
export function schemaToOpenAPI(
1111
schema: Schema,
1212
): OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined {
1313
schema = optimize(schema);
@@ -226,8 +226,10 @@ function schemaToOpenAPI(
226226
const format = jsdoc?.tags?.format ?? schema.format ?? schema.format;
227227
const title = jsdoc?.tags?.title ?? schema.title;
228228

229-
const deprecated =
230-
Object.keys(jsdoc?.tags || {}).includes('deprecated') || !!schema.deprecated;
229+
const keys = Object.keys(jsdoc?.tags || {});
230+
231+
const deprecated = keys.includes('deprecated') || !!schema.deprecated;
232+
const isPrivate = keys.includes('private');
231233
const description = schema.comment?.description ?? schema.description;
232234

233235
const defaultOpenAPIObject = {
@@ -252,6 +254,7 @@ function schemaToOpenAPI(
252254
...(writeOnly ? { writeOnly: true } : {}),
253255
...(format ? { format } : {}),
254256
...(title ? { title } : {}),
257+
...(isPrivate ? { 'x-internal': true } : {}),
255258
};
256259

257260
return defaultOpenAPIObject;
@@ -322,12 +325,18 @@ function routeToOpenAPI(route: Route): [string, string, OpenAPIV3.OperationObjec
322325
delete schema.description;
323326
}
324327

328+
const isPrivate = schema && 'x-internal' in schema;
329+
if (isPrivate) {
330+
delete schema['x-internal'];
331+
}
332+
325333
return {
326334
name: p.name,
327335
...(p.schema?.comment?.description !== undefined
328336
? { description: p.schema.comment.description }
329337
: {}),
330338
in: p.type,
339+
...(isPrivate ? { 'x-internal': true } : {}),
331340
...(p.required ? { required: true } : {}),
332341
...(p.explode ? { style: 'form', explode: true } : {}),
333342
schema: schema as any, // TODO: Something to disallow arrays

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

Lines changed: 153 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as E from 'fp-ts/lib/Either';
22
import assert from 'node:assert/strict';
33
import test from 'node:test';
4-
import { OpenAPIV3_1 } from 'openapi-types';
54

65
import {
76
convertRoutesToOpenAPI,
@@ -17,11 +16,7 @@ import { SourceFile } from '../src/sourceFile';
1716
async function testCase(
1817
description: string,
1918
src: string,
20-
expected: OpenAPIV3_1.Document<{
21-
'x-internal'?: boolean;
22-
'x-unstable'?: boolean;
23-
'x-unknown-tags'?: object;
24-
}>,
19+
expected: any,
2520
expectedErrors: string[] = [],
2621
) {
2722
test(description, async () => {
@@ -3940,13 +3935,162 @@ testCase("route with nested array examples", ROUTE_WITH_NESTED_ARRAY_EXAMPLES, {
39403935
}
39413936
});
39423937

3943-
const ROUTE_WITH_RECORD_TYPES = `
3938+
const ROUTE_WITH_PRIVATE_PROPERTIES = `
39443939
import * as t from 'io-ts';
39453940
import * as h from '@api-ts/io-ts-http';
39463941
3942+
const SampleType = t.type({
3943+
foo: t.string,
3944+
/** @private */
3945+
bar: t.string, // This should show up with x-internal,
3946+
/** @private */
3947+
privateObject: t.type({
3948+
privateFieldInObject: t.boolean
3949+
})
3950+
});
3951+
3952+
export const route = h.httpRoute({
3953+
path: '/foo',
3954+
method: 'GET',
3955+
request: h.httpRequest({
3956+
params: {
3957+
/** @private */
3958+
path: t.string
3959+
},
3960+
query: {
3961+
/** @private */
3962+
query: t.string
3963+
},
3964+
body: SampleType
3965+
}),
3966+
response: {
3967+
200: SampleType
3968+
},
3969+
});
3970+
`;
3971+
3972+
testCase("route with private properties in request query, params, body, and response", ROUTE_WITH_PRIVATE_PROPERTIES, {
3973+
openapi: "3.0.3",
3974+
info: {
3975+
title: "Test",
3976+
version: "1.0.0"
3977+
},
3978+
paths: {
3979+
'/foo': {
3980+
get: {
3981+
parameters: [
3982+
{
3983+
'x-internal': true,
3984+
description: '',
3985+
in: 'query',
3986+
name: 'query',
3987+
required: true,
3988+
schema: {
3989+
type: 'string'
3990+
}
3991+
},
3992+
{
3993+
'x-internal': true,
3994+
description: '',
3995+
in: 'path',
3996+
name: 'path',
3997+
required: true,
3998+
schema: {
3999+
type: 'string'
4000+
}
4001+
}
4002+
],
4003+
requestBody: {
4004+
content: {
4005+
'application/json': {
4006+
schema: {
4007+
properties: {
4008+
bar: {
4009+
'x-internal': true,
4010+
type: 'string'
4011+
},
4012+
foo: {
4013+
type: 'string'
4014+
},
4015+
privateObject: {
4016+
'x-internal': true,
4017+
properties: {
4018+
privateFieldInObject: {
4019+
type: 'boolean'
4020+
}
4021+
},
4022+
required: [
4023+
'privateFieldInObject'
4024+
],
4025+
type: 'object'
4026+
}
4027+
},
4028+
required: [
4029+
'foo',
4030+
'bar',
4031+
'privateObject'
4032+
],
4033+
type: 'object'
4034+
}
4035+
}
4036+
},
4037+
},
4038+
responses: {
4039+
'200': {
4040+
content: {
4041+
'application/json': {
4042+
schema: {
4043+
'$ref': '#/components/schemas/SampleType'
4044+
}
4045+
}
4046+
},
4047+
description: 'OK'
4048+
}
4049+
}
4050+
}
4051+
},
4052+
},
4053+
components: {
4054+
schemas: {
4055+
SampleType: {
4056+
properties: {
4057+
bar: {
4058+
'x-internal': true,
4059+
type: 'string'
4060+
},
4061+
foo: {
4062+
type: 'string'
4063+
},
4064+
privateObject: {
4065+
'x-internal': true,
4066+
properties: {
4067+
privateFieldInObject: {
4068+
type: 'boolean'
4069+
}
4070+
},
4071+
required: [
4072+
'privateFieldInObject'
4073+
],
4074+
type: 'object'
4075+
}
4076+
},
4077+
required: [
4078+
'foo',
4079+
'bar',
4080+
'privateObject'
4081+
],
4082+
title: 'SampleType',
4083+
type: 'object'
4084+
}
4085+
}
4086+
},
4087+
});
4088+
4089+
const ROUTE_WITH_RECORD_TYPES = `
4090+
import * as t from 'io-ts';
4091+
import * as h from '@api-ts/io-ts-http';
39474092
const ValidKeys = t.keyof({ name: "name", age: "age", address: "address" });
39484093
const PersonObject = t.type({ bigName: t.string, bigAge: t.number });
3949-
39504094
export const route = h.httpRoute({
39514095
path: '/foo',
39524096
method: 'GET',
@@ -4076,4 +4220,4 @@ testCase("route with record types", ROUTE_WITH_RECORD_TYPES, {
40764220
}
40774221
}
40784222
}
4079-
});
4223+
});

0 commit comments

Comments
 (0)