-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create json-schema Springwolf add-on (#447)
* feat(json-schema): create json-schema add-on * feat(json-schema): add json-schema to kafka and sns example * feat(json-schema): update README.md * test(json-schema): add logging docker container logging to ApiIntegrationWithDockerIntegrationTest * test(json-schema): enable gradle test logging * feat(core): customizer logging Co-authored-by: sam0r040 <[email protected]>
- Loading branch information
Showing
22 changed files
with
1,074 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# Springwolf Json Schema Add-on | ||
|
||
### Table Of Contents | ||
|
||
- [About](#about) | ||
- [Usage](#usage) | ||
- [Dependencies](#dependencies) | ||
- [Result](#result) | ||
|
||
### About | ||
|
||
This module generates the [json-schema](https://json-schema.org) for all Springwolf detected schemas (payloads, headers, etc.). | ||
|
||
No configuration needed, only add the dependency. | ||
|
||
As Springwolf uses `swagger-parser` to create an `OpenApi` schema, this module maps the `OpenApi` schema to `json-schema`. | ||
|
||
### Usage | ||
|
||
Add the following dependency: | ||
|
||
#### Dependencies | ||
|
||
```groovy | ||
dependencies { | ||
runtimeOnly 'io.github.springwolf:springwolf-json-schema:<springwolf-version>' | ||
} | ||
``` | ||
|
||
#### Result | ||
|
||
The `x-json-schema` field is added for each `Schema`. | ||
|
||
Example: | ||
|
||
```json | ||
{ | ||
"MonetaryAmount-Header": { | ||
"...": "", | ||
"x-json-schema": { | ||
"$schema": "https://json-schema.org/draft-04/schema#", | ||
"name": "MonetaryAmount-Header", | ||
"properties": { | ||
"__TypeId__": { | ||
"description": "Spring Type Id Header", | ||
"enum": [ | ||
"javax.money.MonetaryAmount" | ||
], | ||
"type": "string" | ||
} | ||
}, | ||
"type": "object" | ||
} | ||
} | ||
} | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
plugins { | ||
id 'java-library' | ||
|
||
id 'org.springframework.boot' | ||
id 'io.spring.dependency-management' | ||
id 'ca.cutterslade.analyze' | ||
} | ||
|
||
dependencies { | ||
api project(":springwolf-core") | ||
|
||
implementation "com.fasterxml.jackson.core:jackson-core:${jacksonVersion}" | ||
implementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}" | ||
|
||
testImplementation "io.swagger.core.v3:swagger-core-jakarta:${swaggerVersion}" | ||
implementation "io.swagger.core.v3:swagger-models-jakarta:${swaggerVersion}" | ||
|
||
implementation "org.apache.commons:commons-lang3:${commonsLang3Version}" | ||
|
||
implementation "org.slf4j:slf4j-api:${slf4jApiVersion}" | ||
|
||
implementation "org.springframework:spring-context" | ||
|
||
annotationProcessor "org.projectlombok:lombok:${lombokVersion}" | ||
|
||
testImplementation "org.mockito:mockito-core:${mockitoCoreVersion}" | ||
testImplementation "org.assertj:assertj-core:${assertjCoreVersion}" | ||
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}" | ||
testImplementation "org.junit.jupiter:junit-jupiter-params:${junitJupiterVersion}" | ||
testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}" | ||
|
||
testImplementation "com.networknt:json-schema-validator:${jsonSchemaValidator}" | ||
} | ||
|
||
jar { | ||
enabled = true | ||
archiveClassifier = '' | ||
} | ||
bootJar.enabled = false | ||
|
||
java { | ||
withJavadocJar() | ||
withSourcesJar() | ||
} | ||
|
||
publishing { | ||
publications { | ||
mavenJava(MavenPublication) { | ||
pom { | ||
name = 'springwolf-json-schema' | ||
description = 'Extends Springwolf schemas with json-schema' | ||
} | ||
} | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
...rc/main/java/io/github/stavshamir/springwolf/addons/json_schema/JsonSchemaCustomizer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
package io.github.stavshamir.springwolf.addons.json_schema; | ||
|
||
import io.github.stavshamir.springwolf.asyncapi.AsyncApiCustomizer; | ||
import io.github.stavshamir.springwolf.asyncapi.types.AsyncAPI; | ||
import io.swagger.v3.oas.models.media.Schema; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
@RequiredArgsConstructor | ||
@Slf4j | ||
public class JsonSchemaCustomizer implements AsyncApiCustomizer { | ||
private static final String EXTENSION_JSON_SCHEMA = "x-json-schema"; | ||
|
||
private final JsonSchemaGenerator jsonSchemaGenerator; | ||
|
||
@Override | ||
public void customize(AsyncAPI asyncAPI) { | ||
Map<String, Schema> schemas = asyncAPI.getComponents().getSchemas(); | ||
for (Map.Entry<String, Schema> entry : schemas.entrySet()) { | ||
Schema schema = entry.getValue(); | ||
if (schema.getExtensions() == null) { | ||
schema.setExtensions(new HashMap<>()); | ||
} | ||
|
||
try { | ||
log.debug("Generate json-schema for %s".formatted(entry.getKey())); | ||
|
||
Object jsonSchema = jsonSchemaGenerator.fromSchema(schema, schemas); | ||
schema.getExtensions().putIfAbsent(EXTENSION_JSON_SCHEMA, jsonSchema); | ||
} catch (Exception ex) { | ||
log.warn("Unable to create json-schema for %s".formatted(schema.getName()), ex); | ||
} | ||
} | ||
} | ||
} |
160 changes: 160 additions & 0 deletions
160
...src/main/java/io/github/stavshamir/springwolf/addons/json_schema/JsonSchemaGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
package io.github.stavshamir.springwolf.addons.json_schema; | ||
|
||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
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.ObjectNode; | ||
import io.swagger.v3.oas.models.media.Schema; | ||
import lombok.RequiredArgsConstructor; | ||
import org.apache.commons.lang3.StringUtils; | ||
|
||
import java.util.HashSet; | ||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
@RequiredArgsConstructor | ||
public class JsonSchemaGenerator { | ||
private final ObjectMapper objectMapper; | ||
|
||
public Object fromSchema(Schema<?> schema, Map<String, Schema> definitions) throws JsonProcessingException { | ||
ObjectNode node = fromSchemaInternal(schema, definitions, new HashSet<>()); | ||
node.put("$schema", "https://json-schema.org/draft-04/schema#"); | ||
|
||
return objectMapper.readValue(node.toString(), Object.class); | ||
} | ||
|
||
private ObjectNode fromSchemaInternal(Schema<?> schema, Map<String, Schema> definitions, Set<Schema> visited) { | ||
if (schema != null && !visited.contains(schema)) { | ||
visited.add(schema); | ||
|
||
return mapToJsonSchema(schema, definitions, visited); | ||
} | ||
return objectMapper.createObjectNode(); | ||
} | ||
|
||
private ObjectNode mapToJsonSchema(Schema<?> schema, Map<String, Schema> definitions, Set<Schema> visited) { | ||
ObjectNode node = objectMapper.createObjectNode(); | ||
|
||
if (schema.getAnyOf() != null) { | ||
ArrayNode arrayNode = objectMapper.createArrayNode(); | ||
for (Schema ofSchema : schema.getAnyOf()) { | ||
arrayNode.add(fromSchemaInternal(ofSchema, definitions, visited)); | ||
} | ||
node.put("anyOf", arrayNode); | ||
} | ||
if (schema.getAllOf() != null) { | ||
ArrayNode arrayNode = objectMapper.createArrayNode(); | ||
for (Schema ofSchema : schema.getAllOf()) { | ||
arrayNode.add(fromSchemaInternal(ofSchema, definitions, visited)); | ||
} | ||
node.put("allOf", arrayNode); | ||
} | ||
if (schema.getConst() != null) { | ||
node.put("const", schema.getConst().toString()); | ||
} | ||
if (schema.getDescription() != null) { | ||
node.put("description", schema.getDescription()); | ||
} | ||
if (schema.getEnum() != null) { | ||
ArrayNode arrayNode = objectMapper.createArrayNode(); | ||
for (Object property : schema.getEnum()) { | ||
arrayNode.add(property.toString()); | ||
} | ||
if (schema.getNullable() != null && schema.getNullable()) { | ||
arrayNode.add("null"); | ||
} | ||
node.set("enum", arrayNode); | ||
} | ||
if (schema.getFormat() != null) { | ||
node.put("format", schema.getFormat()); | ||
} | ||
if (schema.getItems() != null) { | ||
node.set("items", fromSchemaInternal(schema.getItems(), definitions, visited)); | ||
} | ||
if (schema.getMaximum() != null) { | ||
node.put("maximum", schema.getMaximum()); | ||
} | ||
if (schema.getMinimum() != null) { | ||
node.put("minimum", schema.getMinimum()); | ||
} | ||
if (schema.getMaxItems() != null) { | ||
node.put("maxItems", schema.getMaxItems()); | ||
} | ||
if (schema.getMinItems() != null) { | ||
node.put("minItems", schema.getMinItems()); | ||
} | ||
if (schema.getMaxLength() != null) { | ||
node.put("maxLength", schema.getMaxLength()); | ||
} | ||
if (schema.getMinLength() != null) { | ||
node.put("minLength", schema.getMinLength()); | ||
} | ||
if (schema.getMultipleOf() != null) { | ||
node.put("multipleOf", schema.getMultipleOf()); | ||
} | ||
if (schema.getName() != null) { | ||
node.put("name", schema.getName()); | ||
} | ||
if (schema.getNot() != null) { | ||
node.put("not", fromSchemaInternal(schema.getNot(), definitions, visited)); | ||
} | ||
if (schema.getOneOf() != null) { | ||
ArrayNode arrayNode = objectMapper.createArrayNode(); | ||
for (Schema ofSchema : schema.getOneOf()) { | ||
arrayNode.add(fromSchemaInternal(ofSchema, definitions, visited)); | ||
} | ||
node.put("oneOf", arrayNode); | ||
} | ||
if (schema.getPattern() != null) { | ||
node.put("pattern", schema.getPattern()); | ||
} | ||
if (schema.getProperties() != null && !schema.getProperties().isEmpty()) { | ||
node.set("properties", buildProperties(schema, definitions, visited)); | ||
} | ||
if (schema.getRequired() != null) { | ||
ArrayNode arrayNode = objectMapper.createArrayNode(); | ||
for (String property : schema.getRequired()) { | ||
arrayNode.add(property); | ||
} | ||
node.set("required", arrayNode); | ||
} | ||
if (schema.getTitle() != null) { | ||
node.put("title", schema.getTitle()); | ||
} | ||
if (schema.getType() != null) { | ||
if (schema.getNullable() != null && schema.getNullable()) { | ||
ArrayNode arrayNode = objectMapper.createArrayNode(); | ||
arrayNode.add(schema.getType()); | ||
arrayNode.add("null"); | ||
node.set("type", arrayNode); | ||
} else { | ||
node.put("type", schema.getType()); | ||
} | ||
} | ||
if (schema.getUniqueItems() != null) { | ||
node.put("uniqueItems", schema.getUniqueItems()); | ||
} | ||
|
||
return node; | ||
} | ||
|
||
private JsonNode buildProperties(Schema<?> schema, Map<String, Schema> definitions, Set<Schema> visited) { | ||
ObjectNode node = objectMapper.createObjectNode(); | ||
|
||
for (Map.Entry<String, Schema> propertySchemaSet : | ||
schema.getProperties().entrySet()) { | ||
Schema propertySchema = propertySchemaSet.getValue(); | ||
|
||
if (propertySchema != null && propertySchema.get$ref() != null) { | ||
String schemaName = StringUtils.substringAfterLast(propertySchema.get$ref(), "/"); | ||
propertySchema = definitions.get(schemaName); | ||
} | ||
|
||
node.set(propertySchemaSet.getKey(), fromSchemaInternal(propertySchema, definitions, visited)); | ||
} | ||
|
||
return node; | ||
} | ||
} |
Oops, something went wrong.