typescript - 从 OpenAPI 的 `oneOf` 关键字生成的不受欢迎的 Flow/TypeScript 类型。是否有另一个 OpenAPI 模式会产生更好的结果?

标签 typescript openapi flowtype

我们有以下 OpenAPI 架构:

{
    "openapi": "3.0.1",
    "paths": {
        "/v1/tool/breadcrumbs/{hierarchyId}/{categoryId}": {
            "get": {
                "tags": [
                    "V1-tool"
                ],
                "summary": "Get Breadcrumbs details",
                "operationId": "getBreadcrumbs",
                "parameters": [
                    {
                        "name": "hierarchyId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "minimum": 1,
                            "type": "integer",
                            "format": "int32"
                        }
                    },
                    {
                        "name": "categoryId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "minimum": 1,
                            "type": "integer",
                            "format": "int32"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "default response",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "array",
                                    "items": {
                                        "$ref": "#/components/schemas/Kitten"
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "Auth": []
                    }
                ]
            }
        },
        "/v1/tool/hierarchies": {
            "get": {
                "tags": [
                    "V1-tool"
                ],
                "summary": "Get all available hierarchies ",
                "operationId": "getAllHierarchies",
                "responses": {
                    "200": {
                        "description": "default response",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "array",
                                    "items": {
                                        "$ref": "#/components/schemas/HierarchyResponse"
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "Auth": []
                    }
                ]
            }
        },
        "/v1/tool/search/{hierarchyId}/{searchTerm}": {
            "get": {
                "tags": [
                    "V1-tool"
                ],
                "summary": "Free text search categories for a given hierarchy",
                "operationId": "searchBy",
                "parameters": [
                    {
                        "name": "hierarchyId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "minimum": 1,
                            "type": "integer",
                            "format": "int32"
                        }
                    },
                    {
                        "name": "searchTerm",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "default response",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "array",
                                    "items": {
                                        "$ref": "#/components/schemas/Kitten"
                                    }
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "Auth": []
                    }
                ]
            }
        },
        "/v1/tool/category/{hierarchyId}/{categoryId}": {
            "get": {
                "tags": [
                    "V1-tool"
                ],
                "summary": "Get Category data needed to render a Table View in the Category Management tool",
                "operationId": "getTableView",
                "parameters": [
                    {
                        "name": "hierarchyId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "minimum": 1,
                            "type": "integer",
                            "format": "int32"
                        }
                    },
                    {
                        "name": "categoryId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "minimum": 1,
                            "type": "integer",
                            "format": "int32"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "default response",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/TableViewResponse"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "Auth": []
                    }
                ]
            }
        }
    },
    "components": {
        "schemas": {
            "Kitten": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "enum": [
                            "Kitten"
                        ]
                    },
                    "id": {
                        "type": "integer",
                        "format": "int32"
                    },
                    "name": {
                        "type": "string"
                    },
                    "uri": {
                        "type": "string"
                    }
                }
            },
            "HierarchyResponse": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "enum": [
                            "Hierarchy"
                        ]
                    },
                    "id": {
                        "type": "integer",
                        "format": "int32"
                    },
                    "name": {
                        "type": "string"
                    }
                }
            },
            "AliasCategoryResponse": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "enum": [
                            "Alias"
                        ]
                    },
                    "level": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    },
                    "id": {
                        "type": "integer",
                        "format": "int32"
                    },
                    "uri": {
                        "type": "string"
                    }
                }
            },
            "ChildCategoryResponse": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "enum": [
                            "Category"
                        ]
                    },
                    "level": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    },
                    "id": {
                        "type": "integer",
                        "format": "int32"
                    },
                    "uri": {
                        "type": "string"
                    }
                }
            },
            "GblSubheaderResponse": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "enum": [
                            "Translation"
                        ]
                    },
                    "brandCatalogId": {
                        "type": "integer",
                        "format": "int32"
                    },
                    "value": {
                        "type": "string"
                    }
                }
            },
            "Item": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string"
                    }
                },
                "oneOf": [
                    {
                        "$ref": "#/components/schemas/ChildCategoryResponse"
                    },
                    {
                        "$ref": "#/components/schemas/AliasCategoryResponse"
                    },
                    {
                        "$ref": "#/components/schemas/SubheaderResponse"
                    }
                ]
            },
            "ParentCategoryResponse": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "enum": [
                            "ParentCategory"
                        ]
                    },
                    "id": {
                        "type": "integer",
                        "format": "int32"
                    },
                    "name": {
                        "type": "string"
                    }
                }
            },
            "SubheaderResponse": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "enum": [
                            "Subheader"
                        ]
                    },
                    "name": {
                        "type": "string"
                    },
                    "translatedNames": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/GblSubheaderResponse"
                        }
                    }
                }
            },
            "TableViewResponse": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "enum": [
                            "DefaultCategory"
                        ]
                    },
                    "uri": {
                        "type": "string"
                    },
                    "level": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    },
                    "id": {
                        "type": "integer",
                        "format": "int32"
                    },
                    "parentCategory": {
                        "$ref": "#/components/schemas/ParentCategoryResponse"
                    },
                    "items": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/Item"
                        }
                    }
                }
            }
        },
        "securitySchemes": {
            "Auth": {
                "type": "http",
                "description": "Token Authentication  e.g. Bearer <placeholder>",
                "scheme": "bearer",
                "bearerFormat": "JWT"
            }
        }
    }
}

我们运行以下命令从上述模式文件生成流类型:
npx swagger-to-flowtype path/to/schema/file -d generated_types.js
这导致以下输出:
// @flow strict
export type Kitten = { type: "Kitten", id: number, name: string, uri: string };
export type HierarchyResponse = { type: "Hierarchy", id: number, name: string };
export type AliasCategoryResponse = {
  type: "Alias",
  level: string,
  name: string,
  id: number,
  uri: string
};
export type ChildCategoryResponse = {
  type: "Category",
  level: string,
  name: string,
  id: number,
  uri: string
};
export type GblSubheaderResponse = {
  type: "Translation",
  brandCatalogId: number,
  value: string
};
export type Item = { type: string };
export type ParentCategoryResponse = {
  type: "ParentCategory",
  id: number,
  name: string
};
export type SubheaderResponse = {
  type: "Subheader",
  name: string,
  translatedNames: Array<GblSubheaderResponse>
};
export type TableViewResponse = {
  type: "DefaultCategory",
  uri: string,
  level: string,
  name: string,
  id: number,
  parentCategory: ParentCategoryResponse,
  items: Array<Item>
};
问题是 Item类型定义为:
export type Item = { type: string };
我们希望将其定义为:
export type Item = ChildCategoryResponse | AliasCategoryResponse | SubheaderResponse;
我们的 OpenAPI 模式的相关部分是:
"Item": {
    "type": "object",
    "properties": {
        "type": {
            "type": "string"
        }
    },
    "oneOf": [
        {
            "$ref": "#/components/schemas/ChildCategoryResponse"
        },
        {
            "$ref": "#/components/schemas/AliasCategoryResponse"
        },
        {
            "$ref": "#/components/schemas/SubheaderResponse"
        }
    ]
},
我们最初认为也许 swagger-to-flowtype这里有一个错误,但使用相同的模式通过 swagger-typescript-api 生成 TypeScript 类型输出类似的东西:
export enum Item {
  Category = "Category",
  Alias = "Alias",
  Subheader = "Subheader",
}
...
export interface TableViewResponse {
  type?: "DefaultCategory";
  uri?: string;
  level?: string;
  name?: string;
  /** @format int32 */
  id?: number;
  parentCategory?: ParentCategoryResponse;
  items?: Item[];
}
请注意,在 Flow 和 TypeScript 类型中,我们应该有每个可能的对象形状的类型信息,Item可能是,但我们没有任何此类类型信息。
这是一个项目数组的具体示例:
const items: Array<Item> = [
  {
    type: "Alias",
    level: "3",
    name: "name",
    id: 42,
    uri: "uri"
  },
  {
    type: "Category",
    level: "2",
    name: "name",
    id: 45,
    uri: "uri"
  }
]
希望现在清楚为什么 export type Item = { type: string };不是正确的类型定义。
问题:
是否有不同的模式可以生成所需的 export type Item = ChildCategoryResponse | AliasCategoryResponse | SubheaderResponse输出而不是当前的 export type Item = { type: string };输出?

最佳答案

根据我的理解阅读 the OpenAPI specification , oneOftype不应该一起使用。它似乎没有明确说明,但规范中的示例使用其中一种,从不同时使用。我假设是 TypeScript 绑定(bind)生成器,看到 type"object" , 简单地构造基于 properties 的类型并忽略 oneOf .
如果要区分type属性作为在联合中的类型之间消除歧义,您应该使用 discriminator 而不是 properties :

"Item": {
    "oneOf": [
        {
            "$ref": "#/components/schemas/ChildCategoryResponse"
        },
        {
            "$ref": "#/components/schemas/AliasCategoryResponse"
        },
        {
            "$ref": "#/components/schemas/SubheaderResponse"
        }
    ],
    "discriminator": {
        "propertyName": "type",
        "mapping": {
            "Category": {
                "$ref": "#/components/schemas/ChildCategoryResponse"
            },
            "Alias": {
                "$ref": "#/components/schemas/AliasCategoryResponse"
            },
            "Subheader": {
                "$ref": "#/components/schemas/SubheaderResponse"
            }
        }
    }
},

关于typescript - 从 OpenAPI 的 `oneOf` 关键字生成的不受欢迎的 Flow/TypeScript 类型。是否有另一个 OpenAPI 模式会产生更好的结果?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68381471/

相关文章:

css - 通过调用函数问题来改变按钮类CSS颜色的 Angular

swagger - 如何在 Swagger 规范中使用 'Authorization: Bearer <token>'

arrays - 如何将数组属性定义为逗号分隔?

flowtype - Facebook 流程中 module.system=haste 和 module.system=node 有什么区别

javascript - IDE 中 jsx 代码的流 'unexpected token <'

javascript - 无法读取未定义 TypeError 的属性 'subscribe'

javascript - 错误 : Cannot invoke an expression whose type lacks a call signature

javascript - MUI + React Hook 表单 : Fill out TextField value but then can't modify the value

servicestack - 是否可以根据 OpenApi 规范生成 ServiceStack DTO?

reactjs - 将 Flow.js 与 React 复合组件结合使用