Skip to content

Commit 2a3f303

Browse files
fix: generation fails when schemas have an $Id field (#241)
* fix: ignore field when generating asyncAPI document * fix: revert jest timeout change
1 parent 51e369b commit 2a3f303

File tree

5 files changed

+253
-15
lines changed

5 files changed

+253
-15
lines changed

hooks/pre-process.js

+8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ const ApplicationModel = require('../lib/applicationModel.js');
22

33
module.exports = {
44
'generate:before': generator => {
5+
generator.asyncapi.allSchemas().forEach((schema, schemaName) => {
6+
// 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,
7+
// ... it's easier to process $id here first. Since we don't use it, removing it is easiest.
8+
if (schema.$id()) {
9+
delete schema._json.$id;
10+
}
11+
});
12+
513
ApplicationModel.asyncapi = generator.asyncapi;
614
}
715
};

lib/applicationModel.js

+18-15
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class ApplicationModel {
2222
modelClass = this.modelClassMap[parserSchemaName];
2323
if (modelClass && _.startsWith(modelClass.getClassName(), 'Anonymous')) {
2424
// 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.
25-
// 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.
25+
// 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.
2626
modelClass.setClassName(_.upperFirst(this.isAnonymousSchema(parserSchemaName) ? schemaName : parserSchemaName));
2727
}
2828
}
@@ -99,26 +99,15 @@ class ApplicationModel {
9999

100100
ApplicationModel.asyncapi.allSchemas().forEach((schema, schemaName) => {
101101
debugApplicationModel(`setupModelClassMap anonymous schemas ${schemaName} type ${schema.type()}`);
102-
this.checkProperties(schema);
103-
104-
const allOf = schema.allOf();
105-
debugApplicationModel('allOf:');
106-
debugApplicationModel(allOf);
107-
if (allOf) {
108-
allOf.forEach(innerSchema => {
109-
const name = innerSchema.ext('x-parser-schema-id');
110-
if (this.isAnonymousSchema(name) && innerSchema.type() === 'object') {
111-
this.registerSchemaNameToModelClass(innerSchema, schemaName);
112-
}
113-
});
114-
}
102+
this.registerSchemasInProperties(schema);
103+
this.registerSchemasInAllOf(schema);
115104
});
116105
debugApplicationModel('modelClassMap:');
117106
debugApplicationModel(this.modelClassMap);
118107
}
119108
}
120109

121-
checkProperties(schema) {
110+
registerSchemasInProperties(schema) {
122111
if (!!Object.keys(schema.properties()).length) {
123112
// 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
124113
Object.keys(schema.properties()).forEach(property => {
@@ -134,6 +123,20 @@ class ApplicationModel {
134123
}
135124
}
136125

126+
registerSchemasInAllOf(schema) {
127+
const allOf = schema.allOf();
128+
debugApplicationModel('allOf:');
129+
debugApplicationModel(allOf);
130+
if (allOf) {
131+
allOf.forEach(innerSchema => {
132+
const name = innerSchema.ext('x-parser-schema-id');
133+
if (this.isAnonymousSchema(name) && innerSchema.type() === 'object') {
134+
this.registerSchemaNameToModelClass(innerSchema, name);
135+
}
136+
});
137+
}
138+
}
139+
137140
isAnonymousSchema(schemaName) {
138141
return schemaName.startsWith('<');
139142
}

test/__snapshots__/integration.test.js.snap

+128
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,134 @@ public class SubObject {
707707
"
708708
`;
709709
710+
exports[`template integration tests using the generator should generate code using schemas that have $id set 1`] = `
711+
"
712+
import java.util.function.Consumer;
713+
714+
import org.slf4j.Logger;
715+
import org.slf4j.LoggerFactory;
716+
import org.springframework.boot.SpringApplication;
717+
import org.springframework.boot.autoconfigure.SpringBootApplication;
718+
import org.springframework.context.annotation.Bean;
719+
720+
@SpringBootApplication
721+
public class Application {
722+
723+
private static final Logger logger = LoggerFactory.getLogger(Application.class);
724+
725+
public static void main(String[] args) {
726+
SpringApplication.run(Application.class);
727+
}
728+
729+
@Bean
730+
public Consumer<DefaultMessageSchema> acmeRideshareRideRequested001Consumer() {
731+
return data -> {
732+
// Add business logic here.
733+
logger.info(data.toString());
734+
};
735+
}
736+
737+
}
738+
"
739+
`;
740+
741+
exports[`template integration tests using the generator should generate code using schemas that have $id set 2`] = `
742+
"
743+
import com.fasterxml.jackson.annotation.JsonInclude;
744+
import com.fasterxml.jackson.annotation.JsonProperty;
745+
746+
747+
748+
@JsonInclude(JsonInclude.Include.NON_NULL)
749+
public class DefaultMessageSchema {
750+
751+
public DefaultMessageSchema () {
752+
}
753+
754+
public DefaultMessageSchema (
755+
java.math.BigDecimal avgMeterReading,
756+
Integer windowDurationSec,
757+
java.math.BigDecimal avgPassengerCount,
758+
Integer windowRideCount,
759+
String timestamp) {
760+
this.avgMeterReading = avgMeterReading;
761+
this.windowDurationSec = windowDurationSec;
762+
this.avgPassengerCount = avgPassengerCount;
763+
this.windowRideCount = windowRideCount;
764+
this.timestamp = timestamp;
765+
}
766+
767+
@JsonProperty(\\"avg_meter_reading\\")
768+
private java.math.BigDecimal avgMeterReading;
769+
@JsonProperty(\\"window_duration_sec\\")
770+
private Integer windowDurationSec;
771+
@JsonProperty(\\"avg_passenger_count\\")
772+
private java.math.BigDecimal avgPassengerCount;
773+
@JsonProperty(\\"window_ride_count\\")
774+
private Integer windowRideCount;
775+
private String timestamp;
776+
public java.math.BigDecimal getAvgMeterReading() {
777+
return avgMeterReading;
778+
}
779+
780+
public DefaultMessageSchema setAvgMeterReading(java.math.BigDecimal avgMeterReading) {
781+
this.avgMeterReading = avgMeterReading;
782+
return this;
783+
}
784+
785+
786+
public Integer getWindowDurationSec() {
787+
return windowDurationSec;
788+
}
789+
790+
public DefaultMessageSchema setWindowDurationSec(Integer windowDurationSec) {
791+
this.windowDurationSec = windowDurationSec;
792+
return this;
793+
}
794+
795+
796+
public java.math.BigDecimal getAvgPassengerCount() {
797+
return avgPassengerCount;
798+
}
799+
800+
public DefaultMessageSchema setAvgPassengerCount(java.math.BigDecimal avgPassengerCount) {
801+
this.avgPassengerCount = avgPassengerCount;
802+
return this;
803+
}
804+
805+
806+
public Integer getWindowRideCount() {
807+
return windowRideCount;
808+
}
809+
810+
public DefaultMessageSchema setWindowRideCount(Integer windowRideCount) {
811+
this.windowRideCount = windowRideCount;
812+
return this;
813+
}
814+
815+
816+
public String getTimestamp() {
817+
return timestamp;
818+
}
819+
820+
public DefaultMessageSchema setTimestamp(String timestamp) {
821+
this.timestamp = timestamp;
822+
return this;
823+
}
824+
825+
public String toString() {
826+
return \\"DefaultMessageSchema [\\"
827+
+ \\" avgMeterReading: \\" + avgMeterReading
828+
+ \\" windowDurationSec: \\" + windowDurationSec
829+
+ \\" avgPassengerCount: \\" + avgPassengerCount
830+
+ \\" windowRideCount: \\" + windowRideCount
831+
+ \\" timestamp: \\" + timestamp
832+
+ \\" ]\\";
833+
}
834+
}
835+
"
836+
`;
837+
710838
exports[`template integration tests using the generator should generate extra config when using the paramatersToHeaders parameter 1`] = `
711839
"package com.acme;
712840

test/integration.test.js

+13
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,17 @@ describe('template integration tests using the generator', () => {
166166
];
167167
await assertExpectedFiles(OUTPUT_DIR, expectedFiles);
168168
});
169+
170+
it('should generate code using schemas that have $id set', async () => {
171+
const OUTPUT_DIR = generateFolderName();
172+
173+
const generator = new Generator(path.normalize('./'), OUTPUT_DIR, { forceWrite: true });
174+
await generator.generateFromFile(path.resolve('test', 'mocks/using-$id-field.yaml'));
175+
176+
const expectedFiles = [
177+
'src/main/java/Application.java',
178+
'src/main/java/DefaultMessageSchema.java'
179+
];
180+
await assertExpectedFiles(OUTPUT_DIR, expectedFiles);
181+
});
169182
});

test/mocks/using-$id-field.yaml

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
components:
3+
schemas:
4+
default message schema:
5+
default: {}
6+
$schema: "http://json-schema.org/draft-07/schema"
7+
$id: "http://example.com/example.json"
8+
examples:
9+
- avg_meter_reading: 21.615217
10+
window_duration_sec: 300
11+
avg_passenger_count: 1.5
12+
window_ride_count: 5
13+
timestamp: "2020-06-04T20:09:59.99832-04:00"
14+
description: "default placeholder for schema"
15+
additionalProperties: true
16+
type: "object"
17+
title: "The root schema"
18+
required:
19+
- "timestamp"
20+
- "avg_meter_reading"
21+
- "avg_passenger_count"
22+
- "window_duration_sec"
23+
- "window_ride_count"
24+
properties:
25+
avg_meter_reading:
26+
default: 0
27+
examples:
28+
- 21.615217
29+
description: "An explanation about the purpose of this instance."
30+
type: "number"
31+
title: "The avg_meter_reading schema"
32+
$id: "#/properties/avg_meter_reading"
33+
window_duration_sec:
34+
default: 0
35+
examples:
36+
- 300
37+
description: "An explanation about the purpose of this instance."
38+
type: "integer"
39+
title: "The window_duration_sec schema"
40+
$id: "#/properties/window_duration_sec"
41+
avg_passenger_count:
42+
default: 0
43+
examples:
44+
- 1.5
45+
description: "An explanation about the purpose of this instance."
46+
type: "number"
47+
title: "The avg_passenger_count schema"
48+
$id: "#/properties/avg_passenger_count"
49+
window_ride_count:
50+
default: 0
51+
examples:
52+
- 5
53+
description: "An explanation about the purpose of this instance."
54+
type: "integer"
55+
title: "The window_ride_count schema"
56+
$id: "#/properties/window_ride_count"
57+
timestamp:
58+
default: ""
59+
examples:
60+
- "2020-06-04T20:09:59.99832-04:00"
61+
description: "An explanation about the purpose of this instance."
62+
type: "string"
63+
title: "The timestamp schema"
64+
$id: "#/properties/timestamp"
65+
messages:
66+
ride requested 2:
67+
payload:
68+
$ref: "#/components/schemas/default message schema"
69+
schemaFormat: "application/vnd.aai.asyncapi+json;version=2.0.0"
70+
contentType: "application/json"
71+
servers:
72+
production:
73+
protocol: "smf"
74+
url: "my_server"
75+
channels:
76+
acme/rideshare/ride/requested/0.0.1:
77+
publish:
78+
message:
79+
$ref: "#/components/messages/ride requested 2"
80+
asyncapi: "2.0.0"
81+
info:
82+
x-generated-time: "2021-09-29 13:44 UTC"
83+
description: "test\n\n---\n\ntest"
84+
title: "mh acme 2"
85+
x-view: "provider"
86+
version: "1"

0 commit comments

Comments
 (0)