Skip to content

Commit

Permalink
Add single-union key to YAML (#774)
Browse files Browse the repository at this point in the history
  • Loading branch information
zachkirsch authored Oct 2, 2022
1 parent 608a912 commit ae7b17d
Show file tree
Hide file tree
Showing 27 changed files with 388 additions and 12 deletions.
1 change: 1 addition & 0 deletions .pnp.cjs

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

13 changes: 12 additions & 1 deletion fern.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,18 @@
"properties": {
"docs": { "$ref": "#/properties/ids/items/anyOf/1/properties/docs" },
"name": { "type": "string" },
"type": { "type": "string" }
"type": { "type": "string" },
"key": {
"anyOf": [
{ "type": "string" },
{
"type": "object",
"properties": { "name": { "type": "string" }, "value": { "type": "string" } },
"required": ["value"],
"additionalProperties": false
}
]
}
},
"additionalProperties": false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { TypeResolver } from "../../resolvers/TypeResolver";
import { generateWireStringWithAllCasings } from "../../utils/generateCasings";
import { getDocs } from "../../utils/getDocs";

const UNION_VALUE_PROPERTY_NAME = "value";
const DEFAULT_UNION_VALUE_PROPERTY_VALUE = "value";

export function convertUnionTypeDeclaration({
union,
Expand Down Expand Up @@ -48,7 +48,13 @@ export function convertUnionTypeDeclaration({
return {
discriminantValue,
valueType,
shape: getSingleUnionTypeProperties(rawType, valueType, file, typeResolver),
shape: getSingleUnionTypeProperties({
rawType,
valueType,
file,
typeResolver,
rawSingleUnionType: typeof unionedType !== "string" ? unionedType.key : undefined,
}),
docs: getDocs(unionedType),
};
}),
Expand Down Expand Up @@ -101,23 +107,54 @@ export function getUnionedTypeName({
};
}

function getSingleUnionTypeProperties(
rawType: string,
valueType: TypeReference,
file: FernFileContext,
typeResolver: TypeResolver
): SingleUnionTypeProperties {
function getSingleUnionTypeProperties({
rawType,
valueType,
file,
typeResolver,
rawSingleUnionType,
}: {
rawType: string;
valueType: TypeReference;
file: FernFileContext;
typeResolver: TypeResolver;
rawSingleUnionType: string | RawSchemas.SingleUnionTypeKeySchema | undefined;
}): SingleUnionTypeProperties {
const resolvedType = typeResolver.resolveType({ type: rawType, file });

if (resolvedType._type === "named" && isRawObjectDefinition(resolvedType.declaration)) {
return SingleUnionTypeProperties.samePropertiesAsObject(resolvedType.name);
} else {
return SingleUnionTypeProperties.singleProperty({
name: generateWireStringWithAllCasings({
wireValue: UNION_VALUE_PROPERTY_NAME,
name: UNION_VALUE_PROPERTY_NAME,
wireValue: getSinglePropertyKeyValue(rawSingleUnionType),
name: getSinglePropertyKeyName(rawSingleUnionType),
}),
type: valueType,
});
}
}

function getSinglePropertyKeyName(
rawSingleUnionType: string | RawSchemas.SingleUnionTypeKeySchema | undefined
): string {
if (rawSingleUnionType != null) {
if (typeof rawSingleUnionType === "string") {
return rawSingleUnionType;
}
return rawSingleUnionType.name ?? rawSingleUnionType.value;
}
return DEFAULT_UNION_VALUE_PROPERTY_VALUE;
}

function getSinglePropertyKeyValue(
rawSingleUnionType: string | RawSchemas.SingleUnionTypeKeySchema | undefined
): string {
if (rawSingleUnionType != null) {
if (typeof rawSingleUnionType === "string") {
return rawSingleUnionType;
}
return rawSingleUnionType.value;
}
return DEFAULT_UNION_VALUE_PROPERTY_VALUE;
}
1 change: 1 addition & 0 deletions packages/cli/migrations/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"glob-promise": "^4.2.2",
"inquirer": "^9.1.0",
"js-yaml": "^4.1.0",
"yaml": "^2.1.1",
"zod": "^3.14.3"
},
"devDependencies": {
Expand Down
9 changes: 9 additions & 0 deletions packages/cli/migrations/src/migrations/0.0.203/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { VersionMigrations } from "../../types/VersionMigrations";
import UnionSinglePropertyKeyMigration from "./union-single-property-key";

const versionMigrations: VersionMigrations = {
version: "0.0.203",
migrations: [UnionSinglePropertyKeyMigration],
};

export default versionMigrations;
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`union-single-property-key simple 1`] = `
"types:
a: string
b: boolean
c:
properties:
a: integer
d:
union:
a:
type: string
key: a
b:
type: boolean
key: b
d:
type: d
key: d
e:
union:
a:
type: string
key: a
b:
type: boolean
key: b
d:
type: d
docs: hello
key: d
"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: my-api
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
types:
a: string
b: boolean
c:
properties:
a: integer
d:
union:
a: string
b: boolean
d: d
e:
union:
a:
type: string
b: boolean
d:
type: d
docs: hello
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
generators:
- name: fernapi/fern-typescript
version: 0.0.144
config:
mode: model
- name: fernapi/fern-java
version: 0.0.84
generate: true
config:
packagePrefix: "com.fern"
mode: MODEL
- name: fernapi/fern-postman
version: 0.0.18
- name: fernapi/fern-openapi
version: 0.0.5
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"organization": "fern",
"version": "0.0.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { AbsoluteFilePath, join, RelativeFilePath } from "@fern-api/core-utils";
import { createMockTaskContext } from "@fern-api/task-context";
import { cp, readFile } from "fs/promises";
import tmp from "tmp-promise";
import { migration } from "../migration";

const FIXTURES_PATH = join(AbsoluteFilePath.of(__dirname), RelativeFilePath.of("fixtures"));

describe("union-single-property-key", () => {
it("simple", async () => {
const fixturePath = join(FIXTURES_PATH, RelativeFilePath.of("simple"));
const tmpDir = await tmp.dir();

await cp(fixturePath, tmpDir.path, { recursive: true });
process.chdir(tmpDir.path);

await migration.run({
context: createMockTaskContext(),
});

const newBlogYaml = (
await readFile(
join(AbsoluteFilePath.of(tmpDir.path), RelativeFilePath.of("fern/api/definition/blog/blog.yml"))
)
).toString();

expect(newBlogYaml).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AbsoluteFilePath } from "@fern-api/core-utils";
import { TaskContext, TASK_FAILURE } from "@fern-api/task-context";
import { findUp } from "find-up";
import glob from "glob-promise";

const FERN_DIRECTORY = "fern";

export async function getAllYamlFiles(context: TaskContext): Promise<AbsoluteFilePath[] | TASK_FAILURE> {
const fernDirectory = await getFernDirectory();
if (fernDirectory == null) {
return context.fail(`Directory "${FERN_DIRECTORY}" not found.`);
}
const filepaths = await glob("*/definition/**/*.yml", {
cwd: fernDirectory,
absolute: true,
});
return filepaths.map(AbsoluteFilePath.of);
}

async function getFernDirectory(): Promise<AbsoluteFilePath | undefined> {
const fernDirectoryStr = await findUp(FERN_DIRECTORY, { type: "directory" });
if (fernDirectoryStr == null) {
return undefined;
}
return AbsoluteFilePath.of(fernDirectoryStr);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { migration as default } from "./migration";
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { AbsoluteFilePath } from "@fern-api/core-utils";
import { TaskContext, TASK_FAILURE } from "@fern-api/task-context";
import { readFile, writeFile } from "fs/promises";
import YAML from "yaml";
import { Migration } from "../../../types/Migration";
import { getAllYamlFiles } from "./getAllYamlFiles";

export const migration: Migration = {
name: "union-single-property-migration",
summary: "migrates union types to set the `key` property on non-object subtypes to the discriminant value.",
run: async ({ context }) => {
const yamlFiles = await getAllYamlFiles(context);
if (yamlFiles === TASK_FAILURE) {
return;
}
for (const filepath of yamlFiles) {
try {
await migrateFile(filepath, context);
} catch (error) {
context.fail(`Failed to add 'key' property to union in ${filepath}`, error);
}
}
},
};

async function migrateFile(filepath: AbsoluteFilePath, context: TaskContext): Promise<void> {
const contents = await readFile(filepath);
const parsedDocument = YAML.parseDocument(contents.toString());
const types = parsedDocument.get("types");
if (types == null) {
return;
}
if (!YAML.isMap(types)) {
context.fail(`"types" is not a map in ${filepath}`);
return;
}

for (const typeDeclaration of types.items) {
if (YAML.isMap(typeDeclaration.value)) {
const union = typeDeclaration.value.get("union");
console.log(typeDeclaration, union);
if (union == null) {
continue;
}
if (!YAML.isMap(union)) {
context.fail(`"union" is not a map in ${filepath}`);
continue;
}
for (const singleUnionType of union.items) {
if (YAML.isScalar(singleUnionType.value)) {
singleUnionType.value = {
type: singleUnionType.value,
key: singleUnionType.key,
};
} else if (YAML.isMap(singleUnionType.value)) {
singleUnionType.value.add(new YAML.Pair("key", singleUnionType.key));
}
}
}
}

await writeFile(filepath, parsedDocument.toString());
}
3 changes: 2 additions & 1 deletion packages/cli/migrations/src/migrations/all.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { VersionMigrations } from "../types/VersionMigrations";
import migrations_0_0_188 from "./0.0.188";
import migrations_0_0_191 from "./0.0.191";
import migrations_0_0_203 from "./0.0.203";

export const ALL_MIGRATIONS: VersionMigrations[] = [migrations_0_0_188, migrations_0_0_191];
export const ALL_MIGRATIONS: VersionMigrations[] = [migrations_0_0_188, migrations_0_0_191, migrations_0_0_203];
2 changes: 2 additions & 0 deletions packages/cli/yaml/validator/src/getAllRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import NoCircularImportsRule from "./rules/no-circular-imports";
import NoDuplicateDeclarationsRule from "./rules/no-duplicate-declarations";
import NoDuplicateEnumValuesRule from "./rules/no-duplicate-enum-values";
import NoDuplicateFieldNamesRule from "./rules/no-duplicate-field-names";
import NoObjectSinglePropertyKey from "./rules/no-object-single-property-key";
import NoUndefinedErrorReferenceRule from "./rules/no-undefined-error-reference";
import NoUndefinedPathParametersRule from "./rules/no-undefined-path-parameters";
import NoUndefinedTypeReferenceRule from "./rules/no-undefined-type-reference";
Expand All @@ -20,5 +21,6 @@ export function getAllRules(): Rule[] {
NoCircularImportsRule,
ValidFieldNamesRule,
NoDuplicateFieldNamesRule,
NoObjectSinglePropertyKey,
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: simple-api
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
types:
MyPrimitiveAlias: string
MyObjectAlias: MyObject
MyObject:
properties:
a: string
b: number
MyUnion:
union:
a: MyPrimitiveAlias
b:
type: string
key: hello
c: MyObjectAlias
d:
type: MyObjectAlias
key: hello
e:
key: yoyo
f:
key: yoyo
type: void
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Loading

0 comments on commit ae7b17d

Please sign in to comment.