From 036a68922f135ce08fe04de90dadbd737540c2e7 Mon Sep 17 00:00:00 2001 From: GErP83 Date: Fri, 20 Feb 2026 19:35:45 +0100 Subject: [PATCH 1/5] Fix null default/example encoding in scalar schemas --- .../Schema/BoolSchemaRepresentable.swift | 4 +- .../Schema/DoubleSchemaRepresentable.swift | 4 +- .../Schema/FloatSchemaRepresentable.swift | 4 +- .../Schema/Int32SchemaRepresentable.swift | 4 +- .../Schema/Int64SchemaRepresentable.swift | 4 +- .../Schema/IntSchemaRepresentable.swift | 4 +- .../Schema/StringSchemaRepresentable.swift | 4 +- .../SchemaSerializationTestSuite.swift | 47 +++++++++++++++++++ 8 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 Tests/FeatherOpenAPITests/SchemaSerializationTestSuite.swift diff --git a/Sources/FeatherOpenAPI/Schema/BoolSchemaRepresentable.swift b/Sources/FeatherOpenAPI/Schema/BoolSchemaRepresentable.swift index d4976da..03f00eb 100644 --- a/Sources/FeatherOpenAPI/Schema/BoolSchemaRepresentable.swift +++ b/Sources/FeatherOpenAPI/Schema/BoolSchemaRepresentable.swift @@ -35,8 +35,8 @@ extension BoolSchemaRepresentable { discriminator: nil, externalDocs: nil, allowedValues: nil, - defaultValue: .init(defaultValue), - example: .init(example) + defaultValue: defaultValue.map { .init($0) }, + example: example.map { .init($0) } ) } } diff --git a/Sources/FeatherOpenAPI/Schema/DoubleSchemaRepresentable.swift b/Sources/FeatherOpenAPI/Schema/DoubleSchemaRepresentable.swift index b4a6287..b136a0d 100644 --- a/Sources/FeatherOpenAPI/Schema/DoubleSchemaRepresentable.swift +++ b/Sources/FeatherOpenAPI/Schema/DoubleSchemaRepresentable.swift @@ -40,8 +40,8 @@ extension DoubleSchemaRepresentable { maximum: nil, minimum: nil, allowedValues: allowedValues?.map { .init($0) }, - defaultValue: .init(defaultValue), - example: .init(example) + defaultValue: defaultValue.map { .init($0) }, + example: example.map { .init($0) } ) } } diff --git a/Sources/FeatherOpenAPI/Schema/FloatSchemaRepresentable.swift b/Sources/FeatherOpenAPI/Schema/FloatSchemaRepresentable.swift index 7b641e7..923f708 100644 --- a/Sources/FeatherOpenAPI/Schema/FloatSchemaRepresentable.swift +++ b/Sources/FeatherOpenAPI/Schema/FloatSchemaRepresentable.swift @@ -40,8 +40,8 @@ extension FloatSchemaRepresentable { maximum: nil, minimum: nil, allowedValues: allowedValues?.map { .init($0) }, - defaultValue: .init(defaultValue), - example: .init(example) + defaultValue: defaultValue.map { .init($0) }, + example: example.map { .init($0) } ) } } diff --git a/Sources/FeatherOpenAPI/Schema/Int32SchemaRepresentable.swift b/Sources/FeatherOpenAPI/Schema/Int32SchemaRepresentable.swift index ce2d557..ef446c9 100644 --- a/Sources/FeatherOpenAPI/Schema/Int32SchemaRepresentable.swift +++ b/Sources/FeatherOpenAPI/Schema/Int32SchemaRepresentable.swift @@ -40,8 +40,8 @@ extension Int32SchemaRepresentable { maximum: nil, minimum: nil, allowedValues: allowedValues?.map { .init($0) }, - defaultValue: .init(defaultValue), - example: .init(example) + defaultValue: defaultValue.map { .init($0) }, + example: example.map { .init($0) } ) } } diff --git a/Sources/FeatherOpenAPI/Schema/Int64SchemaRepresentable.swift b/Sources/FeatherOpenAPI/Schema/Int64SchemaRepresentable.swift index 43e15a4..1f01181 100644 --- a/Sources/FeatherOpenAPI/Schema/Int64SchemaRepresentable.swift +++ b/Sources/FeatherOpenAPI/Schema/Int64SchemaRepresentable.swift @@ -40,8 +40,8 @@ extension Int64SchemaRepresentable { maximum: nil, minimum: nil, allowedValues: allowedValues?.map { .init($0) }, - defaultValue: .init(defaultValue), - example: .init(example) + defaultValue: defaultValue.map { .init($0) }, + example: example.map { .init($0) } ) } } diff --git a/Sources/FeatherOpenAPI/Schema/IntSchemaRepresentable.swift b/Sources/FeatherOpenAPI/Schema/IntSchemaRepresentable.swift index 3030ef6..1dc3bf8 100644 --- a/Sources/FeatherOpenAPI/Schema/IntSchemaRepresentable.swift +++ b/Sources/FeatherOpenAPI/Schema/IntSchemaRepresentable.swift @@ -40,8 +40,8 @@ extension IntSchemaRepresentable { maximum: nil, minimum: nil, allowedValues: allowedValues?.map { .init($0) }, - defaultValue: .init(defaultValue), - example: .init(example) + defaultValue: defaultValue.map { .init($0) }, + example: example.map { .init($0) } ) } } diff --git a/Sources/FeatherOpenAPI/Schema/StringSchemaRepresentable.swift b/Sources/FeatherOpenAPI/Schema/StringSchemaRepresentable.swift index f0d9223..a553c9d 100644 --- a/Sources/FeatherOpenAPI/Schema/StringSchemaRepresentable.swift +++ b/Sources/FeatherOpenAPI/Schema/StringSchemaRepresentable.swift @@ -40,8 +40,8 @@ extension StringSchemaRepresentable { maxLength: nil, pattern: nil, allowedValues: allowedValues?.map { .init($0) }, - defaultValue: .init(defaultValue), - example: .init(example) + defaultValue: defaultValue.map { .init($0) }, + example: example.map { .init($0) } ) } } diff --git a/Tests/FeatherOpenAPITests/SchemaSerializationTestSuite.swift b/Tests/FeatherOpenAPITests/SchemaSerializationTestSuite.swift new file mode 100644 index 0000000..29180e9 --- /dev/null +++ b/Tests/FeatherOpenAPITests/SchemaSerializationTestSuite.swift @@ -0,0 +1,47 @@ +import OpenAPIKit30 +import Testing +import Yams + +@testable import FeatherOpenAPI + +@Suite +struct SchemaSerializationTestSuite { + + @Test + func omittedStringDefaultAndExampleDoNotSerializeAsNull() throws { + let schema = OmittedStringSchema() + let openAPI = schema.openAPISchema() + let yaml = try YAMLEncoder().encode(openAPI) + + #expect(yaml.contains("default: null") == false) + #expect(yaml.contains("example: null") == false) + } + + @Test + func omittedIntDefaultAndExampleDoNotSerializeAsNull() throws { + let schema = OmittedIntSchema() + let openAPI = schema.openAPISchema() + let yaml = try YAMLEncoder().encode(openAPI) + + #expect(yaml.contains("default: null") == false) + #expect(yaml.contains("example: null") == false) + } + + @Test + func explicitDefaultAndExampleStillSerialize() throws { + let schema = ExplicitStringSchema() + let openAPI = schema.openAPISchema() + let yaml = try YAMLEncoder().encode(openAPI) + + #expect(yaml.contains("default: abc")) + #expect(yaml.contains("example: xyz")) + } +} + +private struct OmittedStringSchema: StringSchemaRepresentable {} +private struct OmittedIntSchema: IntSchemaRepresentable {} + +private struct ExplicitStringSchema: StringSchemaRepresentable { + var defaultValue: String? { "abc" } + var example: String? { "xyz" } +} From 96e3adccf79ab1c9513a9ed3d51619abd255de6c Mon Sep 17 00:00:00 2001 From: GErP83 Date: Fri, 20 Feb 2026 19:41:28 +0100 Subject: [PATCH 2/5] more tests --- .../SchemaSerializationTestSuite.swift | 53 +++++++++++++++---- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/Tests/FeatherOpenAPITests/SchemaSerializationTestSuite.swift b/Tests/FeatherOpenAPITests/SchemaSerializationTestSuite.swift index 29180e9..2fe80df 100644 --- a/Tests/FeatherOpenAPITests/SchemaSerializationTestSuite.swift +++ b/Tests/FeatherOpenAPITests/SchemaSerializationTestSuite.swift @@ -9,30 +9,56 @@ struct SchemaSerializationTestSuite { @Test func omittedStringDefaultAndExampleDoNotSerializeAsNull() throws { - let schema = OmittedStringSchema() - let openAPI = schema.openAPISchema() - let yaml = try YAMLEncoder().encode(openAPI) - + let yaml = try YAMLEncoder().encode(OmittedStringSchema().openAPISchema()) #expect(yaml.contains("default: null") == false) #expect(yaml.contains("example: null") == false) } @Test func omittedIntDefaultAndExampleDoNotSerializeAsNull() throws { - let schema = OmittedIntSchema() - let openAPI = schema.openAPISchema() - let yaml = try YAMLEncoder().encode(openAPI) + let yaml = try YAMLEncoder().encode(OmittedIntSchema().openAPISchema()) + #expect(yaml.contains("default: null") == false) + #expect(yaml.contains("example: null") == false) + } + @Test + func omittedInt32DefaultAndExampleDoNotSerializeAsNull() throws { + let yaml = try YAMLEncoder().encode(OmittedInt32Schema().openAPISchema()) #expect(yaml.contains("default: null") == false) #expect(yaml.contains("example: null") == false) } @Test - func explicitDefaultAndExampleStillSerialize() throws { - let schema = ExplicitStringSchema() - let openAPI = schema.openAPISchema() - let yaml = try YAMLEncoder().encode(openAPI) + func omittedInt64DefaultAndExampleDoNotSerializeAsNull() throws { + let yaml = try YAMLEncoder().encode(OmittedInt64Schema().openAPISchema()) + #expect(yaml.contains("default: null") == false) + #expect(yaml.contains("example: null") == false) + } + + @Test + func omittedFloatDefaultAndExampleDoNotSerializeAsNull() throws { + let yaml = try YAMLEncoder().encode(OmittedFloatSchema().openAPISchema()) + #expect(yaml.contains("default: null") == false) + #expect(yaml.contains("example: null") == false) + } + @Test + func omittedDoubleDefaultAndExampleDoNotSerializeAsNull() throws { + let yaml = try YAMLEncoder().encode(OmittedDoubleSchema().openAPISchema()) + #expect(yaml.contains("default: null") == false) + #expect(yaml.contains("example: null") == false) + } + + @Test + func omittedBoolDefaultAndExampleDoNotSerializeAsNull() throws { + let yaml = try YAMLEncoder().encode(OmittedBoolSchema().openAPISchema()) + #expect(yaml.contains("default: null") == false) + #expect(yaml.contains("example: null") == false) + } + + @Test + func explicitDefaultAndExampleStillSerialize() throws { + let yaml = try YAMLEncoder().encode(ExplicitStringSchema().openAPISchema()) #expect(yaml.contains("default: abc")) #expect(yaml.contains("example: xyz")) } @@ -40,6 +66,11 @@ struct SchemaSerializationTestSuite { private struct OmittedStringSchema: StringSchemaRepresentable {} private struct OmittedIntSchema: IntSchemaRepresentable {} +private struct OmittedInt32Schema: Int32SchemaRepresentable {} +private struct OmittedInt64Schema: Int64SchemaRepresentable {} +private struct OmittedFloatSchema: FloatSchemaRepresentable {} +private struct OmittedDoubleSchema: DoubleSchemaRepresentable {} +private struct OmittedBoolSchema: BoolSchemaRepresentable {} private struct ExplicitStringSchema: StringSchemaRepresentable { var defaultValue: String? { "abc" } From 46420f008e69aceacad9929dd5848fd3f6daf0cd Mon Sep 17 00:00:00 2001 From: GErP83 Date: Fri, 20 Feb 2026 19:43:03 +0100 Subject: [PATCH 3/5] Update SchemaSerializationTestSuite.swift --- .../SchemaSerializationTestSuite.swift | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Tests/FeatherOpenAPITests/SchemaSerializationTestSuite.swift b/Tests/FeatherOpenAPITests/SchemaSerializationTestSuite.swift index 2fe80df..255404c 100644 --- a/Tests/FeatherOpenAPITests/SchemaSerializationTestSuite.swift +++ b/Tests/FeatherOpenAPITests/SchemaSerializationTestSuite.swift @@ -9,7 +9,8 @@ struct SchemaSerializationTestSuite { @Test func omittedStringDefaultAndExampleDoNotSerializeAsNull() throws { - let yaml = try YAMLEncoder().encode(OmittedStringSchema().openAPISchema()) + let yaml = try YAMLEncoder() + .encode(OmittedStringSchema().openAPISchema()) #expect(yaml.contains("default: null") == false) #expect(yaml.contains("example: null") == false) } @@ -23,28 +24,32 @@ struct SchemaSerializationTestSuite { @Test func omittedInt32DefaultAndExampleDoNotSerializeAsNull() throws { - let yaml = try YAMLEncoder().encode(OmittedInt32Schema().openAPISchema()) + let yaml = try YAMLEncoder() + .encode(OmittedInt32Schema().openAPISchema()) #expect(yaml.contains("default: null") == false) #expect(yaml.contains("example: null") == false) } @Test func omittedInt64DefaultAndExampleDoNotSerializeAsNull() throws { - let yaml = try YAMLEncoder().encode(OmittedInt64Schema().openAPISchema()) + let yaml = try YAMLEncoder() + .encode(OmittedInt64Schema().openAPISchema()) #expect(yaml.contains("default: null") == false) #expect(yaml.contains("example: null") == false) } @Test func omittedFloatDefaultAndExampleDoNotSerializeAsNull() throws { - let yaml = try YAMLEncoder().encode(OmittedFloatSchema().openAPISchema()) + let yaml = try YAMLEncoder() + .encode(OmittedFloatSchema().openAPISchema()) #expect(yaml.contains("default: null") == false) #expect(yaml.contains("example: null") == false) } @Test func omittedDoubleDefaultAndExampleDoNotSerializeAsNull() throws { - let yaml = try YAMLEncoder().encode(OmittedDoubleSchema().openAPISchema()) + let yaml = try YAMLEncoder() + .encode(OmittedDoubleSchema().openAPISchema()) #expect(yaml.contains("default: null") == false) #expect(yaml.contains("example: null") == false) } @@ -58,7 +63,8 @@ struct SchemaSerializationTestSuite { @Test func explicitDefaultAndExampleStillSerialize() throws { - let yaml = try YAMLEncoder().encode(ExplicitStringSchema().openAPISchema()) + let yaml = try YAMLEncoder() + .encode(ExplicitStringSchema().openAPISchema()) #expect(yaml.contains("default: abc")) #expect(yaml.contains("example: xyz")) } From 18ffd2e735de49b1cb70f33e8c6458076344bf16 Mon Sep 17 00:00:00 2001 From: GErP83 Date: Fri, 20 Feb 2026 19:45:32 +0100 Subject: [PATCH 4/5] Update testing.yml --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 68dadfa..0a8c61d 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -9,7 +9,7 @@ jobs: swiftlang_checks: name: Swiftlang Checks - uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main + uses: swiftlang/github-workflows/.github/workflows/soundness.yml@0.0.7 with: license_header_check_project_name: "project" format_check_enabled : true From 9bfb72859f87d9f23fafcf48307161e94eb17ba1 Mon Sep 17 00:00:00 2001 From: GErP83 Date: Fri, 20 Feb 2026 19:48:45 +0100 Subject: [PATCH 5/5] Update testing.yml --- .github/workflows/testing.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 0a8c61d..218fd2f 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -7,6 +7,10 @@ on: jobs: + api_breakage: + name: Check API breakage + uses: BinaryBirds/github-workflows/.github/workflows/api_breakage.yml@main + swiftlang_checks: name: Swiftlang Checks uses: swiftlang/github-workflows/.github/workflows/soundness.yml@0.0.7