Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SpecVersionDetector;
import io.github.springwolf.asyncapi.v3.model.components.ComponentSchema;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaFormat;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaReference;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaType;
Expand Down Expand Up @@ -42,7 +43,7 @@ class JsonSchemaGeneratorTest {
@MethodSource
void validateJsonSchemaTest(String expectedJsonSchema, Supplier<Schema<?>> asyncApiSchema) throws Exception {
// given
SchemaObject actualSchema = swaggerSchemaUtil.mapSchema(asyncApiSchema.get());
ComponentSchema actualSchema = swaggerSchemaUtil.mapSchema(asyncApiSchema.get(), SchemaFormat.ASYNCAPI_V3);

// when
verifyValidJsonSchema(expectedJsonSchema);
Expand All @@ -67,7 +68,7 @@ void validateJsonSchemaTest(String expectedJsonSchema, Supplier<Schema<?>> async
ComponentSchema.of(pongSchema));

// when
Object jsonSchema = jsonSchemaGenerator.fromSchema(ComponentSchema.of(actualSchema), definitions);
Object jsonSchema = jsonSchemaGenerator.fromSchema(actualSchema, definitions);

// then
String jsonSchemaString = mapper.writeValueAsString(jsonSchema);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,11 @@ public class MultiFormatSchema extends ExtendableObject {
*/
@JsonProperty(value = "schema")
private Object schema;

public static MultiFormatSchema of(Object schema) {
// if payloadSchema.payload is already an instance of MultiFormatSchema, do not wrap again.
return (schema instanceof MultiFormatSchema mfs)
? mfs
: MultiFormatSchema.builder().schema(schema).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum SchemaFormat {
ASYNCAPI_V3_JSON("application/vnd.aai.asyncapi+json;version=" + AsyncAPI.ASYNCAPI_DEFAULT_VERSION),
ASYNCAPI_V3_YAML("application/vnd.aai.asyncapi+yaml;version=" + AsyncAPI.ASYNCAPI_DEFAULT_VERSION),
OPENAPI_V3("application/vnd.oai.openapi;version=3.0.0"),
OPENAPI_V3_1("application/vnd.oai.openapi;version=3.1.0"),
OPENAPI_V3_JSON("application/vnd.oai.openapi+json;version=3.0.0"),
OPENAPI_V3_YAML("application/vnd.oai.openapi+yaml;version=3.0.0"),
JSON_SCHEMA_JSON("application/schema+json;version=draft-07"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.asyncapi.v3.model.components.ComponentSchema;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaReference;
import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties;
import jakarta.annotation.Nullable;

Expand Down Expand Up @@ -33,17 +34,17 @@ public interface ComponentsService {
*
* @param type Type to resolve a schema from
* @param contentType Runtime ContentType of Schema
* @return the root schema for the given type.
* @return a {@link SchemaReference} referencing the root schema, or null if no schema could be resolved.
*/
@Nullable
ComponentSchema resolvePayloadSchema(Type type, String contentType);

/**
* registers the given schema with this {@link ComponentsService}
* @param headers the schema to register, typically a header schema
* @param schemaWithoutRef the schema to register, typically a header schema
* @return the title attribute of the given schema
*/
String registerSchema(SchemaObject headers);
String registerSchema(SchemaObject schemaWithoutRef);

/**
* Provides a map of all registered messages.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.asyncapi.v3.model.components.ComponentSchema;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaFormat;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import io.github.springwolf.core.asyncapi.schemas.SwaggerSchemaService;
import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties;
Expand Down Expand Up @@ -51,12 +52,14 @@ public Map<String, ComponentSchema> getSchemas() {
*
* @param type Type to resolve a schema from
* @param contentType Runtime ContentType of Schema
* @return the root schema for the given type.
* @return the root schema for the given type
*/
@Override
public ComponentSchema resolvePayloadSchema(Type type, String contentType) {

SwaggerSchemaService.ExtractedSchemas payload = schemaService.resolveSchema(type, contentType);
SchemaFormat payloadSchemaFormat =
springwolfConfigProperties.getDocket().getPayloadSchemaFormat().getSchemaFormat();
SwaggerSchemaService.ExtractedSchemas payload =
schemaService.resolveSchema(type, contentType, payloadSchemaFormat);
payload.referencedSchemas().forEach(schemas::putIfAbsent);
return payload.rootSchema();
}
Expand All @@ -65,21 +68,21 @@ public ComponentSchema resolvePayloadSchema(Type type, String contentType) {
* registers the given schema with this {@link ComponentsService}.
* <p>NOTE</p>
* Use only with schemas with max. one level of properties. Providing {@link SchemaObject}s with deep
* property hierarchy will result in an corrupted result.
* property hierarchy will result in a corrupted result.
* <br/>
* A typical usecase for this method is registering of header schemas, which have typically a simple structure.
* A typical usecase for this method is registering of header schemas, which have typically a simple structure.
*
* @param headers the schema to register, typically a header schema
* @param schemaWithoutRef the schema to register, typically a header schema
* @return the title attribute of the given schema
*/
@Override
public String registerSchema(SchemaObject headers) {
log.debug("Registering schema for {}", headers.getTitle());
public String registerSchema(SchemaObject schemaWithoutRef) {
log.debug("Registering schema for {}", schemaWithoutRef.getTitle());

SchemaObject headerSchema = schemaService.extractSchema(headers);
this.schemas.putIfAbsent(headers.getTitle(), ComponentSchema.of(headerSchema));
ComponentSchema processedSchema = schemaService.postProcessSchemaWithoutRef(schemaWithoutRef);
this.schemas.putIfAbsent(schemaWithoutRef.getTitle(), processedSchema);

return headers.getTitle();
return schemaWithoutRef.getTitle();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.swagger.v3.oas.models.media.StringSchema;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;

import java.text.SimpleDateFormat;
Expand Down Expand Up @@ -175,7 +176,14 @@ private Optional<T> buildExampleFromUnvisitedSchema(
return composedSchemaExample;
}

String type = schema.getType();
// schema may be an openapi v3 or v3.1 schema. While v3 uses an simple 'type' field, v3.1 supports a set of
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

building examples from types happend to be a little bit 'tricky' because openapi v3 and v3.1 changed the field for the type(s). I moved the "type-lookup" in an extra method 'getTypeForExampleValue'.

// types, for example ["string", "null"].

String type = getTypeForExampleValue(schema);
if (type == null) {
return Optional.empty();
}

return switch (type) {
case "array" -> buildArrayExample(schema, definitions, visited);
case "boolean" -> exampleValueGenerator.createBooleanExample(DEFAULT_BOOLEAN_EXAMPLE, schema);
Expand Down Expand Up @@ -235,6 +243,33 @@ private String getFirstEnumValue(Schema schema) {
return null;
}

/**
* looks in schemas openapi-v3 'type' and openapi-v3.1 'types' fields to
* find the best candidate to use as an example value.
*
* @param schema
* @return the type to use for example values, or null if no suitable type was found.
*/
@Nullable
String getTypeForExampleValue(Schema schema) {
// if the single type field is present, it has precedence over the types field
if (schema.getType() != null) {
return schema.getType();
}

Set<String> types = schema.getTypes();

if (types == null || types.isEmpty()) {
return null;
}

return types.stream()
.filter(t -> !"null".equals(t))
.sorted() // sort types to be deterministic
.findFirst()
.orElse(null);
}

private Optional<T> buildFromComposedSchema(
Optional<String> name, Schema schema, Map<String, Schema> definitions, Set<Schema> visited) {
final List<Schema> schemasAllOf = schema.getAllOf();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ private void markSchemas(AsyncAPI fullAsyncApi, MarkingContext markingContext, S
* properties, allOf, anyOf, oneOf, not- and items references. Trys to deduce the schema id from the ref path and
* returns a Set of detected schema ids.
*
* @param markingContext the current {@link MarkingContext}
* @param markingContext the current {@link MarkingContext}
* @param componentSchema the {@link ComponentSchema} to analyze
* @return Set of schema ids representing nested schema refs
*/
Expand All @@ -217,14 +217,16 @@ private static Set<String> findUnmarkedNestedSchemas(
if (componentSchema.getMultiFormatSchema() != null) {
MultiFormatSchema multiFormatSchema = componentSchema.getMultiFormatSchema();

// Currently we support async_api and open_api format.
// Currently we support async_api and open_api v3 and v3.1 format.
// The concrete schemaformat mediatype can contain json/yaml postfix, so we check wether the begin of the
// media type matches.
if (multiFormatSchema.getSchemaFormat().startsWith(SchemaFormat.ASYNCAPI_V3.toString())
String schemaFormat = multiFormatSchema.getSchemaFormat();
if (schemaFormat.startsWith(SchemaFormat.ASYNCAPI_V3.toString())
&& multiFormatSchema.getSchema() instanceof SchemaObject schemaObject) {
return findUnmarkedNestedSchemasForAsyncAPISchema(markingContext, schemaObject);
}
if (multiFormatSchema.getSchemaFormat().startsWith(SchemaFormat.OPENAPI_V3.toString())
if ((schemaFormat.startsWith(SchemaFormat.OPENAPI_V3.toString())
|| schemaFormat.startsWith(SchemaFormat.OPENAPI_V3_1.toString()))
&& multiFormatSchema.getSchema() instanceof Schema openapiSchema) {
return findUnmarkedNestedSchemasForOpenAPISchema(markingContext, openapiSchema);
}
Expand All @@ -240,7 +242,7 @@ private static Set<String> findUnmarkedNestedSchemas(
* returns a Set of detected schema ids.
*
* @param markingContext the current {@link MarkingContext}
* @param schema the {@link SchemaObject} to analyze
* @param schema the {@link SchemaObject} to analyze
* @return Set of schema ids representing nested schema refs
*/
private static Set<String> findUnmarkedNestedSchemasForAsyncAPISchema(
Expand Down Expand Up @@ -276,7 +278,7 @@ private static Set<String> findUnmarkedNestedSchemasForAsyncAPISchema(
* returns a Set of detected schema ids.
*
* @param markingContext the current {@link MarkingContext}
* @param openapiSchema the Swagger {@link Schema} to analyze
* @param openapiSchema the Swagger {@link Schema} to analyze
* @return Set of schema ids representing nested schema refs
*/
private static Set<String> findUnmarkedNestedSchemasForOpenAPISchema(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public SchemaObject extractHeader(Method method, PayloadSchemaObject payload) {
Header headerAnnotation = argument.getAnnotation(Header.class);
String headerName = getHeaderAnnotationName(headerAnnotation);

SwaggerSchemaService.ExtractedSchemas extractedSchema = schemaService.extractSchema(argument.getType());
SwaggerSchemaService.ExtractedSchemas extractedSchema =
schemaService.postProcessSimpleSchema(argument.getType());
ComponentSchema rootComponentSchema = extractedSchema.rootSchema();

// to stay compatible with former versions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ public MessageObject buildMessage(AsyncOperation operationData, Method method) {
Map<String, MessageBinding> messageBinding =
AsyncAnnotationUtil.processMessageBindingFromAnnotation(method, messageBindingProcessors);

var messagePayload = MessagePayload.of(
MultiFormatSchema.builder().schema(payloadSchema.payload()).build());
MultiFormatSchema multiFormatSchema = MultiFormatSchema.of(payloadSchema.payload());
MessagePayload messagePayload = MessagePayload.of(multiFormatSchema);

var builder = MessageObject.builder()
.messageId(payloadSchema.name())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ public PayloadSchemaObject buildSchema(Type payloadType) {
return buildSchema(contentType, payloadType);
}

/**
* creates a {@link PayloadSchemaObject} from the given type and content type. Registers the created schema objects
* with this {@link ComponentsService}.
*
* @param contentType
* @param payloadType
* @return
*/
public PayloadSchemaObject buildSchema(String contentType, Type payloadType) {
String schemaName = componentsService.getSchemaName(payloadType);
String simpleSchemaName = componentsService.getSimpleSchemaName(payloadType);
Expand Down
Loading
Loading