diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee081f41..b903e2ba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,9 +6,13 @@
- Passing unaltered sourceCode to PolyAPI for display within Canopy application.
+- Fixed duplicate-field collisions by preprocessing JSON schemas to inject suffixed properties (e.g. order_id_1) into both properties and required before code generation, and enhanced NameHelper to preserve suffixes and special‐character mappings, eliminating “path not present” and “same field twice” errors.
+
### Changed
--
+- PolyApiService.java: Replaced full-body buffering and Commons-IO parsing with a single BufferedInputStream using mark/reset for unified JSON/text/stream parsing and 1 KB error-preview logging.
+
+- Added lombok dependency and plugin to parent-pom
### Fixed
diff --git a/commons/pom.xml b/commons/pom.xml
index 13ffe8d2..539fcfd0 100644
--- a/commons/pom.xml
+++ b/commons/pom.xml
@@ -73,12 +73,6 @@
commons-text
1.10.0
-
- org.projectlombok
- lombok
- 1.18.38
- provided
-
com.kjetland
mbknor-jackson-jsonschema_2.12
@@ -137,25 +131,6 @@
test
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.14.0
-
-
-
- org.projectlombok
- lombok
- 1.18.38
-
-
-
-
-
-
@@ -166,20 +141,6 @@
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.14.0
-
-
-
- org.projectlombok
- lombok
- 1.18.38
-
-
-
-
org.apache.maven.plugins
maven-gpg-plugin
diff --git a/commons/src/main/java/io/polyapi/commons/api/service/PolyApiService.java b/commons/src/main/java/io/polyapi/commons/api/service/PolyApiService.java
index 228730fe..7d09b969 100644
--- a/commons/src/main/java/io/polyapi/commons/api/service/PolyApiService.java
+++ b/commons/src/main/java/io/polyapi/commons/api/service/PolyApiService.java
@@ -8,8 +8,7 @@
import io.polyapi.commons.api.http.Response;
import io.polyapi.commons.api.json.JsonParser;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.io.IOUtils;
-
+import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
@@ -24,7 +23,7 @@
import static java.util.function.Predicate.not;
/**
- * Parent implementation class for all services that connec to the PolyAPI service.
+ * Parent implementation class for all services that connect to the PolyAPI service.
*/
@Slf4j
public class PolyApiService {
@@ -44,7 +43,10 @@ public O get(String relativePath, Type expectedResponseType) {
return get(relativePath, new HashMap<>(), new HashMap<>(), expectedResponseType);
}
- public O get(String relativePath, Map> headers, Map> queryParams, Type expectedResponseType) {
+ public O get(String relativePath,
+ Map> headers,
+ Map> queryParams,
+ Type expectedResponseType) {
return parsedCall(GET, relativePath, headers, queryParams, null, expectedResponseType);
}
@@ -52,7 +54,11 @@ public O post(String relativePath, I body, Type expectedResponseType) {
return post(relativePath, new HashMap<>(), new HashMap<>(), body, expectedResponseType);
}
- public O post(String relativePath, Map> headers, Map> queryParams, I body, Type expectedResponseType) {
+ public O post(String relativePath,
+ Map> headers,
+ Map> queryParams,
+ I body,
+ Type expectedResponseType) {
return parsedCall(POST, relativePath, headers, queryParams, body, expectedResponseType);
}
@@ -60,7 +66,10 @@ public void patch(String relativePath, I body) {
parsedCall(PATCH, relativePath, new HashMap<>(), new HashMap<>(), body, Void.TYPE);
}
- public void patch(String relativePath, Map> headers, Map> queryParams, I body) {
+ public void patch(String relativePath,
+ Map> headers,
+ Map> queryParams,
+ I body) {
parsedCall(PATCH, relativePath, headers, queryParams, body, Void.TYPE);
}
@@ -68,62 +77,124 @@ public void delete(String relativePath) {
delete(relativePath, new HashMap<>(), new HashMap<>(), null);
}
-
- public void delete(String relativePath, Map> headers, Map> queryParams, I body) {
+ public void delete(String relativePath,
+ Map> headers,
+ Map> queryParams,
+ I body) {
parsedCall(DELETE, relativePath, headers, queryParams, body, Void.TYPE);
}
- private O parsedCall(HttpMethod method, String relativePath, Map> headers, Map> queryParams, I body, Type expectedResponseType) {
+ private O parsedCall(HttpMethod method,
+ String relativePath,
+ Map> headers,
+ Map> queryParams,
+ I body,
+ Type expectedResponseType) {
+
Map> allHeaders = new HashMap<>();
- //allHeaders.put("Accept", List.of("application/json"));
allHeaders.put("Content-type", List.of("application/json"));
headers.forEach((key, value) -> allHeaders.put(key, value.stream().toList()));
- Response response = callApi(method, relativePath, allHeaders, queryParams, jsonParser.toJsonInputStream(body));
+ Response response = callApi(
+ method,
+ relativePath,
+ allHeaders,
+ queryParams,
+ jsonParser.toJsonInputStream(body)
+ );
+
log.debug("Response is successful. Status code is {}.", response.statusCode());
log.debug("Parsing response.");
- O result = Optional.of(expectedResponseType)
- .filter(not(Void.TYPE::equals))
- .map(type ->{
- String contentType = response.headers().get("Content-type").stream().findFirst().orElseThrow();
- O parsedResult;
- log.debug("Content type is {}.", contentType);
- log.debug("Type class is {}.", type.getClass());
- log.debug("Type is {}.", type);
- if (contentType.startsWith("application/json")) {
- parsedResult = jsonParser.parseInputStream(response.body(), TypeVariable.class.isAssignableFrom(type.getClass()) ? Object.class : type);
- } else {
+
+ final int PREVIEW = 1024;
+ byte[] previewBuf = new byte[PREVIEW];
+ int previewLen = 0;
+ BufferedInputStream bodyStream = new BufferedInputStream(response.body());
+
+ // mark & read first PREVIEW bytes for potential error logging
+ bodyStream.mark(PREVIEW);
+ try {
+ previewLen = bodyStream.read(previewBuf);
+ } catch (IOException ignored) {
+ }
+ try {
+ bodyStream.reset();
+ } catch (IOException ignored) {
+ }
+
+ try {
+ O parsed = Optional.of(expectedResponseType)
+ .filter(not(Void.TYPE::equals))
+ .map(type -> {
+ String contentType = response.headers()
+ .get("Content-type")
+ .stream()
+ .findFirst()
+ .orElse("application/json");
+
+ if (contentType.startsWith("application/json")) {
+ return jsonParser.parseInputStream(
+ bodyStream,
+ TypeVariable.class.isAssignableFrom(type.getClass())
+ ? Object.class
+ : type
+ );
+ }
+
if (checkType(type, String.class) && contentType.startsWith("text/")) {
try {
- parsedResult = (O)IOUtils.toString(response.body(), defaultCharset());
- } catch (IOException e) {
- throw new ParsingException("An error occurred while parsing the response.", e);
- }
- } else {
- if (checkType(type, InputStream.class)) {
- parsedResult = (O)response.body();
- } else {
- throw new UnsupportedContentTypeException(contentType, type);
+ @SuppressWarnings("unchecked")
+ O result = (O) new String(
+ bodyStream.readAllBytes(),
+ defaultCharset()
+ );
+ return result;
+ } catch (IOException ioe) {
+ throw new ParsingException("Could not read text response", ioe);
}
}
- }
- return parsedResult;
- })
- .orElse(null);
- log.debug("Response parsed successfully.");
- return result;
+
+ if (checkType(type, InputStream.class)) {
+ @SuppressWarnings("unchecked")
+ O result = (O) bodyStream;
+ return result;
+ }
+
+ throw new UnsupportedContentTypeException(contentType, type);
+ })
+ .orElse(null);
+
+ log.debug("Response parsed successfully.");
+ return parsed;
+
+ } catch (RuntimeException ex) {
+ String snippet = previewLen > 0
+ ? new String(previewBuf, 0, previewLen, defaultCharset())
+ : "";
+ log.error("Failed to parse response from {} {} (first {} bytes):\n{}",
+ method, relativePath, previewLen, snippet);
+ throw ex;
+ }
}
private boolean checkType(Type type, Class> expectedClass) {
- return (type.getClass().isAssignableFrom(Class.class) && Class.class.cast(type).isAssignableFrom(expectedClass)) || TypeVariable.class.isAssignableFrom(type.getClass());
+ return (type.getClass().isAssignableFrom(Class.class)
+ && ((Class>) type).isAssignableFrom(expectedClass))
+ || TypeVariable.class.isAssignableFrom(type.getClass());
}
- private Response callApi(HttpMethod method, String relativePath, Map> headers, Map> queryParams, InputStream body) {
+ private Response callApi(HttpMethod method,
+ String relativePath,
+ Map> headers,
+ Map> queryParams,
+ InputStream body) {
+
Request request = client.prepareAuthenticatedRequest(host, port, method, relativePath)
.withHeaders(headers)
.withQueryParams(queryParams)
.withBody(body)
.build();
+
log.debug("Executing authenticated {} request with target {}", method, request.getUrl());
return client.send(request);
}
diff --git a/library/pom.xml b/library/pom.xml
index 7cb819bd..eca445a0 100644
--- a/library/pom.xml
+++ b/library/pom.xml
@@ -57,12 +57,6 @@
3.9.0
provided
-
- org.projectlombok
- lombok
- 1.18.38
- provided
-
com.google.code.gson
gson
@@ -71,20 +65,6 @@
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.14.0
-
-
-
- org.projectlombok
- lombok
- 1.18.38
-
-
-
-
org.apache.maven.plugins
maven-plugin-plugin
diff --git a/parent-pom/pom.xml b/parent-pom/pom.xml
index f6414b55..32862ca7 100644
--- a/parent-pom/pom.xml
+++ b/parent-pom/pom.xml
@@ -39,6 +39,7 @@
5.10.0
2.0.9
2.0.9
+ 1.18.38
@@ -99,6 +100,12 @@
${slf4j.version}
test
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+ provided
+
@@ -162,6 +169,20 @@
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.14.0
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+
diff --git a/polyapi-maven-plugin/pom.xml b/polyapi-maven-plugin/pom.xml
index e8865130..8ee5cb6a 100644
--- a/polyapi-maven-plugin/pom.xml
+++ b/polyapi-maven-plugin/pom.xml
@@ -1,5 +1,6 @@
-
+
4.0.0
io.polyapi
@@ -68,12 +69,6 @@
jsonschema2pojo-core
1.2.1
-
- org.projectlombok
- lombok
- 1.18.38
- provided
-
io.socket
socket.io-client
@@ -100,23 +95,14 @@
json
20231013
+
+ com.github.erosb
+ everit-json-schema
+ 1.14.6
+
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.14.0
-
-
-
- org.projectlombok
- lombok
- 1.18.38
-
-
-
-
org.apache.maven.plugins
maven-plugin-plugin
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/schema/JsonSchemaNameHelper.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/schema/JsonSchemaNameHelper.java
index 31dac47c..2c5de797 100644
--- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/schema/JsonSchemaNameHelper.java
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/schema/JsonSchemaNameHelper.java
@@ -1,15 +1,49 @@
package io.polyapi.plugin.service.schema;
+import com.fasterxml.jackson.databind.JsonNode;
import org.jsonschema2pojo.GenerationConfig;
import org.jsonschema2pojo.util.NameHelper;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+/**
+ * A NameHelper that preserves trailing numeric suffixes on JSON property names
+ * and maps '+' to 'Plus' and '-' to 'Minus'.
+ */
public class JsonSchemaNameHelper extends NameHelper {
- public JsonSchemaNameHelper(GenerationConfig generationConfig) {
- super(generationConfig);
+
+ private static final Pattern SUFFIX_PATTERN = Pattern.compile("^(.*?)(_\\d+)$");
+
+ public JsonSchemaNameHelper(GenerationConfig config) {
+ super(config);
}
@Override
public String replaceIllegalCharacters(String name) {
- return super.replaceIllegalCharacters(name.replace("+", "Plus").replace("-", "Minus"));
+ String mapped = name.replace("+", "Plus").replace("-", "Minus");
+ return super.replaceIllegalCharacters(mapped);
+ }
+
+ @Override
+ public String getPropertyName(String jsonName, JsonNode node) {
+ String mapped = jsonName.replace("+", "Plus").replace("-", "Minus");
+ Matcher m = SUFFIX_PATTERN.matcher(mapped);
+ String base = mapped;
+ String suffix = "";
+ if (m.matches()) {
+ base = m.group(1);
+ suffix = m.group(2).replaceAll("[^0-9]", "");
+ }
+ String javaBase = super.getPropertyName(base, node);
+ if (!suffix.isEmpty()) {
+ javaBase = javaBase + suffix;
+ }
+ return javaBase;
+ }
+
+ @Override
+ public String getClassName(String raw, JsonNode node) {
+ String mapped = raw.replace("+", "Plus").replace("-", "Minus");
+ return super.getClassName(mapped, node);
}
}
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/schema/JsonSchemaParser.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/schema/JsonSchemaParser.java
index fa2a11a1..e1a52774 100644
--- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/schema/JsonSchemaParser.java
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/schema/JsonSchemaParser.java
@@ -2,6 +2,11 @@
import com.sun.codemodel.JClass;
import com.sun.codemodel.JCodeModel;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import io.polyapi.plugin.error.PolyApiMavenPluginException;
import io.polyapi.plugin.model.ParsedType;
import io.polyapi.plugin.model.generation.CustomType;
@@ -12,60 +17,132 @@
import org.jsonschema2pojo.SchemaMapper;
import java.io.IOException;
-import java.net.URLEncoder;
-import java.nio.charset.Charset;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
@Slf4j
public class JsonSchemaParser {
- // FIXME: This whole JSON schema parsing needs:
- // FIXME: 1. More automated testing.
- // FIXME: 2. A refactor.
public List parse(String defaultName, String packageName, String schema) {
try {
- var codeModel = new JCodeModel();
- log.trace("Generating Java code from JSON schema {}.", schema);
+ ObjectMapper om = new ObjectMapper();
+ JsonNode root = om.readTree(Optional.ofNullable(schema).orElse(""));
- // This cannot be put as an attribute of this class as it does not take well when being reused and has many errors.
+ int dupCount = patchDuplicates(root);
+ if (dupCount > 0) {
+ log.warn("⚠️ [{}] injected {} duplicate-field suffix(es)", defaultName, dupCount);
+ }
+
+ String patched = om.writeValueAsString(root);
+
+ // generate Java code from the patched schema
+ JCodeModel codeModel = new JCodeModel();
new SchemaMapper(new PolyRuleFactory(new PolyGenerationConfig()), new SchemaGenerator())
- .generate(codeModel, defaultName, packageName, Optional.ofNullable(schema).orElse(""));
+ .generate(codeModel, defaultName, packageName, patched);
+
log.debug("Code generated. Writing to string.");
try (var codeWriter = new PolyCodeWriter()) {
codeModel.build(codeWriter);
var result = codeWriter.getClasses();
if (log.isTraceEnabled()) {
- result.forEach((String name, String code) -> log.trace("Generated code for {} is: {}", name, code));
+ result.forEach((name, code) -> log.trace("Generated code for {} is: {}", name, code));
}
return result.entrySet().stream()
.map(entry -> new CustomType(packageName, entry.getKey(), entry.getValue()))
- .toList();
+ .collect(Collectors.toList());
}
} catch (IOException e) {
- //FIXME: Throw the appropriate exception
throw new PolyApiMavenPluginException(e);
}
}
public ParsedType getType(String defaultName, String packageName, String schema) {
- // This cannot be put as an attribute of this class as it does not take well when being reused and has many errors.
try {
- return getType(new SchemaMapper(new PolyRuleFactory(new PolyGenerationConfig()), new SchemaGenerator())
- .generate(new JCodeModel(), defaultName, packageName, Optional.ofNullable(schema).orElse(""))
- .boxify());
+ ObjectMapper om = new ObjectMapper();
+ JsonNode root = om.readTree(Optional.ofNullable(schema).orElse(""));
+ patchDuplicates(root);
+ String patched = om.writeValueAsString(root);
+
+ JClass jClass = new SchemaMapper(new PolyRuleFactory(new PolyGenerationConfig()), new SchemaGenerator())
+ .generate(new JCodeModel(), defaultName, packageName, patched)
+ .boxify();
+ return getType(jClass);
} catch (IOException e) {
- //FIXME: Throw the appropriate exception
throw new PolyApiMavenPluginException(e);
}
}
private ParsedType getType(JClass jClass) {
- return new ParsedType(jClass.erasure().fullName(), Optional.ofNullable(jClass.getTypeParameters())
- .orElseGet(ArrayList::new)
- .stream()
- .map(this::getType)
- .toList());
+ return new ParsedType(
+ jClass.erasure().fullName(),
+ Optional.ofNullable(jClass.getTypeParameters()).orElseGet(ArrayList::new).stream()
+ .map(this::getType)
+ .collect(Collectors.toList())
+ );
+ }
+
+ /**
+ * Recursively mutates the JSON schema tree, injecting any properties whose
+ * JSON names collapse to the same Java identifier under a suffix,
+ * into both "properties" and "required".
+ */
+ private int patchDuplicates(JsonNode root) {
+ if (!root.isObject()) return 0;
+ int injectedCount = 0;
+ ObjectNode obj = (ObjectNode) root;
+
+ if (obj.has("properties")) {
+ ObjectNode props = (ObjectNode) obj.get("properties");
+ ArrayNode reqs = obj.has("required")
+ ? (ArrayNode) obj.get("required")
+ : new ArrayNode(JsonNodeFactory.instance);
+
+ Set seen = new HashSet<>();
+ List collisions = new ArrayList<>();
+ Iterator it = props.fieldNames();
+ while (it.hasNext()) {
+ String jsonName = it.next();
+ String normalized = jsonName.replaceAll("[^A-Za-z0-9]+","")
+ .toLowerCase(Locale.ROOT);
+ log.trace(" – jsonName='{}' → normalized='{}'", jsonName, normalized);
+ if (!seen.add(normalized)) {
+ collisions.add(jsonName);
+ }
+ }
+
+ int suffix = 1;
+ for (String dup : collisions) {
+ injectedCount++;
+ String injected = dup + "_" + suffix++;
+ JsonNode originalNode = props.get(dup);
+ props.remove(dup);
+
+ // re-insert under the suffixed name
+ props.set(injected, originalNode);
+
+ // now fix up "required": swap dup -> injected
+ if (obj.has("required")) {
+ ArrayNode reqsArray = (ArrayNode) obj.get("required");
+ for (int i = 0; i < reqsArray.size(); i++) {
+ if (reqsArray.get(i).asText().equals(dup)) {
+ reqsArray.set(i, JsonNodeFactory.instance.textNode(injected));
+ }
+ }
+ }
+ }
+ }
+
+ Iterator fieldsIt = obj.fieldNames();
+ while (fieldsIt.hasNext()) {
+ patchDuplicates(obj.get(fieldsIt.next()));
+ }
+
+ return injectedCount;
}
}
diff --git a/polyapi-maven-plugin/src/test/java/io/polyapi/plugin/service/JsonSchemaParserTest.java b/polyapi-maven-plugin/src/test/java/io/polyapi/plugin/service/JsonSchemaParserTest.java
index c0a73ffa..c29fe997 100644
--- a/polyapi-maven-plugin/src/test/java/io/polyapi/plugin/service/JsonSchemaParserTest.java
+++ b/polyapi-maven-plugin/src/test/java/io/polyapi/plugin/service/JsonSchemaParserTest.java
@@ -40,24 +40,31 @@ public static Stream generateSource() {
createArguments(13, "Schema with different types that have the same enum.", "Identifier", DEFAULT_RESPONSE_NAME, "Data"),
createArguments(14, "Schema that is an Integer."),
createArguments(15, "Schema with multiple enums with the same name and properties.", DEFAULT_RESPONSE_NAME, "DashMinusstyle", "DashMinusstyle_", "Other"),
- createArguments(16, "Schema with json property yhat has a space in it.", "Data", DEFAULT_RESPONSE_NAME));
+ createArguments(16, "Schema with json property yhat has a space in it.", "Data", DEFAULT_RESPONSE_NAME),
+ createArguments(17, "Schema with both order_id and orderId to force a collision", "Case17SchemaWithDuplicateFields"));
}
public static Stream getTypeSource() {
- return Stream.of(Arguments.of(1, "Simple recursive schema with no base type.", createClassName(DEFAULT_RESPONSE_NAME)),
+ return Stream.of(
+ Arguments.of(1, "Simple recursive schema with no base type.", createClassName(DEFAULT_RESPONSE_NAME)),
Arguments.of(2, "Recursive schema with base type.", createClassName(DEFAULT_RESPONSE_NAME)),
- Arguments.of(3, "Schema that has a text value evaluated to null.", createClassName(DEFAULT_RESPONSE_NAME)),
+ Arguments.of(3, "Schema that has a text value evaluated to null.",
+ createClassName(DEFAULT_RESPONSE_NAME)),
Arguments.of(4, "Schema with base type and no definitions.", createClassName(DEFAULT_RESPONSE_NAME)),
Arguments.of(5, "Schema for array of numbers.", createListClassName(Double.class.getName())),
Arguments.of(6, "Schema for array of integers.", createListClassName(Long.class.getName())),
Arguments.of(7, "Simple schema with attribute.", createClassName(DEFAULT_RESPONSE_NAME)),
- Arguments.of(8, "Schema with duplicate fields.", createListClassName(createClassName("ResponseTypeElement"))),
+ Arguments.of(8, "Schema with duplicate fields.",
+ createListClassName(createClassName("ResponseTypeElement"))),
Arguments.of(9, "Schema with enum.", createClassName(DEFAULT_RESPONSE_NAME)),
Arguments.of(10, "Schema that is a String.", String.class.getName()),
Arguments.of(11, "Schema that uses allof.", Object.class.getName()),
- Arguments.of(12, "Schema with enum with '-' in one of the options.", createClassName(DEFAULT_RESPONSE_NAME)),
- Arguments.of(13, "Schema with different types that have the same enum.", createClassName(DEFAULT_RESPONSE_NAME)),
- Arguments.of(14, "Schema that is an integer.", Long.class.getName()));
+ Arguments.of(12, "Schema with enum with '-' in one of the options.",
+ createClassName(DEFAULT_RESPONSE_NAME)),
+ Arguments.of(13, "Schema with different types that have the same enum.",
+ createClassName(DEFAULT_RESPONSE_NAME)),
+ Arguments.of(14, "Schema that is an integer.", Long.class.getName()),
+ Arguments.of(17, "Schema with both order_id and orderId to force a collision", createClassName("Case17SchemaWithDuplicateFields")));
}
private static String createClassName(String className) {
@@ -81,6 +88,12 @@ public void generateTest(Integer caseNumber, String description, List ex
var customTypes = jsonSchemaParser.parse("TestResponse", specification.getPackageName(), getSchema(caseNumber));
assertThat(customTypes, notNullValue());
assertThat(customTypes.size(), equalTo(expectedNames.size()));
+ if (caseNumber.equals(17)) {
+ CustomType ct = customTypes.get(0);
+ String code = ct.getCode();
+ // check that we injected the suffixed field into the class
+ assertTrue(code.contains("orderId1"), "Expected generated code to contain field 'orderId1'");
+ }
var customTypeNames = customTypes.stream().map(CustomType::getName).toList();
expectedNames.forEach(expectedName -> assertTrue(customTypeNames.contains(expectedName), format("Result should contain object with name %s. Result contains %s.", expectedName, customTypeNames)));
customTypes.forEach(customType -> assertTrue(customType.getCode().contains(format("public class %s {", customType.getName())) || customType.getCode().contains(format("public enum %s {", customType.getName()))));
@@ -89,12 +102,18 @@ public void generateTest(Integer caseNumber, String description, List ex
@ParameterizedTest(name = "Case {0}: {1}")
@MethodSource("getTypeSource")
public void getTypeTest(Integer caseNumber, String description, String expectedType) {
- assertThat(jsonSchemaParser.getType(DEFAULT_RESPONSE_NAME, JsonSchemaParserTest.class.getPackageName(), getSchema(caseNumber)).getFullName(), equalTo(expectedType));
+ assertThat(jsonSchemaParser
+ .getType(DEFAULT_RESPONSE_NAME, JsonSchemaParserTest.class.getPackageName(), getSchema(caseNumber))
+ .getFullName(), equalTo(expectedType));
}
private String getSchema(Integer caseNumber) {
try {
- return IOUtils.toString(JsonSchemaParser.class.getResourceAsStream(format("/%s/cases/Case %s.schema.json", JsonSchemaParser.class.getPackageName().replace(".", "/"), caseNumber)), defaultCharset());
+ return IOUtils
+ .toString(
+ JsonSchemaParser.class.getResourceAsStream(format("/%s/cases/Case %s.schema.json",
+ JsonSchemaParser.class.getPackageName().replace(".", "/"), caseNumber)),
+ defaultCharset());
} catch (IOException e) {
throw new RuntimeException(e);
}
diff --git a/polyapi-maven-plugin/src/test/resources/io/polyapi/plugin/service/schema/cases/Case 17.schema.json b/polyapi-maven-plugin/src/test/resources/io/polyapi/plugin/service/schema/cases/Case 17.schema.json
new file mode 100644
index 00000000..3180c84b
--- /dev/null
+++ b/polyapi-maven-plugin/src/test/resources/io/polyapi/plugin/service/schema/cases/Case 17.schema.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Case 17: Schema with duplicate fields",
+ "type": "object",
+ "properties": {
+ "order_id": {
+ "type": "string"
+ },
+ "orderId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "order_id",
+ "orderId"
+ ]
+}
\ No newline at end of file