Skip to content

Commit

Permalink
feat: Add support for additionalProperties (asyncapi#272)
Browse files Browse the repository at this point in the history
  • Loading branch information
JapuDCret committed Jan 27, 2023
1 parent d2667cf commit 7b10c94
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 2 deletions.
10 changes: 9 additions & 1 deletion filters/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ const filter = module.exports;
const _ = require('lodash');

function defineType(prop, propName) {
if (prop.type() === 'object') {
if (prop.additionalProperties() && prop.additionalProperties().type() === 'object') {
if (prop.additionalProperties().type() === 'object') {
return 'Map<String, ' + _.upperFirst(_.camelCase(prop.additionalProperties().uid())) + '>';
} else if (prop.additionalProperties().format()) {
return 'Map<String, ' + toClass(toJavaType(prop.additionalProperties().format())) + '>';
} else {
return 'Map<String, ' + toClass(toJavaType(prop.additionalProperties().type())) + '>';
}
} else if (prop.type() === 'object') {
return _.upperFirst(_.camelCase(prop.uid()));
} else if (prop.type() === 'array') {
if (prop.items().type() === 'object') {
Expand Down
10 changes: 9 additions & 1 deletion template/src/main/java/com/asyncapi/model/$$objectSchema$$.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@
public class {{schemaName | camelCase | upperFirst}} {
{% for propName, prop in schema.properties() %}
{%- set isRequired = propName | isRequired(schema.required()) %}
{%- if prop.type() === 'object' %}
{%- if prop.additionalProperties() %}
{%- if prop.additionalProperties().type() === 'object' %}
private @Valid Map<String, {{prop.additionalProperties().uid() | camelCase | upperFirst}}> {{propName | camelCase}};
{%- elif prop.additionalProperties().format() %}
private @Valid Map<String, {{prop.additionalProperties().format() | toJavaType | toClass}}> {{propName | camelCase}};
{%- else %}
private @Valid Map<String, {{prop.additionalProperties().type() | toJavaType | toClass}}> {{propName | camelCase}};
{%- endif %}
{%- elif prop.type() === 'object' %}
private @Valid {{prop.uid() | camelCase | upperFirst}} {{propName | camelCase}};
{%- elif prop.type() === 'array' %}
{%- if prop.items().type() === 'object' %}
Expand Down
212 changes: 212 additions & 0 deletions tests/__snapshots__/map-format.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`template integration tests for additional formats of data types should generate DTO file with proper type classes 1`] = `
"package com.asyncapi.model;
import javax.validation.constraints.*;
import javax.validation.Valid;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.List;
import java.util.Objects;
public class SongMetaData {
private @Valid Map<String, String> tags;
private @Valid Map<String, Long> stats;
private @Valid Map<String, Interpret> interprets;
/**
* Tags
*/
@JsonProperty(\\"tags\\")
public Map<String, String> getTags() {
return tags;
}
public void setTags(Map<String, String> tags) {
this.tags = tags;
}
/**
* Stats
*/
@JsonProperty(\\"stats\\")
public Map<String, Long> getStats() {
return stats;
}
public void setStats(Map<String, Long> stats) {
this.stats = stats;
}
/**
* Interprets
*/
@JsonProperty(\\"interprets\\")
public Map<String, Interpret> getInterprets() {
return interprets;
}
public void setInterprets(Map<String, Interpret> interprets) {
this.interprets = interprets;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SongPayload songPayload = (SongPayload) o;
return
Objects.equals(this.tags, songPayload.tags) &&
Objects.equals(this.stats, songPayload.stats) &&
Objects.equals(this.interprets, songPayload.interprets);
}
@Override
public int hashCode() {
return Objects.hash(tags, stats, interprets);
}
@Override
public String toString() {
return \\"class SongPayload {\\\\n\\" +
\\" tags: \\" + toIndentedString(tags) + \\"\\\\n\\" +
\\" stats: \\" + toIndentedString(stats) + \\"\\\\n\\" +
\\" interprets: \\" + toIndentedString(interprets) + \\"\\\\n\\" +
\\"}\\";
}
/**
* Convert the given object to string with each line indented by 4 spaces (except the first line).
*/
private String toIndentedString(Object o) {
if (o == null) {
return \\"null\\";
}
return o.toString().replace(\\"\\\\n\\", \\"\\\\n \\");
}
}"
`;

exports[`template integration tests for map format should generate DTO file with proper map types 1`] = `
"package com.asyncapi.model;
import javax.validation.constraints.*;
import javax.validation.Valid;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.List;
import java.util.Objects;
public class SongMetaData {
private @Valid Map<String, String> tags;
private @Valid Map<String, Long> stats;
private @Valid Map<String, Interpret> interprets;
/**
* Tags
*/
@JsonProperty(\\"tags\\")
public Object getTags() {
return tags;
}
public void setTags(Object tags) {
this.tags = tags;
}
/**
* Stats
*/
@JsonProperty(\\"stats\\")
public Object getStats() {
return stats;
}
public void setStats(Object stats) {
this.stats = stats;
}
/**
* Interprets
*/
@JsonProperty(\\"interprets\\")
public Map<String, Interpret> getInterprets() {
return interprets;
}
public void setInterprets(Map<String, Interpret> interprets) {
this.interprets = interprets;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SongMetaData songMetaData = (SongMetaData) o;
return
Objects.equals(this.tags, songMetaData.tags) &&
Objects.equals(this.stats, songMetaData.stats) &&
Objects.equals(this.interprets, songMetaData.interprets);
}
@Override
public int hashCode() {
return Objects.hash(tags, stats, interprets);
}
@Override
public String toString() {
return \\"class SongMetaData {\\\\n\\" +
\\" tags: \\" + toIndentedString(tags) + \\"\\\\n\\" +
\\" stats: \\" + toIndentedString(stats) + \\"\\\\n\\" +
\\" interprets: \\" + toIndentedString(interprets) + \\"\\\\n\\" +
\\"}\\";
}
/**
* Convert the given object to string with each line indented by 4 spaces (except the first line).
*/
private String toIndentedString(Object o) {
if (o == null) {
return \\"null\\";
}
return o.toString().replace(\\"\\\\n\\", \\"\\\\n \\");
}
}"
`;
32 changes: 32 additions & 0 deletions tests/map-format.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const path = require('path');
const Generator = require('@asyncapi/generator');
const { readFile } = require('fs').promises;

const MAIN_TEST_RESULT_PATH = path.join('tests', 'temp', 'integrationTestResult');

const generateFolderName = () => {
// you always want to generate to new directory to make sure test runs in clear environment
return path.resolve(MAIN_TEST_RESULT_PATH, Date.now().toString());
};

describe('template integration tests for map format', () => {

jest.setTimeout(30000);

it('should generate DTO file with proper map types', async() => {
const outputDir = generateFolderName();
const params = {};
const mapFormatExamplePath = './mocks/map-format.yml';

const generator = new Generator(path.normalize('./'), outputDir, { forceWrite: true, templateParams: params });
await generator.generateFromFile(path.resolve('tests', mapFormatExamplePath));

const expectedFiles = [
'/src/main/java/com/asyncapi/model/SongMetaData.java'
];
for (const index in expectedFiles) {
const file = await readFile(path.join(outputDir, expectedFiles[index]), 'utf8');
expect(file).toMatchSnapshot();
}
});
});
45 changes: 45 additions & 0 deletions tests/mocks/map-format.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
asyncapi: 2.0.0
info:
title: Record Label Service
version: 1.0.0
description: This service is in charge of processing music
servers:
production:
url: 'my-kafka-hostname:9092'
protocol: kafka
description: Production Instance 1
channels:
song.metadata:
publish:
message:
$ref: '#/components/messages/metadata'
subscribe:
message:
$ref: '#/components/messages/metadata'
components:
messages:
metadata:
payload:
$id: SongMetaData
type: object
properties:
tags:
description: Tags
additionalProperties:
type: string
stats:
description: Stats
additionalProperties:
type: integer
format: int64
interprets:
description: Interprets
additionalProperties:
$ref: '#/components/schemas/Interpret'
schemas:
Interpret:
type: object
properties:
name:
description: Interpret name
type: string

0 comments on commit 7b10c94

Please sign in to comment.