From 523965e34fb3451bcc5835b25d5c78051a87f848 Mon Sep 17 00:00:00 2001 From: Deep Singhvi Date: Sun, 22 Dec 2024 21:42:57 -0500 Subject: [PATCH] feat(cli): allow configuring example depth (#5465) --- .../generators-yml/definition/generators.yml | 14 ++ fern/pages/changelogs/cli/2024-12-23.mdx | 16 ++ generators-yml.schema.json | 52 ++++++ .../v3/converters/ExampleEndpointFactory.ts | 16 +- .../openapi/openapi-ir-parser/src/options.ts | 8 +- .../src/schema/examples/ExampleTypeFactory.ts | 1 + .../openapi-docs/example-depth.json | 118 +++++++++++++ .../openapi-ir-in-memory/example-depth.json | 53 ++++++ .../openapi-ir/example-depth.json | 164 ++++++++++++++++++ .../__snapshots__/openapi/example-depth.json | 118 +++++++++++++ .../example-depth/fern/fern.config.json | 4 + .../example-depth/fern/generators.yml | 7 + .../fixtures/example-depth/openapi.yml | 34 ++++ packages/cli/cli/versions.yml | 19 ++ .../convertGeneratorsConfiguration.ts | 34 ++-- .../generators-yml/GeneratorsConfiguration.ts | 14 +- .../types/OpenApiExampleGenerationSchema.ts | 10 ++ .../generators/types/OpenApiSettingsSchema.ts | 2 + ...equestOrResponseExampleGenerationSchema.ts | 8 + .../api/resources/generators/types/index.ts | 2 + .../types/OpenApiExampleGenerationSchema.ts | 23 +++ .../generators/types/OpenApiSettingsSchema.ts | 3 + ...equestOrResponseExampleGenerationSchema.ts | 20 +++ .../resources/generators/types/index.ts | 2 + .../src/OpenAPIWorkspace.ts | 11 +- .../src/openapi/BaseOpenAPIWorkspace.ts | 4 + .../lazy-fern-workspace/src/OSSWorkspace.ts | 18 +- .../workspace/loader/src/loadAPIWorkspace.ts | 18 +- 28 files changed, 748 insertions(+), 45 deletions(-) create mode 100644 fern/pages/changelogs/cli/2024-12-23.mdx create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/example-depth.json create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir-in-memory/example-depth.json create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/example-depth.json create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/example-depth.json create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/example-depth/fern/fern.config.json create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/example-depth/fern/generators.yml create mode 100644 packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/example-depth/openapi.yml create mode 100644 packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/OpenApiExampleGenerationSchema.ts create mode 100644 packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/RequestOrResponseExampleGenerationSchema.ts create mode 100644 packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/OpenApiExampleGenerationSchema.ts create mode 100644 packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/RequestOrResponseExampleGenerationSchema.ts diff --git a/fern/apis/generators-yml/definition/generators.yml b/fern/apis/generators-yml/definition/generators.yml index c5ebb6b93e5..c2c74c07917 100644 --- a/fern/apis/generators-yml/definition/generators.yml +++ b/fern/apis/generators-yml/definition/generators.yml @@ -237,6 +237,20 @@ types: filter: type: optional docs: Filter to apply to the OpenAPI specification. + example-generation: + type: optional + docs: Fine-tune your example generation + + OpenAPIExampleGenerationSchema: + properties: + request: optional + response: optional + + RequestOrResponseExampleGenerationSchema: + properties: + max-depth: + type: optional + docs: Controls the maximum depth for which optional properties will have examples generated. A depth of 0 means no optional properties will have examples. OpenAPIFilterSchema: properties: diff --git a/fern/pages/changelogs/cli/2024-12-23.mdx b/fern/pages/changelogs/cli/2024-12-23.mdx new file mode 100644 index 00000000000..2bf773b00a4 --- /dev/null +++ b/fern/pages/changelogs/cli/2024-12-23.mdx @@ -0,0 +1,16 @@ +## 0.46.11 +**`(fix):`** Allow for configuring the depth of example generation in API Docs. For example, +if you want to generate optional properties that are 5 levels deep, you can add +the following configuration in your `generators.yml` + +```yml generators.yml +api: + specs: + - openapi: ./openapi.json + settings: + example-generation: + response: + max-depth: 10 +``` + + diff --git a/generators-yml.schema.json b/generators-yml.schema.json index 2859da37801..b873268a9a6 100644 --- a/generators-yml.schema.json +++ b/generators-yml.schema.json @@ -1291,6 +1291,48 @@ }, "additionalProperties": false }, + "generators.RequestOrResponseExampleGenerationSchema": { + "type": "object", + "properties": { + "max-depth": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "generators.OpenAPIExampleGenerationSchema": { + "type": "object", + "properties": { + "request": { + "oneOf": [ + { + "$ref": "#/definitions/generators.RequestOrResponseExampleGenerationSchema" + }, + { + "type": "null" + } + ] + }, + "response": { + "oneOf": [ + { + "$ref": "#/definitions/generators.RequestOrResponseExampleGenerationSchema" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, "generators.OpenAPISettingsSchema": { "type": "object", "properties": { @@ -1373,6 +1415,16 @@ "type": "null" } ] + }, + "example-generation": { + "oneOf": [ + { + "$ref": "#/definitions/generators.OpenAPIExampleGenerationSchema" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false diff --git a/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/converters/ExampleEndpointFactory.ts b/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/converters/ExampleEndpointFactory.ts index 02f6926913f..d94ef30dbd5 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/converters/ExampleEndpointFactory.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/converters/ExampleEndpointFactory.ts @@ -1,4 +1,5 @@ import { assertNever, isNonNullish } from "@fern-api/core-utils"; +import { RawSchemas } from "@fern-api/fern-definition-schema"; import { Logger } from "@fern-api/logger"; import { CustomCodeSample, @@ -16,7 +17,6 @@ import { SchemaWithExample, SupportedSdkLanguage } from "@fern-api/openapi-ir"; -import { RawSchemas } from "@fern-api/fern-definition-schema"; import { ExampleTypeFactory } from "../../../schema/examples/ExampleTypeFactory"; import { convertSchemaToSchemaWithExample } from "../../../schema/utils/convertSchemaToSchemaWithExample"; import { isSchemaRequired } from "../../../schema/utils/isSchemaRequired"; @@ -26,9 +26,11 @@ import { OpenAPIV3ParserContext } from "../OpenAPIV3ParserContext"; export class ExampleEndpointFactory { private exampleTypeFactory: ExampleTypeFactory; private logger: Logger; - private schemas: Record; - constructor(schemas: Record, context: OpenAPIV3ParserContext) { + constructor( + private readonly schemas: Record, + private readonly context: OpenAPIV3ParserContext + ) { this.schemas = schemas; this.exampleTypeFactory = new ExampleTypeFactory(schemas, context.nonRequestReferencedSchemas, context); this.logger = context.logger; @@ -61,6 +63,8 @@ export class ExampleEndpointFactory { options: { isParameter: false, ignoreOptionals: true + // TODO(dsinghvi): Respect depth on request examples + // maxDepth: this.context.options.exampleGeneration?.request?.["max-depth"] ?? 0, } }); if (example != null) { @@ -76,6 +80,8 @@ export class ExampleEndpointFactory { options: { isParameter: false, ignoreOptionals: true + // TODO(dsinghvi): Respect depth on request examples + // maxDepth: this.context.options.exampleGeneration?.request?.["max-depth"] ?? 0, } }); if (example != null) { @@ -103,7 +109,7 @@ export class ExampleEndpointFactory { exampleId: undefined, example: undefined, options: { - maxDepth: 3, + maxDepth: this.context.options.exampleGeneration?.response?.["max-depth"] ?? 3, isParameter: false, ignoreOptionals: false } @@ -132,7 +138,7 @@ export class ExampleEndpointFactory { exampleId, example: rawExample, options: { - maxDepth: 3, + maxDepth: this.context.options.exampleGeneration?.response?.["max-depth"] ?? 3, isParameter: false, ignoreOptionals: false } diff --git a/packages/cli/api-importers/openapi/openapi-ir-parser/src/options.ts b/packages/cli/api-importers/openapi/openapi-ir-parser/src/options.ts index 4fbf61a2cdb..34a483b8ec8 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-parser/src/options.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-parser/src/options.ts @@ -35,6 +35,8 @@ export interface ParseOpenAPIOptions { // For now, we include an AsyncAPI-specific option here, but this is better // handled with a discriminated union. asyncApiNaming: "v1" | "v2"; + + exampleGeneration: generatorsYml.OpenApiExampleGenerationSchema | undefined; } export const DEFAULT_PARSE_OPENAPI_SETTINGS: ParseOpenAPIOptions = { @@ -51,7 +53,8 @@ export const DEFAULT_PARSE_OPENAPI_SETTINGS: ParseOpenAPIOptions = { objectQueryParameters: false, shouldUseUndiscriminatedUnionsWithLiterals: false, filter: undefined, - asyncApiNaming: "v1" + asyncApiNaming: "v1", + exampleGeneration: undefined }; export function getParseOptions({ @@ -101,6 +104,7 @@ export function getParseOptions({ DEFAULT_PARSE_OPENAPI_SETTINGS.objectQueryParameters, filter: overrides?.filter ?? options?.filter ?? DEFAULT_PARSE_OPENAPI_SETTINGS.filter, asyncApiNaming: - overrides?.asyncApiNaming ?? options?.asyncApiNaming ?? DEFAULT_PARSE_OPENAPI_SETTINGS.asyncApiNaming + overrides?.asyncApiNaming ?? options?.asyncApiNaming ?? DEFAULT_PARSE_OPENAPI_SETTINGS.asyncApiNaming, + exampleGeneration: overrides?.exampleGeneration ?? options?.exampleGeneration ?? undefined }; } diff --git a/packages/cli/api-importers/openapi/openapi-ir-parser/src/schema/examples/ExampleTypeFactory.ts b/packages/cli/api-importers/openapi/openapi-ir-parser/src/schema/examples/ExampleTypeFactory.ts index 078d8dea8ff..c9b44d65e7f 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-parser/src/schema/examples/ExampleTypeFactory.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-parser/src/schema/examples/ExampleTypeFactory.ts @@ -449,6 +449,7 @@ export class ExampleTypeFactory { } const required = property in requiredProperties; const inExample = Object.keys(fullExample).includes(property); + const propertyExample = this.buildExampleHelper({ schema: schema.schema, exampleId, diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/example-depth.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/example-depth.json new file mode 100644 index 00000000000..1b1cf77e781 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/example-depth.json @@ -0,0 +1,118 @@ +{ + "absoluteFilePath": "/DUMMY_PATH", + "importedDefinitions": {}, + "namedDefinitionFiles": { + "__package__.yml": { + "absoluteFilepath": "/DUMMY_PATH", + "contents": { + "service": { + "auth": false, + "base-path": "", + "endpoints": { + "getTree": { + "auth": false, + "display-name": "Get tree", + "docs": undefined, + "examples": [ + { + "response": { + "body": { + "value": "root", + }, + }, + }, + ], + "method": "GET", + "pagination": undefined, + "path": "/tree", + "response": { + "docs": "Successfully retrieved tree", + "type": "TreeNode", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "types": { + "TreeNode": { + "docs": undefined, + "inline": undefined, + "properties": { + "left": { + "docs": "Child nodes of this tree node", + "type": "optional", + }, + "right": { + "docs": "Child nodes of this tree node", + "type": "optional", + }, + "value": { + "docs": "The value stored in this node", + "type": "optional", + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + }, + "rawContents": "service: + auth: false + base-path: '' + endpoints: + getTree: + path: /tree + method: GET + auth: false + source: + openapi: ../openapi.yml + display-name: Get tree + response: + docs: Successfully retrieved tree + type: TreeNode + examples: + - response: + body: + value: root + source: + openapi: ../openapi.yml +types: + TreeNode: + properties: + value: + type: optional + docs: The value stored in this node + left: + type: optional + docs: Child nodes of this tree node + right: + type: optional + docs: Child nodes of this tree node + source: + openapi: ../openapi.yml +", + }, + }, + "packageMarkers": {}, + "rootApiFile": { + "contents": { + "display-name": "Tree API", + "error-discrimination": { + "strategy": "status-code", + }, + "name": "api", + }, + "defaultUrl": undefined, + "rawContents": "name: api +error-discrimination: + strategy: status-code +display-name: Tree API +", + }, +} \ 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/example-depth.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir-in-memory/example-depth.json new file mode 100644 index 00000000000..2da79656055 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir-in-memory/example-depth.json @@ -0,0 +1,53 @@ +{ + "type": "openapi", + "value": { + "openapi": "3.0.0", + "info": { + "title": "Tree API", + "version": "1.0.0", + "description": "API for working with tree data structures" + }, + "paths": { + "/tree": { + "get": { + "summary": "Get tree", + "operationId": "getTree", + "responses": { + "200": { + "description": "Successfully retrieved tree", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TreeNode" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "TreeNode": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "The value stored in this node", + "example": "root" + }, + "left": { + "description": "Child nodes of this tree node", + "$ref": "#/components/schemas/TreeNode" + }, + "right": { + "description": "Child nodes of this tree node", + "$ref": "#/components/schemas/TreeNode" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/example-depth.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/example-depth.json new file mode 100644 index 00000000000..5bf9fdff014 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/example-depth.json @@ -0,0 +1,164 @@ +{ + "title": "Tree API", + "description": "API for working with tree data structures", + "servers": [], + "tags": { + "tagsById": {} + }, + "hasEndpointsMarkedInternal": false, + "endpoints": [ + { + "summary": "Get tree", + "audiences": [], + "operationId": "getTree", + "tags": [], + "pathParameters": [], + "queryParameters": [], + "headers": [], + "generatedRequestName": "GetTreeRequest", + "response": { + "description": "Successfully retrieved tree", + "schema": { + "generatedName": "GetTreeResponse", + "schema": "TreeNode", + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "reference" + }, + "fullExamples": [], + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "json" + }, + "errors": {}, + "server": [], + "authed": false, + "method": "GET", + "path": "/tree", + "examples": [ + { + "pathParameters": [], + "queryParameters": [], + "headers": [], + "response": { + "value": { + "properties": { + "value": { + "value": { + "value": "root", + "type": "string" + }, + "type": "primitive" + } + }, + "type": "object" + }, + "type": "withoutStreaming" + }, + "codeSamples": [], + "type": "full" + } + ], + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + } + ], + "webhooks": [], + "channel": [], + "groupedSchemas": { + "rootSchemas": { + "TreeNode": { + "allOf": [], + "properties": [ + { + "conflict": {}, + "generatedName": "treeNodeValue", + "key": "value", + "schema": { + "generatedName": "treeNodeValue", + "value": { + "description": "The value stored in this node", + "schema": { + "example": "root", + "type": "string" + }, + "generatedName": "TreeNodeValue", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "treeNodeLeft", + "key": "left", + "schema": { + "generatedName": "treeNodeLeft", + "value": { + "description": "Child nodes of this tree node", + "generatedName": "TreeNodeLeft", + "schema": "TreeNode", + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "reference" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [], + "readonly": false + }, + { + "conflict": {}, + "generatedName": "treeNodeRight", + "key": "right", + "schema": { + "generatedName": "treeNodeRight", + "value": { + "description": "Child nodes of this tree node", + "generatedName": "TreeNodeRight", + "schema": "TreeNode", + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "reference" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [], + "readonly": false + } + ], + "allOfPropertyConflicts": [], + "generatedName": "TreeNode", + "groupName": [], + "additionalProperties": false, + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "object" + } + }, + "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/example-depth.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/example-depth.json new file mode 100644 index 00000000000..1b1cf77e781 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/example-depth.json @@ -0,0 +1,118 @@ +{ + "absoluteFilePath": "/DUMMY_PATH", + "importedDefinitions": {}, + "namedDefinitionFiles": { + "__package__.yml": { + "absoluteFilepath": "/DUMMY_PATH", + "contents": { + "service": { + "auth": false, + "base-path": "", + "endpoints": { + "getTree": { + "auth": false, + "display-name": "Get tree", + "docs": undefined, + "examples": [ + { + "response": { + "body": { + "value": "root", + }, + }, + }, + ], + "method": "GET", + "pagination": undefined, + "path": "/tree", + "response": { + "docs": "Successfully retrieved tree", + "type": "TreeNode", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "types": { + "TreeNode": { + "docs": undefined, + "inline": undefined, + "properties": { + "left": { + "docs": "Child nodes of this tree node", + "type": "optional", + }, + "right": { + "docs": "Child nodes of this tree node", + "type": "optional", + }, + "value": { + "docs": "The value stored in this node", + "type": "optional", + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + }, + "rawContents": "service: + auth: false + base-path: '' + endpoints: + getTree: + path: /tree + method: GET + auth: false + source: + openapi: ../openapi.yml + display-name: Get tree + response: + docs: Successfully retrieved tree + type: TreeNode + examples: + - response: + body: + value: root + source: + openapi: ../openapi.yml +types: + TreeNode: + properties: + value: + type: optional + docs: The value stored in this node + left: + type: optional + docs: Child nodes of this tree node + right: + type: optional + docs: Child nodes of this tree node + source: + openapi: ../openapi.yml +", + }, + }, + "packageMarkers": {}, + "rootApiFile": { + "contents": { + "display-name": "Tree API", + "error-discrimination": { + "strategy": "status-code", + }, + "name": "api", + }, + "defaultUrl": undefined, + "rawContents": "name: api +error-discrimination: + strategy: status-code +display-name: Tree API +", + }, +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/example-depth/fern/fern.config.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/example-depth/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/example-depth/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/example-depth/fern/generators.yml b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/example-depth/fern/generators.yml new file mode 100644 index 00000000000..94e8535c23c --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/example-depth/fern/generators.yml @@ -0,0 +1,7 @@ +api: + specs: + - openapi: ../openapi.yml + settings: + example-generation: + response: + max-depth: 10 diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/example-depth/openapi.yml b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/example-depth/openapi.yml new file mode 100644 index 00000000000..1983b7694e9 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/example-depth/openapi.yml @@ -0,0 +1,34 @@ +openapi: 3.0.0 +info: + title: Tree API + version: 1.0.0 + description: API for working with tree data structures + +paths: + /tree: + get: + summary: Get tree + operationId: getTree + responses: + '200': + description: Successfully retrieved tree + content: + application/json: + schema: + $ref: '#/components/schemas/TreeNode' + +components: + schemas: + TreeNode: + type: object + properties: + value: + type: string + description: The value stored in this node + example: "root" + left: + description: Child nodes of this tree node + $ref: '#/components/schemas/TreeNode' + right: + description: Child nodes of this tree node + $ref: '#/components/schemas/TreeNode' diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index 38a6a0294c7..7045fa57062 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,3 +1,22 @@ +- changelogEntry: + - summary: | + Allow for configuring the depth of example generation in API Docs. For example, + if you want to generate optional properties that are 5 levels deep, you can add + the following configuration in your `generators.yml` + + ```yml generators.yml + api: + specs: + - openapi: ./openapi.json + settings: + example-generation: + response: + max-depth: 10 + ``` + type: fix + irVersion: 53 + version: 0.46.11 + - changelogEntry: - summary: | Correctly support AdditionalProperties on object schemas. diff --git a/packages/cli/configuration-loader/src/generators-yml/convertGeneratorsConfiguration.ts b/packages/cli/configuration-loader/src/generators-yml/convertGeneratorsConfiguration.ts index b1aa6f620ff..a6a5a36142f 100644 --- a/packages/cli/configuration-loader/src/generators-yml/convertGeneratorsConfiguration.ts +++ b/packages/cli/configuration-loader/src/generators-yml/convertGeneratorsConfiguration.ts @@ -1,11 +1,11 @@ +import { generatorsYml } from "@fern-api/configuration"; import { assertNever } from "@fern-api/core-utils"; +import { visitRawApiAuth } from "@fern-api/fern-definition-schema"; import { AbsoluteFilePath, dirname, join, RelativeFilePath, resolve } from "@fern-api/fs-utils"; import { FernFiddle } from "@fern-fern/fiddle-sdk"; import { GithubPullRequestReviewer, OutputMetadata, PublishingMetadata, PypiMetadata } from "@fern-fern/fiddle-sdk/api"; import { readFile } from "fs/promises"; import path from "path"; -import { generatorsYml } from "@fern-api/configuration"; -import { visitRawApiAuth } from "@fern-api/fern-definition-schema"; export async function convertGeneratorsConfiguration({ absolutePathToGeneratorsConfiguration, @@ -73,7 +73,8 @@ async function parseAPIConfigurationToApiLocations( objectQueryParameters: undefined, respectReadonlySchemas: undefined, inlinePathParameters: undefined, - filter: undefined + filter: undefined, + exampleGeneration: undefined } }); } else if (generatorsYml.isRawProtobufAPIDefinitionSchema(apiConfiguration)) { @@ -97,7 +98,8 @@ async function parseAPIConfigurationToApiLocations( respectReadonlySchemas: undefined, onlyIncludeReferencedSchemas: undefined, inlinePathParameters: undefined, - filter: undefined + filter: undefined, + exampleGeneration: undefined } }); } else if (Array.isArray(apiConfiguration)) { @@ -121,7 +123,8 @@ async function parseAPIConfigurationToApiLocations( respectReadonlySchemas: undefined, onlyIncludeReferencedSchemas: undefined, inlinePathParameters: undefined, - filter: undefined + filter: undefined, + exampleGeneration: undefined } }); } else if (generatorsYml.isRawProtobufAPIDefinitionSchema(definition)) { @@ -145,7 +148,8 @@ async function parseAPIConfigurationToApiLocations( respectReadonlySchemas: undefined, onlyIncludeReferencedSchemas: undefined, inlinePathParameters: undefined, - filter: undefined + filter: undefined, + exampleGeneration: undefined } }); } else { @@ -167,7 +171,8 @@ async function parseAPIConfigurationToApiLocations( objectQueryParameters: undefined, respectReadonlySchemas: undefined, inlinePathParameters: undefined, - filter: undefined + filter: undefined, + exampleGeneration: undefined } }); } @@ -191,7 +196,8 @@ async function parseAPIConfigurationToApiLocations( objectQueryParameters: undefined, respectReadonlySchemas: undefined, inlinePathParameters: undefined, - filter: undefined + filter: undefined, + exampleGeneration: undefined } }); } @@ -220,7 +226,8 @@ async function parseAPIConfigurationToApiLocations( objectQueryParameters: undefined, respectReadonlySchemas: undefined, inlinePathParameters: undefined, - filter: undefined + filter: undefined, + exampleGeneration: undefined } }); } else if (openapi != null) { @@ -242,7 +249,8 @@ async function parseAPIConfigurationToApiLocations( objectQueryParameters: undefined, respectReadonlySchemas: undefined, inlinePathParameters: undefined, - filter: undefined + filter: undefined, + exampleGeneration: undefined } }); } @@ -266,7 +274,8 @@ async function parseAPIConfigurationToApiLocations( objectQueryParameters: undefined, respectReadonlySchemas: undefined, inlinePathParameters: undefined, - filter: undefined + filter: undefined, + exampleGeneration: undefined } }); } @@ -335,7 +344,8 @@ async function parseApiConfigurationV2Schema({ objectQueryParameters: spec.settings?.["object-query-parameters"], respectReadonlySchemas: spec.settings?.["respect-readonly-schemas"], inlinePathParameters: spec.settings?.["inline-path-parameters"], - filter: spec.settings?.filter + filter: spec.settings?.filter, + exampleGeneration: spec.settings?.["example-generation"] } }; if (spec.namespace == null) { diff --git a/packages/cli/configuration/src/generators-yml/GeneratorsConfiguration.ts b/packages/cli/configuration/src/generators-yml/GeneratorsConfiguration.ts index 2b28aca9bc3..fbbd1112b93 100644 --- a/packages/cli/configuration/src/generators-yml/GeneratorsConfiguration.ts +++ b/packages/cli/configuration/src/generators-yml/GeneratorsConfiguration.ts @@ -2,12 +2,15 @@ import { Values } from "@fern-api/core-utils"; import { RawSchemas } from "@fern-api/fern-definition-schema"; import { AbsoluteFilePath } from "@fern-api/path-utils"; import { FernFiddle } from "@fern-fern/fiddle-sdk"; +import { generatorsYml } from ".."; import { Audiences } from "../commons"; -import { ApiDefinitionSettingsSchema } from "./schemas"; -import { GeneratorInvocationSchema } from "./schemas"; -import { GeneratorsConfigurationSchema } from "./schemas"; -import { ReadmeSchema } from "./schemas"; -import { OpenApiFilterSchema } from "./schemas"; +import { + ApiDefinitionSettingsSchema, + GeneratorInvocationSchema, + GeneratorsConfigurationSchema, + OpenApiFilterSchema, + ReadmeSchema +} from "./schemas"; export interface GeneratorsConfiguration { api?: APIDefinition; @@ -58,6 +61,7 @@ export interface APIDefinitionSettings { onlyIncludeReferencedSchemas: boolean | undefined; inlinePathParameters: boolean | undefined; filter: OpenApiFilterSchema | undefined; + exampleGeneration: generatorsYml.OpenApiExampleGenerationSchema | undefined; } export interface APIDefinitionLocation { diff --git a/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/OpenApiExampleGenerationSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/OpenApiExampleGenerationSchema.ts new file mode 100644 index 00000000000..f7ea5899903 --- /dev/null +++ b/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/OpenApiExampleGenerationSchema.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernDefinition from "../../../index"; + +export interface OpenApiExampleGenerationSchema { + request?: FernDefinition.RequestOrResponseExampleGenerationSchema; + response?: FernDefinition.RequestOrResponseExampleGenerationSchema; +} diff --git a/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/OpenApiSettingsSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/OpenApiSettingsSchema.ts index d150d93c1ba..4c6fd3a134c 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/OpenApiSettingsSchema.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/OpenApiSettingsSchema.ts @@ -18,4 +18,6 @@ export interface OpenApiSettingsSchema { "inline-path-parameters"?: boolean; /** Filter to apply to the OpenAPI specification. */ filter?: FernDefinition.OpenApiFilterSchema; + /** Fine-tune your example generation */ + "example-generation"?: FernDefinition.OpenApiExampleGenerationSchema; } diff --git a/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/RequestOrResponseExampleGenerationSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/RequestOrResponseExampleGenerationSchema.ts new file mode 100644 index 00000000000..1048f0ce18b --- /dev/null +++ b/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/RequestOrResponseExampleGenerationSchema.ts @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface RequestOrResponseExampleGenerationSchema { + /** Controls the maximum depth for which optional properties will have examples generated. A depth of 0 means no optional properties will have examples. */ + "max-depth"?: number; +} diff --git a/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/index.ts b/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/index.ts index fc851b456fa..91491b2fa9e 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/index.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/index.ts @@ -23,6 +23,8 @@ export * from "./ApiConfigurationSchemaInternal"; export * from "./ApiConfigurationV2Schema"; export * from "./ApiConfigurationV2SpecsSchema"; export * from "./OpenApiSettingsSchema"; +export * from "./OpenApiExampleGenerationSchema"; +export * from "./RequestOrResponseExampleGenerationSchema"; export * from "./OpenApiFilterSchema"; export * from "./OpenApiSpecSchema"; export * from "./AsyncApiSettingsSchema"; diff --git a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/OpenApiExampleGenerationSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/OpenApiExampleGenerationSchema.ts new file mode 100644 index 00000000000..b09103cc5e7 --- /dev/null +++ b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/OpenApiExampleGenerationSchema.ts @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as FernDefinition from "../../../../api/index"; +import * as core from "../../../../core"; +import { RequestOrResponseExampleGenerationSchema } from "./RequestOrResponseExampleGenerationSchema"; + +export const OpenApiExampleGenerationSchema: core.serialization.ObjectSchema< + serializers.OpenApiExampleGenerationSchema.Raw, + FernDefinition.OpenApiExampleGenerationSchema +> = core.serialization.object({ + request: RequestOrResponseExampleGenerationSchema.optional(), + response: RequestOrResponseExampleGenerationSchema.optional(), +}); + +export declare namespace OpenApiExampleGenerationSchema { + interface Raw { + request?: RequestOrResponseExampleGenerationSchema.Raw | null; + response?: RequestOrResponseExampleGenerationSchema.Raw | null; + } +} diff --git a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/OpenApiSettingsSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/OpenApiSettingsSchema.ts index 88cd9b65582..98d50717fae 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/OpenApiSettingsSchema.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/OpenApiSettingsSchema.ts @@ -6,6 +6,7 @@ import * as serializers from "../../../index"; import * as FernDefinition from "../../../../api/index"; import * as core from "../../../../core"; import { OpenApiFilterSchema } from "./OpenApiFilterSchema"; +import { OpenApiExampleGenerationSchema } from "./OpenApiExampleGenerationSchema"; export const OpenApiSettingsSchema: core.serialization.ObjectSchema< serializers.OpenApiSettingsSchema.Raw, @@ -19,6 +20,7 @@ export const OpenApiSettingsSchema: core.serialization.ObjectSchema< "only-include-referenced-schemas": core.serialization.boolean().optional(), "inline-path-parameters": core.serialization.boolean().optional(), filter: OpenApiFilterSchema.optional(), + "example-generation": OpenApiExampleGenerationSchema.optional(), }); export declare namespace OpenApiSettingsSchema { @@ -31,5 +33,6 @@ export declare namespace OpenApiSettingsSchema { "only-include-referenced-schemas"?: boolean | null; "inline-path-parameters"?: boolean | null; filter?: OpenApiFilterSchema.Raw | null; + "example-generation"?: OpenApiExampleGenerationSchema.Raw | null; } } diff --git a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/RequestOrResponseExampleGenerationSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/RequestOrResponseExampleGenerationSchema.ts new file mode 100644 index 00000000000..15c0cb26998 --- /dev/null +++ b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/RequestOrResponseExampleGenerationSchema.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as FernDefinition from "../../../../api/index"; +import * as core from "../../../../core"; + +export const RequestOrResponseExampleGenerationSchema: core.serialization.ObjectSchema< + serializers.RequestOrResponseExampleGenerationSchema.Raw, + FernDefinition.RequestOrResponseExampleGenerationSchema +> = core.serialization.object({ + "max-depth": core.serialization.number().optional(), +}); + +export declare namespace RequestOrResponseExampleGenerationSchema { + interface Raw { + "max-depth"?: number | null; + } +} diff --git a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/index.ts b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/index.ts index fc851b456fa..91491b2fa9e 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/index.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/index.ts @@ -23,6 +23,8 @@ export * from "./ApiConfigurationSchemaInternal"; export * from "./ApiConfigurationV2Schema"; export * from "./ApiConfigurationV2SpecsSchema"; export * from "./OpenApiSettingsSchema"; +export * from "./OpenApiExampleGenerationSchema"; +export * from "./RequestOrResponseExampleGenerationSchema"; export * from "./OpenApiFilterSchema"; export * from "./OpenApiSpecSchema"; export * from "./AsyncApiSettingsSchema"; diff --git a/packages/cli/workspace/browser-compatible-fern-workspace/src/OpenAPIWorkspace.ts b/packages/cli/workspace/browser-compatible-fern-workspace/src/OpenAPIWorkspace.ts index b9a515a98fa..f4f45ffa734 100644 --- a/packages/cli/workspace/browser-compatible-fern-workspace/src/OpenAPIWorkspace.ts +++ b/packages/cli/workspace/browser-compatible-fern-workspace/src/OpenAPIWorkspace.ts @@ -1,11 +1,11 @@ import { BaseOpenAPIWorkspace, BaseOpenAPIWorkspaceSync } from "@fern-api/api-workspace-commons"; -import { OpenAPI } from "openapi-types"; -import { AbsoluteFilePath } from "@fern-api/path-utils"; -import { TaskContext } from "@fern-api/task-context"; +import { generatorsYml } from "@fern-api/configuration"; import { OpenApiIntermediateRepresentation } from "@fern-api/openapi-ir"; import { parse } from "@fern-api/openapi-ir-parser"; +import { AbsoluteFilePath } from "@fern-api/path-utils"; +import { TaskContext } from "@fern-api/task-context"; +import { OpenAPI } from "openapi-types"; import { InMemoryOpenAPILoader } from "./InMemoryOpenAPILoader"; -import { generatorsYml } from "@fern-api/configuration"; const IN_MEMORY_ABSOLUTE_FILEPATH = AbsoluteFilePath.of("/"); @@ -41,7 +41,8 @@ export class OpenAPIWorkspace extends BaseOpenAPIWorkspaceSync { respectReadonlySchemas: spec.settings?.respectReadonlySchemas, onlyIncludeReferencedSchemas: spec.settings?.onlyIncludeReferencedSchemas, inlinePathParameters: spec.settings?.inlinePathParameters, - objectQueryParameters: spec.settings?.objectQueryParameters + objectQueryParameters: spec.settings?.objectQueryParameters, + exampleGeneration: spec.settings?.exampleGeneration }); this.spec = spec; this.loader = new InMemoryOpenAPILoader(); diff --git a/packages/cli/workspace/commons/src/openapi/BaseOpenAPIWorkspace.ts b/packages/cli/workspace/commons/src/openapi/BaseOpenAPIWorkspace.ts index 25a4dae936c..0ae1c651aea 100644 --- a/packages/cli/workspace/commons/src/openapi/BaseOpenAPIWorkspace.ts +++ b/packages/cli/workspace/commons/src/openapi/BaseOpenAPIWorkspace.ts @@ -4,6 +4,7 @@ import { TaskContext } from "@fern-api/task-context"; import { OpenApiIntermediateRepresentation } from "@fern-api/openapi-ir"; import { OpenAPISettings } from "./OpenAPISettings"; import { FernDefinitionConverter } from "./FernDefinitionConverter"; +import { generatorsYml } from "@fern-api/configuration"; export declare namespace BaseOpenAPIWorkspace { export interface Args extends AbstractAPIWorkspace.Args { @@ -11,6 +12,7 @@ export declare namespace BaseOpenAPIWorkspace { objectQueryParameters: boolean | undefined; onlyIncludeReferencedSchemas: boolean | undefined; respectReadonlySchemas: boolean | undefined; + exampleGeneration: generatorsYml.OpenApiExampleGenerationSchema | undefined; } export type Settings = Partial; @@ -21,6 +23,7 @@ export abstract class BaseOpenAPIWorkspace extends AbstractAPIWorkspace spec.settings?.respectReadonlySchemas), onlyIncludeReferencedSchemas: specs.every((spec) => spec.settings?.onlyIncludeReferencedSchemas), inlinePathParameters: specs.every((spec) => spec.settings?.inlinePathParameters), - objectQueryParameters: specs.every((spec) => spec.settings?.objectQueryParameters) + objectQueryParameters: specs.every((spec) => spec.settings?.objectQueryParameters), + exampleGeneration: specs[0]?.settings?.exampleGeneration }); this.specs = specs; this.sources = this.convertSpecsToIdentifiableSources(specs); @@ -64,7 +65,8 @@ export class OSSWorkspace extends BaseOpenAPIWorkspace { onlyIncludeReferencedSchemas: settings?.onlyIncludeReferencedSchemas ?? this.onlyIncludeReferencedSchemas, inlinePathParameters: settings?.inlinePathParameters ?? this.inlinePathParameters, - objectQueryParameters: settings?.objectQueryParameters ?? this.objectQueryParameters + objectQueryParameters: settings?.objectQueryParameters ?? this.objectQueryParameters, + exampleGeneration: settings?.exampleGeneration ?? this.exampleGeneration } }); } diff --git a/packages/cli/workspace/loader/src/loadAPIWorkspace.ts b/packages/cli/workspace/loader/src/loadAPIWorkspace.ts index 01842d37d8c..0be1de8415f 100644 --- a/packages/cli/workspace/loader/src/loadAPIWorkspace.ts +++ b/packages/cli/workspace/loader/src/loadAPIWorkspace.ts @@ -1,3 +1,4 @@ +import { Spec } from "@fern-api/api-workspace-commons"; import { DEFINITION_DIRECTORY, generatorsYml, @@ -5,16 +6,15 @@ import { OPENAPI_DIRECTORY } from "@fern-api/configuration-loader"; import { AbsoluteFilePath, doesPathExist, join, RelativeFilePath } from "@fern-api/fs-utils"; -import { TaskContext } from "@fern-api/task-context"; -import { loadAPIChangelog } from "./loadAPIChangelog"; -import { Spec } from "@fern-api/api-workspace-commons"; import { - OSSWorkspace, + ConjureWorkspace, LazyFernWorkspace, + OSSWorkspace, WorkspaceLoader, - WorkspaceLoaderFailureType, - ConjureWorkspace + WorkspaceLoaderFailureType } from "@fern-api/lazy-fern-workspace"; +import { TaskContext } from "@fern-api/task-context"; +import { loadAPIChangelog } from "./loadAPIChangelog"; export async function loadSingleNamespaceAPIWorkspace({ absolutePathToWorkspace, @@ -84,7 +84,8 @@ export async function loadSingleNamespaceAPIWorkspace({ discriminatedUnionV2: false, preserveSchemaIds: false, asyncApiNaming: definition.settings?.asyncApiMessageNaming ?? "v1", - filter: definition.settings?.filter + filter: definition.settings?.filter, + exampleGeneration: undefined } }); continue; @@ -133,7 +134,8 @@ export async function loadSingleNamespaceAPIWorkspace({ discriminatedUnionV2: false, preserveSchemaIds: false, asyncApiNaming: definition.settings?.asyncApiMessageNaming ?? "v1", - filter: definition.settings?.filter + filter: definition.settings?.filter, + exampleGeneration: definition.settings?.exampleGeneration }, source: { type: "openapi",