Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor ExampleJsonGenerator and Support XML Examples #581

Merged
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f367e39
test(examples): trim all expect asyncapi specs before comparison
sam0r040 Jan 12, 2024
5432562
feat(core): pass content type to example generator
sam0r040 Jan 12, 2024
81fdad0
refactor(core): rename ExampleGenerator to SchemaWalker
sam0r040 Jan 12, 2024
2892706
WIP First xml exmaple working
sam0r040 Jan 19, 2024
18409d5
WIP XML
sam0r040 Jan 19, 2024
725c1a0
WIP XML
sam0r040 Jan 26, 2024
e50df68
All tests green
sam0r040 Jan 26, 2024
8d44a37
refactor and fix map schema
sam0r040 Jan 26, 2024
08e097d
chore(core): Fixes after rebase, fix special cases
sam0r040 Feb 2, 2024
89392b0
chore(core): use in kafka example
sam0r040 Feb 2, 2024
1464f0c
feat(core): generated examples are either of type JsonNode for Json o…
sam0r040 Feb 9, 2024
6272f98
chore(core): cleanup ToDos, fix tests
sam0r040 Feb 9, 2024
86d7132
chore(core): adapt new implementation for generating examples to chan…
sam0r040 Feb 16, 2024
141399a
refactor(core): rename Default*SchemasServiceTests to Default*Compone…
sam0r040 Feb 16, 2024
aa0b955
refactor(core): removed combineObjectExample() from ExampleValueGener…
sam0r040 Feb 16, 2024
82610a5
refactor(core): properly specify version of jakarta.validation-api de…
sam0r040 Feb 16, 2024
bbd0222
chore(core): Removed TODO to throw exception for some schema types
sam0r040 Feb 16, 2024
cfcc15d
refactor(core): parse example string to xml node in ExampleXmlValueGe…
sam0r040 Feb 16, 2024
2324ba8
refactor(core): extract ExampleXmlValueSerializer to enable custom se…
sam0r040 Feb 16, 2024
2581589
fix(examples): fix asyncapi.json for the amqp-example
sam0r040 Feb 16, 2024
8079a5c
fix(examples): fix asyncapi.json for the sqs-example
sam0r040 Feb 16, 2024
e50cc19
fix(examples): fix asyncapi.json for the jms-example
sam0r040 Feb 16, 2024
c03a0a8
fix(examples): fix asyncapi.json for the kafka-example
sam0r040 Feb 16, 2024
5352dbe
refactor(core): moves methods and add class comments
timonback Feb 20, 2024
be45bcc
chore(core): add org.slf4j:slf4j-simple to version management
sam0r040 Feb 23, 2024
dcdec2f
feat(core): Add support for examples in YAML format
sam0r040 Feb 23, 2024
bb8623d
chore(core): cleanup test setup in DefaultJsonComponentsServiceTest a…
sam0r040 Feb 23, 2024
91043a5
chore(kafka-example): add example for schema with with examples in ya…
sam0r040 Feb 23, 2024
b4ceb2a
refactor(core): unify method names of ExampleValueGenerator interface
sam0r040 Feb 23, 2024
892c72c
refactor(core): remove debug log from ExampleXmlValueGenerator
sam0r040 Feb 23, 2024
d13c604
chore(ui): add temporary fix for spec validation of other content typ…
sam0r040 Feb 23, 2024
d3472ec
chore(core): unify naming of internal methods of DefaultSchemaWalker.…
sam0r040 Feb 23, 2024
a6a36db
chore(ui): document github issue about non json payload in examples
sam0r040 Feb 23, 2024
895f721
refactor(core): minimal serialization configuration for DefaultExampl…
sam0r040 Feb 23, 2024
6dcaa17
chore(core): cleanup deprecations
sam0r040 Feb 23, 2024
d84cf59
chore(kafka-example): fix operation description
sam0r040 Feb 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
sam0r040 marked this conversation as resolved.
Show resolved Hide resolved
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 Down Expand Up @@ -93,8 +92,7 @@ public static class ConfigDocket {
*
* @see AsyncAPI#getDefaultContentType()
*/
@Nullable
private String defaultContentType;
private String defaultContentType = "application/json";

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

String registerSchema(AsyncHeaders headers);

@Deprecated
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 @@ -31,6 +32,9 @@
@Slf4j
public class DefaultComponentsService implements ComponentsService {

@Deprecated
private static final String DEFAULT_CONTENT_TYPE = "application/json";

private final ModelConverters converter = ModelConverters.getInstance();
private final List<SchemasPostProcessor> schemaPostProcessors;
private final SwaggerSchemaUtil swaggerSchemaUtil;
Expand Down Expand Up @@ -67,21 +71,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 +107,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 +162,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 +181,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,30 @@
// 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.ENCODING, "ISO-8859-1");
sam0r040 marked this conversation as resolved.
Show resolved Hide resolved
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
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
Loading