Skip to content

Commit

Permalink
feat(cli): allow configuring example depth (#5465)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsinghvi authored Dec 23, 2024
1 parent faa2b30 commit 523965e
Show file tree
Hide file tree
Showing 28 changed files with 748 additions and 45 deletions.
14 changes: 14 additions & 0 deletions fern/apis/generators-yml/definition/generators.yml
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,20 @@ types:
filter:
type: optional<OpenAPIFilterSchema>
docs: Filter to apply to the OpenAPI specification.
example-generation:
type: optional<OpenAPIExampleGenerationSchema>
docs: Fine-tune your example generation

OpenAPIExampleGenerationSchema:
properties:
request: optional<RequestOrResponseExampleGenerationSchema>
response: optional<RequestOrResponseExampleGenerationSchema>

RequestOrResponseExampleGenerationSchema:
properties:
max-depth:
type: optional<integer>
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:
Expand Down
16 changes: 16 additions & 0 deletions fern/pages/changelogs/cli/2024-12-23.mdx
Original file line number Diff line number Diff line change
@@ -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
```
52 changes: 52 additions & 0 deletions generators-yml.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -1373,6 +1415,16 @@
"type": "null"
}
]
},
"example-generation": {
"oneOf": [
{
"$ref": "#/definitions/generators.OpenAPIExampleGenerationSchema"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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";
Expand All @@ -26,9 +26,11 @@ import { OpenAPIV3ParserContext } from "../OpenAPIV3ParserContext";
export class ExampleEndpointFactory {
private exampleTypeFactory: ExampleTypeFactory;
private logger: Logger;
private schemas: Record<string, SchemaWithExample>;

constructor(schemas: Record<string, SchemaWithExample>, context: OpenAPIV3ParserContext) {
constructor(
private readonly schemas: Record<string, SchemaWithExample>,
private readonly context: OpenAPIV3ParserContext
) {
this.schemas = schemas;
this.exampleTypeFactory = new ExampleTypeFactory(schemas, context.nonRequestReferencedSchemas, context);
this.logger = context.logger;
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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({
Expand Down Expand Up @@ -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
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<TreeNode>",
},
"right": {
"docs": "Child nodes of this tree node",
"type": "optional<TreeNode>",
},
"value": {
"docs": "The value stored in this node",
"type": "optional<string>",
},
},
"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<string>
docs: The value stored in this node
left:
type: optional<TreeNode>
docs: Child nodes of this tree node
right:
type: optional<TreeNode>
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
",
},
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
}
}
}
Loading

0 comments on commit 523965e

Please sign in to comment.