Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions lib/codegen/fromcto/openapi/openapivisitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class OpenApiVisitor {
refRoot: '#/components/schemas',
};
const jsonSchema = modelManager.accept(visitor, childParameters);
this.renameDecorators(jsonSchema.definitions);

// Visit all of the files in the model manager.
const title = parameters?.openApiTitle ?? 'Generated Open API from Concerto Models';
Expand Down Expand Up @@ -100,6 +101,26 @@ class OpenApiVisitor {
return result;
}

/**
* Renames the $decorators key that JSONSchemaVisitor adds to type and
* property schemas, because Open API 3.0 schema objects only allow
* extension keys that start with x-.
* @param {Object} definitions - the JSON schema definitions, changed in place
* @private
*/
renameDecorators(definitions) {
const rename = (schema) => {
if (schema.$decorators) {
schema['x-concerto-decorators'] = schema.$decorators;
delete schema.$decorators;
}
};
Object.values(definitions).forEach((schema) => {
rename(schema);
Object.values(schema.properties ?? {}).forEach(rename);
});
}

/**
* Visitor design pattern
* @param {ModelFile} modelFile - the object being visited
Expand Down
20 changes: 10 additions & 10 deletions test/codegen/__snapshots__/codegen.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4361,7 +4361,7 @@ exports[`codegen #formats check we can convert all formats from namespace versio
"isPii",
"category"
],
"$decorators": {
"x-concerto-decorators": {
"example": [
"@pii(true, GeneralCategory)"
],
Expand All @@ -4383,7 +4383,7 @@ exports[`codegen #formats check we can convert all formats from namespace versio
},
"name": {
"type": "string",
"$decorators": {
"x-concerto-decorators": {
"pii": [
false,
{
Expand Down Expand Up @@ -4414,7 +4414,7 @@ exports[`codegen #formats check we can convert all formats from namespace versio
"name": {
"type": "string",
"pattern": "abc.*",
"$decorators": {
"x-concerto-decorators": {
"description": [
"The name of the company"
]
Expand Down Expand Up @@ -4497,7 +4497,7 @@ exports[`codegen #formats check we can convert all formats from namespace versio
"name",
"headquarters"
],
"$decorators": {
"x-concerto-decorators": {
"description": [
"A company"
]
Expand All @@ -4514,7 +4514,7 @@ exports[`codegen #formats check we can convert all formats from namespace versio
"Engineering",
"Design"
],
"$decorators": {
"x-concerto-decorators": {
"category": [
{
"type": "Identifier",
Expand Down Expand Up @@ -4547,7 +4547,7 @@ exports[`codegen #formats check we can convert all formats from namespace versio
"$class",
"serialNumber"
],
"$decorators": {
"x-concerto-decorators": {
"resource": []
}
},
Expand All @@ -4558,7 +4558,7 @@ exports[`codegen #formats check we can convert all formats from namespace versio
"Apple",
"Microsoft"
],
"$decorators": {
"x-concerto-decorators": {
"test": [
"one"
]
Expand Down Expand Up @@ -4654,7 +4654,7 @@ exports[`codegen #formats check we can convert all formats from namespace versio
"dob",
"nextOfKin"
],
"$decorators": {
"x-concerto-decorators": {
"resource": []
}
},
Expand Down Expand Up @@ -4767,7 +4767,7 @@ exports[`codegen #formats check we can convert all formats from namespace versio
"dob",
"nextOfKin"
],
"$decorators": {
"x-concerto-decorators": {
"category": [
{
"type": "Identifier",
Expand All @@ -4794,7 +4794,7 @@ exports[`codegen #formats check we can convert all formats from namespace versio
"manager": {
"type": "string",
"description": "The identifier of an instance of org.acme.hr@1.0.0.Manager",
"$decorators": {
"x-concerto-decorators": {
"level": [
{
"type": "Identifier",
Expand Down
40 changes: 40 additions & 0 deletions test/codegen/fromcto/openapi/openapivisitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,32 @@ concept Person identified by email {
}
`;

const MODEL_DECORATED = `
namespace test@1.0.0

@Term("Colour")
enum Color {
o RED
o GREEN
}

concept Address {
o String city
}

@resource
@Term("Person")
concept Person identified by email {
o String email
@Term("Age in years")
o Integer age
@Term("Home")
o Address home
@Term("Friends")
--> Person[] friends
}
`;

describe('OpenApi (samples)', function () {

describe('samples', () => {
Expand Down Expand Up @@ -84,6 +110,20 @@ describe('OpenApi (samples)', function () {
const spec = modelManager.accept(visitor);
spec.paths.should.have.property('/peeps');
});
it('should write decorators under an x- extension key', () => {
const modelManager = new ModelManager();
modelManager.addCTOModel( MODEL_DECORATED );
const visitor = new OpenApiVisitor();
const spec = modelManager.accept(visitor);
const schemas = spec.components.schemas;
const person = schemas['test@1.0.0.Person'];
expect(schemas['test@1.0.0.Color']['x-concerto-decorators']).deep.equal({ Term: ['Colour'] });
expect(person['x-concerto-decorators']).deep.equal({ resource: [], Term: ['Person'] });
expect(person.properties.age['x-concerto-decorators']).deep.equal({ Term: ['Age in years'] });
expect(person.properties.home['x-concerto-decorators']).deep.equal({ Term: ['Home'] });
expect(person.properties.friends['x-concerto-decorators']).deep.equal({ Term: ['Friends'] });
JSON.stringify(spec).should.not.contain('$decorators');
});
it('should throw an error when an unrecognised type is supplied', () => {
let thing = 'Something of unrecognised type';
const visitor = new OpenApiVisitor();
Expand Down
4 changes: 1 addition & 3 deletions test/verification/cases.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const CASES = [
graphql: 'map types emit invalid GraphQL SDL (key/value field syntax)',
rust: 'map declarations reference scalar key/value types (e.g. SSN, Time) that the Rust visitor does not emit, so cargo check fails',
avro: 'imported types are referenced by short name (e.g. Category from org.acme.hr.base), so avro-tools cannot resolve them',
openapi: 'JSON Schema $decorators and invalid schema nesting fail OpenAPI struct validation',
openapi: 'Map schema nesting (schema property) fails OpenAPI struct validation',
golang: 'GoLangVisitor emits bare package-name imports (e.g. import "concerto_decorator_1_0_0") instead of Go module-relative import paths, so any cross-namespace model fails go build',
},
},
Expand All @@ -86,15 +86,13 @@ const CASES = [
name: 'model-base',
files: ['model-base.cto'],
skip: {
openapi: 'JSON Schema $decorators fail OpenAPI struct validation',
golang: 'GoLangVisitor emits bare package-name imports (e.g. import "concerto_decorator_1_0_0") instead of Go module-relative import paths, so any cross-namespace model fails go build',
},
},
{
name: 'agreement',
files: ['agreement.cto'],
skip: {
openapi: 'JSON Schema $decorators fail OpenAPI struct validation',
golang: 'GoLangVisitor emits bare package-name imports (e.g. import "concerto_decorator_1_0_0") instead of Go module-relative import paths, so any cross-namespace model fails go build',
},
},
Expand Down
Loading