question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[BUG] [Swift5] support for oneOf, anyOf, allOf, and discriminator directives

See original GitHub issue

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What’s the actual output vs expected output? The generated code should support the oneOf, anyOf, allOf directives
Description

If one has a response type defined as :

responses:
    '200':
        content:
            application/json:
                schema:
                    oneOf:
                        - $ref: '#/schemas/someObject1'
                        - $ref: '#/schemas/someObject2'

the generated Swift client has this requestBuilder definition:

let requestBuilder: RequestBuilder<OneOfsomeObject1someObject2>.Type = YourAPI.requestBuilderFactory.getBuilder()

but there is no trace of the OneOfsomeObject1someObject2 type definition in the generated code, thus making it not working.

Is there any way to get a working client for an openApi definition that uses oneOf (or anyOf, allOf) ?

openapi-generator version

5.0.0-SNAPSHOT, compiled from git.

Generation Details

openapi-generator generate -I defs.yaml -g swift5 ./output_dir

Related issues/PRs

https://github.com/OpenAPITools/openapi-generator/issues/2588#issue-429102544 (really old one, possibly relating to the old Swift 4 generator, gives a little suggestion, but not much)

@jgavris @ehyche @Edubits @jaz-ah @4brunu could one you look into the issue and see if there is any way to solve it ? Even a simple idea to point me in the right direction to implement the missing feature would be great…

Issue Analytics

  • State:open
  • Created 3 years ago
  • Comments:7 (6 by maintainers)

github_iconTop GitHub Comments

7reactions
adarhefcommented, Apr 25, 2022

Hello! I wanted to use discriminator and mapping to utilize this feature with the oneOf directive. Since I was already using custom templates I edited the relevant mustache file (modelOneOf.mustache). Couldn’t spend too much time on it but it got things working for me:

public enum {{classname}}: {{#useVapor}}Content{{/useVapor}}{{^useVapor}}Codable, JSONEncodable{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}}{{/useVapor}} {
    {{#oneOf}}
    case type{{.}}({{.}})
    {{/oneOf}}

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        {{#oneOf}}
        case .type{{.}}(let value):
            try container.encode(value)
        {{/oneOf}}
        }
    }

    {{#discriminator}}
    enum CodingKeys: String, CodingKey {
        case {{discriminator.propertyName}}
    }
    {{/discriminator}}

    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        {{#discriminator}}
        let keyedContainer = try decoder.container(keyedBy: CodingKeys.self)
        let discriminator = try keyedContainer.decode(String.self, forKey: .{{discriminator.propertyName}})
        switch discriminator {
        {{#discriminator.mappedModels}}
        case "{{mappingName}}":
            let value = try container.decode({{modelName}}.self)
            self = .type{{modelName}}(value)
        {{/discriminator.mappedModels}}
        default:
            throw DecodingError.typeMismatch(Self.Type.self, .init(codingPath: decoder.codingPath, debugDescription: "Unable to decode instance of {{classname}}"))
        }
        {{/discriminator}}
        {{^discriminator}}
        {{#oneOf}}
        {{#-first}}
        if let value = try? container.decode({{.}}.self) {
        {{/-first}}
        {{^-first}}
        } else if let value = try? container.decode({{.}}.self) {
        {{/-first}}
            self = .type{{.}}(value)
        {{/oneOf}}
        } else {
            throw DecodingError.typeMismatch(Self.Type.self, .init(codingPath: decoder.codingPath, debugDescription: "Unable to decode instance of {{classname}}"))
        }
        {{/discriminator}}
    }
}

This results in a Codable enum that represent all of the options discriminated by the discriminator property.

For my use case the code ends up looking like this:

//
// DiscriminatedSearchMatchResult.swift
//
// Generated by openapi-generator
// https://openapi-generator.tech
//

import Foundation
#if canImport(AnyCodable)
import AnyCodable
#endif

public enum DiscriminatedSearchMatchResult: Codable, JSONEncodable, Hashable {
    case typeAggravatingFactorResult(AggravatingFactorResult)
    case typeConditionResult(ConditionResult)
    case typeEntityTypeResult(EntityTypeResult)
    case typeSubstanceGroupResult(SubstanceGroupResult)
    case typeSymptomResult(SymptomResult)
    case typeTreatmentResult(TreatmentResult)

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .typeAggravatingFactorResult(let value):
            try container.encode(value)
        case .typeConditionResult(let value):
            try container.encode(value)
        case .typeEntityTypeResult(let value):
            try container.encode(value)
        case .typeSubstanceGroupResult(let value):
            try container.encode(value)
        case .typeSymptomResult(let value):
            try container.encode(value)
        case .typeTreatmentResult(let value):
            try container.encode(value)
        }
    }

    enum CodingKeys: String, CodingKey {
        case matchType
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let keyedContainer = try decoder.container(keyedBy: CodingKeys.self)
        let discriminator = try keyedContainer.decode(String.self, forKey: .matchType)
        switch discriminator {
        case "Aggravating Factor":
            let value = try container.decode(AggravatingFactorResult.self)
            self = .typeAggravatingFactorResult(value)
        case "Condition":
            let value = try container.decode(ConditionResult.self)
            self = .typeConditionResult(value)
        case "Entity Type":
            let value = try container.decode(EntityTypeResult.self)
            self = .typeEntityTypeResult(value)
        case "Substance Group":
            let value = try container.decode(SubstanceGroupResult.self)
            self = .typeSubstanceGroupResult(value)
        case "Symptom":
            let value = try container.decode(SymptomResult.self)
            self = .typeSymptomResult(value)
        case "Treatment":
            let value = try container.decode(TreatmentResult.self)
            self = .typeTreatmentResult(value)
        default:
            throw DecodingError.typeMismatch(Self.Type.self, .init(codingPath: decoder.codingPath, debugDescription: "Unable to decode instance of DiscriminatedSearchMatchResult"))
        }
    }
}

When using the schema:

    DiscriminatedSearchMatchResult:
      oneOf:
        - $ref: '#/components/schemas/ConditionResult'
        - $ref: '#/components/schemas/SubstanceGroupResult'
        - $ref: '#/components/schemas/SymptomResult'
        - $ref: '#/components/schemas/AggravatingFactorResult'
        - $ref: '#/components/schemas/EntityTypeResult'
        - $ref: '#/components/schemas/TreatmentResult'
      discriminator:
        propertyName: matchType
        mapping:
          "Condition": '#/components/schemas/ConditionResult'
          "Substance Group": '#/components/schemas/SubstanceGroupResult'
          "Symptom": '#/components/schemas/SymptomResult'
          "Aggravating Factor": '#/components/schemas/AggravatingFactorResult'
          "Entity Type": '#/components/schemas/EntityTypeResult'
          "Treatment": '#/components/schemas/TreatmentResult'

If anyone wants to take this and create a PR that’d be great. @4brunu

0reactions
4brunucommented, Apr 26, 2022

Currently I don’t have much time to work on this. If someone want’s to create a PR I’m happy to help with review. If not, I will try to create a PR when I have some time.

Read more comments on GitHub >

github_iconTop Results From Across the Web

oneOf, anyOf, allOf, not - Swagger
oneOf – validates the value against exactly one of the subschemas; allOf – validates the value ... For that purpose, you should include...
Read more >
OpenAPI Specification
In both the oneOf and anyOf use cases, all possible schemas MUST be listed explicitly. To avoid redundancy, the discriminator MAY be added...
Read more >
OpenAPI Discrimintors - SmartBear Community
I have been trying to figure out how to handle a situation where a query string parameter changes the response object. Here's my...
Read more >
Programming | Macs in Chemistry
1 is the first version of Python to support macOS 11 Big Sur. With Xcode 11 and later it is now possible to...
Read more >
Episodes Tagged with “Coder Radio”
CBS Quits Twitter, then Returns — On Friday, one of the media outlet's ... we don't know any of these things now, and...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found