Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cli): openapi importer supports a setting for making additional properties optional #4667

Merged
merged 4 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions packages/cli/cli/versions.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
- changelogEntry:
- summary: |
Previously the OpenAPI converter would incorrectly mark
the values of `additionalProperties` as optional. Now, we have
introduced a feature flag to turn this behavior off.

The feature flag can be configured in generators.yml:
```yml
api:
specs:
- openapi: /path/to/openapi
settings:
optional-additional-properties: false
```
type: fix
irVersion: 53
version: 0.41.16

- changelogEntry:
- summary: |
Performance improvements for stringifiying large Intermediate Representations. If
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface APIDefinitionSettings {
shouldUseTitleAsName: boolean | undefined;
shouldUseUndiscriminatedUnionsWithLiterals: boolean | undefined;
asyncApiMessageNaming: "v1" | "v2" | undefined;
shouldUseOptionalAdditionalProperties: boolean | undefined;
}

export interface APIDefinitionLocation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ async function parseAPIConfigurationToApiLocations(
settings: {
shouldUseTitleAsName: undefined,
shouldUseUndiscriminatedUnionsWithLiterals: undefined,
asyncApiMessageNaming: undefined
asyncApiMessageNaming: undefined,
shouldUseOptionalAdditionalProperties: undefined
}
});
} else if (isRawProtobufAPIDefinitionSchema(apiConfiguration)) {
Expand All @@ -111,7 +112,8 @@ async function parseAPIConfigurationToApiLocations(
settings: {
shouldUseTitleAsName: undefined,
shouldUseUndiscriminatedUnionsWithLiterals: undefined,
asyncApiMessageNaming: undefined
asyncApiMessageNaming: undefined,
shouldUseOptionalAdditionalProperties: undefined
}
});
} else if (Array.isArray(apiConfiguration)) {
Expand All @@ -128,7 +130,8 @@ async function parseAPIConfigurationToApiLocations(
settings: {
shouldUseTitleAsName: undefined,
shouldUseUndiscriminatedUnionsWithLiterals: undefined,
asyncApiMessageNaming: undefined
asyncApiMessageNaming: undefined,
shouldUseOptionalAdditionalProperties: undefined
}
});
} else if (isRawProtobufAPIDefinitionSchema(definition)) {
Expand All @@ -145,7 +148,8 @@ async function parseAPIConfigurationToApiLocations(
settings: {
shouldUseTitleAsName: undefined,
shouldUseUndiscriminatedUnionsWithLiterals: undefined,
asyncApiMessageNaming: undefined
asyncApiMessageNaming: undefined,
shouldUseOptionalAdditionalProperties: undefined
}
});
} else {
Expand All @@ -160,7 +164,8 @@ async function parseAPIConfigurationToApiLocations(
settings: {
shouldUseTitleAsName: definition.settings?.["use-title"],
shouldUseUndiscriminatedUnionsWithLiterals: definition.settings?.unions === "v1",
asyncApiMessageNaming: definition.settings?.["message-naming"]
asyncApiMessageNaming: definition.settings?.["message-naming"],
shouldUseOptionalAdditionalProperties: undefined
}
});
}
Expand All @@ -177,7 +182,8 @@ async function parseAPIConfigurationToApiLocations(
settings: {
shouldUseTitleAsName: apiConfiguration.settings?.["use-title"],
shouldUseUndiscriminatedUnionsWithLiterals: apiConfiguration.settings?.unions === "v1",
asyncApiMessageNaming: apiConfiguration.settings?.["message-naming"]
asyncApiMessageNaming: apiConfiguration.settings?.["message-naming"],
shouldUseOptionalAdditionalProperties: undefined
}
});
}
Expand All @@ -199,7 +205,8 @@ async function parseAPIConfigurationToApiLocations(
settings: {
shouldUseTitleAsName: settings?.["use-title"],
shouldUseUndiscriminatedUnionsWithLiterals: settings?.unions === "v1",
asyncApiMessageNaming: undefined
asyncApiMessageNaming: undefined,
shouldUseOptionalAdditionalProperties: undefined
}
});
} else if (openapi != null) {
Expand All @@ -214,7 +221,8 @@ async function parseAPIConfigurationToApiLocations(
settings: {
shouldUseTitleAsName: openapi.settings?.["use-title"],
shouldUseUndiscriminatedUnionsWithLiterals: openapi.settings?.unions === "v1",
asyncApiMessageNaming: undefined
asyncApiMessageNaming: undefined,
shouldUseOptionalAdditionalProperties: undefined
}
});
}
Expand All @@ -231,7 +239,8 @@ async function parseAPIConfigurationToApiLocations(
settings: {
shouldUseTitleAsName: settings?.["use-title"],
shouldUseUndiscriminatedUnionsWithLiterals: settings?.unions === "v1",
asyncApiMessageNaming: settings?.["message-naming"]
asyncApiMessageNaming: settings?.["message-naming"],
shouldUseOptionalAdditionalProperties: undefined
}
});
}
Expand Down Expand Up @@ -263,7 +272,8 @@ async function parseApiConfigurationV2Schema({
settings: {
shouldUseTitleAsName: undefined,
shouldUseUndiscriminatedUnionsWithLiterals: undefined,
asyncApiMessageNaming: undefined
asyncApiMessageNaming: undefined,
shouldUseOptionalAdditionalProperties: spec.settings?.["optional-additional-properties"] ?? true
}
};
if (spec.namespace == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
import { z } from "zod";
import { RawSchemas } from "@fern-api/fern-definition-schema";

/*********** OpenAPI Spec ***********/

export const OpenAPISettingsSchema = z.strictObject({
"optional-additional-properties": z.optional(z.boolean())
});

export type OpenAPISettingsSchema = z.infer<typeof OpenAPISettingsSchema>;

export const OpenAPISpecSchema = z.strictObject({
openapi: z.string(),
overrides: z.string().optional(),
namespace: z.string().optional()
namespace: z.string().optional(),
settings: z.optional(OpenAPISettingsSchema)
});

export type OpenAPISpecSchema = z.infer<typeof OpenAPISpecSchema>;

/*********** AsyncAPI Spec ***********/

export const AsyncAPISettingsSchema = z.strictObject({
"optional-additional-properties": z.optional(z.boolean())
});

export type AsyncAPISettingsSchema = z.infer<typeof AsyncAPISettingsSchema>;

export const AsyncAPISchema = z.strictObject({
asyncapi: z.string(),
overrides: z.string().optional(),
namespace: z.string().optional()
namespace: z.string().optional(),
settings: z.optional(AsyncAPISettingsSchema)
});

export type AsyncAPISchema = z.infer<typeof AsyncAPISchema>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`anyOf > additional-properties > parse open api 1`] = `
{
"apiVersion": null,
"basePath": null,
"channel": [],
"description": null,
"endpoints": [],
"globalHeaders": [],
"groups": {},
"hasEndpointsMarkedInternal": false,
"idempotencyHeaders": [],
"nonRequestReferencedSchemas": [],
"schemas": {
"CheckFlagRequestBody": {
"additionalProperties": false,
"allOf": [],
"allOfPropertyConflicts": [],
"availability": null,
"description": null,
"generatedName": "CheckFlagRequestBody",
"groupName": [],
"nameOverride": null,
"properties": [
{
"audiences": [],
"availability": null,
"conflict": {},
"generatedName": "checkFlagRequestBodyCompany",
"key": "company",
"nameOverride": null,
"schema": {
"availability": null,
"description": null,
"generatedName": "checkFlagRequestBodyCompany",
"groupName": [],
"nameOverride": null,
"title": null,
"type": "optional",
"value": {
"availability": null,
"description": null,
"generatedName": "CheckFlagRequestBodyCompany",
"groupName": [],
"nameOverride": null,
"title": null,
"type": "nullable",
"value": {
"availability": null,
"description": null,
"encoding": null,
"generatedName": "CheckFlagRequestBodyCompany",
"groupName": [],
"key": {
"availability": null,
"description": null,
"generatedName": "CheckFlagRequestBodyCompanyKey",
"groupName": [],
"nameOverride": null,
"schema": {
"default": null,
"format": null,
"maxLength": null,
"minLength": null,
"pattern": null,
"type": "string",
},
"title": null,
},
"nameOverride": null,
"title": null,
"type": "map",
"value": {
"availability": null,
"description": null,
"generatedName": "CheckFlagRequestBodyCompanyValue",
"groupName": [],
"nameOverride": null,
"schema": {
"default": null,
"format": null,
"maxLength": null,
"minLength": null,
"pattern": null,
"type": "string",
},
"title": null,
"type": "primitive",
},
},
},
},
},
{
"audiences": [],
"availability": null,
"conflict": {},
"generatedName": "checkFlagRequestBodyUser",
"key": "user",
"nameOverride": null,
"schema": {
"availability": null,
"description": null,
"generatedName": "checkFlagRequestBodyUser",
"groupName": [],
"nameOverride": null,
"title": null,
"type": "optional",
"value": {
"availability": null,
"description": null,
"generatedName": "CheckFlagRequestBodyUser",
"groupName": [],
"nameOverride": null,
"title": null,
"type": "nullable",
"value": {
"availability": null,
"description": null,
"encoding": null,
"generatedName": "CheckFlagRequestBodyUser",
"groupName": [],
"key": {
"availability": null,
"description": null,
"generatedName": "CheckFlagRequestBodyUserKey",
"groupName": [],
"nameOverride": null,
"schema": {
"default": null,
"format": null,
"maxLength": null,
"minLength": null,
"pattern": null,
"type": "string",
},
"title": null,
},
"nameOverride": null,
"title": null,
"type": "map",
"value": {
"availability": null,
"description": null,
"generatedName": "CheckFlagRequestBodyUserValue",
"groupName": [],
"nameOverride": null,
"schema": {
"default": null,
"format": null,
"maxLength": null,
"minLength": null,
"pattern": null,
"type": "string",
},
"title": null,
"type": "primitive",
},
},
},
},
},
],
"source": {
"file": "additional-properties/openapi.yml",
"type": "openapi",
},
"title": null,
"type": "object",
},
},
"securitySchemes": {},
"servers": [],
"tags": {
"orderedTagIds": null,
"tagsById": {},
},
"title": "Additional Properties",
"variables": {},
"webhooks": [],
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { testParseOpenAPI } from "./testParseOpenApi";

describe("anyOf", () => {
testParseOpenAPI("additional-properties", "openapi.yml", undefined, {
audiences: [],
shouldUseTitleAsName: true,
shouldUseUndiscriminatedUnionsWithLiterals: true,
optionalAdditionalProperties: false
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
openapi: 3.1.0
info:
title: Additional Properties
version: "0"
paths: {}
components:
schemas:
CheckFlagRequestBody:
type: object
properties:
company:
additionalProperties:
type: string
type: object
nullable: true
user:
additionalProperties:
type: string
type: object
nullable: true

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ describe("open api parser", () => {
testParseOpenAPI("gen-yml-make-undisc-unions", "openapi.json", undefined, {
audiences: [],
shouldUseTitleAsName: true,
shouldUseUndiscriminatedUnionsWithLiterals: true
shouldUseUndiscriminatedUnionsWithLiterals: true,
optionalAdditionalProperties: true
});
});
Loading
Loading