Giter Club home page Giter Club logo

Comments (7)

simonjbeaumont avatar simonjbeaumont commented on August 16, 2024 1

Precedence for geneating types using schema composition

Background

Inputs

I decided to use the following snippet of OpenAPI YAML for this research:

components:
  schemas:
    Badger:
      type: object
      properties:
        b:
          type: boolean
    Mushroom:
      type: object
      properties:
        m:
          type: integer
    Snake:
      type: object
      properties:
        s:
          type: string
    MyAnyOf:
      anyOf: &schemas
      - $ref: '#/components/schemas/Badger'
      - $ref: '#/components/schemas/Mushroom'
      - $ref: '#/components/schemas/Snake'
      - type: string
      - type: string
        pattern: '\d'
      - type: string
        enum:
          - foo
      - type: integer
      - type: object
      - type: object
        properties:
          foo:
            type: string
      - type: array
        items:
          type: integer
    MyAllOf:
      allOf: *schemas
    MyOneOf:
      oneOf: *schemas

Generators

I'll use the following set of generators initially:

  • openapi-generator -g swift
  • openapi-generator -g go
  • openapi-generator -g rust
  • openapi-typescript
  • openapi-typescript-codegen

Outputs

I've tried to capture just enough of the output for each generator to draw themes about the question at hand.

openapi-generator -g swift

public struct MyAnyOf: Codable, JSONEncodable, Hashable {

    public var b: Bool?
    public var m: Int?
    public var s: String?
    public var foo: String?
}

public struct MyAllOf: Codable, JSONEncodable, Hashable {

    public var b: Bool?
    public var m: Int?
    public var s: String?
    public var foo: String?
}

public enum MyOneOf: Codable, JSONEncodable, Hashable {
    case typeAnyCodable(AnyCodable)
    case typeBadger(Badger)
    case typeInt(Int)
    case typeMushroom(Mushroom)
    case typeMyAnyOfAnyOf(MyAnyOfAnyOf)
    case typeSnake(Snake)
    case typeString(String)
    case type[Int]([Int])
}

openapi-generator -g go

type MyAnyOf struct {
	Badger *Badger
	Mushroom *Mushroom
	MyAnyOfAnyOf *MyAnyOfAnyOf
	Snake *Snake
	[]int32 *[]int32
	int32 *int32
	map[string]interface{} *map[string]interface{}
	string *string
}

type MyAllOf struct {
	B *bool `json:"b,omitempty"`
	M *int32 `json:"m,omitempty"`
	S *string `json:"s,omitempty"`
	Foo *string `json:"foo,omitempty"`
}

type MyOneOf struct {
	Badger *Badger
	Mushroom *Mushroom
	MyAnyOfAnyOf *MyAnyOfAnyOf
	Snake *Snake
	ArrayOfInt32 *[]int32
	Int32 *int32
	MapmapOfStringinterface{} *map[string]interface{}
	String *string
}

openapi-generator -g rust

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MyAnyOf {
    #[serde(rename = "b", skip_serializing_if = "Option::is_none")]
    pub b: Option<bool>,
    #[serde(rename = "m", skip_serializing_if = "Option::is_none")]
    pub m: Option<i32>,
    #[serde(rename = "s", skip_serializing_if = "Option::is_none")]
    pub s: Option<String>,
    #[serde(rename = "foo", skip_serializing_if = "Option::is_none")]
    pub foo: Option<String>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MyAllOf {
    #[serde(rename = "b", skip_serializing_if = "Option::is_none")]
    pub b: Option<bool>,
    #[serde(rename = "m", skip_serializing_if = "Option::is_none")]
    pub m: Option<i32>,
    #[serde(rename = "s", skip_serializing_if = "Option::is_none")]
    pub s: Option<String>,
    #[serde(rename = "foo", skip_serializing_if = "Option::is_none")]
    pub foo: Option<String>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MyOneOf {
    #[serde(rename = "b", skip_serializing_if = "Option::is_none")]
    pub b: Option<bool>,
    #[serde(rename = "m", skip_serializing_if = "Option::is_none")]
    pub m: Option<i32>,
    #[serde(rename = "s", skip_serializing_if = "Option::is_none")]
    pub s: Option<String>,
    #[serde(rename = "foo", skip_serializing_if = "Option::is_none")]
    pub foo: Option<String>,
}

openapi-typescript

export interface components {
  schemas: {
    Badger: {
      b?: boolean;
    };
    Mushroom: {
      m?: number;
    };
    Snake: {
      s?: string;
    };
    MyAnyOf: components["schemas"]["Badger"] | components["schemas"]["Mushroom"] | components["schemas"]["Snake"] | string | "foo" | number | Record<string, never> | {
      foo?: string;
    } | number[];
    MyAllOf: components["schemas"]["Badger"] & components["schemas"]["Mushroom"] & components["schemas"]["Snake"] & string & string & "foo" & number & Record<string, never> & {
      foo?: string;
    } & number[];
    MyOneOf: components["schemas"]["Badger"] | components["schemas"]["Mushroom"] | components["schemas"]["Snake"] | string | "foo" | number | Record<string, never> | {
      foo?: string;
    } | number[];
  };
  responses: never;
  parameters: never;
  requestBodies: never;
  headers: never;
  pathItems: never;
}

openapi-typescript-codegen

export type MyAnyOf = (Badger | Mushroom | Snake | string | 'foo' | number | Record<string, any> | {
    foo?: string;
} | Array<number>);
export type MyAllOf = (Badger & Mushroom & Snake & string & 'foo' & number & Record<string, any> & {
    foo?: string;
} & Array<number>);
export type MyOneOf = (Badger | Mushroom | Snake | string | 'foo' | number | Record<string, any> | {
    foo?: string;
} | Array<number>);

Learnings

The above outputs show different strategies for handling schema composition,
including flattened types, union types, variant types, etc.

However, none of them seem to need to make use of an index-based naming, e.g.
case1, case2, etc.

from swift-openapi-generator.

czechboy0 avatar czechboy0 commented on August 16, 2024

@simonjbeaumont to gather some data.

from swift-openapi-generator.

czechboy0 avatar czechboy0 commented on August 16, 2024

Yeah if constrained to just references to objects as subschemas, those can be handled pretty well, because they have a global name.

But all of these aggregate types (except for oneOfs with a discriminator) can have any schema as a subschemas, and those types can be inlined (in which case you don't have a global name) and don't even have to be unique (you can have two string schemas that only differ by their regex constraint).

Curious to see the output for the following additional subschemas:

- type: string
- type: string
  pattern: '\d'
- type: string
  enum:
    - foo
- type: integer
- type: object
- type: object
  properties:
    foo:
      type: string
- type: array
- type: array
  items:
    type: integer

I also suspect that variadic generics in Swift could help us here, but all this was designed before they were available.

from swift-openapi-generator.

simonjbeaumont avatar simonjbeaumont commented on August 16, 2024

Those set of subschemas won't work for all of the schema composition methods, because they cannot be used in conjunction in some cases in an OAS-compliant way. However, I threw them into the generators and updated the comment with the findings.

Still no index-based type names.

from swift-openapi-generator.

czechboy0 avatar czechboy0 commented on August 16, 2024

Sure, in the case of allOf and anyOf, there are some mutually incompatible schemas, but the oneOf should be able to support all 10 schemas. From the above, it seems none of the generators were able to generate more than 8, and some stopped at 7.

And getting all the way to 10 is where they'd have to choose either an order-based name, or create a unique name from the contents, such as string_with_pattern_backslash_d_minItems_10_... etc. The order-based name is a lot simpler to implement, but the content-based name would also work.

Would love to see a generator that can actually handle all 10 cases, and which method of naming they chose.

from swift-openapi-generator.

simonjbeaumont avatar simonjbeaumont commented on August 16, 2024

but the oneOf should be able to support all 10 schemas.

I don't think that's true. For example you suggested these:

- type: string
- type: string
  pattern: '\d'
- type: string
  enum:
    - foo

which cannot work because it needs to satisfy (exactly one)1. But the first is always satisfied, which is why I think some of the generators skipped it (there was some diagnostic output to this effect too).

Footnotes

  1. https://json-schema.org/understanding-json-schema/reference/combining.html#id7

from swift-openapi-generator.

czechboy0 avatar czechboy0 commented on August 16, 2024

Good catch, let me amend to:

- type: string
  pattern: '\s'
- type: string
  pattern: '\d'

from swift-openapi-generator.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.