From 2e304de0dcb5dac2032c5c7680d89cd28de668e2 Mon Sep 17 00:00:00 2001 From: Guy Davenport Date: Thu, 14 Nov 2024 19:21:30 +1300 Subject: [PATCH] added check to see if a defs is present in a referenced json --- ...brapi.schema-tools.java-conventions.gradle | 2 +- .../core/brapischema/BrAPISchemaReader.java | 956 +++++++++--------- 2 files changed, 494 insertions(+), 464 deletions(-) diff --git a/java/buildSrc/src/main/groovy/brapi.schema-tools.java-conventions.gradle b/java/buildSrc/src/main/groovy/brapi.schema-tools.java-conventions.gradle index 3ac62d7..991fc98 100644 --- a/java/buildSrc/src/main/groovy/brapi.schema-tools.java-conventions.gradle +++ b/java/buildSrc/src/main/groovy/brapi.schema-tools.java-conventions.gradle @@ -3,7 +3,7 @@ plugins { } group = 'org.brapi' -version = '0.10.0-SNAPSHOT' +version = '0.11.0-SNAPSHOT' sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 diff --git a/java/core/src/main/java/org/brapi/schematools/core/brapischema/BrAPISchemaReader.java b/java/core/src/main/java/org/brapi/schematools/core/brapischema/BrAPISchemaReader.java index e62e1ac..12510ba 100644 --- a/java/core/src/main/java/org/brapi/schematools/core/brapischema/BrAPISchemaReader.java +++ b/java/core/src/main/java/org/brapi/schematools/core/brapischema/BrAPISchemaReader.java @@ -20,11 +20,7 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.regex.Matcher; @@ -66,15 +62,7 @@ public BrAPISchemaReader() { * @return a response containing a list of BrAPIClass with one type per JSON Schema or validation errors */ public Response> readDirectories(Path schemaDirectory) { - - try (Stream schemas = find(schemaDirectory, 3, this::schemaPathMatcher)) { - return dereferenceAndValidate(schemas.map(this::createBrAPISchemas).collect(Response.mergeLists())); - } catch (NoSuchFileException noSuchFileException) { - return fail(Response.ErrorType.VALIDATION, String.format("The schema directory '%s' does not exist", schemaDirectory)); - } - catch (RuntimeException | IOException e) { - return fail(Response.ErrorType.VALIDATION, schemaDirectory, String.format("%s: %s", e.getClass().getSimpleName(), e.getMessage())); - } + return new Reader().readDirectories(schemaDirectory); } /** @@ -89,7 +77,7 @@ public Response> readDirectories(Path schemaDirectory) { */ public Response readSchema(Path schemaPath, String module) throws BrAPISchemaReaderException { try { - return createBrAPISchemas(schemaPath, module).mapResult(list -> list.get(0)); + return new Reader().createBrAPISchemas(schemaPath, module).mapResult(list -> list.get(0)); } catch (RuntimeException e) { throw new BrAPISchemaReaderException(e); } @@ -108,608 +96,650 @@ public Response readSchema(Path schemaPath, String module) throws Br */ public Response readSchema(Path path, String schema, String module) throws BrAPISchemaReaderException { try { - return createBrAPISchemas(path, objectMapper.readTree(schema), module).mapResult(list -> list.get(0)); + return new Reader().createBrAPISchemas(path, objectMapper.readTree(schema), module).mapResult(list -> list.get(0)); } catch (RuntimeException | JsonProcessingException e) { throw new BrAPISchemaReaderException(String.format("Can not read schema at '%s' in module '%s' from '%s', due to '%s'", path, module, schema, e.getMessage()), e); } } - private Response> dereferenceAndValidate(Response> types) { - return types.mapResult(this::dereference).mapResultToResponse(this::validate); - } - - private List dereference(List types) { - - Map typeMap = types.stream().collect(Collectors.toMap(BrAPIType::getName, Function.identity())); + private class Reader { - List brAPIClasses = new ArrayList<>(); + private Response> readDirectories(Path schemaDirectory) { - types.forEach(type -> { - if (type instanceof BrAPIAllOfType brAPIAllOfType) { - brAPIClasses.add(BrAPIObjectType.builder() - .name(brAPIAllOfType.getName()) - .description(brAPIAllOfType.getDescription()) - .module(brAPIAllOfType.getModule()) - .metadata(brAPIAllOfType.getMetadata() != null ? brAPIAllOfType.getMetadata().toBuilder().build() : null) - .interfaces(extractInterfaces(brAPIAllOfType, typeMap)) - .properties(extractProperties(new ArrayList<>(), brAPIAllOfType, typeMap)) - .build()); - } else { - brAPIClasses.add(type); + try (Stream schemas = find(schemaDirectory, 3, this::schemaPathMatcher)) { + return dereferenceAndValidate(schemas.map(this::createBrAPISchemas).collect(Response.mergeLists())); + } catch (NoSuchFileException noSuchFileException) { + return fail(Response.ErrorType.VALIDATION, String.format("The schema directory '%s' does not exist", schemaDirectory)); + } catch (RuntimeException | IOException e) { + return fail(Response.ErrorType.VALIDATION, schemaDirectory, String.format("%s: %s", e.getClass().getSimpleName(), e.getMessage())); } - }); + } - return brAPIClasses; - } + private Response> dereferenceAndValidate(Response> types) { + return types.mapResult(this::dereference).mapResultToResponse(this::validate); + } - private Response> validate(List brAPIClasses) { - Map classesMap = brAPIClasses.stream().collect(Collectors.toMap(BrAPIType::getName, Function.identity())); + private List dereference(List types) { - return brAPIClasses.stream().map(brAPIClass -> validateClass(classesMap, brAPIClass).mapResult(t -> (BrAPIClass) t)).collect(Response.toList()); - } + Map typeMap = types.stream().collect(Collectors.toMap(BrAPIType::getName, Function.identity())); - private Response validateClass(final Map classesMap, BrAPIClass brAPIClass) { - return validateBrAPIMetadata(brAPIClass).map(() -> { - if (brAPIClass instanceof BrAPIAllOfType brAPIAllOfType) { - return fail(Response.ErrorType.VALIDATION, String.format("BrAPIAllOfType '%s' was not de-referenced", brAPIAllOfType.getName())); - } + List brAPIClasses = new ArrayList<>(); - if (brAPIClass instanceof BrAPIOneOfType brAPIOneOfType) { - return brAPIOneOfType.getPossibleTypes().stream().map(possibleType -> validateType(classesMap, possibleType)).collect(Response.toList()).withResult(brAPIClass); - } + types.forEach(type -> { + if (type instanceof BrAPIAllOfType brAPIAllOfType) { + brAPIClasses.add(BrAPIObjectType.builder() + .name(brAPIAllOfType.getName()) + .description(brAPIAllOfType.getDescription()) + .module(brAPIAllOfType.getModule()) + .metadata(brAPIAllOfType.getMetadata() != null ? brAPIAllOfType.getMetadata().toBuilder().build() : null) + .interfaces(extractInterfaces(brAPIAllOfType, typeMap)) + .properties(extractProperties(new ArrayList<>(), brAPIAllOfType, typeMap)) + .build()); + } else { + brAPIClasses.add(type); + } + }); - if (brAPIClass instanceof BrAPIObjectType brAPIObjectType) { - return brAPIObjectType.getProperties().stream().map(property -> validateProperty(classesMap, brAPIObjectType, property)).collect(Response.toList()).withResult(brAPIClass); - } + return brAPIClasses; + } - return success(brAPIClass); - }); - } + private Response> validate(List brAPIClasses) { + Map classesMap = brAPIClasses.stream().collect(Collectors.toMap(BrAPIType::getName, Function.identity())); - private Response validateType(final Map classesMap, BrAPIType brAPIType) { - if (brAPIType instanceof BrAPIClass brAPIAllOfType) { - return validateClass(classesMap, brAPIAllOfType); - } else { - return success(brAPIType); + return brAPIClasses.stream().map(brAPIClass -> validateClass(classesMap, brAPIClass).mapResult(t -> (BrAPIClass) t)).collect(Response.toList()); } - } - private Response validateBrAPIMetadata(BrAPIClass brAPIClass) { - BrAPIMetadata metadata = brAPIClass.getMetadata(); + private Response validateClass(final Map classesMap, BrAPIClass brAPIClass) { + return validateBrAPIMetadata(brAPIClass).map(() -> { + if (brAPIClass instanceof BrAPIAllOfType brAPIAllOfType) { + return fail(Response.ErrorType.VALIDATION, String.format("BrAPIAllOfType '%s' was not de-referenced", brAPIAllOfType.getName())); + } - if (metadata != null) { - int i = 0; - if (metadata.isPrimaryModel()) { - ++i; - } - if (metadata.isRequest()) { - ++i; - } - if (metadata.isParameters()) { - ++i; - } - if (metadata.isInterfaceClass()) { - ++i; - } - if (i > 1) { - return fail(Response.ErrorType.VALIDATION, String.format("In class '%s', 'primaryModel', 'request', 'properties', 'interface' are mutually exclusive, only one can be set to to true", brAPIClass.getName())); + if (brAPIClass instanceof BrAPIOneOfType brAPIOneOfType) { + return brAPIOneOfType.getPossibleTypes().stream().map(possibleType -> validateType(classesMap, possibleType)).collect(Response.toList()).withResult(brAPIClass); + } + + if (brAPIClass instanceof BrAPIObjectType brAPIObjectType) { + return brAPIObjectType.getProperties().stream().map(property -> validateProperty(classesMap, brAPIObjectType, property)).collect(Response.toList()).withResult(brAPIClass); + } + + return success(brAPIClass); + }); + } + + private Response validateType(final Map classesMap, BrAPIType brAPIType) { + if (brAPIType instanceof BrAPIClass brAPIAllOfType) { + return validateClass(classesMap, brAPIAllOfType); + } else { + return success(brAPIType); } } - return success(metadata); - } + private Response validateBrAPIMetadata(BrAPIClass brAPIClass) { + BrAPIMetadata metadata = brAPIClass.getMetadata(); - private Response validateProperty(Map classesMap, BrAPIObjectType brAPIObjectType, BrAPIObjectProperty property) { - BrAPIType propertyType = property.getType(); + if (metadata != null) { + int i = 0; + if (metadata.isPrimaryModel()) { + ++i; + } + if (metadata.isRequest()) { + ++i; + } + if (metadata.isParameters()) { + ++i; + } + if (metadata.isInterfaceClass()) { + ++i; + } + if (i > 1) { + return fail(Response.ErrorType.VALIDATION, String.format("In class '%s', 'primaryModel', 'request', 'properties', 'interface' are mutually exclusive, only one can be set to to true", brAPIClass.getName())); + } + } + + return success(metadata); + } + + private Response validateProperty(Map classesMap, BrAPIObjectType brAPIObjectType, BrAPIObjectProperty property) { + BrAPIType propertyType = property.getType(); - if (propertyType instanceof BrAPIReferenceType brAPIReferenceType) { - propertyType = classesMap.get(brAPIReferenceType.getName()); + if (propertyType instanceof BrAPIReferenceType brAPIReferenceType) { + propertyType = classesMap.get(brAPIReferenceType.getName()); - if (propertyType == null) { - return Response.fail(Response.ErrorType.VALIDATION, - String.format("Property '%s' in type '%s' is a Reference, but the referenced type '%s' is not available", - property.getName(), brAPIObjectType.getName(), brAPIReferenceType.getName())); + if (propertyType == null) { + return Response.fail(Response.ErrorType.VALIDATION, + String.format("Property '%s' in type '%s' is a Reference, but the referenced type '%s' is not available", + property.getName(), brAPIObjectType.getName(), brAPIReferenceType.getName())); + } } - } - BrAPIRelationshipType relationshipType = property.getRelationshipType() ; + BrAPIRelationshipType relationshipType = property.getRelationshipType(); - if (relationshipType != null) { - switch (relationshipType) { - case MANY_TO_ONE, ONE_TO_ONE -> { - if (propertyType instanceof BrAPIArrayType) { - return Response.fail(Response.ErrorType.VALIDATION, String.format("Property '%s' in type '%s' has relationshipType '%s', referenced type '%s' is an array", - property.getName(), brAPIObjectType.getName(), relationshipType, propertyType.getName())); + if (relationshipType != null) { + switch (relationshipType) { + case MANY_TO_ONE, ONE_TO_ONE -> { + if (propertyType instanceof BrAPIArrayType) { + return Response.fail(Response.ErrorType.VALIDATION, String.format("Property '%s' in type '%s' has relationshipType '%s', referenced type '%s' is an array", + property.getName(), brAPIObjectType.getName(), relationshipType, propertyType.getName())); + } } - } - case ONE_TO_MANY, MANY_TO_MANY -> { - if (!(propertyType instanceof BrAPIArrayType)) { - return Response.fail(Response.ErrorType.VALIDATION, String.format("Property '%s' in type '%s' has relationshipType '%s', referenced type '%s' is not an array", - property.getName(), brAPIObjectType.getName(), relationshipType, propertyType.getName())); + case ONE_TO_MANY, MANY_TO_MANY -> { + if (!(propertyType instanceof BrAPIArrayType)) { + return Response.fail(Response.ErrorType.VALIDATION, String.format("Property '%s' in type '%s' has relationshipType '%s', referenced type '%s' is not an array", + property.getName(), brAPIObjectType.getName(), relationshipType, propertyType.getName())); + } } } } - } - if (property.getReferencedAttribute() != null) { + if (property.getReferencedAttribute() != null) { - BrAPIType type = unwrapType(property.getType()); + BrAPIType type = unwrapType(property.getType()); - BrAPIClass referencedType = classesMap.get(type.getName()); + BrAPIClass referencedType = classesMap.get(type.getName()); - if (referencedType == null) { - return Response.fail(Response.ErrorType.VALIDATION, - String.format("Property '%s' in type '%s' has a Referenced Attribute '%s', but the referenced type '%s' is not available", - property.getName(), brAPIObjectType.getName(), property.getReferencedAttribute(), property.getType().getName())); - } + if (referencedType == null) { + return Response.fail(Response.ErrorType.VALIDATION, + String.format("Property '%s' in type '%s' has a Referenced Attribute '%s', but the referenced type '%s' is not available", + property.getName(), brAPIObjectType.getName(), property.getReferencedAttribute(), property.getType().getName())); + } - if (referencedType instanceof BrAPIObjectType referencedObjectType) { - if (referencedObjectType.getProperties().stream().noneMatch(childProperty -> property.getReferencedAttribute().equals(childProperty.getName()))) { - return Response.fail(Response.ErrorType.VALIDATION, String.format("Property '%s' in type '%s' has a Referenced Attribute '%s', but the property does not exist in the referenced type '%s'", - property.getName(), brAPIObjectType.getName(), property.getReferencedAttribute(), referencedType.getName())); + if (referencedType instanceof BrAPIObjectType referencedObjectType) { + if (referencedObjectType.getProperties().stream().noneMatch(childProperty -> property.getReferencedAttribute().equals(childProperty.getName()))) { + return Response.fail(Response.ErrorType.VALIDATION, String.format("Property '%s' in type '%s' has a Referenced Attribute '%s', but the property does not exist in the referenced type '%s'", + property.getName(), brAPIObjectType.getName(), property.getReferencedAttribute(), referencedType.getName())); + } + } else { + return Response.fail(Response.ErrorType.VALIDATION, + String.format("Property '%s' in type '%s' has a Referenced Attribute '%s', but the referenced type '%s' is not a BrAPIObjectType", + property.getName(), brAPIObjectType.getName(), property.getReferencedAttribute(), referencedType.getName())); } - } else { - return Response.fail(Response.ErrorType.VALIDATION, - String.format("Property '%s' in type '%s' has a Referenced Attribute '%s', but the referenced type '%s' is not a BrAPIObjectType", - property.getName(), brAPIObjectType.getName(), property.getReferencedAttribute(), referencedType.getName())); } + + return Response.success(property); } - return Response.success(property); - } + private BrAPIType unwrapType(BrAPIType type) { + if (type instanceof BrAPIArrayType brAPIArrayType) { + return unwrapType(brAPIArrayType.getItems()); + } - private BrAPIType unwrapType(BrAPIType type) { - if (type instanceof BrAPIArrayType brAPIArrayType) { - return unwrapType(brAPIArrayType.getItems()); + return type; } - return type; - } - - private List extractProperties(List properties, BrAPIType brAPIType, Map typeMap) { + private List extractProperties(List properties, BrAPIType brAPIType, Map typeMap) { - if (brAPIType instanceof BrAPIObjectType brAPIObjectType) { - properties.addAll(brAPIObjectType.getProperties()); - } else { - if (brAPIType instanceof BrAPIAllOfType brAPIAllOfType) { - brAPIAllOfType.getAllTypes().forEach(type -> extractProperties(properties, type, typeMap)); + if (brAPIType instanceof BrAPIObjectType brAPIObjectType) { + properties.addAll(brAPIObjectType.getProperties()); } else { - if (brAPIType instanceof BrAPIReferenceType brAPIReferenceType) { - extractProperties(properties, typeMap.get(brAPIReferenceType.getName()), typeMap); + if (brAPIType instanceof BrAPIAllOfType brAPIAllOfType) { + brAPIAllOfType.getAllTypes().forEach(type -> extractProperties(properties, type, typeMap)); + } else { + if (brAPIType instanceof BrAPIReferenceType brAPIReferenceType) { + extractProperties(properties, typeMap.get(brAPIReferenceType.getName()), typeMap); + } } } + + return properties; } - return properties; - } + private List extractInterfaces(BrAPIAllOfType brAPIAllOfType, Map typeMap) { - private List extractInterfaces(BrAPIAllOfType brAPIAllOfType, Map typeMap) { + List interfaces = new ArrayList<>(); - List interfaces = new ArrayList<>(); + brAPIAllOfType.getAllTypes().forEach(type -> { + BrAPIType allType = typeMap.get(type.getName()); - brAPIAllOfType.getAllTypes().forEach(type -> { - BrAPIType allType = typeMap.get(type.getName()); + if (allType instanceof BrAPIObjectType && isInterface((BrAPIObjectType) allType)) { + interfaces.add((BrAPIObjectType) allType); + } + }); - if (allType instanceof BrAPIObjectType && isInterface((BrAPIObjectType) allType)) { - interfaces.add((BrAPIObjectType) allType); - } - }); + return interfaces; + } - return interfaces; - } + private boolean isInterface(BrAPIClass brAPIClass) { + return brAPIClass.getMetadata() != null && brAPIClass.getMetadata().isInterfaceClass(); + } - private boolean isInterface(BrAPIClass brAPIClass) { - return brAPIClass.getMetadata() != null && brAPIClass.getMetadata().isInterfaceClass(); - } + private Response> createBrAPISchemas(Path path) { + return createBrAPISchemas(path, findModule(path)); + } - private Response> createBrAPISchemas(Path path) { - return createBrAPISchemas(path, findModule(path)); - } + private String findModule(Path path) { + String module = path != null ? path.getParent().getFileName().toString() : null; - private String findModule(Path path) { - String module = path != null ? path.getParent().getFileName().toString() : null; + return module != null && COMMON_MODULES.contains(module) ? null : module; + } - return module != null && COMMON_MODULES.contains(module) ? null : module; - } + private Response> createBrAPISchemas(Path path, String module) { + return findSchema(path).mapResultToResponse(json -> createBrAPISchemas(path, json, module)); + } - private Response> createBrAPISchemas(Path path, String module) { - try { - JsonSchema schema = factory.getSchema(path.toUri()); + private Response findSchema(Path path) { - JsonNode json = schema.getSchemaNode(); + JsonNode json = schemaNodes.get(path); - return createBrAPISchemas(path, json, module); - } catch (RuntimeException e) { - return Response.fail(Response.ErrorType.VALIDATION, path, String.format("Can not read schemas from for module '%s' from path '%s' due to '%s'", module, path, e.getMessage())); - } - } + if (json != null) { + return success(json); + } - private Response> createBrAPISchemas(Path path, JsonNode json, String module) { - JsonNode defs = json.get("$defs"); + try { - if (defs != null) { - json = defs; - } + JsonSchema schema = factory.getSchema(path.toUri()); - Iterator> iterator = json.fields(); + json = schema.getSchemaNode(); - return Stream.generate(() -> null) - .takeWhile(x -> iterator.hasNext()) - .map(n -> iterator.next()).map(entry -> createBrAPIClass(path, entry.getValue(), entry.getKey(), module)).collect(Response.toList()); - } + schemaNodes.put(path, json); - private Response createBrAPIClass(Path path, JsonNode jsonNode, String fallbackName, String module) { - try { - return createType(path, jsonNode, fallbackName, module).mapResult(type -> (BrAPIClass) type); - } catch (ClassCastException e) { - return Response.fail(Response.ErrorType.VALIDATION, path, String.format("Can not cast type '%s' to BrAPIClass!", fallbackName)); + return success(json); + } catch (RuntimeException e) { + return Response.fail(Response.ErrorType.VALIDATION, path, String.format("Can not read json node from path '%s' due to '%s'", path, e.getMessage())); + } } - } - private boolean schemaPathMatcher(Path path, BasicFileAttributes basicFileAttributes) { - return basicFileAttributes.isRegularFile(); - } + private final Map schemaNodes = new HashMap<>(); + + private Response> createBrAPISchemas(Path path, JsonNode json, String module) { + JsonNode defs = json.get("$defs"); - private Response createType(Path path, JsonNode jsonNode, String fallbackName, String module) { - if (jsonNode.has("allOf")) { - return createAllOfType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), module); + if (defs != null) { + json = defs; + } + + Iterator> iterator = json.fields(); + + return Stream.generate(() -> null) + .takeWhile(x -> iterator.hasNext()) + .map(n -> iterator.next()).map(entry -> createBrAPIClass(path, entry.getValue(), entry.getKey(), module)).collect(Response.toList()); } - if (jsonNode.has("oneOf")) { - return createOneOfType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), module); + private Response createBrAPIClass(Path path, JsonNode jsonNode, String fallbackName, String module) { + try { + return createType(path, jsonNode, fallbackName, module).mapResult(type -> (BrAPIClass) type); + } catch (ClassCastException e) { + return Response.fail(Response.ErrorType.VALIDATION, path, String.format("Can not cast type '%s' to BrAPIClass!", fallbackName)); + } } - boolean isEnum = jsonNode.has("enum"); + private boolean schemaPathMatcher(Path path, BasicFileAttributes basicFileAttributes) { + return basicFileAttributes.isRegularFile(); + } - return findChildNode(path, jsonNode, "$ref", false).ifPresentMapResultToResponseOr( - ref -> createReferenceType(path, ref), + private Response createType(Path path, JsonNode jsonNode, String fallbackName, String module) { + if (jsonNode.has("allOf")) { + return createAllOfType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), module); + } - () -> findStringList(path, jsonNode, "type", true). - mapResultToResponse(types -> { - if (types.contains("object")) { - if (isEnum) { - return fail(Response.ErrorType.VALIDATION, path, String.format("Object Type '%s' can not be an enum!", fallbackName)); - } else { - return createObjectType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), module); - } - } + if (jsonNode.has("oneOf")) { + return createOneOfType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), module); + } + + boolean isEnum = jsonNode.has("enum"); + + return findChildNode(path, jsonNode, "$ref", false).ifPresentMapResultToResponseOr( + ref -> createReferenceType(path, ref), - if (types.contains("array")) { - if (isEnum) { - return fail(Response.ErrorType.VALIDATION, path, String.format("Array Type '%s' can not be an enum!", fallbackName)); + () -> findStringList(path, jsonNode, "type", true). + mapResultToResponse(types -> { + if (types.contains("object")) { + if (isEnum) { + return fail(Response.ErrorType.VALIDATION, path, String.format("Object Type '%s' can not be an enum!", fallbackName)); + } else { + return createObjectType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), module); + } } - return createArrayType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), module); - } + if (types.contains("array")) { + if (isEnum) { + return fail(Response.ErrorType.VALIDATION, path, String.format("Array Type '%s' can not be an enum!", fallbackName)); + } - if (types.contains("string")) { - if (isEnum) { - return createEnumType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), "string", module); - } else { - return success(BrAPIPrimitiveType.STRING); + return createArrayType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), module); } - } - if (types.contains("integer")) { - if (isEnum) { - return createEnumType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), "integer", module); - } else { - return success(BrAPIPrimitiveType.INTEGER); + if (types.contains("string")) { + if (isEnum) { + return createEnumType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), "string", module); + } else { + return success(BrAPIPrimitiveType.STRING); + } } - } - if (types.contains("number")) { - if (isEnum) { - return createEnumType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), "number", module); - } else { - return success(BrAPIPrimitiveType.NUMBER); + if (types.contains("integer")) { + if (isEnum) { + return createEnumType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), "integer", module); + } else { + return success(BrAPIPrimitiveType.INTEGER); + } } - } - if (types.contains("boolean")) { - if (isEnum) { - return createEnumType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), "boolean", module); - } else { - return success(BrAPIPrimitiveType.BOOLEAN); + if (types.contains("number")) { + if (isEnum) { + return createEnumType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), "number", module); + } else { + return success(BrAPIPrimitiveType.NUMBER); + } } - } - return Response.fail(Response.ErrorType.VALIDATION, path, String.format("Unknown type(s) '%s' in node '%s'", types, jsonNode)); + if (types.contains("boolean")) { + if (isEnum) { + return createEnumType(path, jsonNode, findNameFromTitle(path, jsonNode).getResultIfPresentOrElseResult(fallbackName), "boolean", module); + } else { + return success(BrAPIPrimitiveType.BOOLEAN); + } + } - })); + return Response.fail(Response.ErrorType.VALIDATION, path, String.format("Unknown type(s) '%s' in node '%s'", types, jsonNode)); - } + })); - private Response findNameFromTitle(Path path, JsonNode jsonNode) { - return findString(path, jsonNode, "title", false).mapResult(name -> name != null ? name.replace(" ", "") : null); - } + } - private Response createReferenceType(Path path, JsonNode jsonNode) { + private Response findNameFromTitle(Path path, JsonNode jsonNode) { + return findString(path, jsonNode, "title", false).mapResult(name -> name != null ? name.replace(" ", "") : null); + } - BrAPIReferenceType.BrAPIReferenceTypeBuilder builder = BrAPIReferenceType.builder(); + private Response createReferenceType(Path path, JsonNode jsonNode) { - return findString(path, jsonNode). - mapResultToResponse(ref -> parseRef(path, ref)). - onSuccessDoWithResult(builder::name). - map(() -> success(builder.build())); - } + BrAPIReferenceType.BrAPIReferenceTypeBuilder builder = BrAPIReferenceType.builder(); - private Response parseRef(Path path, String ref) { - Matcher matcher = REF_PATTERN.matcher(ref); + return findString(path, jsonNode). + mapResultToResponse(ref -> parseRef(path, ref)). + onSuccessDoWithResult(builder::name). + map(() -> success(builder.build())); + } + + private Response parseRef(Path path, String ref) { + Matcher matcher = REF_PATTERN.matcher(ref); - if (matcher.matches()) { + if (matcher.matches()) { - if (path != null && matcher.group(1) != null) { - Path refPath = path.getParent().resolve(matcher.group(1)); + if (path != null && matcher.group(1) != null) { + Path refPath = path.getParent().resolve(matcher.group(1)); - if (!refPath.toFile().isFile()) { - return fail(Response.ErrorType.VALIDATION, path, String.format("Can not find json file '%s' referenced in '%s'", refPath, path)); + if (!refPath.toFile().isFile()) { + return fail(Response.ErrorType.VALIDATION, path, String.format("Can not find json file '%s' referenced in '%s'", refPath, path)); + } + + return findSchema(refPath).mapResultToResponse(json -> findInJson(path, refPath, json, matcher.group(2))) ; + } else { + return success(matcher.group(2)); } + } else { + return fail(Response.ErrorType.VALIDATION, path, String.format("Ref '%s' does not match ref pattern '%s'", ref, REF_PATTERN)); } - return success(matcher.group(2)); - } else { - return fail(Response.ErrorType.VALIDATION, path, String.format("Ref '%s' does not match ref pattern '%s'", ref, REF_PATTERN)); } - } - private Response createArrayType(Path path, JsonNode jsonNode, String name, String module) { + private Response findInJson(Path path, Path refPath, JsonNode json, String schemaName) { + JsonNode defs = json.get("$defs"); - BrAPIArrayType.BrAPIArrayTypeBuilder builder = BrAPIArrayType.builder().name(name); + if (defs != null && defs.has(schemaName)) { + return success(schemaName) ; + } - return findChildNode(path, jsonNode, "items", true). - mapResultToResponse(childNode -> createType(path, childNode, toSingular(name), module). - onSuccessDoWithResult(builder::items)). - map(() -> success(builder.build())); - } + return fail(Response.ErrorType.VALIDATION, path, String.format("Can not find '%s' referenced in '%s'", schemaName, refPath)); + } - private Response createObjectType(Path path, JsonNode jsonNode, String name, String module) { - - BrAPIObjectType.BrAPIObjectTypeBuilder builder = BrAPIObjectType.builder() - .name(name) - .module(module) - .interfaces(new ArrayList<>()); - - findString(path, jsonNode, "description", false). - onSuccessDoWithResult(builder::description); - - List required = findStringList(path, jsonNode, "required", false).getResultIfPresentOrElseResult(Collections.emptyList()); - - List properties = new ArrayList<>(); - return Response.empty() - .mapOnCondition(jsonNode.has("additionalProperties"), () -> findChildNode(path, jsonNode, "additionalProperties", true). - mapResultToResponse(additionalPropertiesNode -> createProperty(path, additionalPropertiesNode, "additionalProperties", - module, required.contains("additionalProperties")).onSuccessDoWithResult(properties::add))) - .mapOnCondition(jsonNode.has("properties"), () -> findChildNode(path, jsonNode, "properties", true) - .mapResult(JsonNode::fields) - .mapResultToResponse(fields -> createProperties(path, fields, module, required)) - .onSuccessDoWithResult(properties::addAll)) - .onSuccessDo(() -> builder.properties(properties)) - .merge(validateRequiredProperties(required, properties, name)) - .mapOnCondition(jsonNode.has("brapi-metadata"), () -> findChildNode(path, jsonNode, "brapi-metadata", true) - .mapResultToResponse(metadata -> parseMetadata(path, metadata)).onSuccessDoWithResult(builder::metadata)) - .map(() -> success(builder.build())); - } + private Response createArrayType(Path path, JsonNode jsonNode, String name, String module) { - private Response> validateRequiredProperties(List requiredPropertyNames, List properties, String objectName) { - return requiredPropertyNames.stream().map(name -> validateRequiredProperty(name, properties, objectName)).collect(Response.toList()); - } + BrAPIArrayType.BrAPIArrayTypeBuilder builder = BrAPIArrayType.builder().name(name); - private Response validateRequiredProperty(String requiredPropertyName, List properties, String objectName) { - return properties.stream().filter(property -> property.getName().equals(requiredPropertyName)) - .findAny().map(Response::success) - .orElse(fail(Response.ErrorType.VALIDATION, - String.format("The required property '%s' is not found in the list of properties of '%s', expecting one of '%s'", requiredPropertyName, objectName, - properties.stream().map(BrAPIObjectProperty::getName).collect(Collectors.joining(", "))))); - } + return findChildNode(path, jsonNode, "items", true). + mapResultToResponse(childNode -> createType(path, childNode, toSingular(name), module). + onSuccessDoWithResult(builder::items)). + map(() -> success(builder.build())); + } - private Response> createProperties(Path path, Iterator> fields, String module, List required) { - return Streams.stream(fields).map(field -> createProperty(path, field.getValue(), field.getKey(), module, required.contains(field.getKey()))).collect(Response.toList()); - } + private Response createObjectType(Path path, JsonNode jsonNode, String name, String module) { + + BrAPIObjectType.BrAPIObjectTypeBuilder builder = BrAPIObjectType.builder() + .name(name) + .module(module) + .interfaces(new ArrayList<>()); + + findString(path, jsonNode, "description", false). + onSuccessDoWithResult(builder::description); + + List required = findStringList(path, jsonNode, "required", false).getResultIfPresentOrElseResult(Collections.emptyList()); + + List properties = new ArrayList<>(); + return Response.empty() + .mapOnCondition(jsonNode.has("additionalProperties"), () -> findChildNode(path, jsonNode, "additionalProperties", true). + mapResultToResponse(additionalPropertiesNode -> createProperty(path, additionalPropertiesNode, "additionalProperties", + module, required.contains("additionalProperties")).onSuccessDoWithResult(properties::add))) + .mapOnCondition(jsonNode.has("properties"), () -> findChildNode(path, jsonNode, "properties", true) + .mapResult(JsonNode::fields) + .mapResultToResponse(fields -> createProperties(path, fields, module, required)) + .onSuccessDoWithResult(properties::addAll)) + .onSuccessDo(() -> builder.properties(properties)) + .merge(validateRequiredProperties(required, properties, name)) + .mapOnCondition(jsonNode.has("brapi-metadata"), () -> findChildNode(path, jsonNode, "brapi-metadata", true) + .mapResultToResponse(metadata -> parseMetadata(path, metadata)).onSuccessDoWithResult(builder::metadata)) + .map(() -> success(builder.build())); + } - private Response createProperty(Path path, JsonNode jsonNode, String name, String module, boolean required) { + private Response> validateRequiredProperties(List requiredPropertyNames, List properties, String objectName) { + return requiredPropertyNames.stream().map(name -> validateRequiredProperty(name, properties, objectName)).collect(Response.toList()); + } - BrAPIObjectProperty.BrAPIObjectPropertyBuilder builder = BrAPIObjectProperty.builder(). - name(name). - required(required); + private Response validateRequiredProperty(String requiredPropertyName, List properties, String objectName) { + return properties.stream().filter(property -> property.getName().equals(requiredPropertyName)) + .findAny().map(Response::success) + .orElse(fail(Response.ErrorType.VALIDATION, + String.format("The required property '%s' is not found in the list of properties of '%s', expecting one of '%s'", requiredPropertyName, objectName, + properties.stream().map(BrAPIObjectProperty::getName).collect(Collectors.joining(", "))))); + } - findString(path, jsonNode, "description", false). - onSuccessDoWithResult(builder::description); + private Response> createProperties(Path path, Iterator> fields, String module, List required) { + return Streams.stream(fields).map(field -> createProperty(path, field.getValue(), field.getKey(), module, required.contains(field.getKey()))).collect(Response.toList()); + } - findString(path, jsonNode, "referencedAttribute", false). - onSuccessDoWithResult(builder::referencedAttribute); + private Response createProperty(Path path, JsonNode jsonNode, String name, String module, boolean required) { - return createType(path, jsonNode, StringUtils.toSentenceCase(name), module). - onSuccessDoWithResult(builder::type). - mapOnCondition(jsonNode.has("relationshipType"), () -> findString(path, jsonNode, "relationshipType", true). - mapResultToResponse(BrAPIRelationshipType::fromNameOrLabel). - onSuccessDoWithResult(builder::relationshipType)). - map(() -> success(builder.build())); - } + BrAPIObjectProperty.BrAPIObjectPropertyBuilder builder = BrAPIObjectProperty.builder(). + name(name). + required(required); - private Response parseMetadata(Path path, JsonNode metadata) { - BrAPIMetadata.BrAPIMetadataBuilder builder = BrAPIMetadata.builder(); - - return findBoolean(path, metadata, "primaryModel", false, false). - onSuccessDoWithResult(builder::primaryModel). - merge(findBoolean(path, metadata, "request", false, false)). - onSuccessDoWithResult(builder::request). - merge(findBoolean(path, metadata, "parameters", false, false)). - onSuccessDoWithResult(builder::parameters). - merge(findBoolean(path, metadata, "interface", false, false)). - onSuccessDoWithResult(builder::interfaceClass). - map(() -> success(builder.build())); - } + findString(path, jsonNode, "description", false). + onSuccessDoWithResult(builder::description); - private Response createAllOfType(Path path, JsonNode jsonNode, String name, String module) { + findString(path, jsonNode, "referencedAttribute", false). + onSuccessDoWithResult(builder::referencedAttribute); - BrAPIAllOfType.BrAPIAllOfTypeBuilder builder = BrAPIAllOfType.builder(). - name(name). - module(module); + return createType(path, jsonNode, StringUtils.toSentenceCase(name), module). + onSuccessDoWithResult(builder::type). + mapOnCondition(jsonNode.has("relationshipType"), () -> findString(path, jsonNode, "relationshipType", true). + mapResultToResponse(BrAPIRelationshipType::fromNameOrLabel). + onSuccessDoWithResult(builder::relationshipType)). + map(() -> success(builder.build())); + } - findString(path, jsonNode, "description", false). - onSuccessDoWithResult(builder::description); + private Response parseMetadata(Path path, JsonNode metadata) { + BrAPIMetadata.BrAPIMetadataBuilder builder = BrAPIMetadata.builder(); + + return findBoolean(path, metadata, "primaryModel", false, false). + onSuccessDoWithResult(builder::primaryModel). + merge(findBoolean(path, metadata, "request", false, false)). + onSuccessDoWithResult(builder::request). + merge(findBoolean(path, metadata, "parameters", false, false)). + onSuccessDoWithResult(builder::parameters). + merge(findBoolean(path, metadata, "interface", false, false)). + onSuccessDoWithResult(builder::interfaceClass). + map(() -> success(builder.build())); + } - return findChildNode(path, jsonNode, "allOf", true). - mapResult(node -> childNodes(path, node)). - mapResultToResponse(childNodes -> childNodes.mapResultToResponse(nodes -> createAllTypes(path, nodes, name, module))). - onSuccessDoWithResult(builder::allTypes). - mapOnCondition(jsonNode.has("brapi-metadata"), () -> findChildNode(path, jsonNode, "brapi-metadata", true). - mapResultToResponse(metadata -> parseMetadata(path, metadata)).onSuccessDoWithResult(builder::metadata)). - map(() -> success(builder.build())); - } + private Response createAllOfType(Path path, JsonNode jsonNode, String name, String module) { - private Response> createAllTypes(Path path, List jsonNodes, String fallbackNamePrefix, String module) { + BrAPIAllOfType.BrAPIAllOfTypeBuilder builder = BrAPIAllOfType.builder(). + name(name). + module(module); - AtomicInteger i = new AtomicInteger(); + findString(path, jsonNode, "description", false). + onSuccessDoWithResult(builder::description); - return jsonNodes.stream().map(jsonNode -> createType(path, jsonNode, String.format("%s%d", fallbackNamePrefix, i.incrementAndGet()), module)).collect(Response.toList()); - } + return findChildNode(path, jsonNode, "allOf", true). + mapResult(node -> childNodes(path, node)). + mapResultToResponse(childNodes -> childNodes.mapResultToResponse(nodes -> createAllTypes(path, nodes, name, module))). + onSuccessDoWithResult(builder::allTypes). + mapOnCondition(jsonNode.has("brapi-metadata"), () -> findChildNode(path, jsonNode, "brapi-metadata", true). + mapResultToResponse(metadata -> parseMetadata(path, metadata)).onSuccessDoWithResult(builder::metadata)). + map(() -> success(builder.build())); + } - private Response createOneOfType(Path path, JsonNode jsonNode, String name, String module) { + private Response> createAllTypes(Path path, List jsonNodes, String fallbackNamePrefix, String module) { - BrAPIOneOfType.BrAPIOneOfTypeBuilder builder = BrAPIOneOfType.builder(). - name(name). - module(module); + AtomicInteger i = new AtomicInteger(); - findString(path, jsonNode, "description", false). - onSuccessDoWithResult(builder::description); + return jsonNodes.stream().map(jsonNode -> createType(path, jsonNode, String.format("%s%d", fallbackNamePrefix, i.incrementAndGet()), module)).collect(Response.toList()); + } - return findChildNode(path, jsonNode, "oneOf", true). - mapResult(node -> childNodes(path, node)). - mapResultToResponse(childNodes -> childNodes.mapResultToResponse(nodes -> createPossibleTypes(path, nodes, name, module))). - onSuccessDoWithResult(builder::possibleTypes). - mapOnCondition(jsonNode.has("brapi-metadata"), () -> findChildNode(path, jsonNode, "brapi-metadata", true). - mapResultToResponse(metadata -> parseMetadata(path, metadata)).onSuccessDoWithResult(builder::metadata)). - map(() -> success(builder.build())); - } + private Response createOneOfType(Path path, JsonNode jsonNode, String name, String module) { - private Response> createPossibleTypes(Path path, List jsonNodes, String fallbackNamePrefix, String module) { + BrAPIOneOfType.BrAPIOneOfTypeBuilder builder = BrAPIOneOfType.builder(). + name(name). + module(module); - AtomicInteger i = new AtomicInteger(); + findString(path, jsonNode, "description", false). + onSuccessDoWithResult(builder::description); - return jsonNodes.stream().map(jsonNode -> createType(path, jsonNode, String.format("%s%d", fallbackNamePrefix, i.incrementAndGet()), module)).collect(Response.toList()); - } + return findChildNode(path, jsonNode, "oneOf", true). + mapResult(node -> childNodes(path, node)). + mapResultToResponse(childNodes -> childNodes.mapResultToResponse(nodes -> createPossibleTypes(path, nodes, name, module))). + onSuccessDoWithResult(builder::possibleTypes). + mapOnCondition(jsonNode.has("brapi-metadata"), () -> findChildNode(path, jsonNode, "brapi-metadata", true). + mapResultToResponse(metadata -> parseMetadata(path, metadata)).onSuccessDoWithResult(builder::metadata)). + map(() -> success(builder.build())); + } - private Response createEnumType(Path path, JsonNode jsonNode, String name, String type, String module) { + private Response> createPossibleTypes(Path path, List jsonNodes, String fallbackNamePrefix, String module) { - BrAPIEnumType.BrAPIEnumTypeBuilder builder = BrAPIEnumType.builder(). - name(name). - type(type). - module(module); + AtomicInteger i = new AtomicInteger(); - findString(path, jsonNode, "description", false). - onSuccessDoWithResult(builder::description); + return jsonNodes.stream().map(jsonNode -> createType(path, jsonNode, String.format("%s%d", fallbackNamePrefix, i.incrementAndGet()), module)).collect(Response.toList()); + } - return findStringList(path, jsonNode, "enum", true). - mapResultToResponse(strings -> createEnumValues(path, strings, type)). - onSuccessDoWithResult(builder::values). - mapOnCondition(jsonNode.has("brapi-metadata"), () -> findChildNode(path, jsonNode, "brapi-metadata", true). - mapResultToResponse(metadata -> parseMetadata(path, metadata)).onSuccessDoWithResult(builder::metadata)). - map(() -> success(builder.build())); - } + private Response createEnumType(Path path, JsonNode jsonNode, String name, String type, String module) { - private Response> createEnumValues(Path path, List strings, String type) { - return strings.stream().map(string -> createEnumValue(path, string, type)).collect(Response.toList()); - } + BrAPIEnumType.BrAPIEnumTypeBuilder builder = BrAPIEnumType.builder(). + name(name). + type(type). + module(module); - private Response createEnumValue(Path path, String string, String type) { - BrAPIEnumValue.BrAPIEnumValueBuilder builder = BrAPIEnumValue.builder(). - name(string); + findString(path, jsonNode, "description", false). + onSuccessDoWithResult(builder::description); - try { - return switch (type) { - case "string" -> success(builder.value(string).build()); - case "integer" -> success(builder.value(Integer.valueOf(string)).build()); - case "number" -> success(builder.value(Float.valueOf(string)).build()); - case "boolean" -> success(builder.value(Boolean.valueOf(string)).build()); - default -> - Response.fail(Response.ErrorType.VALIDATION, path, String.format("Unknown primitive type '%s'", type)); - }; - } catch (NumberFormatException e) { - return Response.fail(Response.ErrorType.VALIDATION, path, String.format("Can not convert '%s' to type '%s'", string, type)); + return findStringList(path, jsonNode, "enum", true). + mapResultToResponse(strings -> createEnumValues(path, strings, type)). + onSuccessDoWithResult(builder::values). + mapOnCondition(jsonNode.has("brapi-metadata"), () -> findChildNode(path, jsonNode, "brapi-metadata", true). + mapResultToResponse(metadata -> parseMetadata(path, metadata)).onSuccessDoWithResult(builder::metadata)). + map(() -> success(builder.build())); } - } - private Response findString(Path path, JsonNode parentNode, String fieldName, boolean required) { - return findChildNode(path, parentNode, fieldName, required).mapResultToResponse(jsonNode -> { - if (jsonNode instanceof TextNode textNode) { - return success(textNode.asText()); - } - return required ? - fail(Response.ErrorType.VALIDATION, path, - String.format("Child node type '%s' was not TextNode with field name '%s' for parent node '%s'", jsonNode.getClass().getName(), parentNode, fieldName)) : - Response.empty(); - }); - } + private Response> createEnumValues(Path path, List strings, String type) { + return strings.stream().map(string -> createEnumValue(path, string, type)).collect(Response.toList()); + } - private Response findBoolean(Path path, JsonNode parentNode, String fieldName, boolean required, boolean defaultValue) { - return findChildNode(path, parentNode, fieldName, required).mapResultToResponse(jsonNode -> { - if (jsonNode instanceof BooleanNode booleanNode) { - return success(booleanNode.asBoolean()); + private Response createEnumValue(Path path, String string, String type) { + BrAPIEnumValue.BrAPIEnumValueBuilder builder = BrAPIEnumValue.builder(). + name(string); + + try { + return switch (type) { + case "string" -> success(builder.value(string).build()); + case "integer" -> success(builder.value(Integer.valueOf(string)).build()); + case "number" -> success(builder.value(Float.valueOf(string)).build()); + case "boolean" -> success(builder.value(Boolean.valueOf(string)).build()); + default -> + Response.fail(Response.ErrorType.VALIDATION, path, String.format("Unknown primitive type '%s'", type)); + }; + } catch (NumberFormatException e) { + return Response.fail(Response.ErrorType.VALIDATION, path, String.format("Can not convert '%s' to type '%s'", string, type)); } - return required ? - fail(Response.ErrorType.VALIDATION, path, - String.format("Child node type '%s' was not BooleanNode with field name '%s' for parent node '%s'", jsonNode.getClass().getName(), parentNode, fieldName)) : - Response.success(defaultValue); - }); - } + } - private Response> findStringList(Path path, JsonNode parentNode, String fieldName, boolean required) { + private Response findString(Path path, JsonNode parentNode, String fieldName, boolean required) { + return findChildNode(path, parentNode, fieldName, required).mapResultToResponse(jsonNode -> { + if (jsonNode instanceof TextNode textNode) { + return success(textNode.asText()); + } + return required ? + fail(Response.ErrorType.VALIDATION, path, + String.format("Child node type '%s' was not TextNode with field name '%s' for parent node '%s'", jsonNode.getClass().getName(), parentNode, fieldName)) : + Response.empty(); + }); + } - return findChildNode(path, parentNode, fieldName, required).mapResultToResponse(jsonNode -> { - if (jsonNode instanceof ArrayNode arrayNode) { - return StreamSupport.stream(arrayNode.spliterator(), false). - filter(childNode -> !(childNode instanceof NullNode)). - map(node -> findString(path, node)).filter(stringResponse -> stringResponse.getResult() != null). - collect(Response.toList()); - } + private Response findBoolean(Path path, JsonNode parentNode, String fieldName, boolean required, boolean defaultValue) { + return findChildNode(path, parentNode, fieldName, required).mapResultToResponse(jsonNode -> { + if (jsonNode instanceof BooleanNode booleanNode) { + return success(booleanNode.asBoolean()); + } + return required ? + fail(Response.ErrorType.VALIDATION, path, + String.format("Child node type '%s' was not BooleanNode with field name '%s' for parent node '%s'", jsonNode.getClass().getName(), parentNode, fieldName)) : + Response.success(defaultValue); + }); + } - if (jsonNode instanceof TextNode textNode) { - return success(singletonList(textNode.asText())); - } + private Response> findStringList(Path path, JsonNode parentNode, String fieldName, boolean required) { - return required ? - fail(Response.ErrorType.VALIDATION, path, - String.format("Unknown child node type '%s' with field name '%s' for parent node '%s'", jsonNode.getClass().getName(), parentNode, fieldName)) : - Response.empty(); - }); - } + return findChildNode(path, parentNode, fieldName, required).mapResultToResponse(jsonNode -> { + if (jsonNode instanceof ArrayNode arrayNode) { + return StreamSupport.stream(arrayNode.spliterator(), false). + filter(childNode -> !(childNode instanceof NullNode)). + map(node -> findString(path, node)).filter(stringResponse -> stringResponse.getResult() != null). + collect(Response.toList()); + } - private Response findString(Path path, JsonNode jsonNode) { - if (jsonNode instanceof TextNode textNode) { - return success(textNode.asText()); - } + if (jsonNode instanceof TextNode textNode) { + return success(singletonList(textNode.asText())); + } - if (jsonNode != null) { - return fail(Response.ErrorType.VALIDATION, path, String.format("Node type '%s' is not string", jsonNode.getClass().getName())); - } else { - return Response.empty(); + return required ? + fail(Response.ErrorType.VALIDATION, path, + String.format("Unknown child node type '%s' with field name '%s' for parent node '%s'", jsonNode.getClass().getName(), parentNode, fieldName)) : + Response.empty(); + }); } - } - private Response findChildNode(Path path, JsonNode parentNode, String fieldName, boolean required) { - JsonNode jsonNode = parentNode.get(fieldName); + private Response findString(Path path, JsonNode jsonNode) { + if (jsonNode instanceof TextNode textNode) { + return success(textNode.asText()); + } - if (jsonNode != null) { - return success(jsonNode); + if (jsonNode != null) { + return fail(Response.ErrorType.VALIDATION, path, String.format("Node type '%s' is not string", jsonNode.getClass().getName())); + } else { + return Response.empty(); + } } - if (required) { - return fail(Response.ErrorType.VALIDATION, path, String.format("Parent node '%s' does not have child node with field name '%s'", parentNode, fieldName)); - } else { - return Response.empty(); - } - } + private Response findChildNode(Path path, JsonNode parentNode, String fieldName, boolean required) { + JsonNode jsonNode = parentNode.get(fieldName); - private Response> childNodes(Path path, JsonNode parentNode) { + if (jsonNode != null) { + return success(jsonNode); + } - if (parentNode instanceof ArrayNode arrayNode) { - return success(StreamSupport.stream(arrayNode.spliterator(), false).toList()); + if (required) { + return fail(Response.ErrorType.VALIDATION, path, String.format("Parent node '%s' does not have child node with field name '%s'", parentNode, fieldName)); + } else { + return Response.empty(); + } } - return fail(Response.ErrorType.VALIDATION, path, - String.format("Parent Node type '%s' is not ArrayNode", parentNode.getClass().getName())); - } + private Response> childNodes(Path path, JsonNode parentNode) { + + if (parentNode instanceof ArrayNode arrayNode) { + return success(StreamSupport.stream(arrayNode.spliterator(), false).toList()); + } + return fail(Response.ErrorType.VALIDATION, path, + String.format("Parent Node type '%s' is not ArrayNode", parentNode.getClass().getName())); + } + } }