Skip to content

Commit

Permalink
feat: Support resolve external option (#5360)
Browse files Browse the repository at this point in the history
  • Loading branch information
ZhongpinWang authored Jan 30, 2025
1 parent 0aad598 commit 6cc07bc
Show file tree
Hide file tree
Showing 21 changed files with 177 additions and 87 deletions.
5 changes: 5 additions & 0 deletions .changeset/stale-plums-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sap-cloud-sdk/openapi-generator': minor
---

[New Functionality] Add `resolveExternal` option to determine whether external $ref pointers will be resolved.
3 changes: 2 additions & 1 deletion packages/openapi-generator/src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,8 @@ async function generateService(
serviceOptions,
{
strictNaming: !options.skipValidation,
schemaPrefix: options.schemaPrefix
schemaPrefix: options.schemaPrefix,
resolveExternal: options.resolveExternal
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ describe('parseGeneratorOptions', () => {
overwrite: false,
config: undefined,
generateESM: false,
schemaPrefix: ''
schemaPrefix: '',
resolveExternal: true
};

it('gets default options', () => {
Expand Down
11 changes: 11 additions & 0 deletions packages/openapi-generator/src/options/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export interface OpenAPIGeneratorOptions {
* @experimental
*/
schemaPrefix?: string;
/**
* Resolve external references.
*/
resolveExternal?: boolean;
}

/**
Expand All @@ -48,5 +52,12 @@ export const cliOptions = {
type: 'string',
default: '',
hidden: true
},
resolveExternal: {
describe:
'By default, external $ref pointers will be resolved. If set to false, external $ref pointers will be ignored.',
type: 'boolean',
default: true,
hidden: true
}
} as const satisfies Options<GeneratorOptions>;
2 changes: 1 addition & 1 deletion packages/openapi-generator/src/parser/api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { parseApis } from './api';
import { createRefs } from './refs';
import type { OpenAPIV3 } from 'openapi-types';

const options = { strictNaming: true, schemaPrefix: '' };
const options = { strictNaming: true, schemaPrefix: '', resolveExternal: true };
describe('parseApis', () => {
it('throws an error if there are APIs without paths', async () => {
const refs = await createTestRefs();
Expand Down
6 changes: 3 additions & 3 deletions packages/openapi-generator/src/parser/document.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as api from './api';
import type { ServiceOptions } from '@sap-cloud-sdk/generator-common/dist/options-per-service';
import type { OpenAPIV3 } from 'openapi-types';

const options = { strictNaming: true, schemaPrefix: '' };
const options = { strictNaming: true, schemaPrefix: '', resolveExternal: true };
describe('parseOpenApiDocument()', () => {
it('does not modify input service specification', () => {
const input: OpenAPIV3.Document = {
Expand Down Expand Up @@ -54,7 +54,7 @@ describe('parseOpenApiDocument()', () => {
packageName: '@sap/cloud-sdk-openapi-test-service',
directoryName: 'test-service'
},
{ strictNaming: false, schemaPrefix: '' }
{ strictNaming: false, schemaPrefix: '', resolveExternal: true }
);

expect(parsedDocument.schemas).toEqual([
Expand Down Expand Up @@ -276,7 +276,7 @@ describe('parseOpenApiDocument()', () => {
packageName: '@sap/cloud-sdk-openapi-test-service',
directoryName: 'test-service'
},
{ strictNaming: false, schemaPrefix: '' }
{ strictNaming: false, schemaPrefix: '', resolveExternal: true }
);

expect(parsedDocument.schemas).toEqual([
Expand Down
6 changes: 4 additions & 2 deletions packages/openapi-generator/src/parser/document.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import SwaggerParser from '@apidevtools/swagger-parser';
import {
isAllOfSchema,
isOneOfSchema,
Expand All @@ -10,7 +11,6 @@ import {
} from './schema';
import { parseApis } from './api';
import { createRefs } from './refs';
import { parseBound } from './swagger-parser-workaround';
import type { OpenAPIV3 } from 'openapi-types';
import type { ServiceOptions } from '@sap-cloud-sdk/generator-common/internal';
import type {
Expand All @@ -35,7 +35,9 @@ export async function parseOpenApiDocument(
options: ParserOptions
): Promise<OpenApiDocument> {
const clonedContent = JSON.parse(JSON.stringify(fileContent));
const document = (await parseBound(clonedContent)) as OpenAPIV3.Document;
const document = (await SwaggerParser.parse(
clonedContent
)) as OpenAPIV3.Document;
const refs = await createRefs(document, options);
const schemas = parseSchemas(document, refs, options);
sanitizeDiscriminatedSchemas(schemas, refs, options);
Expand Down
1 change: 0 additions & 1 deletion packages/openapi-generator/src/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,5 @@ export * from './request-body';
export * from './responses';
export * from './schema-naming';
export * from './schema';
export * from './swagger-parser-workaround';
export * from './type-mapping';
export * from './unique-naming';
6 changes: 5 additions & 1 deletion packages/openapi-generator/src/parser/media-type.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { createTestRefs, emptyObjectSchema } from '../../test/test-util';
import { parseTopLevelMediaType, parseMediaType } from './media-type';

const defaultOptions = { strictNaming: true, schemaPrefix: '' };
const defaultOptions = {
strictNaming: true,
schemaPrefix: '',
resolveExternal: true
};
describe('parseTopLevelMediaType', () => {
it('returns undefined if the media type is not supported', async () => {
expect(
Expand Down
12 changes: 9 additions & 3 deletions packages/openapi-generator/src/parser/operation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import {
import type { OpenAPIV3 } from 'openapi-types';
import type { OpenApiParameter } from '../openapi-types';

const defaultOptions = { strictNaming: true, schemaPrefix: '' };
const defaultOptions = {
strictNaming: true,
schemaPrefix: '',
resolveExternal: true
};
describe('getRelevantParameters', () => {
it('ignores cookie parameters', async () => {
expect(
Expand Down Expand Up @@ -111,7 +115,8 @@ describe('parsePathParameters', () => {
expect(
parsePathParameters([], await createTestRefs(), {
strictNaming: false,
schemaPrefix: ''
schemaPrefix: '',
resolveExternal: true
})
).toEqual([]);
});
Expand All @@ -131,7 +136,8 @@ describe('parsePathParameters', () => {
expect(
parsePathParameters([pathParam1, pathParam2], refs, {
strictNaming: false,
schemaPrefix: ''
schemaPrefix: '',
resolveExternal: true
})
).toEqual([
{ ...pathParam1, originalName: 'param1', schemaProperties: {} },
Expand Down
4 changes: 4 additions & 0 deletions packages/openapi-generator/src/parser/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ export interface ParserOptions {
* Add prefix to schema names.
*/
schemaPrefix: string;
/**
* Resolve external references.
*/
resolveExternal: boolean;
}
78 changes: 70 additions & 8 deletions packages/openapi-generator/src/parser/refs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,78 @@ import type { OpenApiDocumentRefs } from './refs';
describe('OpenApiDocumentRefs', () => {
let refs: OpenApiDocumentRefs;
const typeName: OpenAPIV3.SchemaObject = { type: 'string' };
beforeAll(async () => {
beforeEach(async () => {
refs = await createRefs(
{
...emptyDocument,
components: { schemas: { typeName } }
},
{ strictNaming: true, schemaPrefix: '' }
{ strictNaming: true, schemaPrefix: '', resolveExternal: true }
);
});

describe('createRefs', () => {
it('throws if external refs does not exist', async () => {
await expect(() =>
createRefs(
{
...emptyDocument,
paths: {
'/test': {
get: {
responses: {
'200': {
description: 'A test response',
content: {
'application/json': {
schema: {
$ref: '/path/to/external.json'
}
}
}
}
}
}
}
}
},
{ strictNaming: true, schemaPrefix: 'xyz', resolveExternal: true }
)
).rejects.toThrowErrorMatchingInlineSnapshot(
'"Error opening file "/path/to/external.json""'
);
});

it('should ignore external refs if resolveExternal set to false', async () => {
await expect(
createRefs(
{
...emptyDocument,
paths: {
'/test': {
get: {
responses: {
'200': {
description: 'A test response',
content: {
'application/json': {
schema: {
$ref: '/path/to/external.json'
}
}
}
}
}
}
}
}
},
{ strictNaming: true, schemaPrefix: 'xyz', resolveExternal: false }
)
).resolves.toBeDefined();
});
});

describe('resolveObject', () => {
it('resolves reference', async () => {
expect(
Expand Down Expand Up @@ -67,7 +129,7 @@ describe('OpenApiDocumentRefs', () => {
...emptyDocument,
components: { schemas: { typeName } }
},
{ strictNaming: true, schemaPrefix: 'xyz' }
{ strictNaming: true, schemaPrefix: 'xyz', resolveExternal: true }
);

expect(
Expand All @@ -86,7 +148,7 @@ describe('OpenApiDocumentRefs', () => {
...emptyDocument,
components: { schemas: { '123456': {}, 'something.Else%': {} } }
},
{ strictNaming: false, schemaPrefix: '' }
{ strictNaming: false, schemaPrefix: '', resolveExternal: true }
);
expect(refs.getSchemaNaming('#/components/schemas/123456')).toEqual({
fileName: 'schema-123456',
Expand All @@ -109,7 +171,7 @@ describe('OpenApiDocumentRefs', () => {
schemas: { '123456': {}, schema123456: {}, schema12345_6: {} }
}
},
{ strictNaming: false, schemaPrefix: '' }
{ strictNaming: false, schemaPrefix: '', resolveExternal: true }
);
expect(refs.getSchemaNaming('#/components/schemas/123456')).toEqual({
fileName: 'schema-123456',
Expand Down Expand Up @@ -139,7 +201,7 @@ describe('OpenApiDocumentRefs', () => {
...emptyDocument,
components: { schemas: { '123456': {} } }
},
{ strictNaming: true, schemaPrefix: '' }
{ strictNaming: true, schemaPrefix: '', resolveExternal: true }
)
).rejects.toThrowError(
'The service specification contains invalid schema names.'
Expand All @@ -152,7 +214,7 @@ describe('OpenApiDocumentRefs', () => {
...emptyDocument,
components: { schemas: { name: {}, Name: {} } }
},
{ strictNaming: false, schemaPrefix: '' }
{ strictNaming: false, schemaPrefix: '', resolveExternal: true }
);
expect(refs.getSchemaNaming('#/components/schemas/name')).toEqual({
fileName: 'name-1',
Expand All @@ -171,7 +233,7 @@ describe('OpenApiDocumentRefs', () => {
...emptyDocument,
components: { schemas: { name400: {}, Name400: {} } }
},
{ strictNaming: false, schemaPrefix: '' }
{ strictNaming: false, schemaPrefix: '', resolveExternal: true }
);
expect(refs.getSchemaNaming('#/components/schemas/name400')).toEqual({
fileName: 'name-400-1',
Expand Down
6 changes: 4 additions & 2 deletions packages/openapi-generator/src/parser/refs.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { pascalCase, kebabCase } from '@sap-cloud-sdk/util';
import SwaggerParser from '@apidevtools/swagger-parser';
import { isReferenceObject } from '../schema-util';
import { ensureUniqueNames } from './unique-naming';
import { ensureValidSchemaNames } from './schema-naming';
import { resolveBound } from './swagger-parser-workaround';
import type { OpenAPIV3 } from 'openapi-types';
import type { $Refs } from '@apidevtools/swagger-parser';
import type { SchemaNaming } from '../openapi-types';
Expand Down Expand Up @@ -40,7 +40,9 @@ export class OpenApiDocumentRefs {
options: ParserOptions
): Promise<OpenApiDocumentRefs> {
return new OpenApiDocumentRefs(
await resolveBound(document),
await SwaggerParser.resolve(document, {
resolve: { external: options.resolveExternal }
}),
OpenApiDocumentRefs.parseSchemaRefMapping(document, options)
);
}
Expand Down
6 changes: 5 additions & 1 deletion packages/openapi-generator/src/parser/request-body.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { createLogger } from '@sap-cloud-sdk/util';
import { createTestRefs } from '../../test/test-util';
import { parseRequestBody } from './request-body';

const defaultOptions = { strictNaming: true, schemaPrefix: '' };
const defaultOptions = {
strictNaming: true,
schemaPrefix: '',
resolveExternal: true
};
describe('getRequestBody', () => {
it('returns undefined for undefined', async () => {
const logger = createLogger('openapi-generator');
Expand Down
6 changes: 5 additions & 1 deletion packages/openapi-generator/src/parser/responses.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { createTestRefs } from '../../test/test-util';
import { parseResponses } from './responses';

const defaultOptions = { strictNaming: true, schemaPrefix: '' };
const defaultOptions = {
strictNaming: true,
schemaPrefix: '',
resolveExternal: true
};
describe('parseResponses', () => {
it('parses response schema without content', async () => {
expect(
Expand Down
9 changes: 6 additions & 3 deletions packages/openapi-generator/src/parser/schema-naming.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ describe('schema-naming', () => {
expect(
ensureValidSchemaNames(['1234ABC'], {
strictNaming: false,
schemaPrefix: ''
schemaPrefix: '',
resolveExternal: true
})
).toEqual(['schema1234ABC']);
});
Expand All @@ -14,7 +15,8 @@ describe('schema-naming', () => {
expect(
ensureValidSchemaNames(['#som%eth.ing'], {
strictNaming: false,
schemaPrefix: ''
schemaPrefix: '',
resolveExternal: true
})
).toEqual(['something']);
});
Expand All @@ -23,7 +25,8 @@ describe('schema-naming', () => {
expect(() =>
ensureValidSchemaNames(['1234ABC', '#som%eth.ing'], {
strictNaming: true,
schemaPrefix: ''
schemaPrefix: '',
resolveExternal: true
})
).toThrowErrorMatchingInlineSnapshot(`
"The service specification contains invalid schema names. Adjust the definition file or enable automatic name adjustment with \`skipValidation\`.
Expand Down
Loading

0 comments on commit 6cc07bc

Please sign in to comment.