Skip to content

Commit

Permalink
fix: generation fails when schemas have an $id field or array of obje…
Browse files Browse the repository at this point in the history
…cts (#287)

* fix:  is now being handled by changing the value to the classname where appropriate as part of the preprocess hook; items arrays are also working with the  changes; updated tests

* chore: renamed new custom attribute to be more generic; fixed linting issues; const javaPackage instead of let
  • Loading branch information
CameronRushton authored Aug 26, 2022
1 parent db808d4 commit 95fccd4
Show file tree
Hide file tree
Showing 8 changed files with 2,070 additions and 282 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
node_modules
test/temp
coverage
coverage
**/.DS_Store
**/.vscode
49 changes: 41 additions & 8 deletions hooks/pre-process.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,48 @@
const ApplicationModel = require('../lib/applicationModel.js');
const _ = require('lodash');

module.exports = {
'generate:before': generator => {
generator.asyncapi.allSchemas().forEach((schema, schemaName) => {
// The generator will create file names based on the schema's $id. Instead of guessing what the generator named the file so we can fix it in post,
// ... it's easier to process $id here first. Since we don't use it, removing it is easiest.
if (schema.$id()) {
delete schema._json.$id;
function setSchemaIdsForFileName(asyncapi) {
asyncapi.allSchemas().forEach((schema, schemaName) => {
// If we leave the $id the way it is, the generator will name the schema files what their $id is, which is always a bad idea.
// So we leave it in, but $id is going to be changed to be the class name we want.
// If we remove the $id and there's no x-parser-schema-id, then it wont be returned by allSchemas().
if (schema.$id()) {
// Assuming one of x-parser-schema-id and $id must be present.
let classNameForGenerator;
const parserSchemaId = schema.ext('x-parser-schema-id');
classNameForGenerator = parserSchemaId ? parserSchemaId : _.camelCase(schema.$id().substring(schema.$id().lastIndexOf('/') + 1));

if (classNameForGenerator === 'items') {
const parentSchema = schema.options?.parent;
const parentSchemaItems = parentSchema?.items();
if (parentSchemaItems?._json?.$id === schema.$id()) {
const parentParserSchemaId = parentSchema.ext('x-parser-schema-id');
classNameForGenerator = parentParserSchemaId ? parentParserSchemaId : _.camelCase(parentSchema.$id().substring(parentSchema.$id().lastIndexOf('/') + 1));
// If we come across this schema later in the code generator, we'll know to rename it to its parent because the proper settings will be set in the model class.
schema._json['x-model-class-name'] = classNameForGenerator;
classNameForGenerator += 'Items';
}
}
});
schema._json.$id = classNameForGenerator;
}
});
}

function setSchemaIdsForFileNameIncludingDuplicates(asyncapi) {
// We do this multiple times because allSchemas() returns a list of deduplicated schemas, so if we change the $id of a schema,
// we wont change any of the duplicates. We continue until there are no more duplicates to change.
let numSchemas;
let newNumSchemas;
do {
numSchemas = asyncapi.allSchemas().size;
setSchemaIdsForFileName(asyncapi);
newNumSchemas = asyncapi.allSchemas().size;
} while (numSchemas !== newNumSchemas);
}

module.exports = {
'generate:before': generator => {
setSchemaIdsForFileNameIncludingDuplicates(generator.asyncapi);
ApplicationModel.asyncapi = generator.asyncapi;
}
};
105 changes: 38 additions & 67 deletions lib/applicationModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class ApplicationModel {
}
// Using x-parser-schema-id didn't work for us, fall back to trying to get at least something using the provided name.
if (!modelClass) {
modelClass = this.modelClassMap[schemaName];
modelClass = this.modelClassMap[schemaName] || this.modelClassMap[_.camelCase(schemaName)];
}
debugApplicationModel(`returning modelClass for caller ${this.caller} ${schemaName}`);
debugApplicationModel(modelClass);
Expand All @@ -42,25 +42,26 @@ class ApplicationModel {
}

setupSuperClassMap() {
if (!this.superClassMap) {
this.superClassMap = new Map();
this.anonymousSchemaToSubClassMap = new Map();
debugApplicationModel('-------- SCHEMAS -------------');
debugApplicationModel(ApplicationModel.asyncapi.allSchemas());
ApplicationModel.asyncapi.allSchemas().forEach((schema, schemaName) => {
debugApplicationModel(`${schemaName}:`);
debugApplicationModel(schema);
const allOf = schema.allOf();
if (allOf) {
this.handleAllOfSchema(schema, schemaName, allOf);
}
});
debugApplicationModel('-----------------------------');
debugApplicationModel('superclassMap:');
debugApplicationModel(this.superClassMap);
debugApplicationModel('anonymousSchemaToSubClassMap:');
debugApplicationModel(this.anonymousSchemaToSubClassMap);
if (this.superClassMap) {
return;
}
this.superClassMap = new Map();
this.anonymousSchemaToSubClassMap = new Map();
debugApplicationModel('-------- SCHEMAS -------------');
debugApplicationModel(ApplicationModel.asyncapi.allSchemas());
ApplicationModel.asyncapi.allSchemas().forEach((schema, schemaName) => {
debugApplicationModel(`${schemaName}:`);
debugApplicationModel(schema);
const allOf = schema.allOf();
if (allOf) {
this.handleAllOfSchema(schema, schemaName, allOf);
}
});
debugApplicationModel('-----------------------------');
debugApplicationModel('superclassMap:');
debugApplicationModel(this.superClassMap);
debugApplicationModel('anonymousSchemaToSubClassMap:');
debugApplicationModel(this.anonymousSchemaToSubClassMap);
}

handleAllOfSchema(schema, schemaName, allOfSchema) {
Expand Down Expand Up @@ -89,54 +90,19 @@ class ApplicationModel {
}

setupModelClassMap() {
if (!this.modelClassMap) {
this.modelClassMap = new Map();
this.nameToSchemaMap = new Map();
// Register all schemas first, then check the anonymous schemas for duplicates
ApplicationModel.asyncapi.allSchemas().forEach((schema, name) => {
debugApplicationModel(`setupModelClassMap ${name} type ${schema.type()}`);
this.registerSchemaNameToModelClass(schema, name);
this.nameToSchemaMap[name] = schema;
});

ApplicationModel.asyncapi.allSchemas().forEach((schema, schemaName) => {
debugApplicationModel(`setupModelClassMap anonymous schemas ${schemaName} type ${schema.type()}`);
this.registerSchemasInProperties(schema);
this.registerSchemasInAllOf(schema);
});
debugApplicationModel('modelClassMap:');
debugApplicationModel(this.modelClassMap);
}
}

registerSchemasInProperties(schema) {
if (!!Object.keys(schema.properties()).length) {
// Each property name is the name of a schema. It should also have an x-parser-schema-id name. We'll be adding duplicate mappings (two mappings to the same model class) since the anon schemas do have names
Object.keys(schema.properties()).forEach(property => {
const innerSchema = schema.properties()[property];
const innerSchemaParserId = innerSchema.ext('x-parser-schema-id');
const existingModelClass = this.modelClassMap[innerSchemaParserId];
if (existingModelClass) {
this.modelClassMap[property] = existingModelClass;
} else {
this.registerSchemaNameToModelClass(innerSchema, property);
}
});
}
}

registerSchemasInAllOf(schema) {
const allOf = schema.allOf();
debugApplicationModel('allOf:');
debugApplicationModel(allOf);
if (allOf) {
allOf.forEach(innerSchema => {
const name = innerSchema.ext('x-parser-schema-id');
if (this.isAnonymousSchema(name) && innerSchema.type() === 'object') {
this.registerSchemaNameToModelClass(innerSchema, name);
}
});
if (this.modelClassMap) {
return;
}
this.modelClassMap = new Map();
this.nameToSchemaMap = new Map();
// Register all schemas recursively as a flat map of name -> ModelClass
ApplicationModel.asyncapi.allSchemas().forEach((schema, name) => {
debugApplicationModel(`setupModelClassMap ${name} type ${schema.type()}`);
this.registerSchemaNameToModelClass(schema, name);
this.nameToSchemaMap[name] = schema;
});
debugApplicationModel('modelClassMap:');
debugApplicationModel(this.modelClassMap);
}

isAnonymousSchema(schemaName) {
Expand All @@ -158,7 +124,12 @@ class ApplicationModel {
modelClass.setCanBeInnerClass(false);
}

const { className, javaPackage } = scsLib.stripPackageName(schemaName);
const classNameAndLocation = scsLib.stripPackageName(schemaName);
let className = classNameAndLocation.className;
const javaPackage = classNameAndLocation.javaPackage;
if (schema._json['x-model-class-name']) {
className = schema._json['x-model-class-name'];
}
modelClass.setJavaPackage(javaPackage);
modelClass.setClassName(className);
debugApplicationModel(`schemaName ${schemaName} className: ${modelClass.getClassName()} super: ${modelClass.getSuperClassName()} javaPackage: ${javaPackage}`);
Expand Down
Loading

0 comments on commit 95fccd4

Please sign in to comment.