use crate::{
arena::Arena,
ir::{
Enum, EnumVariant, InlineTypePath, InlineTypePathRoot, InlineTypePathSegment,
PrimitiveType, SchemaTypeInfo, SpecContainer, SpecInlineType, SpecInner, SpecSchemaType,
SpecStructField, SpecTaggedVariant, SpecType, SpecUntaggedVariant, StructFieldName,
StructFieldNameHint, UntaggedVariantNameHint,
shape::{Struct, Tagged, Untagged},
transform::transform,
},
parse::{Document, Schema},
tests::assert_matches,
};
#[test]
fn test_enum_string_variants() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: string
enum: [active, inactive, pending]
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Status", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Enum(
SchemaTypeInfo { name: "Status", .. },
Enum {
variants: [
EnumVariant::String("active"),
EnumVariant::String("inactive"),
EnumVariant::String("pending"),
],
..
},
)),
);
}
#[test]
fn test_enum_number_variants() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: string
enum: [1, 2, 3]
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Priority", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Enum(
SchemaTypeInfo {
name: "Priority",
..
},
Enum {
variants: [
EnumVariant::I64(1),
EnumVariant::I64(2),
EnumVariant::I64(3)
],
..
},
)),
);
}
#[test]
fn test_enum_bool_variants() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: string
enum: [true, false]
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Flag", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Enum(
SchemaTypeInfo { name: "Flag", .. },
Enum {
variants: [EnumVariant::Bool(true), EnumVariant::Bool(false)],
..
},
)),
);
}
#[test]
fn test_enum_mixed_types() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: string
enum: [text, 42, true]
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Mixed", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Enum(
SchemaTypeInfo { name: "Mixed", .. },
Enum {
variants: [
EnumVariant::String("text"),
EnumVariant::I64(42),
EnumVariant::Bool(true),
],
..
},
)),
);
}
#[test]
fn test_primitive_string_formats() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let arena = Arena::new();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: string
format: date-time
"})
.unwrap();
let result = transform(&arena, &doc, "Timestamp", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::DateTime)),
);
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: string
format: date
"})
.unwrap();
let result = transform(&arena, &doc, "Date", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::Date)),
);
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: string
format: uri
"})
.unwrap();
let result = transform(&arena, &doc, "Url", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::Url)),
);
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: string
format: uuid
"})
.unwrap();
let result = transform(&arena, &doc, "Id", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::Uuid)),
);
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: string
format: byte
"})
.unwrap();
let result = transform(&arena, &doc, "Data", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::Bytes)),
);
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: string
format: binary
"})
.unwrap();
let result = transform(&arena, &doc, "RawData", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::Binary)),
);
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: string
"})
.unwrap();
let result = transform(&arena, &doc, "Text", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::String)),
);
}
#[test]
fn test_primitive_integer_formats() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let arena = Arena::new();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: integer
format: int32
"})
.unwrap();
let result = transform(&arena, &doc, "Count", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::I32)),
);
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: integer
format: int64
"})
.unwrap();
let result = transform(&arena, &doc, "BigCount", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::I64)),
);
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: integer
format: unix-time
"})
.unwrap();
let result = transform(&arena, &doc, "Timestamp", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::UnixTime)),
);
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: integer
"})
.unwrap();
let result = transform(&arena, &doc, "DefaultInt", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::I32)),
);
}
#[test]
fn test_primitive_number_formats() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let arena = Arena::new();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: number
format: float
"})
.unwrap();
let result = transform(&arena, &doc, "Price", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::F32)),
);
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: number
format: double
"})
.unwrap();
let result = transform(&arena, &doc, "BigPrice", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::F64)),
);
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: number
format: unix-time
"})
.unwrap();
let result = transform(&arena, &doc, "FloatTime", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::UnixTime)),
);
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: number
"})
.unwrap();
let result = transform(&arena, &doc, "DefaultNumber", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::F64)),
);
}
#[test]
fn test_array_with_ref_items() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Item:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: array
items:
$ref: '#/components/schemas/Item'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Items", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo { name: "Items", .. },
SpecContainer::Array(SpecInner {
ty: SpecType::Ref(_),
..
}),
)),
);
}
#[test]
fn test_array_with_inline_items() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: array
items:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Strings", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo {
name: "Strings",
..
},
SpecContainer::Array(SpecInner {
ty: SpecType::Inline(SpecInlineType::Primitive(_, PrimitiveType::String)),
..
}),
)),
);
}
#[test]
fn test_struct_with_own_properties() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let arena = Arena::new();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
required: [name, age]
properties:
name:
type: string
age:
type: integer
"})
.unwrap();
let result = transform(&arena, &doc, "Person", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo { name: "Person", .. },
Struct {
fields: [
SpecStructField {
name: StructFieldName::Name("name"),
ty: SpecType::Inline(SpecInlineType::Primitive(_, PrimitiveType::String)),
..
},
SpecStructField {
name: StructFieldName::Name("age"),
ty: SpecType::Inline(SpecInlineType::Primitive(_, PrimitiveType::I32)),
..
},
],
..
},
)),
);
}
#[test]
fn test_struct_with_additional_properties_ref() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
components:
schemas:
Value:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
properties:
name:
type: string
additionalProperties:
$ref: '#/components/schemas/Value'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Config", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo { name: "Config", .. },
Struct {
fields: [
_,
SpecStructField {
name: StructFieldName::Hint(StructFieldNameHint::AdditionalProperties),
flattened: true,
required: true,
ty: SpecType::Inline(SpecInlineType::Container(
_,
SpecContainer::Map(SpecInner {
ty: SpecType::Ref(_),
..
}),
)),
..
},
],
..
},
)),
);
}
#[test]
fn test_struct_with_additional_properties_inline() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
properties:
name:
type: string
additionalProperties:
type: object
properties:
inner:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Config", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo { name: "Config", .. },
Struct {
fields: [
SpecStructField {
name: StructFieldName::Name("name"),
flattened: false,
..
},
SpecStructField {
name: StructFieldName::Hint(StructFieldNameHint::AdditionalProperties),
flattened: true,
required: true,
ty: SpecType::Inline(SpecInlineType::Container(
InlineTypePath {
root: InlineTypePathRoot::Type("Config"),
segments: [InlineTypePathSegment::Field(StructFieldName::Hint(
StructFieldNameHint::AdditionalProperties,
))],
},
SpecContainer::Map(SpecInner {
ty: SpecType::Inline(SpecInlineType::Struct(
InlineTypePath {
root: InlineTypePathRoot::Type("Config"),
segments: [
InlineTypePathSegment::Field(StructFieldName::Hint(
StructFieldNameHint::AdditionalProperties,
)),
InlineTypePathSegment::MapValue,
],
},
_,
)),
..
}),
)),
..
},
],
..
},
)),
);
}
#[test]
fn test_struct_with_additional_properties_true() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
properties: {}
additionalProperties: true
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "DynamicMap", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo {
name: "DynamicMap",
..
},
Struct {
fields: [SpecStructField {
name: StructFieldName::Hint(StructFieldNameHint::AdditionalProperties),
flattened: true,
required: true,
ty: SpecType::Inline(SpecInlineType::Container(
_,
SpecContainer::Map(SpecInner {
ty: SpecType::Inline(SpecInlineType::Any(_)),
..
}),
)),
..
}],
..
},
)),
);
}
#[test]
fn test_struct_without_properties_falls_through() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
additionalProperties:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "DynamicMap", &schema);
assert_matches!(
&result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo {
name: "DynamicMap",
..
},
SpecContainer::Map(_),
)),
);
}
#[test]
fn test_struct_with_required_fields() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
properties:
name:
type: string
email:
type: string
required:
- name
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "User", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo { name: "User", .. },
Struct {
fields: [
SpecStructField {
name: StructFieldName::Name("name"),
required: true,
..
},
SpecStructField {
name: StructFieldName::Name("email"),
required: false,
..
},
],
..
},
)),
);
}
#[test]
fn test_struct_with_nullable_field_ref() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
NullableString:
type: string
nullable: true
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
properties:
value:
$ref: '#/components/schemas/NullableString'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Container", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo {
name: "Container",
..
},
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("value"),
ty: SpecType::Inline(SpecInlineType::Container(
_,
SpecContainer::Optional(SpecInner {
ty: SpecType::Ref(_),
..
}),
)),
..
}],
..
},
)),
);
}
#[test]
fn test_struct_with_nullable_field_inline() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
properties:
value:
type: string
nullable: true
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Container", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo {
name: "Container",
..
},
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("value"),
ty: SpecType::Inline(SpecInlineType::Container(
_,
SpecContainer::Optional(SpecInner {
ty: SpecType::Inline(SpecInlineType::Primitive(
_,
PrimitiveType::String
)),
..
}),
)),
..
}],
..
},
)),
);
}
#[test]
fn test_struct_with_nullable_field_openapi_31_syntax() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.1.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
properties:
value:
type: [string, 'null']
required:
- value
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Container", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo {
name: "Container",
..
},
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("value"),
ty: SpecType::Inline(SpecInlineType::Container(
_,
SpecContainer::Optional(SpecInner {
ty: SpecType::Inline(SpecInlineType::Primitive(
_,
PrimitiveType::String
)),
..
}),
)),
required: true,
..
}],
..
},
)),
);
}
#[test]
fn test_struct_ref_field_description() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Id:
type: string
description: An identifier
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
properties:
id:
$ref: '#/components/schemas/Id'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Entity", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo { name: "Entity", .. },
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("id"),
description: Some("An identifier"),
..
}],
..
},
)),
);
}
#[test]
fn test_struct_inline_field_description() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
properties:
name:
type: string
description: A user's name
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "User", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo { name: "User", .. },
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("name"),
description: Some("A user's name"),
..
}],
..
},
)),
);
}
#[test]
fn test_struct_inline_all_of_becomes_parent() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
allOf:
- type: object
properties:
name:
type: string
- type: object
properties:
age:
type: integer
properties:
email:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Person", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo { name: "Person", .. },
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("email"),
..
}],
parents: [
SpecType::Inline(SpecInlineType::Struct(
InlineTypePath {
root: InlineTypePathRoot::Type("Person"),
segments: [InlineTypePathSegment::Parent(1)],
},
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("name"),
..
}],
..
},
)),
SpecType::Inline(SpecInlineType::Struct(
InlineTypePath {
root: InlineTypePathRoot::Type("Person"),
segments: [InlineTypePathSegment::Parent(2)],
},
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("age"),
..
}],
..
},
)),
],
..
},
)),
);
}
#[test]
fn test_struct_mixed_all_of_ref_and_inline() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
components:
schemas:
Base:
type: object
properties:
id:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
allOf:
- $ref: '#/components/schemas/Base'
- type: object
properties:
name:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Child", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo { name: "Child", .. },
Struct {
fields: [],
parents: [
SpecType::Ref(r),
SpecType::Inline(SpecInlineType::Struct(
InlineTypePath {
root: InlineTypePathRoot::Type("Child"),
segments: [InlineTypePathSegment::Parent(2)],
},
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("name"),
..
}],
..
},
)),
],
..
},
)) if r.name() == "Base",
);
}
#[test]
fn test_tagged_with_mapping() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Dog:
type: object
properties:
bark:
type: string
Cat:
type: object
properties:
meow:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
oneOf:
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Cat'
discriminator:
propertyName: type
mapping:
dog: '#/components/schemas/Dog'
cat: '#/components/schemas/Cat'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Animal", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Tagged(
SchemaTypeInfo { name: "Animal", .. },
Tagged {
tag: "type",
variants: [
SpecTaggedVariant {
name: "Dog",
aliases: ["dog"],
..
},
SpecTaggedVariant {
name: "Cat",
aliases: ["cat"],
..
},
],
..
},
)),
);
}
#[test]
fn test_tagged_filters_non_refs() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Dog:
type: object
properties:
bark:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
oneOf:
- $ref: '#/components/schemas/Dog'
- type: object
properties:
inline:
type: string
discriminator:
propertyName: type
mapping:
dog: '#/components/schemas/Dog'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Animal", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Untagged(
SchemaTypeInfo { name: "Animal", .. },
Untagged {
variants: [
SpecUntaggedVariant::Some(UntaggedVariantNameHint::Index(1), SpecType::Ref(_)),
SpecUntaggedVariant::Some(
UntaggedVariantNameHint::Index(2),
SpecType::Inline(_)
),
],
..
},
)),
);
}
#[test]
fn test_tagged_multiple_aliases() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Success:
type: object
properties:
data:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
oneOf:
- $ref: '#/components/schemas/Success'
discriminator:
propertyName: status
mapping:
good: '#/components/schemas/Success'
ok: '#/components/schemas/Success'
success: '#/components/schemas/Success'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Result", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Tagged(
SchemaTypeInfo { name: "Result", .. },
Tagged {
variants: [SpecTaggedVariant {
name: "Success",
aliases: ["good", "ok", "success"],
..
}],
..
},
)),
);
}
#[test]
fn test_tagged_description() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Dog:
type: object
properties:
bark:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
description: A tagged union of animals
oneOf:
- $ref: '#/components/schemas/Dog'
discriminator:
propertyName: type
mapping:
dog: '#/components/schemas/Dog'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Animal", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Tagged(
SchemaTypeInfo { name: "Animal", .. },
Tagged {
description: Some("A tagged union of animals"),
tag: "type",
variants: [SpecTaggedVariant {
name: "Dog",
aliases: ["dog"],
..
}],
..
},
)),
);
}
#[test]
fn test_tagged_without_mapping() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Dog:
type: object
properties:
bark:
type: string
Cat:
type: object
properties:
meow:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
oneOf:
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Cat'
discriminator:
propertyName: petType
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Pet", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Tagged(
SchemaTypeInfo { name: "Pet", .. },
Tagged {
tag: "petType",
variants: [
SpecTaggedVariant {
name: "Dog",
aliases: ["Dog"],
..
},
SpecTaggedVariant {
name: "Cat",
aliases: ["Cat"],
..
},
],
..
},
)),
);
}
#[test]
fn test_tagged_with_partial_mapping() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Dog:
type: object
properties:
bark:
type: string
Cat:
type: object
properties:
meow:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
oneOf:
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Cat'
discriminator:
propertyName: type
mapping:
dog: '#/components/schemas/Dog'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Animal", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Tagged(
SchemaTypeInfo { name: "Animal", .. },
Tagged {
tag: "type",
variants: [
SpecTaggedVariant {
name: "Dog",
aliases: ["dog"],
..
},
SpecTaggedVariant {
name: "Cat",
aliases: ["Cat"],
..
},
],
..
},
)),
);
}
#[test]
fn test_untagged_basic() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
String:
type: string
Number:
type: integer
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
oneOf:
- $ref: '#/components/schemas/String'
- $ref: '#/components/schemas/Number'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "StringOrNumber", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Untagged(
SchemaTypeInfo {
name: "StringOrNumber",
..
},
Untagged {
variants: [
SpecUntaggedVariant::Some(UntaggedVariantNameHint::Index(1), SpecType::Ref(_)),
SpecUntaggedVariant::Some(UntaggedVariantNameHint::Index(2), SpecType::Ref(_)),
],
..
},
)),
);
}
#[test]
fn test_untagged_empty_simplifies() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
oneOf: []
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Empty", &schema);
assert_matches!(result, SpecType::Schema(SpecSchemaType::Any(_)));
}
#[test]
fn test_untagged_single_null_simplifies() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
oneOf:
- type: 'null'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "JustNull", &schema);
assert_matches!(result, SpecType::Schema(SpecSchemaType::Any(_)));
}
#[test]
fn test_untagged_single_variant_unwraps() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
String:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
oneOf:
- $ref: '#/components/schemas/String'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "JustString", &schema);
assert_matches!(result, SpecType::Ref(_));
}
#[test]
fn test_untagged_variant_numbering() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
A:
type: string
B:
type: string
C:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
oneOf:
- $ref: '#/components/schemas/A'
- $ref: '#/components/schemas/B'
- $ref: '#/components/schemas/C'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "ABC", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Untagged(
SchemaTypeInfo { name: "ABC", .. },
Untagged {
variants: [
SpecUntaggedVariant::Some(UntaggedVariantNameHint::Index(1), _),
SpecUntaggedVariant::Some(UntaggedVariantNameHint::Index(2), _),
SpecUntaggedVariant::Some(UntaggedVariantNameHint::Index(3), _),
],
..
},
)),
);
}
#[test]
fn test_untagged_null_detection() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
oneOf:
- type: 'null'
- type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "StringOrNull", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo {
name: "StringOrNull",
..
},
SpecContainer::Optional(_),
)),
);
}
#[test]
fn test_any_of_fields_marked_flattened_not_required() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Address:
type: object
properties:
street:
type: string
Email:
type: object
properties:
email:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
anyOf:
- $ref: '#/components/schemas/Address'
- $ref: '#/components/schemas/Email'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Contact", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo {
name: "Contact",
..
},
Struct {
fields: [
SpecStructField {
name: StructFieldName::Name("Address"),
flattened: true,
required: false,
..
},
SpecStructField {
name: StructFieldName::Name("Email"),
flattened: true,
required: false,
..
},
],
..
},
)),
);
}
#[test]
fn test_any_of_ref_uses_type_name() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Address:
type: object
properties:
street:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
anyOf:
- $ref: '#/components/schemas/Address'
- $ref: '#/components/schemas/Address'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Contact", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo {
name: "Contact",
..
},
Struct {
fields: [
SpecStructField {
name: StructFieldName::Name("Address"),
..
},
SpecStructField {
name: StructFieldName::Name("Address"),
..
},
],
..
},
)),
);
}
#[test]
fn test_any_of_inline_uses_index_hint() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
anyOf:
- type: object
properties:
a:
type: string
- type: object
properties:
b:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Mixed", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo { name: "Mixed", .. },
Struct {
fields: [
SpecStructField {
name: StructFieldName::Hint(StructFieldNameHint::Index(1)),
flattened: true,
..
},
SpecStructField {
name: StructFieldName::Hint(StructFieldNameHint::Index(2)),
flattened: true,
..
},
],
..
},
)),
);
}
#[test]
fn test_any_of_with_properties() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Extra1:
type: object
properties:
extra1:
type: string
Extra2:
type: object
properties:
extra2:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
anyOf:
- $ref: '#/components/schemas/Extra1'
- $ref: '#/components/schemas/Extra3'
properties:
Extra2:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Combined", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo {
name: "Combined",
..
},
Struct {
fields: [
SpecStructField {
name: StructFieldName::Name("Extra2"),
flattened: false,
..
},
SpecStructField {
name: StructFieldName::Name("Extra1"),
flattened: true,
..
},
SpecStructField {
name: StructFieldName::Name("Extra3"),
flattened: true,
..
},
],
..
},
)),
);
}
#[test]
fn test_any_of_nullable_refs() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
NullableString1:
type: string
nullable: true
NullableString2:
type: string
nullable: true
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
anyOf:
- $ref: '#/components/schemas/NullableString1'
- $ref: '#/components/schemas/NullableString2'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Container", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo {
name: "Container",
..
},
Struct {
fields: [
SpecStructField {
name: StructFieldName::Name("NullableString1"),
flattened: true,
..
},
SpecStructField {
name: StructFieldName::Name("NullableString2"),
flattened: true,
..
},
],
..
},
)),
);
}
#[test]
fn test_any_of_with_all_of() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Base:
type: object
properties:
id:
type: string
Extra1:
type: object
properties:
extra1:
type: string
Extra2:
type: object
properties:
extra2:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
allOf:
- $ref: '#/components/schemas/Base'
anyOf:
- $ref: '#/components/schemas/Extra1'
- $ref: '#/components/schemas/Extra2'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Combined", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo {
name: "Combined",
..
},
Struct {
fields: [
SpecStructField {
name: StructFieldName::Name("Extra1"),
flattened: true,
..
},
SpecStructField {
name: StructFieldName::Name("Extra2"),
flattened: true,
..
},
],
parents: [_],
..
},
)),
);
}
#[test]
fn test_boolean_primitive_transformation() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: boolean
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Flag", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::Bool)),
);
}
#[test]
fn test_unhandled_string_format_falls_back_to_string() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: string
format: currency
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "CustomType", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::String)),
);
}
#[test]
fn test_empty_type_array_produces_any() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.1.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: []
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "NoType", &schema);
assert_matches!(result, SpecType::Schema(SpecSchemaType::Any(_)));
}
#[test]
fn test_array_without_items_produces_array_of_any() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: array
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "ArrayAny", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo {
name: "ArrayAny",
..
},
SpecContainer::Array(SpecInner {
ty: SpecType::Inline(SpecInlineType::Any(_)),
..
}),
)),
);
}
#[test]
fn test_object_with_empty_properties_produces_struct() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
properties: {}
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "EmptyObject", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo {
name: "EmptyObject",
..
},
Struct { fields: [], .. },
)),
);
}
#[test]
fn test_schema_without_type_or_properties_produces_any() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
{}
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Empty", &schema);
assert_matches!(result, SpecType::Schema(SpecSchemaType::Any(_)));
}
#[test]
fn test_type_and_null_in_type_array_creates_nullable() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.1.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: [string, 'null']
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "StringOrNull", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo {
name: "StringOrNull",
..
},
SpecContainer::Optional(SpecInner {
ty: SpecType::Inline(SpecInlineType::Primitive(_, PrimitiveType::String)),
..
}),
)),
);
}
#[test]
fn test_type_array_and_null_creates_nullable_array() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.1.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: [array, 'null']
items:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "StringArrayOrNull", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo {
name: "StringArrayOrNull",
..
},
SpecContainer::Optional(SpecInner {
ty: SpecType::Inline(SpecInlineType::Container(
_,
SpecContainer::Array(SpecInner {
ty: SpecType::Inline(SpecInlineType::Primitive(_, PrimitiveType::String)),
..
}),
)),
..
}),
)),
);
}
#[test]
fn test_type_object_and_null_creates_nullable_map() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.1.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: [object, 'null']
additionalProperties:
type: integer
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "IntMapOrNull", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo {
name: "IntMapOrNull",
..
},
SpecContainer::Optional(SpecInner {
ty: SpecType::Inline(SpecInlineType::Container(
_,
SpecContainer::Map(SpecInner {
ty: SpecType::Inline(SpecInlineType::Primitive(_, PrimitiveType::I32)),
..
}),
)),
..
}),
)),
);
}
#[test]
fn test_multiple_types_string_and_integer_untagged() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.1.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: [string, integer]
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "StringOrInt", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Untagged(
SchemaTypeInfo {
name: "StringOrInt",
..
},
Untagged {
variants: [
SpecUntaggedVariant::Some(
UntaggedVariantNameHint::Primitive(PrimitiveType::String),
SpecType::Inline(SpecInlineType::Primitive(_, PrimitiveType::String)),
),
SpecUntaggedVariant::Some(
UntaggedVariantNameHint::Primitive(PrimitiveType::I32),
SpecType::Inline(SpecInlineType::Primitive(_, PrimitiveType::I32)),
),
],
..
},
)),
);
}
#[test]
fn test_type_array_with_format_produces_inline_variants() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.1.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: [string, integer]
format: date-time
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "DateOrUnix", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Untagged(
SchemaTypeInfo {
name: "DateOrUnix",
..
},
Untagged {
variants: [
SpecUntaggedVariant::Some(
UntaggedVariantNameHint::Primitive(PrimitiveType::DateTime),
SpecType::Inline(SpecInlineType::Primitive(_, PrimitiveType::DateTime)),
),
SpecUntaggedVariant::Some(
UntaggedVariantNameHint::Primitive(PrimitiveType::I32),
SpecType::Inline(SpecInlineType::Primitive(_, PrimitiveType::I32)),
),
],
..
},
)),
);
}
#[test]
fn test_deeply_nested_inline_types() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
required: [items]
properties:
items:
type: array
items:
type: object
required: [field]
properties:
field:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Outer", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo { name: "Outer", .. },
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("items"),
ty: SpecType::Inline(SpecInlineType::Container(
InlineTypePath {
root: InlineTypePathRoot::Type("Outer"),
segments: [InlineTypePathSegment::Field(StructFieldName::Name(
"items",
))],
},
SpecContainer::Array(SpecInner {
ty: SpecType::Inline(SpecInlineType::Struct(
InlineTypePath {
root: InlineTypePathRoot::Type("Outer"),
segments: [
InlineTypePathSegment::Field(StructFieldName::Name(
"items",
)),
InlineTypePathSegment::ArrayItem,
],
},
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("field"),
ty: SpecType::Inline(SpecInlineType::Primitive(
_,
PrimitiveType::String
)),
..
}],
..
},
)),
..
}),
)),
..
}],
..
},
)),
);
}
#[test]
fn test_enum_with_only_null_json_values_produces_empty_enum() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: 'null'
enum: [null]
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "NullEnum", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Enum(
SchemaTypeInfo {
name: "NullEnum",
..
},
Enum { variants: [], .. },
)),
);
}
#[test]
fn test_additional_properties_false_creates_struct() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
required: [name]
properties:
name:
type: string
additionalProperties: false
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "StrictObject", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo {
name: "StrictObject",
..
},
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("name"),
ty: SpecType::Inline(SpecInlineType::Primitive(_, PrimitiveType::String)),
..
}],
..
},
)),
);
}
#[test]
fn test_array_inline_path_construction() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: array
items:
type: object
properties:
field:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Container", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo {
name: "Container",
..
},
SpecContainer::Array(SpecInner {
ty: SpecType::Inline(SpecInlineType::Struct(
InlineTypePath {
root: InlineTypePathRoot::Type("Container"),
segments: [InlineTypePathSegment::ArrayItem],
},
_,
)),
..
}),
)),
);
}
#[test]
fn test_map_inline_path_construction() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
additionalProperties:
type: object
properties:
field:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Dictionary", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo {
name: "Dictionary",
..
},
SpecContainer::Map(SpecInner {
ty: SpecType::Inline(SpecInlineType::Struct(
InlineTypePath {
root: InlineTypePathRoot::Type("Dictionary"),
segments: [InlineTypePathSegment::MapValue],
},
_,
)),
..
}),
)),
);
}
#[test]
fn test_struct_inline_path_construction() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
required: [nested]
properties:
nested:
type: object
properties:
inner:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Outer", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo { name: "Outer", .. },
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("nested"),
ty: SpecType::Inline(SpecInlineType::Struct(
InlineTypePath {
root: InlineTypePathRoot::Type("Outer"),
segments: [InlineTypePathSegment::Field(StructFieldName::Name(
"nested",
))],
},
_,
)),
..
}],
..
},
)),
);
}
#[test]
fn test_inline_tagged_union_in_struct_field() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Cat:
type: object
properties:
meow:
type: string
Dog:
type: object
properties:
bark:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
required: [animal]
properties:
animal:
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
discriminator:
propertyName: kind
mapping:
cat: '#/components/schemas/Cat'
dog: '#/components/schemas/Dog'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Container", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo {
name: "Container",
..
},
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("animal"),
ty: SpecType::Inline(SpecInlineType::Tagged(
InlineTypePath {
root: InlineTypePathRoot::Type("Container"),
segments: [InlineTypePathSegment::Field(StructFieldName::Name(
"animal",
))],
},
Tagged {
tag: "kind",
variants: [
SpecTaggedVariant {
name: "Cat",
aliases: ["cat"],
..
},
SpecTaggedVariant {
name: "Dog",
aliases: ["dog"],
..
},
],
..
},
)),
..
}],
..
},
)),
);
}
#[test]
fn test_recursive_all_of_ref_nullable() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Node:
type: object
properties:
value:
type: string
next:
nullable: true
allOf:
- $ref: '#/components/schemas/Node'
required:
- value
- next
"})
.unwrap();
let schema = &doc.components.as_ref().unwrap().schemas["Node"];
let arena = Arena::new();
let result = transform(&arena, &doc, "Node", schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo { name: "Node", .. },
Struct {
fields: [
SpecStructField {
name: StructFieldName::Name("value"),
ty: SpecType::Inline(SpecInlineType::Primitive(_, PrimitiveType::String)),
required: true,
..
},
SpecStructField {
name: StructFieldName::Name("next"),
ty: SpecType::Inline(SpecInlineType::Container(
_,
SpecContainer::Optional(_),
)),
required: true,
..
},
],
..
},
)),
);
}
#[test]
fn test_recursive_all_of_ref() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Node:
type: object
properties:
value:
type: string
next:
allOf:
- $ref: '#/components/schemas/Node'
"})
.unwrap();
let schema = &doc.components.as_ref().unwrap().schemas["Node"];
let arena = Arena::new();
let result = transform(&arena, &doc, "Node", schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo { name: "Node", .. },
Struct {
fields: [
SpecStructField {
name: StructFieldName::Name("value"),
ty: SpecType::Inline(SpecInlineType::Container(
_,
SpecContainer::Optional(_),
)),
required: false,
..
},
SpecStructField {
name: StructFieldName::Name("next"),
ty: SpecType::Inline(SpecInlineType::Container(
_,
SpecContainer::Optional(_),
)),
required: false,
..
},
],
..
},
)),
);
}
#[test]
fn test_recursive_multi_all_of_ref_no_stack_overflow() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Mixin:
type: object
properties:
extra:
type: string
Node:
type: object
properties:
value:
type: string
next:
allOf:
- $ref: '#/components/schemas/Node'
- $ref: '#/components/schemas/Mixin'
"})
.unwrap();
let schema = &doc.components.as_ref().unwrap().schemas["Node"];
let arena = Arena::new();
let result = transform(&arena, &doc, "Node", schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo { name: "Node", .. },
Struct {
fields: [
SpecStructField {
name: StructFieldName::Name("value"),
..
},
SpecStructField {
name: StructFieldName::Name("next"),
..
},
],
..
},
)),
);
}
#[test]
fn test_named_array_schema_produces_container() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: array
items:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "StringList", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo {
name: "StringList",
..
},
SpecContainer::Array(SpecInner {
ty: SpecType::Inline(SpecInlineType::Primitive(_, PrimitiveType::String)),
..
}),
)),
);
}
#[test]
fn test_named_array_with_inline_one_of_items_produces_container() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Cat:
type: object
properties:
meow:
type: string
Dog:
type: object
properties:
bark:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: array
items:
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Animals", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo {
name: "Animals",
..
},
SpecContainer::Array(SpecInner {
ty: SpecType::Inline(SpecInlineType::Untagged(..)),
..
}),
)),
);
}
#[test]
fn test_named_map_schema_produces_container() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
additionalProperties:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Metadata", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo {
name: "Metadata",
..
},
SpecContainer::Map(_),
)),
);
}
#[test]
fn test_named_nullable_schema_produces_container() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.1.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: [string, 'null']
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "NullableString", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo {
name: "NullableString",
..
},
SpecContainer::Optional(SpecInner {
ty: SpecType::Inline(SpecInlineType::Primitive(_, PrimitiveType::String)),
..
}),
)),
);
}
#[test]
fn test_named_container_preserves_description() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
description: A list of identifiers
type: array
items:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Ids", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo { name: "Ids", .. },
SpecContainer::Array(SpecInner {
description: Some("A list of identifiers"),
..
}),
)),
);
}
#[test]
fn test_named_primitive_does_not_produce_container() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Name", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Primitive(_, PrimitiveType::String)),
);
}
#[test]
fn test_untagged_single_variant_one_of_ref_produces_container() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
components:
schemas:
Inner:
type: object
properties:
value:
type: string
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
oneOf:
- type: 'null'
- $ref: '#/components/schemas/Inner'
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "MaybeInner", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Container(
SchemaTypeInfo {
name: "MaybeInner",
..
},
SpecContainer::Optional(SpecInner {
ty: SpecType::Ref(_),
..
}),
)),
);
}
#[test]
fn test_inline_array_produces_inline_container() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
type: object
required: [items]
properties:
items:
type: array
items:
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Container", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo {
name: "Container",
..
},
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("items"),
ty: SpecType::Inline(SpecInlineType::Container(_, SpecContainer::Array(_))),
..
}],
..
},
)),
);
}
#[test]
fn test_optional_field_container_description_is_not_parent_schema() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test
version: 1.0.0
"})
.unwrap();
let schema: Schema = serde_yaml::from_str(indoc::indoc! {"
description: A parent struct
type: object
properties:
nickname:
description: The nickname
type: string
"})
.unwrap();
let arena = Arena::new();
let result = transform(&arena, &doc, "Parent", &schema);
assert_matches!(
result,
SpecType::Schema(SpecSchemaType::Struct(
SchemaTypeInfo { name: "Parent", .. },
Struct {
fields: [SpecStructField {
name: StructFieldName::Name("nickname"),
ty: SpecType::Inline(SpecInlineType::Container(
_,
SpecContainer::Optional(SpecInner {
description: Some("The nickname"),
..
}),
)),
..
}],
..
},
)),
);
}