diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java index 80067ec0fc..99f8243b68 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java @@ -53,6 +53,7 @@ import io.swagger.v3.oas.annotations.media.SchemaProperty; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.ExternalDocumentation; +import io.swagger.v3.oas.models.SpecVersion; import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.ComposedSchema; import io.swagger.v3.oas.models.media.Discriminator; @@ -230,8 +231,12 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context } } else { ArraySchema schema = new ArraySchema(); + if (openapi31) { + schema.specVersion(SpecVersion.V31); + } resolveArraySchema(annotatedType, schema, resolvedArrayAnnotation); - return schema.items(new Schema().$ref(resolvedSchemaAnnotation.ref()).name(name)); + Schema itemsSchema = openapi31 ? new JsonSchema() : new Schema(); + return schema.items(itemsSchema.$ref(resolvedSchemaAnnotation.ref()).name(name)); } } @@ -266,6 +271,9 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context .skipOverride(true); if (resolvedArrayAnnotation != null) { ArraySchema schema = new ArraySchema(); + if (openapi31) { + schema.specVersion(SpecVersion.V31); + } resolveArraySchema(annotatedType, schema, resolvedArrayAnnotation); Schema innerSchema = null; @@ -277,10 +285,14 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context if (innerSchema != null && isObjectSchema(innerSchema) && StringUtils.isNotBlank(innerSchema.getName())) { // create a reference for the items if (context.getDefinedModels().containsKey(innerSchema.getName())) { - innerSchema = new Schema().$ref(constructRef(innerSchema.getName())); + String ref = constructRef(innerSchema.getName()); + innerSchema = openapi31 ? new JsonSchema() : new Schema(); + innerSchema.$ref(ref); } } else if (innerSchema != null && innerSchema.get$ref() != null) { - innerSchema = new Schema().$ref(StringUtils.isNotEmpty(innerSchema.get$ref()) ? innerSchema.get$ref() : innerSchema.getName()); + String ref = StringUtils.isNotEmpty(innerSchema.get$ref()) ? innerSchema.get$ref() : innerSchema.getName(); + innerSchema = openapi31 ? new JsonSchema() : new Schema(); + innerSchema.$ref(ref); } } schema.setItems(innerSchema); @@ -290,10 +302,14 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context if (implSchema != null && aType.isResolveAsRef() && isObjectSchema(implSchema) && StringUtils.isNotBlank(implSchema.getName())) { // create a reference for the items if (context.getDefinedModels().containsKey(implSchema.getName())) { - implSchema = new Schema().$ref(constructRef(implSchema.getName())); + String ref = constructRef(implSchema.getName()); + implSchema = openapi31 ? new JsonSchema() : new Schema(); + implSchema.$ref(ref); } } else if (implSchema != null && implSchema.get$ref() != null) { - implSchema = new Schema().$ref(StringUtils.isNotEmpty(implSchema.get$ref()) ? implSchema.get$ref() : implSchema.getName()); + String ref = StringUtils.isNotEmpty(implSchema.get$ref()) ? implSchema.get$ref() : implSchema.getName(); + implSchema = openapi31 ? new JsonSchema() : new Schema(); + implSchema.$ref(ref); } return implSchema; } @@ -365,7 +381,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context } if ("Object".equals(name)) { - Schema schema = new Schema(); + Schema schema = openapi31 ? new JsonSchema() : new Schema(); if (schemaRefFromAnnotation != null) { schema.raw$ref(schemaRefFromAnnotation); } @@ -386,6 +402,9 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context resolveSchemaMembers(model, annotatedType, context, next); if (resolvedArrayAnnotation != null) { ArraySchema schema = new ArraySchema(); + if (openapi31) { + schema.specVersion(SpecVersion.V31); + } resolveArraySchema(annotatedType, schema, resolvedArrayAnnotation); schema.setItems(model); return schema; @@ -394,7 +413,8 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context // Store off the ref and add the enum as a top-level model context.defineModel(name, model, annotatedType, null); // Return the model as a ref only property - model = new Schema().$ref(Components.COMPONENTS_SCHEMAS_REF + name); + model = openapi31 ? new JsonSchema() : new Schema(); + model.$ref(Components.COMPONENTS_SCHEMAS_REF + name); } if (!isComposedSchema) { if (schemaRefFromAnnotation != null && model != null) { @@ -488,14 +508,20 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context addPropertiesSchema = context.getDefinedModels().get(pName); } else { // create a reference for the items - addPropertiesSchema = new Schema().$ref(constructRef(pName)); + addPropertiesSchema = openapi31 ? new JsonSchema() : new Schema(); + addPropertiesSchema.$ref(constructRef(pName)); } } } else if (addPropertiesSchema.get$ref() != null) { - addPropertiesSchema = new Schema().$ref(StringUtils.isNotEmpty(addPropertiesSchema.get$ref()) ? addPropertiesSchema.get$ref() : addPropertiesSchema.getName()); + String ref = StringUtils.isNotEmpty(addPropertiesSchema.get$ref()) ? addPropertiesSchema.get$ref() : addPropertiesSchema.getName(); + addPropertiesSchema = openapi31 ? new JsonSchema() : new Schema(); + addPropertiesSchema.$ref(ref); } } Schema mapModel = new MapSchema().additionalProperties(addPropertiesSchema); + if (openapi31) { + mapModel.specVersion(SpecVersion.V31); + } mapModel.name(name); model = mapModel; } else if (valueType != null) { @@ -540,15 +566,21 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context items = context.getDefinedModels().get(pName); } else { // create a reference for the items - items = new Schema().$ref(constructRef(pName)); + items = openapi31 ? new JsonSchema() : new Schema(); + items.$ref(constructRef(pName)); } } } else if (items.get$ref() != null) { - items = new Schema().$ref(StringUtils.isNotEmpty(items.get$ref()) ? items.get$ref() : items.getName()); + String ref = StringUtils.isNotEmpty(items.get$ref()) ? items.get$ref() : items.getName(); + items = openapi31 ? new JsonSchema() : new Schema(); + items.$ref(ref); } Schema arrayModel = new ArraySchema().items(items); + if (openapi31) { + arrayModel.specVersion(SpecVersion.V31); + } if (_isSetType(type.getRawClass())) { arrayModel.setUniqueItems(true); } @@ -560,7 +592,8 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context } } } else if (isComposedSchema) { - model = new ComposedSchema().name(name); + model = openapi31 ? new JsonSchema() : new ComposedSchema(); + model.name(name); if ( (openapi31 && Boolean.TRUE.equals(PrimitiveType.explicitObjectType)) || (!openapi31 && (!Boolean.FALSE.equals(PrimitiveType.explicitObjectType)))) { @@ -783,7 +816,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context handleUnwrapped(props, innerModel, uw.prefix(), uw.suffix(), requiredProps); return null; } else { - return new Schema(); + return openapi31 ? new JsonSchema() : new Schema(); } }); property = context.resolve(aType); @@ -851,13 +884,15 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context if (Schema.SchemaResolution.INLINE.equals(resolvedSchemaResolution)) { property = context.getDefinedModels().get(pName); } else if (Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) && ctxProperty != null) { - property = new Schema() + property = openapi31 ? new JsonSchema() : new Schema(); + property .addAllOfItem(ctxProperty) - .addAllOfItem(new Schema().$ref(constructRef(pName))); + .addAllOfItem(openapi31 ? new JsonSchema().$ref(constructRef(pName)) : new Schema().$ref(constructRef(pName))); } else if (Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) && ctxProperty != null) { - property = ctxProperty.addAllOfItem(new Schema().$ref(constructRef(pName))); + property = ctxProperty.addAllOfItem(openapi31 ? new JsonSchema().$ref(constructRef(pName)) : new Schema().$ref(constructRef(pName))); } else { - property = new Schema().$ref(constructRef(pName)); + property = openapi31 ? new JsonSchema() : new Schema(); + property.$ref(constructRef(pName)); } property = clone(property); // TODO: why is this needed? is it not handled before? @@ -1078,7 +1113,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context if (!composedModelPropertiesAsSibling) { if (composedSchema.getAllOf() != null && !composedSchema.getAllOf().isEmpty()) { if (composedSchema.getProperties() != null && !composedSchema.getProperties().isEmpty()) { - ObjectSchema propSchema = new ObjectSchema(); + Schema propSchema = openapi31 ? new JsonSchema().typesItem("object") : new ObjectSchema(); propSchema.properties(composedSchema.getProperties()); composedSchema.setProperties(null); composedSchema.addAllOfItem(propSchema); @@ -1116,11 +1151,14 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context if (model != null && resolvedArrayAnnotation != null) { if (!"array".equals(model.getType())) { ArraySchema schema = new ArraySchema(); + if (openapi31) { + schema.specVersion(SpecVersion.V31); + } schema.setItems(model); resolveArraySchema(annotatedType, schema, resolvedArrayAnnotation); return schema; } else { - if (model instanceof ArraySchema) { + if (isArraySchema(model)) { resolveArraySchema(annotatedType, (ArraySchema) model, resolvedArrayAnnotation); } } @@ -1589,7 +1627,8 @@ protected Schema process(Schema id, String propertyName, AnnotatedType type, model = resolve(type, context, null); } model.addProperties(propertyName, id); - return new Schema().$ref(StringUtils.isNotEmpty(model.get$ref()) + Schema retSchema = openapi31 ? new JsonSchema() : new Schema(); + return retSchema.$ref(StringUtils.isNotEmpty(model.get$ref()) ? model.get$ref() : model.getName()); } @@ -2046,7 +2085,8 @@ private boolean resolveSubtypes(Schema model, BeanDescription bean, ModelConvert } else { composedSchema = (ComposedSchema) subtypeModel; } - Schema refSchema = new Schema().$ref(Components.COMPONENTS_SCHEMAS_REF + model.getName()); + Schema refSchema = openapi31 ? new JsonSchema() : new Schema(); + refSchema.$ref(Components.COMPONENTS_SCHEMAS_REF + model.getName()); // allOf could have already being added during type resolving when @Schema(allOf..) is declared if (composedSchema.getAllOf() == null || !composedSchema.getAllOf().contains(refSchema)) { composedSchema.addAllOfItem(refSchema); @@ -2055,7 +2095,7 @@ private boolean resolveSubtypes(Schema model, BeanDescription bean, ModelConvert if (!composedModelPropertiesAsSibling) { if (composedSchema.getAllOf() != null && !composedSchema.getAllOf().isEmpty()) { if (composedSchema.getProperties() != null && !composedSchema.getProperties().isEmpty()) { - ObjectSchema propSchema = new ObjectSchema(); + Schema propSchema = openapi31 ? new JsonSchema().typesItem("object") : new ObjectSchema(); propSchema.properties(composedSchema.getProperties()); composedSchema.setProperties(null); composedSchema.addAllOfItem(propSchema); @@ -2654,7 +2694,7 @@ protected Schema resolveWrapping(JavaType type, ModelConverterContext context, S if (JsonTypeInfo.Id.NAME.equals(id) && name == null) { name = type.getRawClass().getSimpleName(); } - Schema wrapperSchema = new ObjectSchema(); + Schema wrapperSchema = openapi31 ? new JsonSchema().typesItem("object") : new ObjectSchema(); wrapperSchema.name(model.getName()); wrapperSchema.addProperties(name, model); return wrapperSchema; @@ -2890,7 +2930,7 @@ protected String resolveContentMediaType(Annotated a, Annotation[] annotations, protected void resolveContains(AnnotatedType annotatedType, ArraySchema arraySchema, io.swagger.v3.oas.annotations.media.ArraySchema arraySchemaAnnotation) { final io.swagger.v3.oas.annotations.media.Schema containsAnnotation = arraySchemaAnnotation.contains(); - final Schema contains = new Schema(); + final Schema contains = openapi31 ? new JsonSchema() : new Schema(); if (containsAnnotation.types().length > 0) { for (String type : containsAnnotation.types()) { contains.addType(type); @@ -2911,7 +2951,7 @@ protected void resolveContains(AnnotatedType annotatedType, ArraySchema arraySch protected void resolveUnevaluatedItems(AnnotatedType annotatedType, ArraySchema arraySchema, io.swagger.v3.oas.annotations.media.ArraySchema arraySchemaAnnotation) { final io.swagger.v3.oas.annotations.media.Schema unevaluatedItemsAnnotation = arraySchemaAnnotation.unevaluatedItems(); - final Schema unevaluatedItems = new Schema(); + final Schema unevaluatedItems = openapi31 ? new JsonSchema() : new Schema(); if (StringUtils.isNotBlank(unevaluatedItemsAnnotation.type())) { unevaluatedItems.addType(unevaluatedItemsAnnotation.type()); } @@ -3388,7 +3428,7 @@ private void resolveArraySchema(AnnotatedType annotatedType, ArraySchema schema, } if (resolvedArrayAnnotation.prefixItems().length > 0) { for (io.swagger.v3.oas.annotations.media.Schema prefixItemAnnotation : resolvedArrayAnnotation.prefixItems()) { - final Schema prefixItem = new Schema(); + final Schema prefixItem = new JsonSchema(); if (StringUtils.isNotBlank(prefixItemAnnotation.type())) { prefixItem.addType(prefixItemAnnotation.type()); } @@ -3497,7 +3537,8 @@ protected Schema buildRefSchemaIfObject(Schema schema, ModelConverterContext con Schema result = schema; if (isObjectSchema(schema) && StringUtils.isNotBlank(schema.getName())) { if (context.getDefinedModels().containsKey(schema.getName())) { - result = new Schema().$ref(constructRef(schema.getName())); + result = openapi31 ? new JsonSchema() : new Schema(); + result.$ref(constructRef(schema.getName())); } } return result; diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/deserialization/ComprehensiveOAS31ValidationTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/deserialization/ComprehensiveOAS31ValidationTest.java new file mode 100644 index 0000000000..fd514d32a2 --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/deserialization/ComprehensiveOAS31ValidationTest.java @@ -0,0 +1,166 @@ +package io.swagger.v3.core.deserialization; + +import io.swagger.v3.core.matchers.SerializationMatchers; +import io.swagger.v3.core.util.Json31; +import io.swagger.v3.core.util.Yaml31; +import io.swagger.v3.oas.models.OpenAPI; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +/** + * Test class to validate the comprehensive OpenAPI 3.1 files. + */ +public class ComprehensiveOAS31ValidationTest { + + /** + * Test to validate the comprehensive OpenAPI 3.1 file. + * This test ensures that the file can be properly deserialized by the swagger-core library + * and that all references are correctly resolved. + */ + @Test + public void testComprehensiveOAS31Validation() throws IOException { + // Path to the comprehensive OpenAPI 3.1 file + String filePath = "src/test/resources/comprehensiveOAS31/comprehensive-openapi.yaml"; + + // Verify the file exists + File file = new File(filePath); + assertTrue(file.exists(), "Comprehensive OpenAPI 3.1 file does not exist: " + filePath); + + // Deserialize the file + OpenAPI openAPI = Yaml31.mapper().readValue(file, OpenAPI.class); + + // Verify the file was properly deserialized + assertNotNull(openAPI, "Failed to deserialize the comprehensive OpenAPI 3.1 file"); + + // Verify basic structure + assertNotNull(openAPI.getInfo(), "Info object is missing"); + assertNotNull(openAPI.getPaths(), "Paths object is missing"); + assertNotNull(openAPI.getComponents(), "Components object is missing"); + + // Verify components + assertNotNull(openAPI.getComponents().getSchemas(), "Schemas object is missing"); + assertNotNull(openAPI.getComponents().getPathItems(), "PathItems object is missing"); + assertNotNull(openAPI.getComponents().getParameters(), "Parameters object is missing"); + assertNotNull(openAPI.getComponents().getRequestBodies(), "RequestBodies object is missing"); + assertNotNull(openAPI.getComponents().getResponses(), "Responses object is missing"); + assertNotNull(openAPI.getComponents().getHeaders(), "Headers object is missing"); + assertNotNull(openAPI.getComponents().getSecuritySchemes(), "SecuritySchemes object is missing"); + assertNotNull(openAPI.getComponents().getExamples(), "Examples object is missing"); + assertNotNull(openAPI.getComponents().getLinks(), "Links object is missing"); + assertNotNull(openAPI.getComponents().getCallbacks(), "Callbacks object is missing"); + + // Verify webhooks + assertNotNull(openAPI.getWebhooks(), "Webhooks object is missing"); + + // Verify schemas + assertTrue(openAPI.getComponents().getSchemas().size() > 0, "No schemas found"); + + // Verify paths + assertTrue(openAPI.getPaths().size() > 0, "No paths found"); + + // Verify webhooks + assertTrue(openAPI.getWebhooks().size() > 0, "No webhooks found"); + } + + /** + * Test round-trip serialization and deserialization of the comprehensive OpenAPI 3.1 file using YAML. + * This test ensures that the file can be properly serialized and deserialized without losing information. + */ + @Test + public void testComprehensiveOAS31YamlRoundTrip() throws IOException { + // Path to the comprehensive OpenAPI 3.1 file + String filePath = "src/test/resources/comprehensiveOAS31/comprehensive-openapi.yaml"; + + // Verify the file exists + File file = new File(filePath); + assertTrue(file.exists(), "Comprehensive OpenAPI 3.1 file does not exist: " + filePath); + + // Deserialize the file + OpenAPI originalOpenAPI = Yaml31.mapper().readValue(file, OpenAPI.class); + + // Serialize to YAML + String yaml = Yaml31.pretty(originalOpenAPI); + + // Deserialize back + OpenAPI deserializedOpenAPI = Yaml31.mapper().readValue(yaml, OpenAPI.class); + + // Verify basic structure + assertNotNull(deserializedOpenAPI, "Failed to deserialize the serialized YAML"); + assertEquals(deserializedOpenAPI.getOpenapi(), "3.1.0", "OpenAPI version mismatch"); + assertEquals(deserializedOpenAPI.getInfo().getTitle(), originalOpenAPI.getInfo().getTitle(), "Title mismatch"); + assertEquals(deserializedOpenAPI.getInfo().getVersion(), originalOpenAPI.getInfo().getVersion(), "Version mismatch"); + + // Verify components + assertNotNull(deserializedOpenAPI.getComponents(), "Components object is missing"); + assertNotNull(deserializedOpenAPI.getComponents().getSchemas(), "Schemas object is missing"); + assertEquals(deserializedOpenAPI.getComponents().getSchemas().size(), originalOpenAPI.getComponents().getSchemas().size(), "Schemas size mismatch"); + + // Verify paths + assertNotNull(deserializedOpenAPI.getPaths(), "Paths object is missing"); + assertEquals(deserializedOpenAPI.getPaths().size(), originalOpenAPI.getPaths().size(), "Paths size mismatch"); + + // Verify webhooks + assertNotNull(deserializedOpenAPI.getWebhooks(), "Webhooks object is missing"); + assertEquals(deserializedOpenAPI.getWebhooks().size(), originalOpenAPI.getWebhooks().size(), "Webhooks size mismatch"); + + // Serialize again + String yamlAgain = Yaml31.pretty(deserializedOpenAPI); + + // Compare YAML strings (normalize whitespace) + SerializationMatchers.assertEqualsToYaml31(originalOpenAPI, yamlAgain); + } + + /** + * Test round-trip serialization and deserialization of the comprehensive OpenAPI 3.1 file using JSON. + * This test ensures that the file can be properly serialized and deserialized without losing information. + */ + @Test + public void testComprehensiveOAS31JsonRoundTrip() throws IOException { + // Path to the comprehensive OpenAPI 3.1 file + String filePath = "src/test/resources/comprehensiveOAS31/comprehensive-openapi.yaml"; + + // Verify the file exists + File file = new File(filePath); + assertTrue(file.exists(), "Comprehensive OpenAPI 3.1 file does not exist: " + filePath); + + // Deserialize the file + OpenAPI originalOpenAPI = Yaml31.mapper().readValue(file, OpenAPI.class); + + // Serialize to JSON + String json = Json31.pretty(originalOpenAPI); + + // Deserialize back + OpenAPI deserializedOpenAPI = Json31.mapper().readValue(json, OpenAPI.class); + + // Verify basic structure + assertNotNull(deserializedOpenAPI, "Failed to deserialize the serialized JSON"); + assertEquals(deserializedOpenAPI.getOpenapi(), "3.1.0", "OpenAPI version mismatch"); + assertEquals(deserializedOpenAPI.getInfo().getTitle(), originalOpenAPI.getInfo().getTitle(), "Title mismatch"); + assertEquals(deserializedOpenAPI.getInfo().getVersion(), originalOpenAPI.getInfo().getVersion(), "Version mismatch"); + + // Verify components + assertNotNull(deserializedOpenAPI.getComponents(), "Components object is missing"); + assertNotNull(deserializedOpenAPI.getComponents().getSchemas(), "Schemas object is missing"); + assertEquals(deserializedOpenAPI.getComponents().getSchemas().size(), originalOpenAPI.getComponents().getSchemas().size(), "Schemas size mismatch"); + + // Verify paths + assertNotNull(deserializedOpenAPI.getPaths(), "Paths object is missing"); + assertEquals(deserializedOpenAPI.getPaths().size(), originalOpenAPI.getPaths().size(), "Paths size mismatch"); + + // Verify webhooks + assertNotNull(deserializedOpenAPI.getWebhooks(), "Webhooks object is missing"); + assertEquals(deserializedOpenAPI.getWebhooks().size(), originalOpenAPI.getWebhooks().size(), "Webhooks size mismatch"); + + // Serialize again + String jsonAgain = Json31.pretty(deserializedOpenAPI); + + // Compare JSON strings + SerializationMatchers.assertEqualsToJson31(originalOpenAPI, jsonAgain); + } +} diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/roundtrip/ComprehensiveRoundTripTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/roundtrip/ComprehensiveRoundTripTest.java new file mode 100644 index 0000000000..36c2b3c770 --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/roundtrip/ComprehensiveRoundTripTest.java @@ -0,0 +1,546 @@ +package io.swagger.v3.core.roundtrip; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.v3.core.util.Json; +import io.swagger.v3.core.util.Json31; +import io.swagger.v3.core.util.ResourceUtils; +import io.swagger.v3.core.util.Yaml; +import io.swagger.v3.core.util.Yaml31; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.examples.Example; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.media.Discriminator; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.oas.models.responses.ApiResponses; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +/** + * Comprehensive tests for round-trip (serialization + deserialization) of OpenAPI 3.0 and 3.1 documents. + * This test class covers various scenarios, edge cases, and combinations of OpenAPI elements. + */ +public class ComprehensiveRoundTripTest { + + /** + * Test round-trip of OpenAPI 3.0 document with JSON + */ + @Test + public void testRoundTrip30Json() throws IOException { + // Create a simple OpenAPI 3.0 document + OpenAPI originalOpenAPI = createBasicOpenAPI30(); + + // Serialize to JSON + String json = Json.pretty(originalOpenAPI); + + // Deserialize back + OpenAPI deserializedOpenAPI = Json.mapper().readValue(json, OpenAPI.class); + + // Verify basic structure + assertNotNull(deserializedOpenAPI); + assertEquals(deserializedOpenAPI.getOpenapi(), "3.0.1"); + assertEquals(deserializedOpenAPI.getInfo().getTitle(), "Test API"); + assertEquals(deserializedOpenAPI.getInfo().getVersion(), "1.0.0"); + + // Verify paths + assertNotNull(deserializedOpenAPI.getPaths()); + assertTrue(deserializedOpenAPI.getPaths().containsKey("/test")); + + // Verify components + assertNotNull(deserializedOpenAPI.getComponents()); + assertNotNull(deserializedOpenAPI.getComponents().getSchemas()); + assertTrue(deserializedOpenAPI.getComponents().getSchemas().containsKey("TestSchema")); + + // Serialize again + String jsonAgain = Json.pretty(deserializedOpenAPI); + + // Compare JSON strings + assertEquals(json, jsonAgain, "JSON round-trip failed"); + } + + /** + * Test round-trip of OpenAPI 3.0 document with YAML + */ + @Test + public void testRoundTrip30Yaml() throws IOException { + // Create a simple OpenAPI 3.0 document + OpenAPI originalOpenAPI = createBasicOpenAPI30(); + + // Serialize to YAML + String yaml = Yaml.pretty(originalOpenAPI); + + // Deserialize back + OpenAPI deserializedOpenAPI = Yaml.mapper().readValue(yaml, OpenAPI.class); + + // Verify basic structure + assertNotNull(deserializedOpenAPI); + assertEquals(deserializedOpenAPI.getOpenapi(), "3.0.1"); + assertEquals(deserializedOpenAPI.getInfo().getTitle(), "Test API"); + assertEquals(deserializedOpenAPI.getInfo().getVersion(), "1.0.0"); + + // Verify paths + assertNotNull(deserializedOpenAPI.getPaths()); + assertTrue(deserializedOpenAPI.getPaths().containsKey("/test")); + + // Verify components + assertNotNull(deserializedOpenAPI.getComponents()); + assertNotNull(deserializedOpenAPI.getComponents().getSchemas()); + assertTrue(deserializedOpenAPI.getComponents().getSchemas().containsKey("TestSchema")); + + // Serialize again + String yamlAgain = Yaml.pretty(deserializedOpenAPI); + + // Compare YAML strings + assertEquals(yaml, yamlAgain, "YAML round-trip failed"); + } + + /** + * Test round-trip of OpenAPI 3.1 document with JSON + */ + @Test + public void testRoundTrip31Json() throws IOException { + // Create a simple OpenAPI 3.1 document + OpenAPI originalOpenAPI = createBasicOpenAPI31(); + + // Serialize to JSON + String json = Json31.pretty(originalOpenAPI); + + // Deserialize back + OpenAPI deserializedOpenAPI = Json31.mapper().readValue(json, OpenAPI.class); + + // Verify basic structure + assertNotNull(deserializedOpenAPI); + assertEquals(deserializedOpenAPI.getOpenapi(), "3.1.0"); + assertEquals(deserializedOpenAPI.getInfo().getTitle(), "Test API"); + assertEquals(deserializedOpenAPI.getInfo().getVersion(), "1.0.0"); + assertEquals(deserializedOpenAPI.getInfo().getSummary(), "Test API Summary"); + assertEquals(deserializedOpenAPI.getInfo().getLicense().getIdentifier(), "MIT"); + + // Verify paths + assertNotNull(deserializedOpenAPI.getPaths()); + assertTrue(deserializedOpenAPI.getPaths().containsKey("/test")); + + // Verify components + assertNotNull(deserializedOpenAPI.getComponents()); + assertNotNull(deserializedOpenAPI.getComponents().getSchemas()); + assertTrue(deserializedOpenAPI.getComponents().getSchemas().containsKey("TestSchema")); + + // Verify webhooks + assertNotNull(deserializedOpenAPI.getWebhooks()); + assertTrue(deserializedOpenAPI.getWebhooks().containsKey("testWebhook")); + + // Serialize again + String jsonAgain = Json31.pretty(deserializedOpenAPI); + + // Compare JSON strings + assertEquals(json, jsonAgain, "JSON round-trip failed"); + } + + /** + * Test round-trip of OpenAPI 3.1 document with YAML + */ + @Test + public void testRoundTrip31Yaml() throws IOException { + // Create a simple OpenAPI 3.1 document + OpenAPI originalOpenAPI = createBasicOpenAPI31(); + + // Serialize to YAML + String yaml = Yaml31.pretty(originalOpenAPI); + + // Deserialize back + OpenAPI deserializedOpenAPI = Yaml31.mapper().readValue(yaml, OpenAPI.class); + + // Verify basic structure + assertNotNull(deserializedOpenAPI); + assertEquals(deserializedOpenAPI.getOpenapi(), "3.1.0"); + assertEquals(deserializedOpenAPI.getInfo().getTitle(), "Test API"); + assertEquals(deserializedOpenAPI.getInfo().getVersion(), "1.0.0"); + assertEquals(deserializedOpenAPI.getInfo().getSummary(), "Test API Summary"); + assertEquals(deserializedOpenAPI.getInfo().getLicense().getIdentifier(), "MIT"); + + // Verify paths + assertNotNull(deserializedOpenAPI.getPaths()); + assertTrue(deserializedOpenAPI.getPaths().containsKey("/test")); + + // Verify components + assertNotNull(deserializedOpenAPI.getComponents()); + assertNotNull(deserializedOpenAPI.getComponents().getSchemas()); + assertTrue(deserializedOpenAPI.getComponents().getSchemas().containsKey("TestSchema")); + + // Verify webhooks + assertNotNull(deserializedOpenAPI.getWebhooks()); + assertTrue(deserializedOpenAPI.getWebhooks().containsKey("testWebhook")); + + // Serialize again + String yamlAgain = Yaml31.pretty(deserializedOpenAPI); + + // Compare YAML strings + assertEquals(yaml, yamlAgain, "YAML round-trip failed"); + } + + /** + * Test round-trip of OpenAPI 3.1 document with complex structures + */ + @Test + public void testComplexRoundTrip31() throws IOException { + // Create an OpenAPI 3.1 document with complex structures + OpenAPI originalOpenAPI = createComplexOpenAPI31(); + + // Serialize to JSON + String json = Json31.pretty(originalOpenAPI); + + // Deserialize back + OpenAPI deserializedOpenAPI = Json31.mapper().readValue(json, OpenAPI.class); + + // Verify complex structures + + // Verify discriminator extensions + assertNotNull(deserializedOpenAPI.getComponents().getSchemas().get("TestSchema").getDiscriminator()); + assertNotNull(deserializedOpenAPI.getComponents().getSchemas().get("TestSchema").getDiscriminator().getExtensions()); + assertEquals(deserializedOpenAPI.getComponents().getSchemas().get("TestSchema").getDiscriminator().getExtensions().get("x-test-extension"), "test-value"); + + // Verify component path items + assertNotNull(deserializedOpenAPI.getComponents().getPathItems()); + assertTrue(deserializedOpenAPI.getComponents().getPathItems().containsKey("/testPathItem")); + assertEquals(deserializedOpenAPI.getComponents().getPathItems().get("/testPathItem").getDescription(), "Test path item"); + assertEquals(deserializedOpenAPI.getComponents().getPathItems().get("/testPathItem").getSummary(), "Test path item summary"); + assertNotNull(deserializedOpenAPI.getComponents().getPathItems().get("/testPathItem").getGet()); + assertEquals(deserializedOpenAPI.getComponents().getPathItems().get("/testPathItem").getGet().getOperationId(), "getTest"); + + // Verify path item refs and siblings + assertEquals(deserializedOpenAPI.getPaths().get("/refTest").get$ref(), "#/components/pathItems/testPathItem"); + assertEquals(deserializedOpenAPI.getPaths().get("/refTest").getDescription(), "Ref path item description"); + assertEquals(deserializedOpenAPI.getPaths().get("/refTest").getSummary(), "Ref path item summary"); + + // Verify parameter refs and siblings + assertEquals(deserializedOpenAPI.getPaths().get("/test").getGet().getParameters().get(0).get$ref(), "#/components/parameters/testParameter"); + assertEquals(deserializedOpenAPI.getPaths().get("/test").getGet().getParameters().get(0).getDescription(), "Ref parameter description"); + + // Verify example refs and siblings + assertEquals(deserializedOpenAPI.getPaths().get("/test").getGet().getParameters().get(1).getName(), "testParam"); + assertEquals(deserializedOpenAPI.getPaths().get("/test").getGet().getParameters().get(1).getIn(), "query"); + assertEquals(deserializedOpenAPI.getPaths().get("/test").getGet().getParameters().get(1).getExamples().get("refExample").get$ref(), "#/components/examples/testExample"); + assertEquals(deserializedOpenAPI.getPaths().get("/test").getGet().getParameters().get(1).getExamples().get("refExample").getSummary(), "Ref example summary"); + assertEquals(deserializedOpenAPI.getPaths().get("/test").getGet().getParameters().get(1).getExamples().get("refExample").getDescription(), "Ref example description"); + + // Serialize again + String jsonAgain = Json31.pretty(deserializedOpenAPI); + + // Compare JSON strings + assertEquals(json, jsonAgain, "JSON round-trip failed"); + } + + /** + * Test round-trip of OpenAPI 3.1 document with boolean schema + */ + @Test + public void testBooleanSchemaRoundTrip() throws IOException { + // Create an OpenAPI 3.1 document with boolean schema + OpenAPI originalOpenAPI = createBasicOpenAPI31(); + + // Add boolean schema + Schema booleanSchema = new Schema().booleanSchemaValue(true); + originalOpenAPI.getComponents().getSchemas().put("BooleanSchema", booleanSchema); + + // Serialize to JSON + String json = Json31.pretty(originalOpenAPI); + + // Deserialize back + OpenAPI deserializedOpenAPI = Json31.mapper().readValue(json, OpenAPI.class); + + // Verify boolean schema + assertTrue(Boolean.TRUE.equals(deserializedOpenAPI.getComponents().getSchemas().get("BooleanSchema").getBooleanSchemaValue())); + + // Verify that the schema is serialized as a boolean value + ObjectMapper mapper = new ObjectMapper(); + JsonNode jsonNode = mapper.readTree(json); + assertTrue(jsonNode.get("components").get("schemas").get("BooleanSchema").isBoolean()); + assertTrue(jsonNode.get("components").get("schemas").get("BooleanSchema").asBoolean()); + + // Serialize again + String jsonAgain = Json31.pretty(deserializedOpenAPI); + + // Compare JSON strings + assertEquals(json, jsonAgain, "JSON round-trip failed"); + } + + /** + * Test round-trip of OpenAPI 3.1 document with null values + */ + @Test + public void testNullValuesRoundTrip() throws IOException { + // Create an OpenAPI 3.1 document with null values + OpenAPI originalOpenAPI = createBasicOpenAPI31(); + + // Add schema with null example + Schema schema = originalOpenAPI.getComponents().getSchemas().get("TestSchema"); + schema.setExample(null); + schema.setExampleSetFlag(true); + + // Serialize to JSON + String json = Json31.pretty(originalOpenAPI); + + // Deserialize back + OpenAPI deserializedOpenAPI = Json31.mapper().readValue(json, OpenAPI.class); + + // Verify null values + assertNotNull(deserializedOpenAPI.getComponents().getSchemas().get("TestSchema")); + assertEquals(deserializedOpenAPI.getComponents().getSchemas().get("TestSchema").getExample(), null); + assertTrue(deserializedOpenAPI.getComponents().getSchemas().get("TestSchema").getExampleSetFlag()); + + // Serialize again + String jsonAgain = Json31.pretty(deserializedOpenAPI); + + // Compare JSON strings + assertEquals(json, jsonAgain, "JSON round-trip failed"); + } + + /** + * Test round-trip of real-world OpenAPI 3.1 document + */ + @Test + public void testRealWorldRoundTrip31() throws IOException { + // Load a real-world OpenAPI 3.1 document + final String yaml = ResourceUtils.loadClassResource(getClass(), "specFiles/3.1.0/petstore-3.1_sample.yaml"); + final OpenAPI originalOpenAPI = Yaml31.mapper().readValue(yaml, OpenAPI.class); + + // Serialize to YAML + String serializedYaml = Yaml31.pretty(originalOpenAPI); + + // Deserialize back + OpenAPI deserializedOpenAPI = Yaml31.mapper().readValue(serializedYaml, OpenAPI.class); + + // Verify basic structure + assertNotNull(deserializedOpenAPI); + assertEquals(deserializedOpenAPI.getOpenapi(), "3.1.0"); + assertEquals(deserializedOpenAPI.getInfo().getTitle(), "Swagger Petstore"); + assertEquals(deserializedOpenAPI.getInfo().getVersion(), "1.0.0"); + assertEquals(deserializedOpenAPI.getInfo().getSummary(), "petstore sample for OAS 3.1.0"); + assertEquals(deserializedOpenAPI.getInfo().getLicense().getName(), "MIT"); + assertEquals(deserializedOpenAPI.getInfo().getLicense().getIdentifier(), "test"); + + // Verify webhooks + assertNotNull(deserializedOpenAPI.getWebhooks()); + assertNotNull(deserializedOpenAPI.getWebhooks().get("newPet")); + assertNotNull(deserializedOpenAPI.getWebhooks().get("newPet").getPost()); + + // Verify component path items + assertNotNull(deserializedOpenAPI.getComponents().getPathItems()); + assertNotNull(deserializedOpenAPI.getComponents().getPathItems().get("/pet")); + assertEquals(deserializedOpenAPI.getComponents().getPathItems().get("/pet").getDescription(), "get a pet"); + assertNotNull(deserializedOpenAPI.getComponents().getPathItems().get("/pet").getGet()); + assertEquals(deserializedOpenAPI.getComponents().getPathItems().get("/pet").getGet().getOperationId(), "getPet"); + + // Serialize again + String yamlAgain = Yaml31.pretty(deserializedOpenAPI); + + // Compare YAML strings (normalize whitespace) + assertEquals(normalizeWhitespace(serializedYaml), normalizeWhitespace(yamlAgain), "YAML round-trip failed"); + } + + /** + * Helper method to normalize whitespace in YAML strings for comparison + */ + private String normalizeWhitespace(String yaml) { + return yaml.replaceAll("\\s+", " ").trim(); + } + + /** + * Helper method to create a basic OpenAPI 3.0 document + */ + private OpenAPI createBasicOpenAPI30() { + OpenAPI openAPI = new OpenAPI(); + openAPI.setOpenapi("3.0.1"); + + // Add info + Info info = new Info() + .title("Test API") + .version("1.0.0") + .description("Test API Description") + .contact(new Contact().name("Test Contact").email("test@example.com")) + .license(new License().name("MIT")); + openAPI.setInfo(info); + + // Add paths + Paths paths = new Paths(); + PathItem pathItem = new PathItem(); + + // Add GET operation + Operation getOperation = new Operation() + .operationId("getTest") + .summary("Get Test") + .description("Get Test Description"); + + // Add responses + ApiResponses responses = new ApiResponses(); + ApiResponse response = new ApiResponse() + .description("Successful response"); + responses.addApiResponse("200", response); + getOperation.setResponses(responses); + + pathItem.setGet(getOperation); + paths.addPathItem("/test", pathItem); + openAPI.setPaths(paths); + + // Add components + Components components = new Components(); + Schema schema = new Schema() + .type("object") + .title("TestSchema") + .description("Test Schema Description"); + components.addSchemas("TestSchema", schema); + openAPI.setComponents(components); + + return openAPI; + } + + /** + * Helper method to create a basic OpenAPI 3.1 document + */ + private OpenAPI createBasicOpenAPI31() { + OpenAPI openAPI = new OpenAPI(); + openAPI.setOpenapi("3.1.0"); + + // Add info + Info info = new Info() + .title("Test API") + .version("1.0.0") + .description("Test API Description") + .summary("Test API Summary") + .contact(new Contact().name("Test Contact").email("test@example.com")) + .license(new License().name("MIT").identifier("MIT")); + openAPI.setInfo(info); + + // Add paths + Paths paths = new Paths(); + PathItem pathItem = new PathItem(); + + // Add GET operation + Operation getOperation = new Operation() + .operationId("getTest") + .summary("Get Test") + .description("Get Test Description"); + + // Add responses + ApiResponses responses = new ApiResponses(); + ApiResponse response = new ApiResponse() + .description("Successful response"); + responses.addApiResponse("200", response); + getOperation.setResponses(responses); + + pathItem.setGet(getOperation); + paths.addPathItem("/test", pathItem); + openAPI.setPaths(paths); + + // Add components + Components components = new Components(); + Schema schema = new Schema() + .type("object") + .title("TestSchema") + .description("Test Schema Description"); + components.addSchemas("TestSchema", schema); + openAPI.setComponents(components); + + // Add webhooks + Map webhooks = new HashMap<>(); + PathItem webhookPathItem = new PathItem(); + Operation postOperation = new Operation() + .operationId("postWebhook") + .summary("Post Webhook") + .description("Post Webhook Description"); + webhookPathItem.setPost(postOperation); + webhooks.put("testWebhook", webhookPathItem); + openAPI.setWebhooks(webhooks); + + return openAPI; + } + + /** + * Helper method to create a complex OpenAPI 3.1 document + */ + private OpenAPI createComplexOpenAPI31() { + OpenAPI openAPI = createBasicOpenAPI31(); + + // Add discriminator with extensions + Schema schema = openAPI.getComponents().getSchemas().get("TestSchema"); + Discriminator discriminator = new Discriminator().propertyName("type"); + Map extensions = new HashMap<>(); + extensions.put("x-test-extension", "test-value"); + discriminator.setExtensions(extensions); + schema.setDiscriminator(discriminator); + + // Add component path items + PathItem pathItem = new PathItem() + .description("Test path item") + .summary("Test path item summary"); + pathItem.setGet(new Operation().operationId("getTest")); + + openAPI.getComponents().pathItems(new HashMap<>()); + openAPI.getComponents().getPathItems().put("/testPathItem", pathItem); + + // Add path with reference and siblings + PathItem refPathItem = new PathItem() + .$ref("#/components/pathItems/testPathItem") + .description("Ref path item description") + .summary("Ref path item summary"); + openAPI.getPaths().put("/refTest", refPathItem); + + // Add component parameters + Parameter parameter = new Parameter() + .name("testParam") + .in("query") + .description("Test parameter"); + + openAPI.getComponents().parameters(new HashMap<>()); + openAPI.getComponents().getParameters().put("testParameter", parameter); + + // Add operation with parameter reference and siblings + Parameter refParameter = new Parameter() + .$ref("#/components/parameters/testParameter") + .description("Ref parameter description"); + + openAPI.getPaths().get("/test").getGet().addParametersItem(refParameter); + + // Add component examples + Example example = new Example() + .summary("Test example summary") + .description("Test example description") + .value("Test example value"); + + openAPI.getComponents().examples(new HashMap<>()); + openAPI.getComponents().getExamples().put("testExample", example); + + // Add parameter with example reference and siblings + Parameter paramWithExample = new Parameter() + .name("testParam") + .in("query") + .description("Test parameter"); + + Example refExample = new Example() + .$ref("#/components/examples/testExample") + .summary("Ref example summary") + .description("Ref example description"); + + paramWithExample.examples(new HashMap<>()); + paramWithExample.getExamples().put("refExample", refExample); + + openAPI.getPaths().get("/test").getGet().addParametersItem(paramWithExample); + + return openAPI; + } +} \ No newline at end of file diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/ComprehensiveSerializationTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/ComprehensiveSerializationTest.java new file mode 100644 index 0000000000..24d9489c06 --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/serialization/ComprehensiveSerializationTest.java @@ -0,0 +1,511 @@ +package io.swagger.v3.core.serialization; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.v3.core.util.Json; +import io.swagger.v3.core.util.Json31; +import io.swagger.v3.core.util.Yaml; +import io.swagger.v3.core.util.Yaml31; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.media.Discriminator; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.oas.models.responses.ApiResponses; +import io.swagger.v3.oas.models.examples.Example; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +/** + * Comprehensive tests for serialization of OpenAPI 3.0 and 3.1 documents. + * This test class covers various scenarios, edge cases, and combinations of OpenAPI elements. + */ +public class ComprehensiveSerializationTest { + + /** + * Test basic serialization of OpenAPI 3.0 document to JSON + */ + @Test + public void testBasicSerialization30Json() throws IOException { + // Create a simple OpenAPI 3.0 document + OpenAPI openAPI = createBasicOpenAPI30(); + + // Serialize to JSON + String json = Json.pretty(openAPI); + + // Deserialize back to verify + OpenAPI deserializedOpenAPI = Json.mapper().readValue(json, OpenAPI.class); + + // Verify basic structure + assertNotNull(deserializedOpenAPI); + assertEquals(deserializedOpenAPI.getOpenapi(), "3.0.1"); + assertEquals(deserializedOpenAPI.getInfo().getTitle(), "Test API"); + assertEquals(deserializedOpenAPI.getInfo().getVersion(), "1.0.0"); + + // Verify paths + assertNotNull(deserializedOpenAPI.getPaths()); + assertTrue(deserializedOpenAPI.getPaths().containsKey("/test")); + + // Verify components + assertNotNull(deserializedOpenAPI.getComponents()); + assertNotNull(deserializedOpenAPI.getComponents().getSchemas()); + assertTrue(deserializedOpenAPI.getComponents().getSchemas().containsKey("TestSchema")); + } + + /** + * Test basic serialization of OpenAPI 3.0 document to YAML + */ + @Test + public void testBasicSerialization30Yaml() throws IOException { + // Create a simple OpenAPI 3.0 document + OpenAPI openAPI = createBasicOpenAPI30(); + + // Serialize to YAML + String yaml = Yaml.pretty(openAPI); + + // Deserialize back to verify + OpenAPI deserializedOpenAPI = Yaml.mapper().readValue(yaml, OpenAPI.class); + + // Verify basic structure + assertNotNull(deserializedOpenAPI); + assertEquals(deserializedOpenAPI.getOpenapi(), "3.0.1"); + assertEquals(deserializedOpenAPI.getInfo().getTitle(), "Test API"); + assertEquals(deserializedOpenAPI.getInfo().getVersion(), "1.0.0"); + + // Verify paths + assertNotNull(deserializedOpenAPI.getPaths()); + assertTrue(deserializedOpenAPI.getPaths().containsKey("/test")); + + // Verify components + assertNotNull(deserializedOpenAPI.getComponents()); + assertNotNull(deserializedOpenAPI.getComponents().getSchemas()); + assertTrue(deserializedOpenAPI.getComponents().getSchemas().containsKey("TestSchema")); + } + + /** + * Test basic serialization of OpenAPI 3.1 document to JSON + */ + @Test + public void testBasicSerialization31Json() throws IOException { + // Create a simple OpenAPI 3.1 document + OpenAPI openAPI = createBasicOpenAPI31(); + + // Serialize to JSON + String json = Json31.pretty(openAPI); + + // Deserialize back to verify + OpenAPI deserializedOpenAPI = Json31.mapper().readValue(json, OpenAPI.class); + + // Verify basic structure + assertNotNull(deserializedOpenAPI); + assertEquals(deserializedOpenAPI.getOpenapi(), "3.1.0"); + assertEquals(deserializedOpenAPI.getInfo().getTitle(), "Test API"); + assertEquals(deserializedOpenAPI.getInfo().getVersion(), "1.0.0"); + assertEquals(deserializedOpenAPI.getInfo().getSummary(), "Test API Summary"); + assertEquals(deserializedOpenAPI.getInfo().getLicense().getIdentifier(), "MIT"); + + // Verify paths + assertNotNull(deserializedOpenAPI.getPaths()); + assertTrue(deserializedOpenAPI.getPaths().containsKey("/test")); + + // Verify components + assertNotNull(deserializedOpenAPI.getComponents()); + assertNotNull(deserializedOpenAPI.getComponents().getSchemas()); + assertTrue(deserializedOpenAPI.getComponents().getSchemas().containsKey("TestSchema")); + + // Verify webhooks + assertNotNull(deserializedOpenAPI.getWebhooks()); + assertTrue(deserializedOpenAPI.getWebhooks().containsKey("testWebhook")); + } + + /** + * Test basic serialization of OpenAPI 3.1 document to YAML + */ + @Test + public void testBasicSerialization31Yaml() throws IOException { + // Create a simple OpenAPI 3.1 document + OpenAPI openAPI = createBasicOpenAPI31(); + + // Serialize to YAML + String yaml = Yaml31.pretty(openAPI); + + // Deserialize back to verify + OpenAPI deserializedOpenAPI = Yaml31.mapper().readValue(yaml, OpenAPI.class); + + // Verify basic structure + assertNotNull(deserializedOpenAPI); + assertEquals(deserializedOpenAPI.getOpenapi(), "3.1.0"); + assertEquals(deserializedOpenAPI.getInfo().getTitle(), "Test API"); + assertEquals(deserializedOpenAPI.getInfo().getVersion(), "1.0.0"); + assertEquals(deserializedOpenAPI.getInfo().getSummary(), "Test API Summary"); + assertEquals(deserializedOpenAPI.getInfo().getLicense().getIdentifier(), "MIT"); + + // Verify paths + assertNotNull(deserializedOpenAPI.getPaths()); + assertTrue(deserializedOpenAPI.getPaths().containsKey("/test")); + + // Verify components + assertNotNull(deserializedOpenAPI.getComponents()); + assertNotNull(deserializedOpenAPI.getComponents().getSchemas()); + assertTrue(deserializedOpenAPI.getComponents().getSchemas().containsKey("TestSchema")); + + // Verify webhooks + assertNotNull(deserializedOpenAPI.getWebhooks()); + assertTrue(deserializedOpenAPI.getWebhooks().containsKey("testWebhook")); + } + + /** + * Test serialization of OpenAPI 3.1 document with component path items + */ + @Test + public void testComponentPathItemsSerialization() throws IOException { + // Create an OpenAPI 3.1 document with component path items + OpenAPI openAPI = createBasicOpenAPI31(); + + // Add component path items + PathItem pathItem = new PathItem() + .description("Test path item") + .summary("Test path item summary"); + pathItem.setGet(new Operation().operationId("getTest")); + + openAPI.getComponents().pathItems(new HashMap<>()); + openAPI.getComponents().getPathItems().put("/testPathItem", pathItem); + + // Serialize to JSON + String json = Json31.pretty(openAPI); + + // Deserialize back to verify + OpenAPI deserializedOpenAPI = Json31.mapper().readValue(json, OpenAPI.class); + + // Verify component path items + assertNotNull(deserializedOpenAPI.getComponents().getPathItems()); + assertTrue(deserializedOpenAPI.getComponents().getPathItems().containsKey("/testPathItem")); + assertEquals(deserializedOpenAPI.getComponents().getPathItems().get("/testPathItem").getDescription(), "Test path item"); + assertEquals(deserializedOpenAPI.getComponents().getPathItems().get("/testPathItem").getSummary(), "Test path item summary"); + assertNotNull(deserializedOpenAPI.getComponents().getPathItems().get("/testPathItem").getGet()); + assertEquals(deserializedOpenAPI.getComponents().getPathItems().get("/testPathItem").getGet().getOperationId(), "getTest"); + } + + /** + * Test serialization of OpenAPI 3.1 document with discriminator extensions + */ + @Test + public void testDiscriminatorExtensionsSerialization() throws IOException { + // Create an OpenAPI 3.1 document with discriminator extensions + OpenAPI openAPI = createBasicOpenAPI31(); + + // Add discriminator with extensions + Schema schema = openAPI.getComponents().getSchemas().get("TestSchema"); + Discriminator discriminator = new Discriminator().propertyName("type"); + Map extensions = new HashMap<>(); + extensions.put("x-test-extension", "test-value"); + discriminator.setExtensions(extensions); + schema.setDiscriminator(discriminator); + + // Serialize to JSON + String json = Json31.pretty(openAPI); + + // Deserialize back to verify + OpenAPI deserializedOpenAPI = Json31.mapper().readValue(json, OpenAPI.class); + + // Verify discriminator extensions + assertNotNull(deserializedOpenAPI.getComponents().getSchemas().get("TestSchema").getDiscriminator()); + assertNotNull(deserializedOpenAPI.getComponents().getSchemas().get("TestSchema").getDiscriminator().getExtensions()); + assertEquals(deserializedOpenAPI.getComponents().getSchemas().get("TestSchema").getDiscriminator().getExtensions().get("x-test-extension"), "test-value"); + } + + /** + * Test serialization of OpenAPI 3.1 document with path item references and siblings + */ + @Test + public void testPathItemRefsAndSiblingsSerialization() throws IOException { + // Create an OpenAPI 3.1 document with path item references and siblings + OpenAPI openAPI = createBasicOpenAPI31(); + + // Add component path items + PathItem pathItem = new PathItem() + .description("Test path item") + .summary("Test path item summary"); + pathItem.setGet(new Operation().operationId("getTest")); + + openAPI.getComponents().pathItems(new HashMap<>()); + openAPI.getComponents().getPathItems().put("testPathItem", pathItem); + + // Add path with reference and siblings + PathItem refPathItem = new PathItem() + .$ref("#/components/pathItems/testPathItem") + .description("Ref path item description") + .summary("Ref path item summary"); + openAPI.getPaths().put("/refTest", refPathItem); + + // Serialize to JSON + String json = Json31.pretty(openAPI); + + // Deserialize back to verify + OpenAPI deserializedOpenAPI = Json31.mapper().readValue(json, OpenAPI.class); + + // Verify path item refs and siblings + assertEquals(deserializedOpenAPI.getPaths().get("/refTest").get$ref(), "#/components/pathItems/testPathItem"); + assertEquals(deserializedOpenAPI.getPaths().get("/refTest").getDescription(), "Ref path item description"); + assertEquals(deserializedOpenAPI.getPaths().get("/refTest").getSummary(), "Ref path item summary"); + } + + /** + * Test serialization of OpenAPI 3.1 document with parameter references and siblings + */ + @Test + public void testParameterRefsAndSiblingsSerialization() throws IOException { + // Create an OpenAPI 3.1 document with parameter references and siblings + OpenAPI openAPI = createBasicOpenAPI31(); + + // Add component parameters + Parameter parameter = new Parameter() + .name("testParam") + .in("query") + .description("Test parameter"); + + openAPI.getComponents().parameters(new HashMap<>()); + openAPI.getComponents().getParameters().put("testParameter", parameter); + + // Add operation with parameter reference and siblings + Parameter refParameter = new Parameter() + .$ref("#/components/parameters/testParameter") + .description("Ref parameter description"); + + openAPI.getPaths().get("/test").getGet().addParametersItem(refParameter); + + // Serialize to JSON + String json = Json31.pretty(openAPI); + + // Deserialize back to verify + OpenAPI deserializedOpenAPI = Json31.mapper().readValue(json, OpenAPI.class); + + // Verify parameter refs and siblings + assertEquals(deserializedOpenAPI.getPaths().get("/test").getGet().getParameters().get(0).get$ref(), "#/components/parameters/testParameter"); + assertEquals(deserializedOpenAPI.getPaths().get("/test").getGet().getParameters().get(0).getDescription(), "Ref parameter description"); + } + + /** + * Test serialization of OpenAPI 3.1 document with example references and siblings + */ + @Test + public void testExampleRefsAndSiblingsSerialization() throws IOException { + // Create an OpenAPI 3.1 document with example references and siblings + OpenAPI openAPI = createBasicOpenAPI31(); + + // Add component examples + Example example = new Example() + .summary("Test example summary") + .description("Test example description") + .value("Test example value"); + + openAPI.getComponents().examples(new HashMap<>()); + openAPI.getComponents().getExamples().put("testExample", example); + + // Add parameter with example reference and siblings + Parameter parameter = new Parameter() + .name("testParam") + .in("query") + .description("Test parameter"); + + Example refExample = new Example() + .$ref("#/components/examples/testExample") + .summary("Ref example summary") + .description("Ref example description"); + + parameter.examples(new HashMap<>()); + parameter.getExamples().put("refExample", refExample); + + openAPI.getPaths().get("/test").getGet().addParametersItem(parameter); + + // Serialize to JSON + String json = Json31.pretty(openAPI); + + // Deserialize back to verify + OpenAPI deserializedOpenAPI = Json31.mapper().readValue(json, OpenAPI.class); + + // Verify example refs and siblings + assertEquals(deserializedOpenAPI.getPaths().get("/test").getGet().getParameters().get(0).getName(), "testParam"); + assertEquals(deserializedOpenAPI.getPaths().get("/test").getGet().getParameters().get(0).getIn(), "query"); + assertEquals(deserializedOpenAPI.getPaths().get("/test").getGet().getParameters().get(0).getExamples().get("refExample").get$ref(), "#/components/examples/testExample"); + assertEquals(deserializedOpenAPI.getPaths().get("/test").getGet().getParameters().get(0).getExamples().get("refExample").getSummary(), "Ref example summary"); + assertEquals(deserializedOpenAPI.getPaths().get("/test").getGet().getParameters().get(0).getExamples().get("refExample").getDescription(), "Ref example description"); + } + + /** + * Test serialization of OpenAPI 3.1 document with boolean schema + */ + @Test + public void testBooleanSchemaSerialization() throws IOException { + // Create an OpenAPI 3.1 document with boolean schema + OpenAPI openAPI = createBasicOpenAPI31(); + + // Add boolean schema + Schema booleanSchema = new Schema().booleanSchemaValue(true); + openAPI.getComponents().getSchemas().put("BooleanSchema", booleanSchema); + + // Serialize to JSON + String json = Json31.pretty(openAPI); + + // Deserialize back to verify + OpenAPI deserializedOpenAPI = Json31.mapper().readValue(json, OpenAPI.class); + + // Verify boolean schema + assertTrue(Boolean.TRUE.equals(deserializedOpenAPI.getComponents().getSchemas().get("BooleanSchema").getBooleanSchemaValue())); + + // Verify that the schema is serialized as a boolean value + ObjectMapper mapper = new ObjectMapper(); + JsonNode jsonNode = mapper.readTree(json); + assertTrue(jsonNode.get("components").get("schemas").get("BooleanSchema").isBoolean()); + assertTrue(jsonNode.get("components").get("schemas").get("BooleanSchema").asBoolean()); + } + + /** + * Test serialization of OpenAPI 3.1 document with null values + */ + @Test + public void testNullValuesSerialization() throws IOException { + // Create an OpenAPI 3.1 document with null values + OpenAPI openAPI = createBasicOpenAPI31(); + + // Add schema with null example + Schema schema = openAPI.getComponents().getSchemas().get("TestSchema"); + schema.setExample(null); + schema.setExampleSetFlag(true); + + // Serialize to JSON + String json = Json31.pretty(openAPI); + + // Deserialize back to verify + OpenAPI deserializedOpenAPI = Json31.mapper().readValue(json, OpenAPI.class); + + // Verify null values + assertNotNull(deserializedOpenAPI.getComponents().getSchemas().get("TestSchema")); + assertEquals(deserializedOpenAPI.getComponents().getSchemas().get("TestSchema").getExample(), null); + assertTrue(deserializedOpenAPI.getComponents().getSchemas().get("TestSchema").getExampleSetFlag()); + } + + /** + * Helper method to create a basic OpenAPI 3.0 document + */ + private OpenAPI createBasicOpenAPI30() { + OpenAPI openAPI = new OpenAPI(); + openAPI.setOpenapi("3.0.1"); + + // Add info + Info info = new Info() + .title("Test API") + .version("1.0.0") + .description("Test API Description") + .contact(new Contact().name("Test Contact").email("test@example.com")) + .license(new License().name("MIT")); + openAPI.setInfo(info); + + // Add paths + Paths paths = new Paths(); + PathItem pathItem = new PathItem(); + + // Add GET operation + Operation getOperation = new Operation() + .operationId("getTest") + .summary("Get Test") + .description("Get Test Description"); + + // Add responses + ApiResponses responses = new ApiResponses(); + ApiResponse response = new ApiResponse() + .description("Successful response"); + responses.addApiResponse("200", response); + getOperation.setResponses(responses); + + pathItem.setGet(getOperation); + paths.addPathItem("/test", pathItem); + openAPI.setPaths(paths); + + // Add components + Components components = new Components(); + Schema schema = new Schema() + .type("object") + .title("TestSchema") + .description("Test Schema Description"); + components.addSchemas("TestSchema", schema); + openAPI.setComponents(components); + + return openAPI; + } + + /** + * Helper method to create a basic OpenAPI 3.1 document + */ + private OpenAPI createBasicOpenAPI31() { + OpenAPI openAPI = new OpenAPI(); + openAPI.setOpenapi("3.1.0"); + + // Add info + Info info = new Info() + .title("Test API") + .version("1.0.0") + .description("Test API Description") + .summary("Test API Summary") + .contact(new Contact().name("Test Contact").email("test@example.com")) + .license(new License().name("MIT").identifier("MIT")); + openAPI.setInfo(info); + + // Add paths + Paths paths = new Paths(); + PathItem pathItem = new PathItem(); + + // Add GET operation + Operation getOperation = new Operation() + .operationId("getTest") + .summary("Get Test") + .description("Get Test Description"); + + // Add responses + ApiResponses responses = new ApiResponses(); + ApiResponse response = new ApiResponse() + .description("Successful response"); + responses.addApiResponse("200", response); + getOperation.setResponses(responses); + + pathItem.setGet(getOperation); + paths.addPathItem("/test", pathItem); + openAPI.setPaths(paths); + + // Add components + Components components = new Components(); + Schema schema = new Schema() + .type("object") + .title("TestSchema") + .description("Test Schema Description"); + components.addSchemas("TestSchema", schema); + openAPI.setComponents(components); + + // Add webhooks + Map webhooks = new HashMap<>(); + PathItem webhookPathItem = new PathItem(); + Operation postOperation = new Operation() + .operationId("postWebhook") + .summary("Post Webhook") + .description("Post Webhook Description"); + webhookPathItem.setPost(postOperation); + webhooks.put("testWebhook", webhookPathItem); + openAPI.setWebhooks(webhooks); + + return openAPI; + } +} \ No newline at end of file diff --git a/modules/swagger-core/src/test/resources/comprehensiveOAS31/comprehensive-openapi.yaml b/modules/swagger-core/src/test/resources/comprehensiveOAS31/comprehensive-openapi.yaml new file mode 100644 index 0000000000..535f28c75e --- /dev/null +++ b/modules/swagger-core/src/test/resources/comprehensiveOAS31/comprehensive-openapi.yaml @@ -0,0 +1,831 @@ +# Comprehensive OpenAPI 3.1 and JSON Schema 2020/12 Example +# This file demonstrates a comprehensive OpenAPI 3.1 document with JSON Schema 2020/12 features +# It includes all keywords defined in both specifications and uses both internal and external references + +openapi: 3.1.0 +jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema + +# Basic information about the API +info: + title: Comprehensive Pet Store API + version: 1.0.0 + summary: A comprehensive example of an OpenAPI 3.1 document with JSON Schema 2020/12 features + description: | + This is a comprehensive example of an OpenAPI 3.1 document that includes all keywords defined in both + OpenAPI 3.1 and JSON Schema 2020/12 specifications. It demonstrates the use of both internal and external + references, as well as seldom used constructs like $schema, $id, and $dynamicAnchor. + + The API allows you to manage pets, users, and orders in a pet store. + termsOfService: https://example.com/terms/ + contact: + name: API Support + url: https://example.com/support + email: support@example.com + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + identifier: Apache-2.0 + +# Server information +servers: + - url: https://api.example.com/v1 + description: Production server + - url: https://staging-api.example.com/v1 + description: Staging server + variables: + port: + enum: + - '8443' + - '443' + default: '443' + basePath: + default: v1 + - url: https://dev-api.example.com/v1 + description: Development server + - url: http://localhost:{port}/{basePath} + description: Local development server + variables: + port: + enum: + - '8080' + - '8081' + default: '8080' + basePath: + default: v1 + +# External documentation +externalDocs: + description: Find more info here + url: https://example.com/docs + +# Tags for API documentation control and organization +tags: + - name: pets + description: Pet operations + externalDocs: + description: Find more info about pets + url: https://example.com/docs/pets + - name: users + description: User operations + externalDocs: + description: Find more info about users + url: https://example.com/docs/users + - name: orders + description: Order operations + externalDocs: + description: Find more info about orders + url: https://example.com/docs/orders + +# Security schemes +security: + - petstore_auth: + - read:pets + - write:pets + - user_auth: + - read:users + - write:users + - order_auth: + - read:orders + - write:orders + - api_key: [] + +# Paths +paths: + # Pet paths + /pets: + $ref: "./paths/pet-paths.yaml#/~1pets" + /pets/{petId}: + $ref: "./paths/pet-paths.yaml#/~1pets~1{petId}" + /pets/findByStatus: + $ref: "./paths/pet-paths.yaml#/~1pets~1findByStatus" + /pets/findByTags: + $ref: "./paths/pet-paths.yaml#/~1pets~1findByTags" + /pets/{petId}/uploadImage: + $ref: "./paths/pet-paths.yaml#/~1pets~1{petId}~1uploadImage" + + # User paths + /users: + $ref: "./paths/user-paths.yaml#/~1users" + /users/{userId}: + $ref: "./paths/user-paths.yaml#/~1users~1{userId}" + /users/login: + $ref: "./paths/user-paths.yaml#/~1users~1login" + /users/logout: + $ref: "./paths/user-paths.yaml#/~1users~1logout" + /users/{userId}/preferences: + $ref: "./paths/user-paths.yaml#/~1users~1{userId}~1preferences" + + # Order paths + /orders: + $ref: "./paths/order-paths.yaml#/~1orders" + /orders/{orderId}: + $ref: "./paths/order-paths.yaml#/~1orders~1{orderId}" + /orders/{orderId}/status: + $ref: "./paths/order-paths.yaml#/~1orders~1{orderId}~1status" + /orders/{orderId}/invoice: + $ref: "./paths/order-paths.yaml#/~1orders~1{orderId}~1invoice" + /orders/{orderId}/shipment: + $ref: "./paths/order-paths.yaml#/~1orders~1{orderId}~1shipment" + + # Additional path with inline definition + /health: + get: + summary: Health check + description: Returns the health status of the API + operationId: healthCheck + tags: + - system + responses: + '200': + description: Health check response + content: + application/json: + schema: + type: object + properties: + status: + type: string + enum: [ok, degraded, down] + version: + type: string + timestamp: + type: string + format: date-time + required: + - status + - version + - timestamp + examples: + healthy: + summary: Healthy response + value: + status: ok + version: 1.0.0 + timestamp: "2023-01-01T12:00:00Z" + degraded: + summary: Degraded response + value: + status: degraded + version: 1.0.0 + timestamp: "2023-01-01T12:00:00Z" + security: [] + +# Webhooks +webhooks: + newPet: + post: + summary: New pet webhook + description: Webhook for when a new pet is created + operationId: newPetWebhook + tags: + - pets + - webhooks + requestBody: + description: Information about the new pet + required: true + content: + application/json: + schema: + $ref: "./schemas/pet-schemas.yaml#/Pet" + responses: + '200': + description: Webhook processed successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + updatedPet: + post: + summary: Updated pet webhook + description: Webhook for when a pet is updated + operationId: updatedPetWebhook + tags: + - pets + - webhooks + requestBody: + description: Information about the updated pet + required: true + content: + application/json: + schema: + $ref: "./schemas/pet-schemas.yaml#/Pet" + responses: + '200': + description: Webhook processed successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + newOrder: + post: + summary: New order webhook + description: Webhook for when a new order is created + operationId: newOrderWebhook + tags: + - orders + - webhooks + requestBody: + description: Information about the new order + required: true + content: + application/json: + schema: + $ref: "./schemas/order-schemas.yaml#/Order" + responses: + '200': + description: Webhook processed successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + +# Components +components: + # Schemas + schemas: + # Common schemas + Error: + $ref: "./schemas/common-schemas.yaml#/Error" + Identifiable: + $ref: "./schemas/common-schemas.yaml#/Identifiable" + ExtendedSchema: + $ref: "./schemas/common-schemas.yaml#/ExtendedSchema" + CommentedSchema: + $ref: "./schemas/common-schemas.yaml#/CommentedSchema" + SchemaWithDefs: + $ref: "./schemas/common-schemas.yaml#/SchemaWithDefs" + ValidationSchema: + $ref: "./schemas/common-schemas.yaml#/ValidationSchema" + ApplicatorSchema: + $ref: "./schemas/common-schemas.yaml#/ApplicatorSchema" + + # Pet schemas + Pet: + $ref: "./schemas/pet-schemas.yaml#/Pet" + Category: + $ref: "./schemas/pet-schemas.yaml#/Category" + Tag: + $ref: "./schemas/pet-schemas.yaml#/Tag" + Dog: + $ref: "./schemas/pet-schemas.yaml#/Dog" + Cat: + $ref: "./schemas/pet-schemas.yaml#/Cat" + Bird: + $ref: "./schemas/pet-schemas.yaml#/Bird" + Pets: + $ref: "./schemas/pet-schemas.yaml#/Pets" + PetWithValidation: + $ref: "./schemas/pet-schemas.yaml#/PetWithValidation" + PetWithConditional: + $ref: "./schemas/pet-schemas.yaml#/PetWithConditional" + + # User schemas + User: + $ref: "./schemas/user-schemas.yaml#/User" + Address: + $ref: "./schemas/user-schemas.yaml#/Address" + UserArray: + $ref: "./schemas/user-schemas.yaml#/UserArray" + UserWithRoles: + $ref: "./schemas/user-schemas.yaml#/UserWithRoles" + Role: + $ref: "./schemas/user-schemas.yaml#/Role" + Permission: + $ref: "./schemas/user-schemas.yaml#/Permission" + UserCredentials: + $ref: "./schemas/user-schemas.yaml#/UserCredentials" + UserPreferences: + $ref: "./schemas/user-schemas.yaml#/UserPreferences" + UserWithPreferences: + $ref: "./schemas/user-schemas.yaml#/UserWithPreferences" + + # Order schemas + Order: + $ref: "./schemas/order-schemas.yaml#/Order" + OrderItem: + $ref: "./schemas/order-schemas.yaml#/OrderItem" + OrderWithItems: + $ref: "./schemas/order-schemas.yaml#/OrderWithItems" + OrderStatus: + $ref: "./schemas/order-schemas.yaml#/OrderStatus" + OrderArray: + $ref: "./schemas/order-schemas.yaml#/OrderArray" + OrderSummary: + $ref: "./schemas/order-schemas.yaml#/OrderSummary" + Invoice: + $ref: "./schemas/order-schemas.yaml#/Invoice" + Payment: + $ref: "./schemas/order-schemas.yaml#/Payment" + Shipment: + $ref: "./schemas/order-schemas.yaml#/Shipment" + + # External JSON Schema reference + JsonSchema: + $ref: "./schemas/json-schema.yaml" + + # Additional inline schemas with JSON Schema 2020/12 features + BooleanSchema: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/BooleanSchema" + $anchor: "BooleanSchema" + title: "Boolean Schema" + description: "A schema that is just a boolean value" + type: boolean + + NullSchema: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/NullSchema" + $anchor: "NullSchema" + title: "Null Schema" + description: "A schema that is just a null value" + type: "null" + + MultiTypeSchema: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/MultiTypeSchema" + $anchor: "MultiTypeSchema" + title: "Multi-Type Schema" + description: "A schema that accepts multiple types" + type: [string, number, boolean, "null"] + + RecursiveSchema: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/RecursiveSchema" + $anchor: "RecursiveSchema" + title: "Recursive Schema" + description: "A schema that references itself" + type: object + properties: + id: + type: string + name: + type: string + children: + type: array + items: + $ref: "#/components/schemas/RecursiveSchema" + required: + - id + - name + + UnevaluatedSchema: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/UnevaluatedSchema" + $anchor: "UnevaluatedSchema" + title: "Unevaluated Schema" + description: "A schema that uses unevaluatedProperties and unevaluatedItems" + type: object + properties: + id: + type: string + name: + type: string + unevaluatedProperties: false + allOf: + - properties: + age: + type: integer + unevaluatedProperties: false + + ContentSchema: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/ContentSchema" + $anchor: "ContentSchema" + title: "Content Schema" + description: "A schema that uses contentEncoding and contentMediaType" + type: string + contentEncoding: base64 + contentMediaType: image/png + + DynamicRefSchema: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/DynamicRefSchema" + $anchor: "DynamicRefSchema" + title: "Dynamic Ref Schema" + description: "A schema that uses $dynamicRef" + type: object + properties: + entity: + $dynamicRef: "#Identifiable" + required: + - entity + + # Path items + pathItems: + petPath: + summary: Pet path item + description: Path item for pet operations + get: + summary: Get pet + description: Returns a pet + operationId: getPet + tags: + - pets + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + minimum: 1 + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "./schemas/pet-schemas.yaml#/Pet" + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + '404': + description: Pet not found + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + security: + - petstore_auth: + - read:pets + - api_key: [] + userPath: + summary: User path item + description: Path item for user operations + get: + summary: Get user + description: Returns a user + operationId: getUser + tags: + - users + parameters: + - name: userId + in: path + description: ID of user to return + required: true + schema: + type: integer + format: int64 + minimum: 1 + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "./schemas/user-schemas.yaml#/User" + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + '404': + description: User not found + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + security: + - user_auth: + - read:users + - api_key: [] + orderPath: + summary: Order path item + description: Path item for order operations + get: + summary: Get order + description: Returns an order + operationId: getOrder + tags: + - orders + parameters: + - name: orderId + in: path + description: ID of order to return + required: true + schema: + type: integer + format: int64 + minimum: 1 + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "./schemas/order-schemas.yaml#/Order" + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + '404': + description: Order not found + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + security: + - order_auth: + - read:orders + - api_key: [] + + # Parameters + parameters: + limitParam: + name: limit + in: query + description: Maximum number of items to return + required: false + schema: + type: integer + format: int32 + minimum: 1 + maximum: 100 + default: 20 + offsetParam: + name: offset + in: query + description: Offset for pagination + required: false + schema: + type: integer + format: int32 + minimum: 0 + default: 0 + petIdParam: + name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + minimum: 1 + userIdParam: + name: userId + in: path + description: ID of user to return + required: true + schema: + type: integer + format: int64 + minimum: 1 + orderIdParam: + name: orderId + in: path + description: ID of order to return + required: true + schema: + type: integer + format: int64 + minimum: 1 + + # Request bodies + requestBodies: + petRequestBody: + description: Pet object that needs to be added to the store + required: true + content: + application/json: + schema: + $ref: "./schemas/pet-schemas.yaml#/Pet" + userRequestBody: + description: User object that needs to be added to the system + required: true + content: + application/json: + schema: + $ref: "./schemas/user-schemas.yaml#/User" + orderRequestBody: + description: Order object that needs to be added to the store + required: true + content: + application/json: + schema: + $ref: "./schemas/order-schemas.yaml#/Order" + + # Responses + responses: + notFound: + description: The specified resource was not found + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + badRequest: + description: The request was invalid + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + unauthorized: + description: Authentication information is missing or invalid + headers: + WWW-Authenticate: + schema: + type: string + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + forbidden: + description: The server understood the request but refuses to authorize it + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + serverError: + description: An unexpected error occurred + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + + # Headers + headers: + X-Rate-Limit: + description: Calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: Date in UTC when token expires + schema: + type: string + format: date-time + X-Total-Count: + description: Total number of items + schema: + type: integer + format: int32 + minimum: 0 + + # Security schemes + securitySchemes: + petstore_auth: + type: oauth2 + description: OAuth2 authentication for pet operations + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read:pets: read your pets + write:pets: modify pets in your account + user_auth: + type: oauth2 + description: OAuth2 authentication for user operations + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read:users: read user information + write:users: modify user information + order_auth: + type: oauth2 + description: OAuth2 authentication for order operations + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read:orders: read your orders + write:orders: modify orders in your account + api_key: + type: apiKey + description: API key authentication + name: api_key + in: header + + # Examples + examples: + pet: + summary: Example of a pet + value: + id: 1 + name: Fluffy + status: available + user: + summary: Example of a user + value: + id: 1 + username: john_doe + email: john@example.com + order: + summary: Example of an order + value: + id: 1 + petId: 1 + quantity: 1 + shipDate: "2023-01-01T12:00:00Z" + status: placed + complete: false + + # Links + links: + getPetById: + operationId: getPetById + parameters: + petId: $response.body#/id + description: The `id` value returned in the response can be used as the `petId` parameter in `GET /pets/{petId}` + getUserById: + operationId: getUserById + parameters: + userId: $response.body#/id + description: The `id` value returned in the response can be used as the `userId` parameter in `GET /users/{userId}` + getOrderById: + operationId: getOrderById + parameters: + orderId: $response.body#/id + description: The `id` value returned in the response can be used as the `orderId` parameter in `GET /orders/{orderId}` + + # Callbacks + callbacks: + petStatusChanged: + '{$request.body#/status}': + post: + summary: Pet status changed callback + description: Callback for when a pet's status changes + operationId: petStatusChangedCallback + requestBody: + description: Pet status change information + required: true + content: + application/json: + schema: + type: object + properties: + petId: + type: integer + format: int64 + oldStatus: + type: string + newStatus: + type: string + required: + - petId + - oldStatus + - newStatus + responses: + '200': + description: Callback processed successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + orderStatusChanged: + '{$request.body#/status}': + post: + summary: Order status changed callback + description: Callback for when an order's status changes + operationId: orderStatusChangedCallback + requestBody: + description: Order status change information + required: true + content: + application/json: + schema: + type: object + properties: + orderId: + type: integer + format: int64 + oldStatus: + type: string + newStatus: + type: string + required: + - orderId + - oldStatus + - newStatus + responses: + '200': + description: Callback processed successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: "./schemas/common-schemas.yaml#/Error" + +# Extensions +x-api-version: 1.0.0 +x-generated-at: "2023-01-01T12:00:00Z" +x-generated-by: "OpenAPI Generator" +x-logo: + url: "https://example.com/logo.png" + backgroundColor: "#FFFFFF" + altText: "Example API Logo" diff --git a/modules/swagger-core/src/test/resources/comprehensiveOAS31/paths/order-paths.yaml b/modules/swagger-core/src/test/resources/comprehensiveOAS31/paths/order-paths.yaml new file mode 100644 index 0000000000..4b840e63f4 --- /dev/null +++ b/modules/swagger-core/src/test/resources/comprehensiveOAS31/paths/order-paths.yaml @@ -0,0 +1,664 @@ +# Order Paths for OpenAPI 3.1 +# This file contains order-related paths referenced by the root file + +# Path for /orders +/orders: + get: + summary: List all orders + description: Returns all orders from the system that the user has access to + operationId: listOrders + tags: + - orders + parameters: + - name: limit + in: query + description: Maximum number of orders to return + required: false + schema: + type: integer + format: int32 + minimum: 1 + maximum: 100 + default: 20 + - name: offset + in: query + description: Offset for pagination + required: false + schema: + type: integer + format: int32 + minimum: 0 + default: 0 + - name: status + in: query + description: Status values that need to be considered for filter + required: false + schema: + type: string + enum: [placed, approved, delivered, cancelled] + - name: userId + in: query + description: User ID to filter by + required: false + schema: + type: integer + format: int64 + minimum: 1 + - name: sort + in: query + description: Sort order + required: false + schema: + type: string + enum: [id, status, createdAt] + default: createdAt + responses: + '200': + description: A paged array of orders + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + format: uri + x-total-count: + description: Total number of orders + schema: + type: integer + format: int32 + minimum: 0 + content: + application/json: + schema: + $ref: "../schemas/order-schemas.yaml#/OrderArray" + examples: + orders: + summary: Example of orders + value: + - id: 1 + petId: 1 + quantity: 1 + shipDate: "2023-01-01T12:00:00Z" + status: placed + complete: false + - id: 2 + petId: 2 + quantity: 2 + shipDate: "2023-01-02T12:00:00Z" + status: approved + complete: false + '400': + description: Bad request + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '403': + description: Forbidden + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - order_auth: + - read:orders + post: + summary: Create an order + description: Creates a new order in the store + operationId: createOrder + tags: + - orders + requestBody: + description: Order to add to the store + required: true + content: + application/json: + schema: + $ref: "../schemas/order-schemas.yaml#/Order" + examples: + order: + summary: Example of an order + value: + id: 0 + petId: 1 + quantity: 1 + shipDate: "2023-01-01T12:00:00Z" + status: placed + complete: false + responses: + '201': + description: Order created + headers: + Location: + description: URL of the newly created order + schema: + type: string + format: uri + content: + application/json: + schema: + $ref: "../schemas/order-schemas.yaml#/Order" + '400': + description: Bad request + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '403': + description: Forbidden + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - order_auth: + - write:orders + +# Path for /orders/{orderId} +/orders/{orderId}: + parameters: + - name: orderId + in: path + description: ID of order to return + required: true + schema: + type: integer + format: int64 + minimum: 1 + get: + summary: Find order by ID + description: Returns a single order + operationId: getOrderById + tags: + - orders + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "../schemas/order-schemas.yaml#/Order" + examples: + order: + summary: Example of an order + value: + id: 1 + petId: 1 + quantity: 1 + shipDate: "2023-01-01T12:00:00Z" + status: placed + complete: false + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: Order not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - order_auth: + - read:orders + - api_key: [] + put: + summary: Update an existing order + description: Updates an order in the store + operationId: updateOrder + tags: + - orders + requestBody: + description: Order to update + required: true + content: + application/json: + schema: + $ref: "../schemas/order-schemas.yaml#/Order" + examples: + order: + summary: Example of an order + value: + id: 1 + petId: 1 + quantity: 1 + shipDate: "2023-01-01T12:00:00Z" + status: approved + complete: false + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "../schemas/order-schemas.yaml#/Order" + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: Order not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '405': + description: Validation exception + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - order_auth: + - write:orders + delete: + summary: Deletes an order + description: Deletes an order from the store + operationId: deleteOrder + tags: + - orders + responses: + '204': + description: Order deleted + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: Order not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - order_auth: + - write:orders + +# Path for /orders/{orderId}/status +/orders/{orderId}/status: + parameters: + - name: orderId + in: path + description: ID of order to update status + required: true + schema: + type: integer + format: int64 + minimum: 1 + get: + summary: Get order status + description: Returns the status of an order + operationId: getOrderStatus + tags: + - orders + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "../schemas/order-schemas.yaml#/OrderStatus" + examples: + status: + summary: Example of order status + value: + id: 1 + status: placed + timestamp: "2023-01-01T12:00:00Z" + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: Order not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - order_auth: + - read:orders + put: + summary: Update order status + description: Updates the status of an order + operationId: updateOrderStatus + tags: + - orders + requestBody: + description: Status to update + required: true + content: + application/json: + schema: + type: object + required: + - status + properties: + status: + type: string + description: Order status + enum: [placed, approved, delivered, cancelled] + notes: + type: string + description: Status notes + maxLength: 500 + examples: + status: + summary: Example of status update + value: + status: approved + notes: "Approved by manager" + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "../schemas/order-schemas.yaml#/OrderStatus" + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: Order not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - order_auth: + - write:orders + +# Path for /orders/{orderId}/invoice +/orders/{orderId}/invoice: + parameters: + - name: orderId + in: path + description: ID of order to get invoice for + required: true + schema: + type: integer + format: int64 + minimum: 1 + get: + summary: Get order invoice + description: Returns the invoice for an order + operationId: getOrderInvoice + tags: + - orders + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "../schemas/order-schemas.yaml#/Invoice" + examples: + invoice: + summary: Example of an invoice + value: + id: 1 + orderId: 1 + amount: 100.0 + tax: 10.0 + shipping: 5.0 + total: 115.0 + status: pending + createdAt: "2023-01-01T12:00:00Z" + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: Order not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - order_auth: + - read:orders + post: + summary: Create order invoice + description: Creates an invoice for an order + operationId: createOrderInvoice + tags: + - orders + requestBody: + description: Invoice to create + required: true + content: + application/json: + schema: + $ref: "../schemas/order-schemas.yaml#/Invoice" + examples: + invoice: + summary: Example of an invoice + value: + id: 0 + orderId: 1 + amount: 100.0 + tax: 10.0 + shipping: 5.0 + total: 115.0 + status: pending + responses: + '201': + description: Invoice created + headers: + Location: + description: URL of the newly created invoice + schema: + type: string + format: uri + content: + application/json: + schema: + $ref: "../schemas/order-schemas.yaml#/Invoice" + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: Order not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - order_auth: + - write:orders + +# Path for /orders/{orderId}/shipment +/orders/{orderId}/shipment: + parameters: + - name: orderId + in: path + description: ID of order to get shipment for + required: true + schema: + type: integer + format: int64 + minimum: 1 + get: + summary: Get order shipment + description: Returns the shipment for an order + operationId: getOrderShipment + tags: + - orders + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "../schemas/order-schemas.yaml#/Shipment" + examples: + shipment: + summary: Example of a shipment + value: + id: 1 + orderId: 1 + status: shipped + trackingNumber: "1Z999AA10123456784" + carrier: ups + estimatedDelivery: "2023-01-05" + createdAt: "2023-01-01T12:00:00Z" + shippedAt: "2023-01-02T12:00:00Z" + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: Order not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - order_auth: + - read:orders + post: + summary: Create order shipment + description: Creates a shipment for an order + operationId: createOrderShipment + tags: + - orders + requestBody: + description: Shipment to create + required: true + content: + application/json: + schema: + $ref: "../schemas/order-schemas.yaml#/Shipment" + examples: + shipment: + summary: Example of a shipment + value: + id: 0 + orderId: 1 + status: pending + carrier: ups + estimatedDelivery: "2023-01-05" + responses: + '201': + description: Shipment created + headers: + Location: + description: URL of the newly created shipment + schema: + type: string + format: uri + content: + application/json: + schema: + $ref: "../schemas/order-schemas.yaml#/Shipment" + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: Order not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - order_auth: + - write:orders \ No newline at end of file diff --git a/modules/swagger-core/src/test/resources/comprehensiveOAS31/paths/pet-paths.yaml b/modules/swagger-core/src/test/resources/comprehensiveOAS31/paths/pet-paths.yaml new file mode 100644 index 0000000000..dee729bc1d --- /dev/null +++ b/modules/swagger-core/src/test/resources/comprehensiveOAS31/paths/pet-paths.yaml @@ -0,0 +1,470 @@ +# Pet Paths for OpenAPI 3.1 +# This file contains pet-related paths referenced by the root file + +# Path for /pets +/pets: + get: + summary: List all pets + description: Returns all pets from the system that the user has access to + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: Maximum number of pets to return + required: false + schema: + type: integer + format: int32 + minimum: 1 + maximum: 100 + default: 20 + - name: offset + in: query + description: Offset for pagination + required: false + schema: + type: integer + format: int32 + minimum: 0 + default: 0 + - name: status + in: query + description: Status values that need to be considered for filter + required: false + schema: + type: string + enum: [available, pending, sold] + - name: tags + in: query + description: Tags to filter by + required: false + schema: + type: array + items: + type: string + minItems: 1 + maxItems: 10 + uniqueItems: true + - name: sort + in: query + description: Sort order + required: false + schema: + type: string + enum: [name, id, status] + default: name + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + format: uri + x-total-count: + description: Total number of pets + schema: + type: integer + format: int32 + minimum: 0 + content: + application/json: + schema: + $ref: "../schemas/pet-schemas.yaml#/Pets" + examples: + pets: + summary: Example of pets + value: + - id: 1 + name: Fluffy + status: available + - id: 2 + name: Rex + status: pending + '400': + description: Bad request + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '403': + description: Forbidden + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - petstore_auth: + - read:pets + post: + summary: Create a pet + description: Creates a new pet in the store + operationId: createPet + tags: + - pets + requestBody: + description: Pet to add to the store + required: true + content: + application/json: + schema: + $ref: "../schemas/pet-schemas.yaml#/Pet" + examples: + pet: + summary: Example of a pet + value: + id: 0 + name: Fluffy + status: available + responses: + '201': + description: Pet created + headers: + Location: + description: URL of the newly created pet + schema: + type: string + format: uri + content: + application/json: + schema: + $ref: "../schemas/pet-schemas.yaml#/Pet" + '400': + description: Bad request + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '403': + description: Forbidden + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - petstore_auth: + - write:pets + +# Path for /pets/{petId} +/pets/{petId}: + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + minimum: 1 + get: + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + tags: + - pets + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "../schemas/pet-schemas.yaml#/Pet" + examples: + pet: + summary: Example of a pet + value: + id: 1 + name: Fluffy + status: available + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: Pet not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - petstore_auth: + - read:pets + - api_key: [] + put: + summary: Update an existing pet + description: Updates a pet in the store with form data + operationId: updatePet + tags: + - pets + requestBody: + description: Pet to update + required: true + content: + application/json: + schema: + $ref: "../schemas/pet-schemas.yaml#/Pet" + examples: + pet: + summary: Example of a pet + value: + id: 1 + name: Fluffy + status: available + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "../schemas/pet-schemas.yaml#/Pet" + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: Pet not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '405': + description: Validation exception + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - petstore_auth: + - write:pets + delete: + summary: Deletes a pet + description: Deletes a pet from the store + operationId: deletePet + tags: + - pets + responses: + '204': + description: Pet deleted + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: Pet not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - petstore_auth: + - write:pets + +# Path for /pets/findByStatus +/pets/findByStatus: + get: + summary: Finds pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + tags: + - pets + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + schema: + type: array + items: + type: string + enum: [available, pending, sold] + minItems: 1 + maxItems: 3 + uniqueItems: true + style: form + explode: false + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "../schemas/pet-schemas.yaml#/Pets" + '400': + description: Invalid status value + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - petstore_auth: + - read:pets + +# Path for /pets/findByTags +/pets/findByTags: + get: + summary: Finds pets by tags + description: Multiple tags can be provided with comma separated strings + operationId: findPetsByTags + tags: + - pets + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + schema: + type: array + items: + type: string + minItems: 1 + maxItems: 10 + uniqueItems: true + style: form + explode: false + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "../schemas/pet-schemas.yaml#/Pets" + '400': + description: Invalid tag value + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - petstore_auth: + - read:pets + deprecated: true + +# Path for /pets/{petId}/uploadImage +/pets/{petId}/uploadImage: + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + minimum: 1 + post: + summary: Uploads an image for a pet + description: Uploads an image for a pet + operationId: uploadPetImage + tags: + - pets + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + additionalMetadata: + type: string + description: Additional data to pass to server + file: + type: string + format: binary + description: File to upload + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: Pet not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - petstore_auth: + - write:pets \ No newline at end of file diff --git a/modules/swagger-core/src/test/resources/comprehensiveOAS31/paths/user-paths.yaml b/modules/swagger-core/src/test/resources/comprehensiveOAS31/paths/user-paths.yaml new file mode 100644 index 0000000000..f7f3afd0d5 --- /dev/null +++ b/modules/swagger-core/src/test/resources/comprehensiveOAS31/paths/user-paths.yaml @@ -0,0 +1,511 @@ +# User Paths for OpenAPI 3.1 +# This file contains user-related paths referenced by the root file + +# Path for /users +/users: + get: + summary: List all users + description: Returns all users from the system that the requester has access to + operationId: listUsers + tags: + - users + parameters: + - name: limit + in: query + description: Maximum number of users to return + required: false + schema: + type: integer + format: int32 + minimum: 1 + maximum: 100 + default: 20 + - name: offset + in: query + description: Offset for pagination + required: false + schema: + type: integer + format: int32 + minimum: 0 + default: 0 + - name: status + in: query + description: Status values that need to be considered for filter + required: false + schema: + type: integer + format: int32 + enum: [0, 1, 2] + - name: sort + in: query + description: Sort order + required: false + schema: + type: string + enum: [username, id, status] + default: username + responses: + '200': + description: A paged array of users + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + format: uri + x-total-count: + description: Total number of users + schema: + type: integer + format: int32 + minimum: 0 + content: + application/json: + schema: + $ref: "../schemas/user-schemas.yaml#/UserArray" + examples: + users: + summary: Example of users + value: + - id: 1 + username: john_doe + email: john@example.com + - id: 2 + username: jane_doe + email: jane@example.com + '400': + description: Bad request + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '403': + description: Forbidden + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - user_auth: + - read:users + post: + summary: Create a user + description: Creates a new user in the system + operationId: createUser + tags: + - users + requestBody: + description: User to add to the system + required: true + content: + application/json: + schema: + $ref: "../schemas/user-schemas.yaml#/User" + examples: + user: + summary: Example of a user + value: + id: 0 + username: john_doe + firstName: John + lastName: Doe + email: john@example.com + password: password123 + phone: "+1234567890" + userStatus: 0 + responses: + '201': + description: User created + headers: + Location: + description: URL of the newly created user + schema: + type: string + format: uri + content: + application/json: + schema: + $ref: "../schemas/user-schemas.yaml#/User" + '400': + description: Bad request + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '403': + description: Forbidden + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - user_auth: + - write:users + +# Path for /users/{userId} +/users/{userId}: + parameters: + - name: userId + in: path + description: ID of user to return + required: true + schema: + type: integer + format: int64 + minimum: 1 + get: + summary: Find user by ID + description: Returns a single user + operationId: getUserById + tags: + - users + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "../schemas/user-schemas.yaml#/User" + examples: + user: + summary: Example of a user + value: + id: 1 + username: john_doe + firstName: John + lastName: Doe + email: john@example.com + phone: "+1234567890" + userStatus: 0 + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: User not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - user_auth: + - read:users + - api_key: [] + put: + summary: Update an existing user + description: Updates a user in the system + operationId: updateUser + tags: + - users + requestBody: + description: User to update + required: true + content: + application/json: + schema: + $ref: "../schemas/user-schemas.yaml#/User" + examples: + user: + summary: Example of a user + value: + id: 1 + username: john_doe + firstName: John + lastName: Doe + email: john@example.com + phone: "+1234567890" + userStatus: 0 + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "../schemas/user-schemas.yaml#/User" + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: User not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '405': + description: Validation exception + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - user_auth: + - write:users + delete: + summary: Deletes a user + description: Deletes a user from the system + operationId: deleteUser + tags: + - users + responses: + '204': + description: User deleted + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: User not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - user_auth: + - write:users + +# Path for /users/login +/users/login: + post: + summary: User login + description: Logs user into the system + operationId: loginUser + tags: + - users + requestBody: + description: User credentials + required: true + content: + application/json: + schema: + $ref: "../schemas/user-schemas.yaml#/UserCredentials" + examples: + credentials: + summary: Example of user credentials + value: + username: john_doe + password: password123 + responses: + '200': + description: Successful operation + headers: + X-Rate-Limit: + description: Calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: Date in UTC when token expires + schema: + type: string + format: date-time + content: + application/json: + schema: + type: object + properties: + token: + type: string + description: Authentication token + expiresAt: + type: string + format: date-time + description: Token expiration time + '400': + description: Invalid username/password supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + +# Path for /users/logout +/users/logout: + post: + summary: User logout + description: Logs out current logged in user session + operationId: logoutUser + tags: + - users + responses: + '200': + description: Successful operation + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - user_auth: [] + +# Path for /users/{userId}/preferences +/users/{userId}/preferences: + parameters: + - name: userId + in: path + description: ID of user to return preferences for + required: true + schema: + type: integer + format: int64 + minimum: 1 + get: + summary: Get user preferences + description: Returns user preferences + operationId: getUserPreferences + tags: + - users + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "../schemas/user-schemas.yaml#/UserPreferences" + examples: + preferences: + summary: Example of user preferences + value: + theme: dark + language: en + notifications: + email: true + push: true + sms: false + timezone: UTC + currency: USD + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: User not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - user_auth: + - read:users + put: + summary: Update user preferences + description: Updates user preferences + operationId: updateUserPreferences + tags: + - users + requestBody: + description: User preferences to update + required: true + content: + application/json: + schema: + $ref: "../schemas/user-schemas.yaml#/UserPreferences" + examples: + preferences: + summary: Example of user preferences + value: + theme: dark + language: en + notifications: + email: true + push: true + sms: false + timezone: UTC + currency: USD + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: "../schemas/user-schemas.yaml#/UserPreferences" + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '404': + description: User not found + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + '500': + description: Internal server error + content: + application/json: + schema: + $ref: "../schemas/common-schemas.yaml#/Error" + security: + - user_auth: + - write:users \ No newline at end of file diff --git a/modules/swagger-core/src/test/resources/comprehensiveOAS31/schemas/common-schemas.yaml b/modules/swagger-core/src/test/resources/comprehensiveOAS31/schemas/common-schemas.yaml new file mode 100644 index 0000000000..8acfecb5ed --- /dev/null +++ b/modules/swagger-core/src/test/resources/comprehensiveOAS31/schemas/common-schemas.yaml @@ -0,0 +1,249 @@ +# Common Schemas for OpenAPI 3.1 and JSON Schema 2020/12 +# This file contains common schemas referenced by the root file + +# Basic schema with JSON Schema 2020/12 keywords +Error: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Error" + $anchor: "Error" + title: "Error" + description: "Error response" + type: "object" + required: + - "code" + - "message" + properties: + code: + type: "integer" + format: "int32" + description: "Error code" + minimum: 100 + maximum: 600 + message: + type: "string" + description: "Error message" + minLength: 1 + details: + type: "array" + description: "Error details" + items: + type: "string" + minItems: 0 + maxItems: 10 + uniqueItems: true + +# Schema with $dynamicAnchor and $dynamicRef +Identifiable: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Identifiable" + $dynamicAnchor: "Identifiable" + title: "Identifiable" + description: "Base schema for objects with an ID" + type: "object" + required: + - "id" + properties: + id: + type: "string" + format: "uuid" + description: "Unique identifier" + +# Schema with $vocabulary +ExtendedSchema: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/ExtendedSchema" + $vocabulary: + "https://json-schema.org/draft/2020-12/vocab/core": true + "https://json-schema.org/draft/2020-12/vocab/applicator": true + "https://json-schema.org/draft/2020-12/vocab/validation": true + "https://json-schema.org/draft/2020-12/vocab/meta-data": true + "https://json-schema.org/draft/2020-12/vocab/format-annotation": true + "https://json-schema.org/draft/2020-12/vocab/content": true + "https://json-schema.org/draft/2020-12/vocab/unevaluated": true + title: "Extended Schema" + description: "Schema with vocabulary" + type: "object" + properties: + name: + type: "string" + value: + type: "string" + +# Schema with $comment +CommentedSchema: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/CommentedSchema" + $comment: "This is a schema with comments" + title: "Commented Schema" + description: "Schema with comments" + type: "object" + properties: + name: + $comment: "The name property" + type: "string" + value: + $comment: "The value property" + type: "string" + +# Schema with $defs +SchemaWithDefs: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/SchemaWithDefs" + title: "Schema with Definitions" + description: "Schema with $defs" + type: "object" + properties: + person: + $ref: "#/$defs/Person" + address: + $ref: "#/$defs/Address" + $defs: + Person: + type: "object" + properties: + firstName: + type: "string" + lastName: + type: "string" + age: + type: "integer" + minimum: 0 + Address: + type: "object" + properties: + street: + type: "string" + city: + type: "string" + zipCode: + type: "string" + pattern: "^[0-9]{5}(?:-[0-9]{4})?$" + +# Schema with validation keywords +ValidationSchema: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/ValidationSchema" + title: "Validation Schema" + description: "Schema with validation keywords" + type: "object" + required: + - "name" + - "email" + properties: + name: + type: "string" + minLength: 1 + maxLength: 100 + email: + type: "string" + format: "email" + pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" + age: + type: "integer" + minimum: 0 + maximum: 120 + exclusiveMaximum: 121 + score: + type: "number" + multipleOf: 0.5 + minimum: 0 + maximum: 10 + tags: + type: "array" + items: + type: "string" + minItems: 0 + maxItems: 10 + uniqueItems: true + metadata: + type: "object" + minProperties: 0 + maxProperties: 10 + propertyNames: + pattern: "^[a-zA-Z0-9_]+$" + status: + enum: ["active", "inactive", "pending"] + role: + const: "user" + +# Schema with applicator keywords +ApplicatorSchema: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/ApplicatorSchema" + title: "Applicator Schema" + description: "Schema with applicator keywords" + allOf: + - type: "object" + properties: + id: + type: "string" + format: "uuid" + - required: + - "name" + anyOf: + - properties: + type: + const: "personal" + - properties: + type: + const: "business" + oneOf: + - properties: + status: + const: "active" + - properties: + status: + const: "inactive" + not: + properties: + status: + const: "deleted" + if: + properties: + type: + const: "personal" + then: + properties: + personalId: + type: "string" + required: + - "personalId" + else: + properties: + businessId: + type: "string" + required: + - "businessId" + dependentSchemas: + creditCard: + properties: + creditCardNumber: + type: "string" + pattern: "^[0-9]{16}$" + required: + - "creditCardNumber" + properties: + name: + type: "string" + type: + type: "string" + status: + type: "string" + items: + type: "array" + prefixItems: + - type: "string" + - type: "number" + items: false + contains: + type: "array" + contains: + type: "string" + const: "special" + minContains: 1 + maxContains: 3 + patternProperties: + "^x-": + type: "string" + additionalProperties: false + unevaluatedProperties: false \ No newline at end of file diff --git a/modules/swagger-core/src/test/resources/comprehensiveOAS31/schemas/json-schema.yaml b/modules/swagger-core/src/test/resources/comprehensiveOAS31/schemas/json-schema.yaml new file mode 100644 index 0000000000..bf9b2e4d27 --- /dev/null +++ b/modules/swagger-core/src/test/resources/comprehensiveOAS31/schemas/json-schema.yaml @@ -0,0 +1,455 @@ +# JSON Schema 2020/12 Example +# This file demonstrates a comprehensive JSON Schema 2020/12 document +# It includes all keywords defined in the JSON Schema 2020/12 specification + +$schema: https://json-schema.org/draft/2020-12/schema +$id: https://example.com/schemas/json-schema +$vocabulary: + https://json-schema.org/draft/2020-12/vocab/core: true + https://json-schema.org/draft/2020-12/vocab/applicator: true + https://json-schema.org/draft/2020-12/vocab/validation: true + https://json-schema.org/draft/2020-12/vocab/meta-data: true + https://json-schema.org/draft/2020-12/vocab/format-annotation: true + https://json-schema.org/draft/2020-12/vocab/content: true + https://json-schema.org/draft/2020-12/vocab/unevaluated: true +$comment: This is a comprehensive JSON Schema 2020/12 document + +# Root schema +title: Comprehensive JSON Schema +description: A comprehensive JSON Schema 2020/12 document +type: object +properties: + id: + $anchor: id + title: ID + description: Unique identifier + type: string + format: uuid + name: + $anchor: name + title: Name + description: Name of the entity + type: string + minLength: 1 + maxLength: 100 + description: + $anchor: description + title: Description + description: Description of the entity + type: string + maxLength: 1000 + createdAt: + $anchor: createdAt + title: Created At + description: Creation date + type: string + format: date-time + updatedAt: + $anchor: updatedAt + title: Updated At + description: Last update date + type: string + format: date-time + tags: + $anchor: tags + title: Tags + description: Tags for the entity + type: array + items: + type: string + minLength: 1 + maxLength: 50 + minItems: 0 + maxItems: 10 + uniqueItems: true + metadata: + $anchor: metadata + title: Metadata + description: Additional metadata + type: object + additionalProperties: + type: string + propertyNames: + pattern: "^[a-zA-Z0-9_]+$" + minProperties: 0 + maxProperties: 10 + status: + $anchor: status + title: Status + description: Status of the entity + type: string + enum: ["active", "inactive", "pending", "deleted"] + priority: + $anchor: priority + title: Priority + description: Priority of the entity + type: integer + minimum: 1 + maximum: 5 + default: 3 + score: + $anchor: score + title: Score + description: Score of the entity + type: number + minimum: 0 + maximum: 100 + multipleOf: 0.1 + isPublic: + $anchor: isPublic + title: Is Public + description: Whether the entity is public + type: boolean + default: false + nullableField: + $anchor: nullableField + title: Nullable Field + description: A field that can be null + type: ["string", "null"] + constField: + $anchor: constField + title: Const Field + description: A field with a constant value + const: "constant" + contentField: + $anchor: contentField + title: Content Field + description: A field with content encoding and media type + type: string + contentEncoding: base64 + contentMediaType: image/png + dependentField1: + $anchor: dependentField1 + title: Dependent Field 1 + description: A field that depends on another field + type: string + dependentField2: + $anchor: dependentField2 + title: Dependent Field 2 + description: A field that depends on another field + type: string + conditionalField: + $anchor: conditionalField + title: Conditional Field + description: A field that is conditional + type: string + refField: + $anchor: refField + title: Ref Field + description: A field that references another schema + $ref: "#/$defs/RefSchema" + dynamicRefField: + $anchor: dynamicRefField + title: Dynamic Ref Field + description: A field that uses dynamic reference + $dynamicRef: "#Entity" + recursiveField: + $anchor: recursiveField + title: Recursive Field + description: A field that references itself + $ref: "#" + prefixItems: + $anchor: prefixItems + title: Prefix Items + description: An array with prefix items + type: array + prefixItems: + - type: string + - type: number + - type: boolean + items: false + containsField: + $anchor: containsField + title: Contains Field + description: An array that contains a specific item + type: array + contains: + type: string + const: "special" + minContains: 1 + maxContains: 3 + patternProperties: + $anchor: patternProperties + title: Pattern Properties + description: An object with pattern properties + type: object + patternProperties: + "^x-": + type: string + "^y-": + type: number + additionalProperties: false + unevaluatedProperties: + $anchor: unevaluatedProperties + title: Unevaluated Properties + description: An object with unevaluated properties + type: object + properties: + prop1: + type: string + unevaluatedProperties: false + unevaluatedItems: + $anchor: unevaluatedItems + title: Unevaluated Items + description: An array with unevaluated items + type: array + prefixItems: + - type: string + unevaluatedItems: false +required: + - id + - name + - createdAt +dependentRequired: + dependentField1: ["dependentField2"] +dependentSchemas: + isPublic: + properties: + publicationDate: + type: string + format: date + required: + - publicationDate + +# Conditional validation +if: + properties: + status: + const: "active" + required: + - status +then: + properties: + activatedAt: + type: string + format: date-time + required: + - activatedAt +else: + properties: + deactivatedAt: + type: string + format: date-time + +# Applicator keywords +allOf: + - properties: + allOfField: + type: string + required: + - allOfField +anyOf: + - properties: + anyOfField1: + type: string + required: + - anyOfField1 + - properties: + anyOfField2: + type: string + required: + - anyOfField2 +oneOf: + - properties: + oneOfField1: + type: string + required: + - oneOfField1 + - properties: + oneOfField2: + type: string + required: + - oneOfField2 +not: + properties: + notField: + type: string + required: + - notField + +# Definitions +$defs: + RefSchema: + $anchor: RefSchema + title: Ref Schema + description: A schema that is referenced + type: object + properties: + id: + type: string + format: uuid + name: + type: string + minLength: 1 + maxLength: 100 + required: + - id + - name + + Entity: + $dynamicAnchor: Entity + title: Entity + description: A base entity schema + type: object + properties: + id: + type: string + format: uuid + name: + type: string + minLength: 1 + maxLength: 100 + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + required: + - id + - name + - createdAt + + Person: + $anchor: Person + title: Person + description: A person schema + allOf: + - $dynamicRef: "#Entity" + - type: object + properties: + firstName: + type: string + minLength: 1 + maxLength: 50 + lastName: + type: string + minLength: 1 + maxLength: 50 + email: + type: string + format: email + phone: + type: string + pattern: "^\\+?[0-9]{10,15}$" + required: + - firstName + - lastName + - email + + Organization: + $anchor: Organization + title: Organization + description: An organization schema + allOf: + - $dynamicRef: "#Entity" + - type: object + properties: + legalName: + type: string + minLength: 1 + maxLength: 100 + taxId: + type: string + minLength: 1 + maxLength: 50 + website: + type: string + format: uri + employees: + type: integer + minimum: 1 + required: + - legalName + + Address: + $anchor: Address + title: Address + description: An address schema + type: object + properties: + street: + type: string + minLength: 1 + maxLength: 100 + city: + type: string + minLength: 1 + maxLength: 50 + state: + type: string + minLength: 1 + maxLength: 50 + zipCode: + type: string + pattern: "^[0-9]{5}(?:-[0-9]{4})?$" + country: + type: string + minLength: 1 + maxLength: 50 + required: + - street + - city + - zipCode + - country + + Contact: + $anchor: Contact + title: Contact + description: A contact schema + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 100 + email: + type: string + format: email + phone: + type: string + pattern: "^\\+?[0-9]{10,15}$" + address: + $ref: "#/$defs/Address" + required: + - name + - email + + Event: + $anchor: Event + title: Event + description: An event schema + type: object + properties: + id: + type: string + format: uuid + name: + type: string + minLength: 1 + maxLength: 100 + description: + type: string + maxLength: 1000 + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + location: + $ref: "#/$defs/Address" + organizer: + $ref: "#/$defs/Contact" + attendees: + type: array + items: + $ref: "#/$defs/Contact" + minItems: 0 + maxItems: 1000 + uniqueItems: true + required: + - id + - name + - startDate + - endDate \ No newline at end of file diff --git a/modules/swagger-core/src/test/resources/comprehensiveOAS31/schemas/order-schemas.yaml b/modules/swagger-core/src/test/resources/comprehensiveOAS31/schemas/order-schemas.yaml new file mode 100644 index 0000000000..6c2a6d554f --- /dev/null +++ b/modules/swagger-core/src/test/resources/comprehensiveOAS31/schemas/order-schemas.yaml @@ -0,0 +1,402 @@ +# Order Schemas for OpenAPI 3.1 and JSON Schema 2020/12 +# This file contains order-related schemas referenced by the root file + +# Base Order schema +Order: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Order" + $anchor: "Order" + $dynamicAnchor: "Order" + title: "Order" + description: "Order object" + type: "object" + required: + - "id" + - "petId" + - "quantity" + - "shipDate" + - "status" + properties: + id: + type: "integer" + format: "int64" + description: "Order ID" + minimum: 1 + petId: + type: "integer" + format: "int64" + description: "Pet ID" + minimum: 1 + userId: + type: "integer" + format: "int64" + description: "User ID" + minimum: 1 + quantity: + type: "integer" + format: "int32" + description: "Order quantity" + minimum: 1 + maximum: 100 + shipDate: + type: "string" + format: "date-time" + description: "Shipping date" + status: + type: "string" + description: "Order status" + enum: ["placed", "approved", "delivered", "cancelled"] + complete: + type: "boolean" + description: "Whether the order is complete" + default: false + createdAt: + type: "string" + format: "date-time" + description: "Order creation date" + readOnly: true + updatedAt: + type: "string" + format: "date-time" + description: "Order last update date" + readOnly: true + additionalProperties: false + x-extension-example: "Example of an extension" + +# OrderItem schema +OrderItem: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/OrderItem" + $anchor: "OrderItem" + title: "Order Item" + description: "Order item object" + type: "object" + required: + - "id" + - "petId" + - "quantity" + - "price" + properties: + id: + type: "integer" + format: "int64" + description: "Order item ID" + minimum: 1 + petId: + type: "integer" + format: "int64" + description: "Pet ID" + minimum: 1 + quantity: + type: "integer" + format: "int32" + description: "Item quantity" + minimum: 1 + maximum: 100 + price: + type: "number" + format: "double" + description: "Item price" + minimum: 0 + exclusiveMinimum: true + discount: + type: "number" + format: "double" + description: "Item discount" + minimum: 0 + maximum: 100 + default: 0 + notes: + type: "string" + description: "Additional notes" + maxLength: 500 + +# OrderWithItems schema +OrderWithItems: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/OrderWithItems" + $anchor: "OrderWithItems" + title: "Order with Items" + description: "Order object with items" + allOf: + - $ref: "#/Order" + - type: "object" + properties: + items: + type: "array" + description: "Order items" + items: + $ref: "#/OrderItem" + minItems: 1 + maxItems: 100 + uniqueItems: true + required: + - "items" + +# OrderStatus schema +OrderStatus: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/OrderStatus" + $anchor: "OrderStatus" + title: "Order Status" + description: "Order status object" + type: "object" + required: + - "id" + - "status" + properties: + id: + type: "integer" + format: "int64" + description: "Order ID" + minimum: 1 + status: + type: "string" + description: "Order status" + enum: ["placed", "approved", "delivered", "cancelled"] + timestamp: + type: "string" + format: "date-time" + description: "Status update timestamp" + readOnly: true + notes: + type: "string" + description: "Status notes" + maxLength: 500 + +# OrderArray schema +OrderArray: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/OrderArray" + $anchor: "OrderArray" + title: "Order Array" + description: "Array of orders" + type: "array" + items: + $ref: "#/Order" + minItems: 0 + maxItems: 100 + uniqueItems: true + +# OrderSummary schema +OrderSummary: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/OrderSummary" + $anchor: "OrderSummary" + title: "Order Summary" + description: "Order summary object" + type: "object" + required: + - "id" + - "petId" + - "status" + - "createdAt" + properties: + id: + type: "integer" + format: "int64" + description: "Order ID" + minimum: 1 + petId: + type: "integer" + format: "int64" + description: "Pet ID" + minimum: 1 + status: + type: "string" + description: "Order status" + enum: ["placed", "approved", "delivered", "cancelled"] + createdAt: + type: "string" + format: "date-time" + description: "Order creation date" + readOnly: true + additionalProperties: false + +# Invoice schema +Invoice: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Invoice" + $anchor: "Invoice" + title: "Invoice" + description: "Invoice object" + type: "object" + required: + - "id" + - "orderId" + - "amount" + - "status" + - "createdAt" + properties: + id: + type: "integer" + format: "int64" + description: "Invoice ID" + minimum: 1 + orderId: + type: "integer" + format: "int64" + description: "Order ID" + minimum: 1 + amount: + type: "number" + format: "double" + description: "Invoice amount" + minimum: 0 + exclusiveMinimum: true + tax: + type: "number" + format: "double" + description: "Tax amount" + minimum: 0 + default: 0 + shipping: + type: "number" + format: "double" + description: "Shipping amount" + minimum: 0 + default: 0 + total: + type: "number" + format: "double" + description: "Total amount" + minimum: 0 + exclusiveMinimum: true + status: + type: "string" + description: "Invoice status" + enum: ["pending", "paid", "cancelled"] + paymentMethod: + type: "string" + description: "Payment method" + enum: ["credit_card", "debit_card", "paypal", "bank_transfer", "cash"] + createdAt: + type: "string" + format: "date-time" + description: "Invoice creation date" + readOnly: true + paidAt: + type: "string" + format: "date-time" + description: "Invoice payment date" + readOnly: true + additionalProperties: false + +# Payment schema +Payment: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Payment" + $anchor: "Payment" + title: "Payment" + description: "Payment object" + type: "object" + required: + - "id" + - "invoiceId" + - "amount" + - "method" + - "status" + - "createdAt" + properties: + id: + type: "integer" + format: "int64" + description: "Payment ID" + minimum: 1 + invoiceId: + type: "integer" + format: "int64" + description: "Invoice ID" + minimum: 1 + amount: + type: "number" + format: "double" + description: "Payment amount" + minimum: 0 + exclusiveMinimum: true + method: + type: "string" + description: "Payment method" + enum: ["credit_card", "debit_card", "paypal", "bank_transfer", "cash"] + status: + type: "string" + description: "Payment status" + enum: ["pending", "completed", "failed", "refunded"] + transactionId: + type: "string" + description: "Payment transaction ID" + minLength: 1 + maxLength: 100 + createdAt: + type: "string" + format: "date-time" + description: "Payment creation date" + readOnly: true + completedAt: + type: "string" + format: "date-time" + description: "Payment completion date" + readOnly: true + additionalProperties: false + +# Shipment schema +Shipment: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Shipment" + $anchor: "Shipment" + title: "Shipment" + description: "Shipment object" + type: "object" + required: + - "id" + - "orderId" + - "status" + - "createdAt" + properties: + id: + type: "integer" + format: "int64" + description: "Shipment ID" + minimum: 1 + orderId: + type: "integer" + format: "int64" + description: "Order ID" + minimum: 1 + status: + type: "string" + description: "Shipment status" + enum: ["pending", "shipped", "delivered", "returned"] + trackingNumber: + type: "string" + description: "Shipment tracking number" + minLength: 1 + maxLength: 100 + carrier: + type: "string" + description: "Shipping carrier" + enum: ["ups", "fedex", "dhl", "usps"] + estimatedDelivery: + type: "string" + format: "date" + description: "Estimated delivery date" + actualDelivery: + type: "string" + format: "date" + description: "Actual delivery date" + createdAt: + type: "string" + format: "date-time" + description: "Shipment creation date" + readOnly: true + shippedAt: + type: "string" + format: "date-time" + description: "Shipment shipping date" + readOnly: true + deliveredAt: + type: "string" + format: "date-time" + description: "Shipment delivery date" + readOnly: true + additionalProperties: false \ No newline at end of file diff --git a/modules/swagger-core/src/test/resources/comprehensiveOAS31/schemas/pet-schemas.yaml b/modules/swagger-core/src/test/resources/comprehensiveOAS31/schemas/pet-schemas.yaml new file mode 100644 index 0000000000..fd40dbbdf3 --- /dev/null +++ b/modules/swagger-core/src/test/resources/comprehensiveOAS31/schemas/pet-schemas.yaml @@ -0,0 +1,301 @@ +# Pet Schemas for OpenAPI 3.1 and JSON Schema 2020/12 +# This file contains pet-related schemas referenced by the root file + +# Base Pet schema +Pet: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Pet" + $anchor: "Pet" + $dynamicAnchor: "Pet" + title: "Pet" + description: "Pet object" + type: "object" + required: + - "id" + - "name" + - "status" + properties: + id: + type: "integer" + format: "int64" + description: "Pet ID" + minimum: 1 + name: + type: "string" + description: "Pet name" + minLength: 1 + maxLength: 100 + category: + $ref: "#/Category" + photoUrls: + type: "array" + description: "Pet photo URLs" + items: + type: "string" + format: "uri" + minItems: 0 + maxItems: 10 + uniqueItems: true + tags: + type: "array" + description: "Pet tags" + items: + $ref: "#/Tag" + minItems: 0 + maxItems: 10 + uniqueItems: true + status: + type: "string" + description: "Pet status in the store" + enum: ["available", "pending", "sold"] + metadata: + type: "object" + description: "Additional metadata" + additionalProperties: + type: "string" + propertyNames: + pattern: "^[a-zA-Z0-9_]+$" + minProperties: 0 + maxProperties: 10 + discriminator: + propertyName: "petType" + mapping: + "dog": "#/Dog" + "cat": "#/Cat" + "bird": "#/Bird" + x-extension-example: "Example of an extension" + +# Category schema +Category: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Category" + $anchor: "Category" + title: "Category" + description: "Pet category" + type: "object" + required: + - "id" + - "name" + properties: + id: + type: "integer" + format: "int64" + description: "Category ID" + minimum: 1 + name: + type: "string" + description: "Category name" + minLength: 1 + maxLength: 50 + +# Tag schema +Tag: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Tag" + $anchor: "Tag" + title: "Tag" + description: "Pet tag" + type: "object" + required: + - "id" + - "name" + properties: + id: + type: "integer" + format: "int64" + description: "Tag ID" + minimum: 1 + name: + type: "string" + description: "Tag name" + minLength: 1 + maxLength: 50 + +# Dog schema +Dog: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Dog" + $anchor: "Dog" + title: "Dog" + description: "Dog object" + allOf: + - $ref: "#/Pet" + - type: "object" + required: + - "petType" + - "breed" + properties: + petType: + type: "string" + const: "dog" + breed: + type: "string" + description: "Dog breed" + minLength: 1 + maxLength: 50 + bark: + type: "boolean" + description: "Whether the dog can bark" + default: true + vaccinated: + type: "boolean" + description: "Whether the dog is vaccinated" + default: false + +# Cat schema +Cat: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Cat" + $anchor: "Cat" + title: "Cat" + description: "Cat object" + allOf: + - $ref: "#/Pet" + - type: "object" + required: + - "petType" + - "breed" + properties: + petType: + type: "string" + const: "cat" + breed: + type: "string" + description: "Cat breed" + minLength: 1 + maxLength: 50 + declawed: + type: "boolean" + description: "Whether the cat is declawed" + default: false + indoor: + type: "boolean" + description: "Whether the cat is an indoor cat" + default: true + +# Bird schema +Bird: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Bird" + $anchor: "Bird" + title: "Bird" + description: "Bird object" + allOf: + - $ref: "#/Pet" + - type: "object" + required: + - "petType" + - "species" + properties: + petType: + type: "string" + const: "bird" + species: + type: "string" + description: "Bird species" + minLength: 1 + maxLength: 50 + canFly: + type: "boolean" + description: "Whether the bird can fly" + default: true + canTalk: + type: "boolean" + description: "Whether the bird can talk" + default: false + +# Pets array schema +Pets: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Pets" + $anchor: "Pets" + title: "Pets" + description: "Array of pets" + type: "array" + items: + $ref: "#/Pet" + minItems: 0 + maxItems: 100 + uniqueItems: true + +# Pet with validation +PetWithValidation: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/PetWithValidation" + $anchor: "PetWithValidation" + title: "Pet with Validation" + description: "Pet object with additional validation" + allOf: + - $ref: "#/Pet" + - type: "object" + properties: + age: + type: "integer" + description: "Pet age in years" + minimum: 0 + maximum: 30 + weight: + type: "number" + description: "Pet weight in kilograms" + minimum: 0.1 + maximum: 100 + multipleOf: 0.1 + height: + type: "number" + description: "Pet height in centimeters" + minimum: 1 + maximum: 200 + multipleOf: 0.1 + birthDate: + type: "string" + format: "date" + description: "Pet birth date" + microchipped: + type: "boolean" + description: "Whether the pet is microchipped" + default: false + dependentRequired: + microchipped: ["microchipNumber"] + dependentSchemas: + microchipped: + properties: + microchipNumber: + type: "string" + description: "Microchip number" + pattern: "^[0-9]{15}$" + required: + - "microchipNumber" + +# Pet with conditional schema +PetWithConditional: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/PetWithConditional" + $anchor: "PetWithConditional" + title: "Pet with Conditional Schema" + description: "Pet object with conditional schema" + allOf: + - $ref: "#/Pet" + - type: "object" + properties: + type: + type: "string" + enum: ["domestic", "wild"] + dangerous: + type: "boolean" + description: "Whether the pet is dangerous" + if: + properties: + type: + const: "wild" + required: + - "type" + then: + properties: + dangerous: + const: true + required: + - "dangerous" + else: + properties: + dangerous: + default: false \ No newline at end of file diff --git a/modules/swagger-core/src/test/resources/comprehensiveOAS31/schemas/user-schemas.yaml b/modules/swagger-core/src/test/resources/comprehensiveOAS31/schemas/user-schemas.yaml new file mode 100644 index 0000000000..81e89219c1 --- /dev/null +++ b/modules/swagger-core/src/test/resources/comprehensiveOAS31/schemas/user-schemas.yaml @@ -0,0 +1,298 @@ +# User Schemas for OpenAPI 3.1 and JSON Schema 2020/12 +# This file contains user-related schemas referenced by the root file + +# Base User schema +User: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/User" + $anchor: "User" + $dynamicAnchor: "User" + title: "User" + description: "User object" + type: "object" + required: + - "id" + - "username" + - "email" + properties: + id: + type: "integer" + format: "int64" + description: "User ID" + minimum: 1 + username: + type: "string" + description: "User name" + minLength: 3 + maxLength: 50 + pattern: "^[a-zA-Z0-9_]+$" + firstName: + type: "string" + description: "User first name" + minLength: 1 + maxLength: 50 + lastName: + type: "string" + description: "User last name" + minLength: 1 + maxLength: 50 + email: + type: "string" + format: "email" + description: "User email" + pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" + password: + type: "string" + description: "User password" + minLength: 8 + maxLength: 100 + writeOnly: true + phone: + type: "string" + description: "User phone number" + pattern: "^\\+?[0-9]{10,15}$" + userStatus: + type: "integer" + format: "int32" + description: "User status" + enum: [0, 1, 2] + default: 0 + address: + $ref: "#/Address" + createdAt: + type: "string" + format: "date-time" + description: "User creation date" + readOnly: true + updatedAt: + type: "string" + format: "date-time" + description: "User last update date" + readOnly: true + additionalProperties: false + x-extension-example: "Example of an extension" + +# Address schema +Address: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Address" + $anchor: "Address" + title: "Address" + description: "User address" + type: "object" + properties: + street: + type: "string" + description: "Street address" + minLength: 1 + maxLength: 100 + city: + type: "string" + description: "City" + minLength: 1 + maxLength: 50 + state: + type: "string" + description: "State or province" + minLength: 1 + maxLength: 50 + zipCode: + type: "string" + description: "Zip code" + pattern: "^[0-9]{5}(?:-[0-9]{4})?$" + country: + type: "string" + description: "Country" + minLength: 1 + maxLength: 50 + required: + - "street" + - "city" + - "zipCode" + - "country" + +# UserArray schema +UserArray: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/UserArray" + $anchor: "UserArray" + title: "User Array" + description: "Array of users" + type: "array" + items: + $ref: "#/User" + minItems: 0 + maxItems: 100 + uniqueItems: true + +# UserWithRoles schema +UserWithRoles: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/UserWithRoles" + $anchor: "UserWithRoles" + title: "User with Roles" + description: "User object with roles" + allOf: + - $ref: "#/User" + - type: "object" + properties: + roles: + type: "array" + description: "User roles" + items: + $ref: "#/Role" + minItems: 1 + maxItems: 10 + uniqueItems: true + required: + - "roles" + +# Role schema +Role: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Role" + $anchor: "Role" + title: "Role" + description: "User role" + type: "object" + properties: + id: + type: "integer" + format: "int32" + description: "Role ID" + minimum: 1 + name: + type: "string" + description: "Role name" + enum: ["admin", "user", "moderator", "guest"] + description: + type: "string" + description: "Role description" + minLength: 1 + maxLength: 200 + permissions: + type: "array" + description: "Role permissions" + items: + $ref: "#/Permission" + minItems: 0 + maxItems: 50 + uniqueItems: true + required: + - "id" + - "name" + +# Permission schema +Permission: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/Permission" + $anchor: "Permission" + title: "Permission" + description: "User permission" + type: "object" + properties: + id: + type: "integer" + format: "int32" + description: "Permission ID" + minimum: 1 + name: + type: "string" + description: "Permission name" + minLength: 1 + maxLength: 50 + description: + type: "string" + description: "Permission description" + minLength: 1 + maxLength: 200 + required: + - "id" + - "name" + +# UserCredentials schema +UserCredentials: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/UserCredentials" + $anchor: "UserCredentials" + title: "User Credentials" + description: "User credentials for authentication" + type: "object" + properties: + username: + type: "string" + description: "User name" + minLength: 3 + maxLength: 50 + pattern: "^[a-zA-Z0-9_]+$" + password: + type: "string" + description: "User password" + minLength: 8 + maxLength: 100 + writeOnly: true + required: + - "username" + - "password" + additionalProperties: false + +# UserPreferences schema +UserPreferences: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/UserPreferences" + $anchor: "UserPreferences" + title: "User Preferences" + description: "User preferences" + type: "object" + properties: + theme: + type: "string" + description: "UI theme" + enum: ["light", "dark", "system"] + default: "system" + language: + type: "string" + description: "UI language" + enum: ["en", "fr", "es", "de", "it", "ja", "zh"] + default: "en" + notifications: + type: "object" + description: "Notification preferences" + properties: + email: + type: "boolean" + description: "Email notifications" + default: true + push: + type: "boolean" + description: "Push notifications" + default: true + sms: + type: "boolean" + description: "SMS notifications" + default: false + additionalProperties: false + timezone: + type: "string" + description: "User timezone" + default: "UTC" + currency: + type: "string" + description: "Preferred currency" + enum: ["USD", "EUR", "GBP", "JPY", "CAD", "AUD"] + default: "USD" + additionalProperties: false + +# UserWithPreferences schema +UserWithPreferences: + $schema: "https://json-schema.org/draft/2020-12/schema" + $id: "https://example.com/schemas/UserWithPreferences" + $anchor: "UserWithPreferences" + title: "User with Preferences" + description: "User object with preferences" + allOf: + - $ref: "#/User" + - type: "object" + properties: + preferences: + $ref: "#/UserPreferences" \ No newline at end of file