Skip to content

Commit

Permalink
Refactor ExampleJsonGenerator and Support XML Examples (#581)
Browse files Browse the repository at this point in the history
* test(examples): trim all expect asyncapi specs before comparison

* feat(core): pass content type to example generator

* refactor(core): rename ExampleGenerator to SchemaWalker

* WIP First xml exmaple working

* WIP XML

* WIP XML

* All tests green

* refactor and fix map schema

* chore(core): Fixes after rebase, fix special cases

* chore(core): use in kafka example

* feat(core): generated examples are either of type JsonNode for Json oder String for XML examples, ExampleJsonValueGenerator caches already generated examples and reuses them when needed

* chore(core): cleanup ToDos, fix tests

* chore(core): adapt new implementation for generating examples to changes implementation for allOf after rebase

* refactor(core): rename Default*SchemasServiceTests to Default*ComponentsServiceTest, extracted tests that do not cover serialization to DefaultComponentsServiceTest from Default*ComponentsServiceTest to remove duplication

* refactor(core): removed combineObjectExample() from ExampleValueGenerator because it duplicates createObjectExample()

* refactor(core): properly specify version of jakarta.validation-api dependency

* chore(core): Removed TODO to throw exception for some schema types

* refactor(core): parse example string to xml node in ExampleXmlValueGenerator

* refactor(core): extract ExampleXmlValueSerializer to enable custom serialization configurations for xml examples

* fix(examples): fix asyncapi.json for the amqp-example

* fix(examples): fix asyncapi.json for the sqs-example

* fix(examples): fix asyncapi.json for the jms-example

* fix(examples): fix asyncapi.json for the kafka-example

* refactor(core): moves methods and add class comments

* chore(core): add org.slf4j:slf4j-simple to version management

* feat(core): Add support for examples in YAML format

* chore(core): cleanup test setup in DefaultJsonComponentsServiceTest and DefaultXmlComponentsServiceTest

* chore(kafka-example): add example for schema with with examples in yaml format

* refactor(core): unify method names of ExampleValueGenerator interface

* refactor(core): remove debug log from ExampleXmlValueGenerator

* chore(ui): add temporary fix for spec validation of other content types than json

* chore(core): unify naming of internal methods of DefaultSchemaWalker.java, add sorting for all properties in examples

Co-authored-by: Timon Back <[email protected]>

* chore(ui): document github issue about non json payload in examples

Co-authored-by: Timon Back <[email protected]>

* refactor(core): minimal serialization configuration for DefaultExampleXmlValueSerializer

Co-authored-by: Timon Back <[email protected]>

* chore(core): cleanup deprecations

Co-authored-by: Timon Back <[email protected]>

* chore(kafka-example): fix operation description

Co-authored-by: Timon Back <[email protected]>

---------

Co-authored-by: Timon Back <[email protected]>
  • Loading branch information
sam0r040 and timonback authored Feb 23, 2024
1 parent 589e6ab commit 4953771
Show file tree
Hide file tree
Showing 84 changed files with 4,363 additions and 822 deletions.
3 changes: 3 additions & 0 deletions dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ ext {

jacksonVersion = '2.16.1'
jacksonDatatypeProtobufVersion = '0.9.14'

jakartaAnnotationApiVersion = '2.1.1'
jakartaValidationApiVersion = '3.0.2'

jsonSchemaValidator = '1.3.2'

Expand All @@ -64,6 +66,7 @@ ext {
lombokVersion = '1.18.30'

slf4jApiVersion = '2.0.12'
slf4jSimpleVersion = '2.0.12'

swaggerVersion = '2.2.20'

Expand Down
4 changes: 4 additions & 0 deletions springwolf-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies {

implementation "io.swagger.core.v3:swagger-models-jakarta:${swaggerVersion}"
implementation "jakarta.annotation:jakarta.annotation-api:${jakartaAnnotationApiVersion}"
implementation "jakarta.validation:jakarta.validation-api:${jakartaValidationApiVersion}"

implementation "org.apache.commons:commons-lang3:${commonsLang3Version}"

Expand All @@ -48,8 +49,11 @@ dependencies {
testImplementation "net.javacrumbs.json-unit:json-unit-assertj:${jsonUnitAssertJVersion}"
testImplementation "org.awaitility:awaitility:${awaitilityVersion}"
testImplementation "org.mockito:mockito-core:${mockitoCoreVersion}"
testImplementation "org.mockito:mockito-junit-jupiter:${mockitoJunitJupiterVersion}"
testImplementation "org.springframework.boot:spring-boot-test"
testImplementation "org.springframework:spring-test"
permitTestUnusedDeclared "org.slf4j:slf4j-simple:${slf4jSimpleVersion}"


testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,16 @@
import io.github.stavshamir.springwolf.schemas.ComponentsService;
import io.github.stavshamir.springwolf.schemas.DefaultComponentsService;
import io.github.stavshamir.springwolf.schemas.SwaggerSchemaUtil;
import io.github.stavshamir.springwolf.schemas.example.ExampleGenerator;
import io.github.stavshamir.springwolf.schemas.example.ExampleJsonGenerator;
import io.github.stavshamir.springwolf.schemas.example.DefaultExampleXmlValueSerializer;
import io.github.stavshamir.springwolf.schemas.example.DefaultExampleYamlValueSerializer;
import io.github.stavshamir.springwolf.schemas.example.DefaultSchemaWalker;
import io.github.stavshamir.springwolf.schemas.example.ExampleJsonValueGenerator;
import io.github.stavshamir.springwolf.schemas.example.ExampleXmlValueGenerator;
import io.github.stavshamir.springwolf.schemas.example.ExampleXmlValueSerializer;
import io.github.stavshamir.springwolf.schemas.example.ExampleYamlValueGenerator;
import io.github.stavshamir.springwolf.schemas.example.ExampleYamlValueSerializer;
import io.github.stavshamir.springwolf.schemas.example.SchemaWalker;
import io.github.stavshamir.springwolf.schemas.example.SchemaWalkerProvider;
import io.github.stavshamir.springwolf.schemas.postprocessor.AvroSchemaPostProcessor;
import io.github.stavshamir.springwolf.schemas.postprocessor.ExampleGeneratorPostProcessor;
import io.github.stavshamir.springwolf.schemas.postprocessor.SchemasPostProcessor;
Expand Down Expand Up @@ -113,14 +121,42 @@ public AvroSchemaPostProcessor avroSchemaPostProcessor() {
@Bean
@ConditionalOnMissingBean
@Order(10)
public ExampleGeneratorPostProcessor exampleGeneratorPostProcessor(ExampleGenerator exampleGenerator) {
return new ExampleGeneratorPostProcessor(exampleGenerator);
public ExampleGeneratorPostProcessor exampleGeneratorPostProcessor(SchemaWalkerProvider schemaWalkerProvider) {
return new ExampleGeneratorPostProcessor(schemaWalkerProvider);
}

@Bean
@ConditionalOnMissingBean
public ExampleGenerator exampleGenerator() {
return new ExampleJsonGenerator();
public SchemaWalkerProvider exampleGeneratorProvider(List<SchemaWalker> schemaWalkers) {
return new SchemaWalkerProvider(schemaWalkers);
}

@Bean
public SchemaWalker jsonSchemaWalker() {
return new DefaultSchemaWalker<>(new ExampleJsonValueGenerator());
}

@Bean
@ConditionalOnMissingBean
public ExampleXmlValueSerializer defaultExampleXmlValueSerializer() {
return new DefaultExampleXmlValueSerializer();
}

@Bean
public SchemaWalker xmlSchemaWalker(ExampleXmlValueSerializer exampleXmlValueSerializer) {
return new DefaultSchemaWalker<>(new ExampleXmlValueGenerator(exampleXmlValueSerializer));
}

@Bean
@ConditionalOnMissingBean
public ExampleYamlValueSerializer defaultExampleYamlValueSerializer() {
return new DefaultExampleYamlValueSerializer();
}

@Bean
public SchemaWalker yamlSchemaWalker(ExampleYamlValueSerializer exampleYamlValueSerializer) {
return new DefaultSchemaWalker<>(
new ExampleYamlValueGenerator(new ExampleJsonValueGenerator(), exampleYamlValueSerializer));
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ protected MessageObject buildMessage(AsyncOperation operationData, Method method
? operationData.payloadType()
: payloadClassExtractor.extractFrom(method);

String modelName = this.componentsService.registerSchema(payloadType);
String modelName = this.componentsService.registerSchema(
payloadType, operationData.message().contentType());
AsyncHeaders asyncHeaders = AsyncAnnotationScannerUtil.getAsyncHeaders(operationData, resolver);
String headerModelName = this.componentsService.registerSchema(asyncHeaders);
var headers = MessageHeaders.of(MessageReference.toSchema(headerModelName));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public static AsyncHeaders getAsyncHeaders(AsyncOperation op, StringValueResolve
// SchemaObject.builder()
// .description(getDescription(headers, resolver))
// .enumValues(values)
// .examples(exampleValue != null ? List.of(exampleValue) : null)
// .examples(value != null ? List.of(value) : null)
// .build());
});

Expand Down Expand Up @@ -119,6 +119,10 @@ public static void processAsyncMessageAnnotation(
if (StringUtils.hasText(annotationTitle)) {
messageBuilder.title(annotationTitle);
}

if (StringUtils.hasText(asyncMessage.contentType())) {
messageBuilder.contentType(asyncMessage.contentType());
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
/**
* Mapped to {@link MessageObject#getContentType()}
*/
String contentType() default "application/json";
String contentType() default "";

/**
* Mapped to {@link MessageObject#getTitle()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,5 @@ public class SpringwolfConfigConstants {
public static final String SPRINGWOLF_SCANNER_PRODUCER_DATA_ENABLED =
SPRINGWOLF_SCANNER_PREFIX + ".producer-data" + ENABLED;

public static final String SPRINGWOLF_SCHEMA_EXAMPLE_GENERATOR = SPRINGWOLF_CONFIG_PREFIX + ".example-generator";

private SpringwolfConfigConstants() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ public enum InitMode {
@Nullable
private Endpoint endpoint;

@Nullable
private ConfigDocket docket;
private ConfigDocket docket = new ConfigDocket();

@Nullable
private Scanner scanner;
Expand All @@ -72,6 +71,8 @@ public enum InitMode {
@Setter
public static class ConfigDocket {

public static final String DEFAULT_CONTENT_TYPE = "application/json";

/**
* The base package to scan for listeners which are declared inside a class annotated with @Component or @Service.
*
Expand All @@ -93,8 +94,7 @@ public static class ConfigDocket {
*
* @see AsyncAPI#getDefaultContentType()
*/
@Nullable
private String defaultContentType;
private String defaultContentType = DEFAULT_CONTENT_TYPE;

@Nullable
private Map<String, Server> servers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public interface ComponentsService {

String registerSchema(Class<?> type);

String registerSchema(Class<?> type, String contentType);

Map<String, Message> getMessages();

MessageReference registerMessage(MessageObject message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
Expand All @@ -28,6 +29,8 @@
import java.util.function.Function;
import java.util.stream.Collectors;

import static io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigProperties.ConfigDocket.DEFAULT_CONTENT_TYPE;

@Slf4j
public class DefaultComponentsService implements ComponentsService {

Expand Down Expand Up @@ -67,21 +70,29 @@ public String registerSchema(AsyncHeaders headers) {
headerSchema.properties(headers);

this.schemas.put(headers.getSchemaName(), headerSchema);
postProcessSchema(headerSchema);
postProcessSchema(headerSchema, DEFAULT_CONTENT_TYPE);

return headers.getSchemaName();
}

@Override
public String registerSchema(Class<?> type) {
return this.registerSchema(type, properties.getDocket().getDefaultContentType());
}

@Override
public String registerSchema(Class<?> type, String contentType) {
log.debug("Registering schema for {}", type.getSimpleName());
String actualContentType =
StringUtils.isBlank(contentType) ? properties.getDocket().getDefaultContentType() : contentType;

Map<String, Schema> schemas = new LinkedHashMap<>(runWithFqnSetting((unused) -> converter.readAll(type)));

String schemaName = getSchemaName(type, schemas);

preProcessSchemas(schemas, schemaName, type);
this.schemas.putAll(schemas);
schemas.values().forEach(this::postProcessSchema);
schemas.forEach(this.schemas::putIfAbsent);
schemas.values().forEach(schema -> postProcessSchema(schema, actualContentType));

return schemaName;
}
Expand All @@ -95,7 +106,7 @@ public Map<String, Message> getMessages() {
public MessageReference registerMessage(MessageObject message) {
log.debug("Registering message for {}", message.getName());

messages.put(message.getName(), message);
messages.putIfAbsent(message.getName(), message);

return MessageReference.toComponentMessage(message);
}
Expand Down Expand Up @@ -150,7 +161,7 @@ private String registerString() {
schema.setName(String.class.getName());

this.schemas.put(schemaName, schema);
postProcessSchema(schema);
postProcessSchema(schema, DEFAULT_CONTENT_TYPE);

return schemaName;
}
Expand All @@ -169,9 +180,9 @@ private <R> R runWithFqnSetting(Function<Void, R> callable) {
return result;
}

private void postProcessSchema(Schema schema) {
private void postProcessSchema(Schema schema, String contentType) {
for (SchemasPostProcessor processor : schemaPostProcessors) {
processor.process(schema, schemas);
processor.process(schema, schemas, contentType);

if (!schemas.containsValue(schema)) {
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.stavshamir.springwolf.schemas.example;

import org.w3c.dom.Document;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import java.io.StringWriter;

public class DefaultExampleXmlValueSerializer implements ExampleXmlValueSerializer {
@Override
public String writeDocumentAsXmlString(Document document) throws TransformerException {
DOMSource domSource = new DOMSource(document);
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.INDENT, "no");
StringWriter sw = new StringWriter();
StreamResult sr = new StreamResult(sw);
transformer.transform(domSource, sr);
return sw.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.stavshamir.springwolf.schemas.example;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.core.util.Yaml;

public class DefaultExampleYamlValueSerializer implements ExampleYamlValueSerializer {

private static final ObjectMapper yamlMapper = Yaml.mapper();

@Override
public String writeDocumentAsYamlString(JsonNode node) throws JsonProcessingException {
return yamlMapper.writeValueAsString(node);
}
}
Loading

0 comments on commit 4953771

Please sign in to comment.