Skip to content

Commit

Permalink
fix: generation fails when schemas have an $Id field (#241)
Browse files Browse the repository at this point in the history
* fix: ignore  field when generating asyncAPI document

* fix: revert jest timeout change
  • Loading branch information
CameronRushton authored Feb 23, 2022
1 parent 51e369b commit 2a3f303
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 15 deletions.
8 changes: 8 additions & 0 deletions hooks/pre-process.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ const ApplicationModel = require('../lib/applicationModel.js');

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;
}
});

ApplicationModel.asyncapi = generator.asyncapi;
}
};
33 changes: 18 additions & 15 deletions lib/applicationModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ApplicationModel {
modelClass = this.modelClassMap[parserSchemaName];
if (modelClass && _.startsWith(modelClass.getClassName(), 'Anonymous')) {
// If we translated this schema from the map using an anonymous schema key, we have no idea what the name should be, so we use the one provided directly from the source - not the generator.
// If we translated this schema from the map using a known schema (the name of the schema was picked out correctly by the generator), use that name.
// Otherwise, if we translated this schema from the map using a known schema (the name of the schema was picked out correctly by the generator), use that name.
modelClass.setClassName(_.upperFirst(this.isAnonymousSchema(parserSchemaName) ? schemaName : parserSchemaName));
}
}
Expand Down Expand Up @@ -99,26 +99,15 @@ class ApplicationModel {

ApplicationModel.asyncapi.allSchemas().forEach((schema, schemaName) => {
debugApplicationModel(`setupModelClassMap anonymous schemas ${schemaName} type ${schema.type()}`);
this.checkProperties(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, schemaName);
}
});
}
this.registerSchemasInProperties(schema);
this.registerSchemasInAllOf(schema);
});
debugApplicationModel('modelClassMap:');
debugApplicationModel(this.modelClassMap);
}
}

checkProperties(schema) {
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 => {
Expand All @@ -134,6 +123,20 @@ class ApplicationModel {
}
}

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);
}
});
}
}

isAnonymousSchema(schemaName) {
return schemaName.startsWith('<');
}
Expand Down
128 changes: 128 additions & 0 deletions test/__snapshots__/integration.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,134 @@ public class SubObject {
"
`;
exports[`template integration tests using the generator should generate code using schemas that have $id set 1`] = `
"
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
@Bean
public Consumer<DefaultMessageSchema> acmeRideshareRideRequested001Consumer() {
return data -> {
// Add business logic here.
logger.info(data.toString());
};
}
}
"
`;
exports[`template integration tests using the generator should generate code using schemas that have $id set 2`] = `
"
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class DefaultMessageSchema {
public DefaultMessageSchema () {
}
public DefaultMessageSchema (
java.math.BigDecimal avgMeterReading,
Integer windowDurationSec,
java.math.BigDecimal avgPassengerCount,
Integer windowRideCount,
String timestamp) {
this.avgMeterReading = avgMeterReading;
this.windowDurationSec = windowDurationSec;
this.avgPassengerCount = avgPassengerCount;
this.windowRideCount = windowRideCount;
this.timestamp = timestamp;
}
@JsonProperty(\\"avg_meter_reading\\")
private java.math.BigDecimal avgMeterReading;
@JsonProperty(\\"window_duration_sec\\")
private Integer windowDurationSec;
@JsonProperty(\\"avg_passenger_count\\")
private java.math.BigDecimal avgPassengerCount;
@JsonProperty(\\"window_ride_count\\")
private Integer windowRideCount;
private String timestamp;
public java.math.BigDecimal getAvgMeterReading() {
return avgMeterReading;
}
public DefaultMessageSchema setAvgMeterReading(java.math.BigDecimal avgMeterReading) {
this.avgMeterReading = avgMeterReading;
return this;
}
public Integer getWindowDurationSec() {
return windowDurationSec;
}
public DefaultMessageSchema setWindowDurationSec(Integer windowDurationSec) {
this.windowDurationSec = windowDurationSec;
return this;
}
public java.math.BigDecimal getAvgPassengerCount() {
return avgPassengerCount;
}
public DefaultMessageSchema setAvgPassengerCount(java.math.BigDecimal avgPassengerCount) {
this.avgPassengerCount = avgPassengerCount;
return this;
}
public Integer getWindowRideCount() {
return windowRideCount;
}
public DefaultMessageSchema setWindowRideCount(Integer windowRideCount) {
this.windowRideCount = windowRideCount;
return this;
}
public String getTimestamp() {
return timestamp;
}
public DefaultMessageSchema setTimestamp(String timestamp) {
this.timestamp = timestamp;
return this;
}
public String toString() {
return \\"DefaultMessageSchema [\\"
+ \\" avgMeterReading: \\" + avgMeterReading
+ \\" windowDurationSec: \\" + windowDurationSec
+ \\" avgPassengerCount: \\" + avgPassengerCount
+ \\" windowRideCount: \\" + windowRideCount
+ \\" timestamp: \\" + timestamp
+ \\" ]\\";
}
}
"
`;
exports[`template integration tests using the generator should generate extra config when using the paramatersToHeaders parameter 1`] = `
"package com.acme;
Expand Down
13 changes: 13 additions & 0 deletions test/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,17 @@ describe('template integration tests using the generator', () => {
];
await assertExpectedFiles(OUTPUT_DIR, expectedFiles);
});

it('should generate code using schemas that have $id set', async () => {
const OUTPUT_DIR = generateFolderName();

const generator = new Generator(path.normalize('./'), OUTPUT_DIR, { forceWrite: true });
await generator.generateFromFile(path.resolve('test', 'mocks/using-$id-field.yaml'));

const expectedFiles = [
'src/main/java/Application.java',
'src/main/java/DefaultMessageSchema.java'
];
await assertExpectedFiles(OUTPUT_DIR, expectedFiles);
});
});
86 changes: 86 additions & 0 deletions test/mocks/using-$id-field.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
components:
schemas:
default message schema:
default: {}
$schema: "http://json-schema.org/draft-07/schema"
$id: "http://example.com/example.json"
examples:
- avg_meter_reading: 21.615217
window_duration_sec: 300
avg_passenger_count: 1.5
window_ride_count: 5
timestamp: "2020-06-04T20:09:59.99832-04:00"
description: "default placeholder for schema"
additionalProperties: true
type: "object"
title: "The root schema"
required:
- "timestamp"
- "avg_meter_reading"
- "avg_passenger_count"
- "window_duration_sec"
- "window_ride_count"
properties:
avg_meter_reading:
default: 0
examples:
- 21.615217
description: "An explanation about the purpose of this instance."
type: "number"
title: "The avg_meter_reading schema"
$id: "#/properties/avg_meter_reading"
window_duration_sec:
default: 0
examples:
- 300
description: "An explanation about the purpose of this instance."
type: "integer"
title: "The window_duration_sec schema"
$id: "#/properties/window_duration_sec"
avg_passenger_count:
default: 0
examples:
- 1.5
description: "An explanation about the purpose of this instance."
type: "number"
title: "The avg_passenger_count schema"
$id: "#/properties/avg_passenger_count"
window_ride_count:
default: 0
examples:
- 5
description: "An explanation about the purpose of this instance."
type: "integer"
title: "The window_ride_count schema"
$id: "#/properties/window_ride_count"
timestamp:
default: ""
examples:
- "2020-06-04T20:09:59.99832-04:00"
description: "An explanation about the purpose of this instance."
type: "string"
title: "The timestamp schema"
$id: "#/properties/timestamp"
messages:
ride requested 2:
payload:
$ref: "#/components/schemas/default message schema"
schemaFormat: "application/vnd.aai.asyncapi+json;version=2.0.0"
contentType: "application/json"
servers:
production:
protocol: "smf"
url: "my_server"
channels:
acme/rideshare/ride/requested/0.0.1:
publish:
message:
$ref: "#/components/messages/ride requested 2"
asyncapi: "2.0.0"
info:
x-generated-time: "2021-09-29 13:44 UTC"
description: "test\n\n---\n\ntest"
title: "mh acme 2"
x-view: "provider"
version: "1"

0 comments on commit 2a3f303

Please sign in to comment.