@redocly/openapi-core 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,155 @@
1
+ import { outdent } from 'outdent';
2
+ import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
3
+ import { bundleDocument } from '../../../bundle';
4
+ import { BaseResolver } from '../../../resolve';
5
+
6
+ describe('oas2 remove-unused-components', () => {
7
+ it('should remove unused components', async () => {
8
+ const document = parseYamlToDocument(
9
+ outdent`
10
+ swagger: '2.0'
11
+ paths:
12
+ /pets:
13
+ get:
14
+ produces:
15
+ - application/json
16
+ parameters: []
17
+ responses:
18
+ '200':
19
+ schema:
20
+ $ref: '#/definitions/Used'
21
+ operationId: listPets
22
+ summary: List all pets
23
+ definitions:
24
+ Unused:
25
+ enum:
26
+ - 1
27
+ - 2
28
+ type: integer
29
+ Used:
30
+ properties:
31
+ link:
32
+ type: string
33
+ type: object
34
+ `,
35
+ 'foobar.yaml'
36
+ );
37
+
38
+ const results = await bundleDocument({
39
+ externalRefResolver: new BaseResolver(),
40
+ document,
41
+ config: await makeConfig({}),
42
+ removeUnusedComponents: true,
43
+ });
44
+
45
+ expect(results.bundle.parsed).toEqual({
46
+ swagger: '2.0',
47
+ definitions: {
48
+ Used: {
49
+ properties: {
50
+ link: { type: 'string' },
51
+ },
52
+ type: 'object',
53
+ },
54
+ },
55
+ paths: {
56
+ '/pets': {
57
+ get: {
58
+ produces: ['application/json'],
59
+ parameters: [],
60
+ summary: 'List all pets',
61
+ operationId: 'listPets',
62
+ responses: {
63
+ '200': {
64
+ schema: {
65
+ $ref: '#/definitions/Used',
66
+ },
67
+ },
68
+ },
69
+ },
70
+ },
71
+ },
72
+ });
73
+ });
74
+
75
+ it('should not remove components used child reference', async () => {
76
+ const document = parseYamlToDocument(
77
+ outdent`
78
+ swagger: '2.0'
79
+ paths:
80
+ /pets:
81
+ get:
82
+ produces:
83
+ - application/json
84
+ parameters: []
85
+ responses:
86
+ '200':
87
+ schema:
88
+ $ref: '#/definitions/Used'
89
+ operationId: listPets
90
+ summary: List all pets
91
+ definitions:
92
+ InnerUsed:
93
+ properties:
94
+ link:
95
+ type: string
96
+ type: object
97
+ Unused:
98
+ enum:
99
+ - 1
100
+ - 2
101
+ type: integer
102
+ Used:
103
+ properties:
104
+ link:
105
+ $ref: '#/definitions/InnerUsed/properties/link'
106
+ type: object
107
+ `,
108
+ 'foobar.yaml'
109
+ );
110
+
111
+ const results = await bundleDocument({
112
+ externalRefResolver: new BaseResolver(),
113
+ document,
114
+ config: await makeConfig({}),
115
+ removeUnusedComponents: true,
116
+ });
117
+
118
+ expect(results.bundle.parsed).toEqual({
119
+ swagger: '2.0',
120
+ definitions: {
121
+ InnerUsed: {
122
+ properties: {
123
+ link: {
124
+ type: 'string',
125
+ },
126
+ },
127
+ type: 'object',
128
+ },
129
+ Used: {
130
+ properties: {
131
+ link: { $ref: '#/definitions/InnerUsed/properties/link' },
132
+ },
133
+ type: 'object',
134
+ },
135
+ },
136
+ paths: {
137
+ '/pets': {
138
+ get: {
139
+ produces: ['application/json'],
140
+ parameters: [],
141
+ summary: 'List all pets',
142
+ operationId: 'listPets',
143
+ responses: {
144
+ '200': {
145
+ schema: {
146
+ $ref: '#/definitions/Used',
147
+ },
148
+ },
149
+ },
150
+ },
151
+ },
152
+ },
153
+ });
154
+ });
155
+ });
@@ -27,7 +27,12 @@ export const RemoveUnusedComponents: Oas2Rule = () => {
27
27
  if (['Schema', 'Parameter', 'Response', 'SecurityScheme'].includes(type.name)) {
28
28
  const resolvedRef = resolve(ref);
29
29
  if (!resolvedRef.location) return;
30
- components.set(resolvedRef.location.absolutePointer, {
30
+
31
+ const [fileLocation, localPointer] = resolvedRef.location.absolutePointer.split('#', 2);
32
+ const componentLevelLocalPointer = localPointer.split('/').slice(0, 3).join('/');
33
+ const pointer = `${fileLocation}#${componentLevelLocalPointer}`;
34
+
35
+ components.set(pointer, {
31
36
  used: true,
32
37
  name: key.toString(),
33
38
  });
@@ -0,0 +1,171 @@
1
+ import { outdent } from 'outdent';
2
+ import { parseYamlToDocument, makeConfig } from '../../../../__tests__/utils';
3
+ import { bundleDocument } from '../../../bundle';
4
+ import { BaseResolver } from '../../../resolve';
5
+
6
+ describe('oas3 remove-unused-components', () => {
7
+ it('should remove unused components', async () => {
8
+ const document = parseYamlToDocument(
9
+ outdent`
10
+ openapi: "3.0.0"
11
+ paths:
12
+ /pets:
13
+ get:
14
+ summary: List all pets
15
+ operationId: listPets
16
+ parameters:
17
+ - $ref: '#/components/parameters/used'
18
+ components:
19
+ parameters:
20
+ used:
21
+ name: used
22
+ unused:
23
+ name: unused
24
+ responses:
25
+ unused: {}
26
+ examples:
27
+ unused: {}
28
+ requestBodies:
29
+ unused: {}
30
+ headers:
31
+ unused: {}
32
+ schemas:
33
+ Unused:
34
+ type: integer
35
+ enum:
36
+ - 1
37
+ - 2
38
+ `,
39
+ 'foobar.yaml'
40
+ );
41
+
42
+ const results = await bundleDocument({
43
+ externalRefResolver: new BaseResolver(),
44
+ document,
45
+ config: await makeConfig({}),
46
+ removeUnusedComponents: true,
47
+ });
48
+
49
+ expect(results.bundle.parsed).toEqual({
50
+ openapi: '3.0.0',
51
+ paths: {
52
+ '/pets': {
53
+ get: {
54
+ summary: 'List all pets',
55
+ operationId: 'listPets',
56
+ parameters: [
57
+ {
58
+ $ref: '#/components/parameters/used',
59
+ },
60
+ ],
61
+ },
62
+ },
63
+ },
64
+ components: {
65
+ parameters: {
66
+ used: {
67
+ name: 'used',
68
+ },
69
+ },
70
+ },
71
+ });
72
+ });
73
+
74
+ it('should not remove components used child reference', async () => {
75
+ const document = parseYamlToDocument(
76
+ outdent`
77
+ openapi: "3.0.0"
78
+ paths:
79
+ /pets:
80
+ get:
81
+ summary: List all pets
82
+ operationId: listPets
83
+ responses:
84
+ '200':
85
+ content:
86
+ application/json:
87
+ schema:
88
+ $ref: '#/components/schemas/Used'
89
+ components:
90
+ parameters:
91
+ unused:
92
+ name: unused
93
+ responses:
94
+ unused: {}
95
+ examples:
96
+ unused: {}
97
+ requestBodies:
98
+ unused: {}
99
+ headers:
100
+ unused: {}
101
+ schemas:
102
+ InnerUsed:
103
+ type: object
104
+ properties:
105
+ link:
106
+ type: string
107
+ Used:
108
+ type: object
109
+ properties:
110
+ link:
111
+ $ref: '#/components/schemas/InnerUsed/properties/link'
112
+ Unused:
113
+ type: integer
114
+ enum:
115
+ - 1
116
+ - 2
117
+ `,
118
+ 'foobar.yaml'
119
+ );
120
+
121
+ const results = await bundleDocument({
122
+ externalRefResolver: new BaseResolver(),
123
+ document,
124
+ config: await makeConfig({}),
125
+ removeUnusedComponents: true,
126
+ });
127
+
128
+ expect(results.bundle.parsed).toEqual({
129
+ openapi: '3.0.0',
130
+ paths: {
131
+ '/pets': {
132
+ get: {
133
+ summary: 'List all pets',
134
+ operationId: 'listPets',
135
+ responses: {
136
+ '200': {
137
+ content: {
138
+ 'application/json': {
139
+ schema: {
140
+ $ref: '#/components/schemas/Used',
141
+ },
142
+ },
143
+ },
144
+ },
145
+ },
146
+ },
147
+ },
148
+ },
149
+ components: {
150
+ schemas: {
151
+ InnerUsed: {
152
+ type: 'object',
153
+ properties: {
154
+ link: {
155
+ type: 'string',
156
+ },
157
+ },
158
+ },
159
+ Used: {
160
+ type: 'object',
161
+ properties: {
162
+ link: {
163
+ $ref: '#/components/schemas/InnerUsed/properties/link',
164
+ },
165
+ },
166
+ },
167
+ },
168
+ },
169
+ });
170
+ });
171
+ });
@@ -31,7 +31,12 @@ export const RemoveUnusedComponents: Oas3Rule = () => {
31
31
  ) {
32
32
  const resolvedRef = resolve(ref);
33
33
  if (!resolvedRef.location) return;
34
- components.set(resolvedRef.location.absolutePointer, {
34
+
35
+ const [fileLocation, localPointer] = resolvedRef.location.absolutePointer.split('#', 2);
36
+ const componentLevelLocalPointer = localPointer.split('/').slice(0, 4).join('/');
37
+ const pointer = `${fileLocation}#${componentLevelLocalPointer}`;
38
+
39
+ components.set(pointer, {
35
40
  used: true,
36
41
  name: key.toString(),
37
42
  });
@@ -0,0 +1,343 @@
1
+ import {
2
+ ApigeeDevOnboardingIntegrationAuthType,
3
+ AuthProviderType,
4
+ DEFAULT_TEAM_CLAIM_NAME,
5
+ } from '../config';
6
+
7
+ const oidcIssuerMetadataSchema = {
8
+ type: 'object',
9
+ properties: {
10
+ end_session_endpoint: { type: 'string' },
11
+ token_endpoint: { type: 'string' },
12
+ authorization_endpoint: { type: 'string' },
13
+ jwks_uri: { type: 'string' },
14
+ },
15
+ required: ['token_endpoint', 'authorization_endpoint'],
16
+ additionalProperties: true,
17
+ } as const;
18
+
19
+ const oidcProviderConfigSchema = {
20
+ type: 'object',
21
+ properties: {
22
+ type: { type: 'string', const: AuthProviderType.OIDC },
23
+ title: { type: 'string' },
24
+ configurationUrl: { type: 'string', minLength: 1 },
25
+ configuration: oidcIssuerMetadataSchema,
26
+ clientId: { type: 'string', minLength: 1 },
27
+ clientSecret: { type: 'string', minLength: 1 },
28
+ teamsClaimName: { type: 'string' },
29
+ teamsClaimMap: { type: 'object', additionalProperties: { type: 'string' } },
30
+ defaultTeams: { type: 'array', items: { type: 'string' } },
31
+ scopes: { type: 'array', items: { type: 'string' } },
32
+ tokenExpirationTime: { type: 'number' },
33
+ authorizationRequestCustomParams: { type: 'object', additionalProperties: { type: 'string' } },
34
+ tokenRequestCustomParams: { type: 'object', additionalProperties: { type: 'string' } },
35
+ audience: { type: 'array', items: { type: 'string' } },
36
+ },
37
+ required: ['type', 'clientId', 'clientSecret'],
38
+ oneOf: [{ required: ['configurationUrl'] }, { required: ['configuration'] }],
39
+ additionalProperties: false,
40
+ } as const;
41
+
42
+ const saml2ProviderConfigSchema = {
43
+ type: 'object',
44
+ properties: {
45
+ type: { type: 'string', const: AuthProviderType.SAML2 },
46
+ title: { type: 'string' },
47
+ issuerId: { type: 'string' },
48
+ entityId: { type: 'string' },
49
+ ssoUrl: { type: 'string' },
50
+ x509PublicCert: { type: 'string' },
51
+ teamsAttributeName: { type: 'string', default: DEFAULT_TEAM_CLAIM_NAME },
52
+ teamsAttributeMap: { type: 'object', additionalProperties: { type: 'string' } },
53
+ defaultTeams: { type: 'array', items: { type: 'string' } },
54
+ },
55
+ additionalProperties: false,
56
+ required: ['type', 'issuerId', 'ssoUrl', 'x509PublicCert'],
57
+ } as const;
58
+
59
+ const basicAuthProviderConfigSchema = {
60
+ type: 'object',
61
+ properties: {
62
+ type: { type: 'string', const: AuthProviderType.BASIC },
63
+ title: { type: 'string' },
64
+ credentials: {
65
+ type: 'array',
66
+ items: {
67
+ type: 'object',
68
+ properties: {
69
+ username: { type: 'string' },
70
+ password: { type: 'string' },
71
+ passwordHash: { type: 'string' },
72
+ teams: { type: 'array', items: { type: 'string' } },
73
+ },
74
+ required: ['username'],
75
+ additionalProperties: false,
76
+ },
77
+ },
78
+ },
79
+ required: ['type', 'credentials'],
80
+ additionalProperties: false,
81
+ } as const;
82
+
83
+ const authProviderConfigSchema = {
84
+ oneOf: [oidcProviderConfigSchema, saml2ProviderConfigSchema, basicAuthProviderConfigSchema],
85
+ discriminator: { propertyName: 'type' },
86
+ } as const;
87
+
88
+ export const ssoConfigSchema = {
89
+ type: 'object',
90
+ additionalProperties: authProviderConfigSchema,
91
+ } as const;
92
+
93
+ const redirectConfigSchema = {
94
+ type: 'object',
95
+ properties: {
96
+ to: { type: 'string' },
97
+ type: { type: 'number', default: 301 },
98
+ },
99
+ required: ['to'],
100
+ additionalProperties: false,
101
+ } as const;
102
+
103
+ export const apiConfigSchema = {
104
+ type: 'object',
105
+ properties: {
106
+ root: { type: 'string' },
107
+ rbac: { type: 'object', additionalProperties: true },
108
+ theme: {
109
+ type: 'object',
110
+ properties: {
111
+ openapi: { type: 'object', additionalProperties: true },
112
+ },
113
+ additionalProperties: false,
114
+ },
115
+ title: { type: 'string' },
116
+ metadata: { type: 'object', additionalProperties: true },
117
+ },
118
+ additionalProperties: true,
119
+ required: ['root'],
120
+ } as const;
121
+
122
+ const metadataConfigSchema = {
123
+ type: 'object',
124
+ additionalProperties: true,
125
+ } as const;
126
+
127
+ const seoConfigSchema = {
128
+ type: 'object',
129
+ properties: {
130
+ title: { type: 'string' },
131
+ description: { type: 'string' },
132
+ siteUrl: { type: 'string' },
133
+ image: { type: 'string' },
134
+ keywords: { type: 'array', items: { type: 'string' } },
135
+ lang: { type: 'string' },
136
+ jsonLd: { type: 'object' },
137
+ meta: {
138
+ type: 'array',
139
+ items: {
140
+ type: 'object',
141
+ properties: {
142
+ name: { type: 'string' },
143
+ content: { type: 'string' },
144
+ },
145
+ required: ['name', 'content'],
146
+ additionalProperties: false,
147
+ },
148
+ },
149
+ },
150
+ additionalProperties: false,
151
+ } as const;
152
+
153
+ const rbacScopeItemsSchema = { type: 'object', additionalProperties: { type: 'string' } } as const;
154
+
155
+ const rbacConfigSchema = {
156
+ type: 'object',
157
+ properties: {
158
+ defaults: rbacScopeItemsSchema,
159
+ },
160
+ additionalProperties: rbacScopeItemsSchema,
161
+ } as const;
162
+
163
+ const graviteeAdapterConfigSchema = {
164
+ type: 'object',
165
+ properties: {
166
+ type: { type: 'string', const: 'GRAVITEE' },
167
+ apiBaseUrl: { type: 'string' },
168
+ env: { type: 'string' },
169
+ allowApiProductsOutsideCatalog: { type: 'boolean', default: false },
170
+ stage: { type: 'string', default: 'non-production' },
171
+
172
+ auth: { type: 'object', properties: { static: { type: 'string' } } },
173
+ },
174
+ additionalProperties: false,
175
+ required: ['type', 'apiBaseUrl'],
176
+ } as const;
177
+
178
+ const apigeeAdapterAuthOauth2Schema = {
179
+ type: 'object',
180
+ properties: {
181
+ type: { type: 'string', const: ApigeeDevOnboardingIntegrationAuthType.OAUTH2 },
182
+ tokenEndpoint: { type: 'string' },
183
+ clientId: { type: 'string' },
184
+ clientSecret: { type: 'string' },
185
+ },
186
+ additionalProperties: false,
187
+ required: ['type', 'tokenEndpoint', 'clientId', 'clientSecret'],
188
+ } as const;
189
+
190
+ const apigeeAdapterAuthServiceAccountSchema = {
191
+ type: 'object',
192
+ properties: {
193
+ type: { type: 'string', const: ApigeeDevOnboardingIntegrationAuthType.SERVICE_ACCOUNT },
194
+ serviceAccountEmail: { type: 'string' },
195
+ serviceAccountPrivateKey: { type: 'string' },
196
+ },
197
+ additionalProperties: false,
198
+ required: ['type', 'serviceAccountEmail', 'serviceAccountPrivateKey'],
199
+ } as const;
200
+
201
+ const apigeeXAdapterConfigSchema = {
202
+ type: 'object',
203
+ properties: {
204
+ type: { type: 'string', const: 'APIGEE_X' },
205
+ apiUrl: { type: 'string' },
206
+ stage: { type: 'string', default: 'non-production' },
207
+ organizationName: { type: 'string' },
208
+ ignoreApiProducts: { type: 'array', items: { type: 'string' } },
209
+ allowApiProductsOutsideCatalog: { type: 'boolean', default: false },
210
+ auth: {
211
+ type: 'object',
212
+ oneOf: [apigeeAdapterAuthOauth2Schema, apigeeAdapterAuthServiceAccountSchema],
213
+ discriminator: { propertyName: 'type' },
214
+ },
215
+ },
216
+ additionalProperties: false,
217
+ required: ['type', 'organizationName', 'auth'],
218
+ } as const;
219
+
220
+ const apigeeEdgeAdapterConfigSchema = {
221
+ ...apigeeXAdapterConfigSchema,
222
+ properties: {
223
+ ...apigeeXAdapterConfigSchema.properties,
224
+ type: { type: 'string', const: 'APIGEE_EDGE' },
225
+ },
226
+ } as const;
227
+
228
+ const devOnboardingAdapterConfigSchema = {
229
+ type: 'object',
230
+ oneOf: [apigeeXAdapterConfigSchema, apigeeEdgeAdapterConfigSchema, graviteeAdapterConfigSchema],
231
+ discriminator: { propertyName: 'type' },
232
+ } as const;
233
+
234
+ const devOnboardingConfigSchema = {
235
+ type: 'object',
236
+ required: ['adapters'],
237
+ additionalProperties: false,
238
+ properties: {
239
+ adapters: {
240
+ type: 'array',
241
+ items: devOnboardingAdapterConfigSchema,
242
+ },
243
+ },
244
+ } as const;
245
+
246
+ const responseHeaderSchema = {
247
+ type: 'object',
248
+ properties: {
249
+ name: { type: 'string' },
250
+ value: { type: 'string' },
251
+ },
252
+ additionalProperties: false,
253
+ required: ['name', 'value'],
254
+ } as const;
255
+
256
+ export const redoclyConfigSchema = {
257
+ type: 'object',
258
+ properties: {
259
+ licenseKey: { type: 'string' },
260
+ theme: { type: 'object', default: {} }, // ThemeConfig
261
+ redirects: { type: 'object', additionalProperties: redirectConfigSchema, default: {} },
262
+ seo: seoConfigSchema,
263
+ rbac: rbacConfigSchema,
264
+ responseHeaders: {
265
+ type: 'object',
266
+ additionalProperties: {
267
+ type: 'array',
268
+ items: responseHeaderSchema,
269
+ },
270
+ },
271
+ mockServer: {
272
+ type: 'object',
273
+ properties: {
274
+ off: { type: 'boolean', default: false },
275
+ position: { type: 'string', enum: ['first', 'last', 'replace', 'off'], default: 'first' },
276
+ strictExamples: { type: 'boolean', default: false },
277
+ errorIfForcedExampleNotFound: { type: 'boolean', default: false },
278
+ description: { type: 'string' },
279
+ },
280
+ },
281
+ apis: {
282
+ type: 'object',
283
+ additionalProperties: apiConfigSchema,
284
+ },
285
+ sso: ssoConfigSchema,
286
+ developerOnboarding: devOnboardingConfigSchema,
287
+ i18n: {
288
+ type: 'object',
289
+ properties: {
290
+ defaultLocale: {
291
+ type: 'string',
292
+ },
293
+ locales: {
294
+ type: 'array',
295
+ items: {
296
+ type: 'object',
297
+ properties: {
298
+ code: {
299
+ type: 'string',
300
+ },
301
+ name: {
302
+ type: 'string',
303
+ },
304
+ },
305
+ required: ['code'],
306
+ },
307
+ },
308
+ },
309
+ additionalProperties: false,
310
+ required: ['defaultLocale', 'locales'],
311
+ },
312
+ metadata: metadataConfigSchema,
313
+ },
314
+ default: {},
315
+ additionalProperties: true,
316
+ } as const;
317
+
318
+ export const environmentSchema = {
319
+ oneOf: [
320
+ { ...redoclyConfigSchema, additionalProperties: false },
321
+ {
322
+ type: 'object',
323
+ properties: {
324
+ $ref: { type: 'string' },
325
+ },
326
+ required: ['$ref'],
327
+ additionalProperties: false,
328
+ },
329
+ ],
330
+ } as const;
331
+
332
+ export const rootRedoclyConfigSchema = {
333
+ ...redoclyConfigSchema,
334
+ properties: {
335
+ ...redoclyConfigSchema.properties,
336
+ env: {
337
+ type: 'object',
338
+ additionalProperties: environmentSchema,
339
+ },
340
+ },
341
+ default: {},
342
+ required: ['redirects'],
343
+ } as const;