Skip to content

Commit 9e442ce

Browse files
committed
chore: initial commit
1 parent e42b47b commit 9e442ce

3 files changed

Lines changed: 273 additions & 0 deletions

File tree

__tests__/regression.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/* eslint-disable no-new */
2+
import { test } from 'vitest';
3+
import * as oac from '@block65/openapi-constructs';
4+
5+
test('Regression', async () => {
6+
const api = new oac.Api({
7+
openapi: oac.OpenApiVersion.V3_1,
8+
info: {
9+
title: 'Example',
10+
version: '1.0.0',
11+
},
12+
});
13+
14+
const schema1 = new oac.Schema(api, 'Test1', {
15+
schema: {
16+
type: 'object',
17+
additionalProperties: false,
18+
minProperties: 1,
19+
properties: {
20+
description: {
21+
oneOf: [
22+
{
23+
type: ['string', 'null'],
24+
minLength: 1,
25+
maxLength: 1024,
26+
},
27+
{
28+
type: 'null',
29+
},
30+
],
31+
},
32+
},
33+
},
34+
});
35+
36+
new oac.Schema(api, 'Test2', {
37+
schema: {
38+
allOf: [
39+
schema1.schema,
40+
{
41+
type: 'object',
42+
required: ['name'],
43+
properties: {
44+
name: {
45+
type: 'string',
46+
},
47+
},
48+
},
49+
],
50+
},
51+
});
52+
});

examples/example.ts

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/* eslint-disable no-new */
2+
import {
3+
Api,
4+
OpenApiVersion,
5+
Parameter,
6+
Path,
7+
Reference,
8+
Response,
9+
Schema,
10+
SecurityRequirement,
11+
SecurityScheme,
12+
Server,
13+
Tag,
14+
HttpMethods,
15+
} from '@block65/openapi-constructs';
16+
17+
const api = new Api({
18+
openapi: OpenApiVersion.V3_1,
19+
info: {
20+
title: 'Example REST API',
21+
version: '1.0.0',
22+
},
23+
});
24+
25+
new Server(api, 'ExampleServer', {
26+
url: new URL('https://api.example.com'),
27+
});
28+
29+
const httpBearerJwtScheme = new SecurityScheme(api, 'HttpBearerJwtScheme', {
30+
type: 'http',
31+
scheme: 'bearer',
32+
bearerFormat: 'JWT',
33+
});
34+
35+
new SecurityRequirement(api, 'AllScopes', {
36+
securityScheme: httpBearerJwtScheme,
37+
scopes: [],
38+
});
39+
40+
const userTag = new Tag(api, 'UserTag', {
41+
name: 'user',
42+
});
43+
44+
const userDeleteScopeReq = new SecurityRequirement(api, 'UserDeleteScope', {
45+
securityScheme: httpBearerJwtScheme,
46+
scopes: ['users.delete'],
47+
});
48+
49+
const noSecurityRequirement = new SecurityRequirement(api, 'NoSecurity');
50+
51+
const addressSchema = new Schema(api, 'Address', {
52+
schema: {
53+
type: 'object',
54+
required: ['name'],
55+
additionalProperties: false,
56+
properties: {
57+
postcode: {
58+
type: 'integer',
59+
format: 'int32',
60+
minimum: 1000,
61+
maximum: 9999,
62+
},
63+
},
64+
},
65+
});
66+
67+
const idSchema = new Schema(api, 'Id', {
68+
schema: {
69+
type: 'string',
70+
minLength: 6,
71+
maxLength: 6,
72+
},
73+
});
74+
75+
const user = new Schema(api, 'User', {
76+
schema: {
77+
type: 'object',
78+
required: ['name'],
79+
additionalProperties: false,
80+
properties: {
81+
userId: idSchema.referenceObject(),
82+
name: {
83+
type: 'string',
84+
},
85+
address: addressSchema.referenceObject(),
86+
age: {
87+
type: 'integer',
88+
format: 'int32',
89+
minimum: 0,
90+
},
91+
},
92+
},
93+
});
94+
95+
const updateUserRequest = new Schema(api, 'UpdateUserRequest', {
96+
schema: {
97+
type: 'object',
98+
minProperties: 1,
99+
additionalProperties: false,
100+
properties: {
101+
address: addressSchema.referenceObject(),
102+
age: {
103+
type: 'integer',
104+
format: 'int32',
105+
minimum: 0,
106+
},
107+
},
108+
examples: [{ age: 10, address: { postcode: 2000 } }],
109+
},
110+
});
111+
112+
const createUserRequest = new Reference(user, 'CreateUserRequest');
113+
114+
const users = new Schema(api, 'Users', {
115+
schema: {
116+
type: 'array',
117+
uniqueItems: true,
118+
items: user.referenceObject(),
119+
examples: [[1, 2, 3]],
120+
},
121+
});
122+
123+
const userIdParameter = new Parameter(api, 'UserId', {
124+
name: 'userId',
125+
in: 'path',
126+
required: true,
127+
schema: idSchema,
128+
});
129+
130+
new Path(api, {
131+
path: '/users',
132+
tags: new Set([userTag]),
133+
})
134+
.addOperation(HttpMethods.GET, {
135+
operationId: 'listUsersCommand',
136+
responses: {
137+
200: new Response(api, 'ListUsersResponse', {
138+
description: 'User 200 response',
139+
content: {
140+
contentType: 'application/json',
141+
schema: users,
142+
},
143+
}),
144+
},
145+
})
146+
.addOperation(HttpMethods.POST, {
147+
operationId: 'createUserCommand',
148+
requestBody: {
149+
content: {
150+
contentType: 'application/json',
151+
schema: createUserRequest,
152+
},
153+
},
154+
responses: {
155+
200: new Response(api, 'CreateUserResponse', {
156+
description: 'User 200 response',
157+
content: {
158+
contentType: 'application/json',
159+
schema: users,
160+
},
161+
}),
162+
},
163+
});
164+
165+
new Path(api, {
166+
path: '/users/{userId}',
167+
parameters: [userIdParameter],
168+
})
169+
.addOperation(HttpMethods.GET, {
170+
operationId: 'getUserByIdCommand',
171+
responses: {
172+
200: new Response(api, 'GetUserById', {
173+
description: 'User 200 response',
174+
content: {
175+
contentType: 'application/json',
176+
schema: user,
177+
},
178+
}),
179+
},
180+
})
181+
.addOperation(HttpMethods.DELETE, {
182+
operationId: 'deleteUserByIdCommand',
183+
security: userDeleteScopeReq,
184+
})
185+
.addOperation(HttpMethods.HEAD, {
186+
operationId: 'checkUserIdAvailableCommand',
187+
security: noSecurityRequirement,
188+
})
189+
.addOperation(HttpMethods.POST, {
190+
operationId: 'updateUserCommand',
191+
requestBody: {
192+
content: {
193+
contentType: 'application/json',
194+
schema: updateUserRequest,
195+
},
196+
},
197+
responses: {
198+
200: new Response(api, 'UpdateUserResponse', {
199+
content: {
200+
contentType: 'application/json',
201+
schema: user,
202+
},
203+
}),
204+
},
205+
});
206+
207+
process.stdout.write(JSON.stringify(api.synth(), null, 2));

lib/utils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export type WithoutUndefinedProperties<T extends object> = {
2+
[P in keyof T]: Exclude<T[P], undefined>;
3+
};
4+
5+
export type OptionalToUndefined<T extends object> = {
6+
[P in keyof T]: undefined extends T[P] ? T[P] | undefined : T[P];
7+
};
8+
9+
// this is a better version that uses Object.fromEntries
10+
export function stripUndefined<T extends object>(obj: OptionalToUndefined<T>) {
11+
return Object.fromEntries(
12+
Object.entries(obj).filter(([, v]) => typeof v !== 'undefined'),
13+
) as WithoutUndefinedProperties<T>;
14+
}

0 commit comments

Comments
 (0)