Skip to content

Commit

Permalink
fix(openapi): support explicit null semantics on openapi config (#4747)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsinghvi committed Sep 25, 2024
1 parent 0d03d2e commit 260aa7f
Show file tree
Hide file tree
Showing 22 changed files with 259 additions and 67 deletions.
4 changes: 4 additions & 0 deletions fern/pages/changelogs/cli/2024-09-25.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 0.43.1
**`(feat):`** The CLI now supports running OpenAPI generator 0.1.0 with IR version 53.


6 changes: 6 additions & 0 deletions fern/pages/changelogs/openapi/2024-09-25.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## 0.1.0
**`(chore):`** The OpenAPI generator now uses version 53 of the IR (previously was on version 23). You
will need to upgrade your Fern CLI to the latest to run this version of the OpenAPI
generator.


9 changes: 9 additions & 0 deletions fern/pages/changelogs/php-sdk/2024-09-25.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## 0.1.2
**`(feat):`** Represent enums in objects as strings.

**`(fix):`** Generated wrapped requests now implement `SerializableType`.

**`(fix):`** Fix a bug where we don't set the request options baseurl properly.

**`(fix):`** Fix bugs in our numeric type serde and add tests.

1 change: 1 addition & 0 deletions generators/openapi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
},
"dependencies": {
"@fern-api/fs-utils": "workspace:*",
"@fern-api/core-utils": "workspace:*",
"@fern-api/generator-commons": "workspace:*",
"@fern-fern/ir-sdk": "53.9.0",
"js-yaml": "^4.1.0",
Expand Down
17 changes: 10 additions & 7 deletions generators/openapi/src/writeOpenApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
parseIR
} from "@fern-api/generator-commons";
import { AbsoluteFilePath } from "@fern-api/fs-utils";
import { mergeWithOverrides } from "@fern-api/core-utils";

const OPENAPI_JSON_FILENAME = "openapi.json";
const OPENAPI_YML_FILENAME = "openapi.yml";
Expand All @@ -38,24 +39,26 @@ export async function writeOpenApi(mode: Mode, pathToConfig: string): Promise<vo

const ir = await loadIntermediateRepresentation(config.irFilepath);

const openApiDefinition = convertToOpenApi({
let openapi = convertToOpenApi({
apiName: config.workspaceName,
ir,
mode
});

const openApiDefinitionWithCustomOverrides = merge(customConfig.customOverrides, openApiDefinition);
if (customConfig.customOverrides != null) {
openapi = await mergeWithOverrides({
data: openapi,
overrides: customConfig.customOverrides
});
}

if (customConfig.format === "json") {
await writeFile(
path.join(config.output.path, OPENAPI_JSON_FILENAME),
JSON.stringify(openApiDefinitionWithCustomOverrides, undefined, 2)
JSON.stringify(openapi, undefined, 2)
);
} else {
await writeFile(
path.join(config.output.path, OPENAPI_YML_FILENAME),
yaml.dump(openApiDefinitionWithCustomOverrides)
);
await writeFile(path.join(config.output.path, OPENAPI_YML_FILENAME), yaml.dump(openapi));
}
await generatorLoggingClient.sendUpdate(GeneratorUpdate.exitStatusUpdate(ExitStatusUpdate.successful({})));
} catch (e) {
Expand Down
2 changes: 1 addition & 1 deletion generators/openapi/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"extends": "../../shared/tsconfig.shared.json",
"compilerOptions": { "composite": true, "outDir": "lib", "rootDir": "src" },
"include": ["./src/**/*"],
"references": [{ "path": "../commons" }, { "path": "../../packages/commons/fs-utils" }]
"references": [{ "path": "../commons" }, { "path": "../../packages/commons/fs-utils" }, { "path": "../../packages/commons/core-utils" }]
}
21 changes: 20 additions & 1 deletion generators/openapi/versions.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
- version: 0.1.1
createdAt: '2024-09-25'
changelogEntry:
- type: fix
summary: |
The `customOverrides` configuration flag now supports explict nulls as a way to
delete keys in the generated OpenAPI spec. For example, if you want to
exclude the `openapi.info` block in the generated spec, then you can do something
along the lines:
```yml generators.yml
- name: fernapi/fern-openapi
version: 0.1.1
config:
customOverrides:
info: null # excludes the info field
```
irVersion: 53

- version: 0.1.0
createdAt: '2024-03-24'
createdAt: '2024-09-25'
changelogEntry:
- type: chore
summary: |
Expand Down
1 change: 0 additions & 1 deletion packages/cli/openapi-parser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
},
"devDependencies": {
"@types/js-yaml": "^4.0.8",
"@types/lodash": "^4.17.4",
"@types/lodash-es": "^4.17.12",
"@types/node": "^18.7.18",
"@types/swagger2openapi": "^7.0.4",
Expand Down
51 changes: 2 additions & 49 deletions packages/cli/openapi-parser/src/mergeWithOverrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { AbsoluteFilePath } from "@fern-api/fs-utils";
import { TaskContext } from "@fern-api/task-context";
import { readFile } from "fs/promises";
import yaml from "js-yaml";
import type { Dictionary, NumericDictionary, PartialObject, PropertyName, ValueKeyIteratee } from "lodash";
import { isNull, isPlainObject, mergeWith, omitBy } from "lodash-es";
import { mergeWithOverrides as coreMergeWithOverrides } from "@fern-api/core-utils";

export async function mergeWithOverrides<T>({
absoluteFilepathToOverrides,
Expand All @@ -25,51 +24,5 @@ export async function mergeWithOverrides<T>({
} catch (err) {
return context.failAndThrow(`Failed to read overrides from file ${absoluteFilepathToOverrides}`);
}

const merged = mergeWith(data, mergeWith, parsedOverrides, (obj, src) =>
Array.isArray(obj) && Array.isArray(src)
? src.every((element) => typeof element === "object") && obj.every((element) => typeof element === "object")
? // nested arrays of objects are merged
undefined
: // nested arrays of primitives are replaced
[...src]
: undefined
) as T;
// Remove any nullified values
const filtered = omitDeepBy(merged, isNull) as T;
return filtered;
}

// This is essentially lodash's omitBy, but actually running through your object tree.
// The logic has been adapted from https://github.com/siberiacancode/lodash-omitdeep/tree/main.
interface OmitDeepBy {
<T>(object: Dictionary<T> | null | undefined, predicate?: ValueKeyIteratee<T>): Dictionary<T>;
<T>(object: NumericDictionary<T> | null | undefined, predicate?: ValueKeyIteratee<T>): NumericDictionary<T>;
<T extends object>(object: T | null | undefined, predicate: ValueKeyIteratee<T[keyof T]>): PartialObject<T>;
return coreMergeWithOverrides({ data, overrides: parsedOverrides });
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const omitDeepBy: OmitDeepBy = (object: any, cb: any) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function omitByDeepByOnOwnProps(object: any) {
if (!Array.isArray(object) && !isPlainObject(object)) {
return object;
}

if (Array.isArray(object)) {
return object.map((element) => omitDeepBy(element, cb));
}

const temp = {};
// eslint-disable-next-line no-restricted-syntax, @typescript-eslint/consistent-indexed-object-style
for (const [key, value] of Object.entries<{
[x: string]: PropertyName | object;
}>(object)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(temp as any)[key] = omitDeepBy(value, cb);
}
return omitBy(temp, cb);
}

return omitByDeepByOnOwnProps(object);
};
5 changes: 4 additions & 1 deletion packages/commons/core-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@
"dependencies": {
"strip-ansi": "^7.1.0",
"ua-parser-js": "^1.0.37",
"whatwg-mimetype": "^4.0.0"
"whatwg-mimetype": "^4.0.0",
"lodash-es": "^4.17.21"
},
"devDependencies": {
"@types/lodash-es": "^4.17.12",
"@types/lodash": "^4.17.4",
"@types/node": "^18.7.18",
"@types/ua-parser-js": "^0.7.39",
"@types/whatwg-mimetype": "^3.0.2",
Expand Down
1 change: 1 addition & 0 deletions packages/commons/core-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export { replaceEnvVariables } from "./replaceEnvVars";
export type { Digit, Letter, LowercaseLetter, UppercaseLetter } from "./types";
export { visitDiscriminatedUnion } from "./visitDiscriminatedUnion";
export type { WithoutQuestionMarks } from "./withoutQuestionMarks";
export { mergeWithOverrides } from "./mergeWithOverrides";
47 changes: 47 additions & 0 deletions packages/commons/core-utils/src/mergeWithOverrides.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { isNull, isPlainObject, mergeWith, omitBy } from "lodash-es";
import type { Dictionary, NumericDictionary, PartialObject, PropertyName, ValueKeyIteratee } from "lodash";

export async function mergeWithOverrides<T>({ data, overrides }: { data: T; overrides: object }): Promise<T> {
const merged = mergeWith(data, mergeWith, overrides, (obj, src) =>
Array.isArray(obj) && Array.isArray(src)
? src.every((element) => typeof element === "object") && obj.every((element) => typeof element === "object")
? // nested arrays of objects are merged
undefined
: // nested arrays of primitives are replaced
[...src]
: undefined
) as T;
// Remove any nullified values
const filtered = omitDeepBy(merged, isNull) as T;
return filtered;
}

// This is essentially lodash's omitBy, but actually running through your object tree.
// The logic has been adapted from https://github.com/siberiacancode/lodash-omitdeep/tree/main.
interface OmitDeepBy {
<T>(object: Dictionary<T> | null | undefined, predicate?: ValueKeyIteratee<T>): Dictionary<T>;
<T>(object: NumericDictionary<T> | null | undefined, predicate?: ValueKeyIteratee<T>): NumericDictionary<T>;
<T extends object>(object: T | null | undefined, predicate: ValueKeyIteratee<T[keyof T]>): PartialObject<T>;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const omitDeepBy: OmitDeepBy = (object: unknown, cb: any): any => {
function omitByDeepByOnOwnProps(object: unknown) {
if (Array.isArray(object)) {
return object.map((element) => omitDeepBy(element, cb));
}

if (isPlainObject(object)) {
const temp: Record<string, unknown> = {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
for (const [key, value] of Object.entries<Record<string, PropertyName | object>>(object as any)) {
temp[key] = omitDeepBy(value, cb);
}
return omitBy(temp, cb);
}

return object;
}

return omitByDeepByOnOwnProps(object);
};
17 changes: 12 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions seed/openapi/imdb/custom-overrides/openapi.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions seed/openapi/imdb/override/.mock/definition/api.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions seed/openapi/imdb/override/.mock/definition/imdb.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions seed/openapi/imdb/override/.mock/fern.config.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions seed/openapi/imdb/override/.mock/generators.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 260aa7f

Please sign in to comment.