Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for additionalProperties (#272) #273

Merged
merged 10 commits into from
Oct 11, 2023
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

additionalProperties could be boolean true|false. This else will lead to strange consequence

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
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' %}
Tenischev marked this conversation as resolved.
Show resolved Hide resolved
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 %}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

additionalProperties could be boolean true|false. This else will lead to

Unable to call the return value of (prop["additionalProperties"])["type"], which is undefined or falsey

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'
Tenischev marked this conversation as resolved.
Show resolved Hide resolved
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