From 8b3e3ee7beba6011b4ae5e8ad8885434523f3185 Mon Sep 17 00:00:00 2001 From: "eric.neumann" Date: Tue, 20 May 2025 15:54:35 -0700 Subject: [PATCH 1/4] EN #4304 cleanup: move lombok dep and plugin to parent-pom --- commons/pom.xml | 39 ------------------------------------ library/pom.xml | 20 ------------------ parent-pom/pom.xml | 21 +++++++++++++++++++ polyapi-maven-plugin/pom.xml | 20 ------------------ 4 files changed, 21 insertions(+), 79 deletions(-) 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/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..df7d8be3 100644 --- a/polyapi-maven-plugin/pom.xml +++ b/polyapi-maven-plugin/pom.xml @@ -68,12 +68,6 @@ jsonschema2pojo-core 1.2.1 - - org.projectlombok - lombok - 1.18.38 - provided - io.socket socket.io-client @@ -103,20 +97,6 @@ - - org.apache.maven.plugins - maven-compiler-plugin - 3.14.0 - - - - org.projectlombok - lombok - 1.18.38 - - - - org.apache.maven.plugins maven-plugin-plugin From 6e0e35c4c72fe7d49eaf1dd00f7df03f7f025c16 Mon Sep 17 00:00:00 2001 From: "eric.neumann" Date: Tue, 20 May 2025 16:00:14 -0700 Subject: [PATCH 2/4] EN #4304 added error preview logging and updated changelog --- CHANGELOG.md | 4 +- .../commons/api/service/PolyApiService.java | 149 +++++++++++++----- 2 files changed, 113 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee081f41..8a3a0337 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,9 @@ ### 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 limbok dependency and plugin to parent-pom ### Fixed 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); } From 018cbb9f6ea283676a98d2c9dbdcdd3c1ecdf27e Mon Sep 17 00:00:00 2001 From: "eric.neumann" Date: Wed, 21 May 2025 14:01:48 -0700 Subject: [PATCH 3/4] EN #4289 prevent name collisions --- CHANGELOG.md | 2 + polyapi-maven-plugin/pom.xml | 8 +- .../service/schema/JsonSchemaNameHelper.java | 40 +++++- .../service/schema/JsonSchemaParser.java | 121 ++++++++++++++---- .../plugin/service/JsonSchemaParserTest.java | 37 ++++-- .../service/schema/cases/Case 17.schema.json | 17 +++ 6 files changed, 190 insertions(+), 35 deletions(-) create mode 100644 polyapi-maven-plugin/src/test/resources/io/polyapi/plugin/service/schema/cases/Case 17.schema.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a3a0337..ca024019 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - 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. diff --git a/polyapi-maven-plugin/pom.xml b/polyapi-maven-plugin/pom.xml index df7d8be3..8ee5cb6a 100644 --- a/polyapi-maven-plugin/pom.xml +++ b/polyapi-maven-plugin/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 io.polyapi @@ -94,6 +95,11 @@ json 20231013 + + com.github.erosb + everit-json-schema + 1.14.6 + 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 From 61b3907e2a919f44fadd4a4f66be99e8b25cdbe7 Mon Sep 17 00:00:00 2001 From: "eric.neumann" Date: Fri, 23 May 2025 08:27:21 -0700 Subject: [PATCH 4/4] EN #4304 fix typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca024019..b903e2ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ - 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 limbok dependency and plugin to parent-pom +- Added lombok dependency and plugin to parent-pom ### Fixed