From 59791ffe7a33cf7111416a0d42bcac574def2a7f Mon Sep 17 00:00:00 2001 From: Deep Singhvi Date: Sun, 29 Dec 2024 20:01:51 -0500 Subject: [PATCH] fix(cli): default booleans are parsed from strings as well (#5504) --- fern/pages/changelogs/cli/2024-12-30.mdx | 5 + .../src/schema/convertSchemas.ts | 23 +- .../__snapshots__/openapi-docs/defaults.json | 151 ++++++++ .../openapi-ir-in-memory/defaults.json | 104 ++++++ .../__snapshots__/openapi-ir/defaults.json | 335 ++++++++++++++++++ .../__snapshots__/openapi/defaults.json | 151 ++++++++ .../fixtures/defaults/fern/fern.config.json | 4 + .../fixtures/defaults/fern/generators.yml | 3 + .../__test__/fixtures/defaults/openapi.yml | 63 ++++ packages/cli/cli/versions.yml | 8 + 10 files changed, 845 insertions(+), 2 deletions(-) create mode 100644 fern/pages/changelogs/cli/2024-12-30.mdx create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/defaults.json create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir-in-memory/defaults.json create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/defaults.json create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/defaults.json create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/defaults/fern/fern.config.json create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/defaults/fern/generators.yml create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/defaults/openapi.yml diff --git a/fern/pages/changelogs/cli/2024-12-30.mdx b/fern/pages/changelogs/cli/2024-12-30.mdx new file mode 100644 index 00000000000..d30a223db27 --- /dev/null +++ b/fern/pages/changelogs/cli/2024-12-30.mdx @@ -0,0 +1,5 @@ +## 0.46.17 +**`(fix):`** Support parsing string values for boolean defaults in OpenAPI schemas. +* String values like "true" and "false" are now correctly parsed as boolean defaults. + + diff --git a/packages/cli/api-importers/openapi/openapi-ir-parser/src/schema/convertSchemas.ts b/packages/cli/api-importers/openapi/openapi-ir-parser/src/schema/convertSchemas.ts index 9cbd5f826eb..da01a1cde2b 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-parser/src/schema/convertSchemas.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-parser/src/schema/convertSchemas.ts @@ -11,6 +11,7 @@ import { SdkGroupName, Source } from "@fern-api/openapi-ir"; +import { size } from "lodash-es"; import { OpenAPIV3 } from "openapi-types"; import { getExtension } from "../getExtension"; import { OpenAPIExtension } from "../openapi/v3/extensions/extensions"; @@ -40,7 +41,6 @@ import { SchemaParserContext } from "./SchemaParserContext"; import { getBreadcrumbsFromReference } from "./utils/getBreadcrumbsFromReference"; import { getGeneratedTypeName } from "./utils/getSchemaName"; import { isReferenceObject } from "./utils/isReferenceObject"; -import { size } from "lodash-es"; export const SCHEMA_REFERENCE_PREFIX = "#/components/schemas/"; export const SCHEMA_INLINE_REFERENCE_PREFIX = "#/components/responses/"; @@ -351,7 +351,7 @@ export function convertSchemaObject( generatedName, title, primitive: PrimitiveSchemaValueWithExample.boolean({ - default: schema.default, + default: getBooleanFromDefault(schema.default), example: getExampleAsBoolean({ schema, logger: context.logger, fallback }) }), wrapAsNullable, @@ -893,6 +893,25 @@ export function convertSchemaObject( ); } +function getBooleanFromDefault(defaultValue: unknown): boolean | undefined { + if (defaultValue == null) { + return undefined; + } + if (typeof defaultValue === "boolean") { + return defaultValue; + } + if (typeof defaultValue === "string") { + const lowercased = defaultValue.toLowerCase(); + if (lowercased === "true") { + return true; + } + if (lowercased === "false") { + return false; + } + } + return undefined; +} + export function getSchemaIdFromReference(ref: OpenAPIV3.ReferenceObject): string | undefined { if (!ref.$ref.startsWith(SCHEMA_REFERENCE_PREFIX)) { return undefined; diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/defaults.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/defaults.json new file mode 100644 index 00000000000..a81bb70dbd6 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/defaults.json @@ -0,0 +1,151 @@ +{ + "absoluteFilePath": "/DUMMY_PATH", + "importedDefinitions": {}, + "namedDefinitionFiles": { + "__package__.yml": { + "absoluteFilepath": "/DUMMY_PATH", + "contents": { + "service": { + "auth": false, + "base-path": "", + "endpoints": { + "getTest": { + "auth": false, + "docs": undefined, + "examples": [ + { + "response": { + "body": { + "boolField1": true, + "boolField2": true, + "numberField": 1.1, + "stringField": "stringField", + }, + }, + }, + ], + "method": "GET", + "pagination": undefined, + "path": "/test", + "request": { + "name": "GetTestRequest", + "query-parameters": { + "boolParam1": "optional", + "boolParam2": "optional", + "boolParam3": "optional", + "boolParam4": "optional", + "integerParam": "optional", + "numberParam": "optional", + "stringParam": "optional", + }, + }, + "response": { + "docs": "Successful response", + "type": "GetTestResponse", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "types": { + "GetTestResponse": { + "docs": undefined, + "inline": undefined, + "properties": { + "boolField1": { + "default": true, + "type": "optional", + }, + "boolField2": { + "default": true, + "type": "optional", + }, + "numberField": { + "default": 3.14, + "type": "optional", + }, + "stringField": { + "default": "defaultValue", + "type": "optional", + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + }, + "rawContents": "types: + GetTestResponse: + properties: + boolField1: + type: optional + default: true + boolField2: + type: optional + default: true + stringField: + type: optional + default: defaultValue + numberField: + type: optional + default: 3.14 + source: + openapi: ../openapi.yml +service: + auth: false + base-path: '' + endpoints: + getTest: + path: /test + method: GET + auth: false + source: + openapi: ../openapi.yml + request: + name: GetTestRequest + query-parameters: + boolParam1: optional + boolParam2: optional + boolParam3: optional + boolParam4: optional + stringParam: optional + numberParam: optional + integerParam: optional + response: + docs: Successful response + type: GetTestResponse + examples: + - response: + body: + boolField1: true + boolField2: true + stringField: stringField + numberField: 1.1 + source: + openapi: ../openapi.yml +", + }, + }, + "packageMarkers": {}, + "rootApiFile": { + "contents": { + "display-name": "API with Default Values", + "error-discrimination": { + "strategy": "status-code", + }, + "name": "api", + }, + "defaultUrl": undefined, + "rawContents": "name: api +error-discrimination: + strategy: status-code +display-name: API with Default Values +", + }, +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir-in-memory/defaults.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir-in-memory/defaults.json new file mode 100644 index 00000000000..028217f984f --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir-in-memory/defaults.json @@ -0,0 +1,104 @@ +{ + "type": "openapi", + "value": { + "openapi": "3.0.0", + "info": { + "title": "API with Default Values", + "version": "1.0.0" + }, + "paths": { + "/test": { + "get": { + "parameters": [ + { + "name": "boolParam1", + "in": "query", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "boolParam2", + "in": "query", + "schema": { + "type": "boolean", + "default": "true" + } + }, + { + "name": "boolParam3", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "boolParam4", + "in": "query", + "schema": { + "type": "boolean", + "default": "false" + } + }, + { + "name": "stringParam", + "in": "query", + "schema": { + "type": "string", + "default": "defaultString" + } + }, + { + "name": "numberParam", + "in": "query", + "schema": { + "type": "number", + "default": 42 + } + }, + { + "name": "integerParam", + "in": "query", + "schema": { + "type": "integer", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "boolField1": { + "type": "boolean", + "default": true + }, + "boolField2": { + "type": "boolean", + "default": "true" + }, + "stringField": { + "type": "string", + "default": "defaultValue" + }, + "numberField": { + "type": "number", + "default": 3.14 + } + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/defaults.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/defaults.json new file mode 100644 index 00000000000..de6c37f09bb --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/defaults.json @@ -0,0 +1,335 @@ +{ + "title": "API with Default Values", + "servers": [], + "tags": { + "tagsById": {} + }, + "hasEndpointsMarkedInternal": false, + "endpoints": [ + { + "audiences": [], + "tags": [], + "pathParameters": [], + "queryParameters": [ + { + "name": "boolParam1", + "schema": { + "generatedName": "GetTestRequestBoolParam1", + "value": { + "schema": { + "default": true, + "type": "boolean" + }, + "generatedName": "GetTestRequestBoolParam1", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "nullable" + }, + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + }, + { + "name": "boolParam2", + "schema": { + "generatedName": "GetTestRequestBoolParam2", + "value": { + "schema": { + "default": true, + "type": "boolean" + }, + "generatedName": "GetTestRequestBoolParam2", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "nullable" + }, + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + }, + { + "name": "boolParam3", + "schema": { + "generatedName": "GetTestRequestBoolParam3", + "value": { + "schema": { + "default": false, + "type": "boolean" + }, + "generatedName": "GetTestRequestBoolParam3", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "nullable" + }, + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + }, + { + "name": "boolParam4", + "schema": { + "generatedName": "GetTestRequestBoolParam4", + "value": { + "schema": { + "default": false, + "type": "boolean" + }, + "generatedName": "GetTestRequestBoolParam4", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "nullable" + }, + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + }, + { + "name": "stringParam", + "schema": { + "generatedName": "GetTestRequestStringParam", + "value": { + "schema": { + "default": "defaultString", + "type": "string" + }, + "generatedName": "GetTestRequestStringParam", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "nullable" + }, + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + }, + { + "name": "numberParam", + "schema": { + "generatedName": "GetTestRequestNumberParam", + "value": { + "schema": { + "default": 42, + "type": "double" + }, + "generatedName": "GetTestRequestNumberParam", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "nullable" + }, + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + }, + { + "name": "integerParam", + "schema": { + "generatedName": "GetTestRequestIntegerParam", + "value": { + "schema": { + "default": 100, + "type": "int" + }, + "generatedName": "GetTestRequestIntegerParam", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "nullable" + }, + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + } + ], + "headers": [], + "generatedRequestName": "GetTestRequest", + "response": { + "description": "Successful response", + "schema": { + "allOf": [], + "properties": [ + { + "conflict": {}, + "generatedName": "getTestResponseBoolField1", + "key": "boolField1", + "schema": { + "generatedName": "getTestResponseBoolField1", + "value": { + "schema": { + "default": true, + "type": "boolean" + }, + "generatedName": "GetTestResponseBoolField1", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "getTestResponseBoolField2", + "key": "boolField2", + "schema": { + "generatedName": "getTestResponseBoolField2", + "value": { + "schema": { + "default": true, + "type": "boolean" + }, + "generatedName": "GetTestResponseBoolField2", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "getTestResponseStringField", + "key": "stringField", + "schema": { + "generatedName": "getTestResponseStringField", + "value": { + "schema": { + "default": "defaultValue", + "type": "string" + }, + "generatedName": "GetTestResponseStringField", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "getTestResponseNumberField", + "key": "numberField", + "schema": { + "generatedName": "getTestResponseNumberField", + "value": { + "schema": { + "default": 3.14, + "type": "double" + }, + "generatedName": "GetTestResponseNumberField", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + } + ], + "allOfPropertyConflicts": [], + "generatedName": "GetTestResponse", + "groupName": [], + "additionalProperties": false, + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "object" + }, + "fullExamples": [], + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "json" + }, + "errors": {}, + "server": [], + "authed": false, + "method": "GET", + "path": "/test", + "examples": [ + { + "pathParameters": [], + "queryParameters": [], + "headers": [], + "response": { + "value": { + "properties": { + "boolField1": { + "value": { + "value": true, + "type": "boolean" + }, + "type": "primitive" + }, + "boolField2": { + "value": { + "value": true, + "type": "boolean" + }, + "type": "primitive" + }, + "stringField": { + "value": { + "value": "stringField", + "type": "string" + }, + "type": "primitive" + }, + "numberField": { + "value": { + "value": 1.1, + "type": "double" + }, + "type": "primitive" + } + }, + "type": "object" + }, + "type": "withoutStreaming" + }, + "codeSamples": [], + "type": "full" + } + ], + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + } + ], + "webhooks": [], + "channel": [], + "groupedSchemas": { + "rootSchemas": {}, + "namespacedSchemas": {} + }, + "variables": {}, + "nonRequestReferencedSchemas": {}, + "securitySchemes": {}, + "globalHeaders": [], + "idempotencyHeaders": [], + "groups": {} +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/defaults.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/defaults.json new file mode 100644 index 00000000000..a81bb70dbd6 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/defaults.json @@ -0,0 +1,151 @@ +{ + "absoluteFilePath": "/DUMMY_PATH", + "importedDefinitions": {}, + "namedDefinitionFiles": { + "__package__.yml": { + "absoluteFilepath": "/DUMMY_PATH", + "contents": { + "service": { + "auth": false, + "base-path": "", + "endpoints": { + "getTest": { + "auth": false, + "docs": undefined, + "examples": [ + { + "response": { + "body": { + "boolField1": true, + "boolField2": true, + "numberField": 1.1, + "stringField": "stringField", + }, + }, + }, + ], + "method": "GET", + "pagination": undefined, + "path": "/test", + "request": { + "name": "GetTestRequest", + "query-parameters": { + "boolParam1": "optional", + "boolParam2": "optional", + "boolParam3": "optional", + "boolParam4": "optional", + "integerParam": "optional", + "numberParam": "optional", + "stringParam": "optional", + }, + }, + "response": { + "docs": "Successful response", + "type": "GetTestResponse", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "types": { + "GetTestResponse": { + "docs": undefined, + "inline": undefined, + "properties": { + "boolField1": { + "default": true, + "type": "optional", + }, + "boolField2": { + "default": true, + "type": "optional", + }, + "numberField": { + "default": 3.14, + "type": "optional", + }, + "stringField": { + "default": "defaultValue", + "type": "optional", + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + }, + "rawContents": "types: + GetTestResponse: + properties: + boolField1: + type: optional + default: true + boolField2: + type: optional + default: true + stringField: + type: optional + default: defaultValue + numberField: + type: optional + default: 3.14 + source: + openapi: ../openapi.yml +service: + auth: false + base-path: '' + endpoints: + getTest: + path: /test + method: GET + auth: false + source: + openapi: ../openapi.yml + request: + name: GetTestRequest + query-parameters: + boolParam1: optional + boolParam2: optional + boolParam3: optional + boolParam4: optional + stringParam: optional + numberParam: optional + integerParam: optional + response: + docs: Successful response + type: GetTestResponse + examples: + - response: + body: + boolField1: true + boolField2: true + stringField: stringField + numberField: 1.1 + source: + openapi: ../openapi.yml +", + }, + }, + "packageMarkers": {}, + "rootApiFile": { + "contents": { + "display-name": "API with Default Values", + "error-discrimination": { + "strategy": "status-code", + }, + "name": "api", + }, + "defaultUrl": undefined, + "rawContents": "name: api +error-discrimination: + strategy: status-code +display-name: API with Default Values +", + }, +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/defaults/fern/fern.config.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/defaults/fern/fern.config.json new file mode 100644 index 00000000000..7980537f564 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/defaults/fern/fern.config.json @@ -0,0 +1,4 @@ +{ + "organization": "fern", + "version": "*" +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/defaults/fern/generators.yml b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/defaults/fern/generators.yml new file mode 100644 index 00000000000..602118874c2 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/defaults/fern/generators.yml @@ -0,0 +1,3 @@ +api: + specs: + - openapi: ../openapi.yml diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/defaults/openapi.yml b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/defaults/openapi.yml new file mode 100644 index 00000000000..653ef3f3161 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/defaults/openapi.yml @@ -0,0 +1,63 @@ +openapi: 3.0.0 +info: + title: API with Default Values + version: 1.0.0 +paths: + /test: + get: + parameters: + - name: boolParam1 + in: query + schema: + type: boolean + default: true + - name: boolParam2 + in: query + schema: + type: boolean + default: "true" + - name: boolParam3 + in: query + schema: + type: boolean + default: false + - name: boolParam4 + in: query + schema: + type: boolean + default: "false" + - name: stringParam + in: query + schema: + type: string + default: "defaultString" + - name: numberParam + in: query + schema: + type: number + default: 42 + - name: integerParam + in: query + schema: + type: integer + default: 100 + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + boolField1: + type: boolean + default: true + boolField2: + type: boolean + default: "true" + stringField: + type: string + default: "defaultValue" + numberField: + type: number + default: 3.14 diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index 0164da64eb3..fbf6709af4e 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,3 +1,11 @@ +- changelogEntry: + - summary: | + Support parsing string values for boolean defaults in OpenAPI schemas. + * String values like "true" and "false" are now correctly parsed as boolean defaults. + type: fix + irVersion: 53 + version: 0.46.17 + - changelogEntry: - summary: | Improve parsing of OpenAPI schemas with an array in the `type` property.