generated from moontaiworks/package-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: dry out transform of basic properties
also fixed some missing use cases
- Loading branch information
1 parent
710981c
commit 5cae68b
Showing
20 changed files
with
643 additions
and
661 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Type } from "@sinclair/typebox"; | ||
import { expect, it } from "vitest"; | ||
|
||
import { isUnionLiteral } from "./is-union-literal"; | ||
|
||
it("should return true if schema is TUnion of TLiteral", () => { | ||
const schema = Type.Union([ | ||
Type.Literal("foo"), | ||
Type.Literal(10), | ||
Type.Literal(true), | ||
]); | ||
|
||
const result = isUnionLiteral(schema); | ||
|
||
expect(result).toBe(true); | ||
}); | ||
|
||
it("should return false if schema is not TUnion of TLiteral", () => { | ||
const schema = Type.Union([Type.Literal("foo")]); | ||
|
||
const result = isUnionLiteral(schema); | ||
|
||
expect(result).toBe(false); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { | ||
type TLiteral, | ||
type TSchema, | ||
type TUnion, | ||
TypeGuard, | ||
} from "@sinclair/typebox"; | ||
|
||
export function isUnionLiteral(schema: TSchema): schema is TUnion<TLiteral[]> { | ||
if (!TypeGuard.IsUnion(schema)) return false; | ||
|
||
return schema.anyOf.every(item => TypeGuard.IsLiteral(item)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Type } from "@sinclair/typebox"; | ||
import { expect, it } from "vitest"; | ||
|
||
import { tUnionToTuple } from "./t-union-to-tuple"; | ||
|
||
it("should transform TUnion to tuple", () => { | ||
const schema = Type.Union([ | ||
Type.Literal("foo"), | ||
Type.Literal(10), | ||
Type.Literal(true), | ||
]); | ||
|
||
const result = tUnionToTuple(schema); | ||
|
||
expect(result).toEqual(["foo", 10, true]); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { | ||
type Static, | ||
type TLiteral, | ||
type TUnion, | ||
type UnionToTuple, | ||
} from "@sinclair/typebox"; | ||
|
||
export function tUnionToTuple<S extends TUnion<TLiteral[]>>( | ||
schema: S, | ||
): UnionToTuple<Static<S>> { | ||
return schema.anyOf.map(item => item.const) as never; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,120 +1,93 @@ | ||
import { Type } from "@sinclair/typebox"; | ||
import { describe, expect, it } from "vitest"; | ||
import { beforeAll, beforeEach, expect, it, vi } from "vitest"; | ||
import type { Options } from "yargs"; | ||
|
||
import { getArrayOption } from "./array"; | ||
const isUnionLiteral = vi.fn(); | ||
const tUnionToTuple = vi.fn(); | ||
const transform = vi.fn(); | ||
|
||
it("should transform TArray to yargs option", () => { | ||
const schema = Type.Array(Type.String()); | ||
let getArrayOption: typeof import("./array").getArrayOption; | ||
|
||
const result = getArrayOption(schema); | ||
beforeAll(async () => { | ||
vi.doMock("@/helpers/is-union-literal", () => ({ isUnionLiteral })); | ||
vi.doMock("@/helpers/t-union-to-tuple", () => ({ tUnionToTuple })); | ||
vi.doMock("./transform", () => ({ transform })); | ||
|
||
expect(result).toEqual({ | ||
type: "array", | ||
requiresArg: true, | ||
}); | ||
getArrayOption = await import("./array").then(m => m.getArrayOption); | ||
}); | ||
|
||
describe("should transform TArray with choices to yargs option", () => { | ||
it("when item is literal", () => { | ||
const schema = Type.Array(Type.Literal("foo")); | ||
|
||
const result = getArrayOption(schema); | ||
|
||
expect(result).toEqual({ | ||
type: "array", | ||
requiresArg: true, | ||
choices: ["foo"], | ||
}); | ||
}); | ||
|
||
it("when items are union of literal", () => { | ||
const schema = Type.Array( | ||
Type.Union([Type.Literal("foo"), Type.Literal("bar")]), | ||
); | ||
|
||
const result = getArrayOption(schema); | ||
|
||
expect(result).toEqual({ | ||
type: "array", | ||
requiresArg: true, | ||
choices: ["foo", "bar"], | ||
}); | ||
}); | ||
|
||
it("should not transform when items are union of literal and non-literal", () => { | ||
const schema = Type.Array( | ||
Type.Union([Type.Literal("foo"), Type.Literal("bar"), Type.Boolean()]), | ||
); | ||
|
||
const result = getArrayOption(schema); | ||
|
||
expect(result).toEqual({ | ||
type: "array", | ||
requiresArg: true, | ||
}); | ||
}); | ||
beforeEach(() => { | ||
vi.resetAllMocks(); | ||
}); | ||
|
||
it("should transform TArray with truthy default value to yargs option", () => { | ||
const schema = Type.Array(Type.String(), { default: ["foo"] }); | ||
it("should call transform to transform", () => { | ||
const schema = Type.Array(Type.Any()); | ||
const expectedResponse = { mocked: true }; | ||
transform.mockReturnValue(expectedResponse); | ||
|
||
const result = getArrayOption(schema); | ||
const response = getArrayOption(schema); | ||
|
||
expect(result).toEqual({ | ||
type: "array", | ||
requiresArg: false, | ||
default: ["foo"], | ||
}); | ||
expect(transform).toBeCalledWith("array", schema, {}); | ||
expect(response).toEqual(expectedResponse); | ||
}); | ||
|
||
it("should transform TArray with falsy default value to yargs option", () => { | ||
const schema = Type.Array(Type.String(), { default: [] }); | ||
it("should take its value as choices if it is literal", () => { | ||
const schema = Type.Array(Type.Union([Type.Literal("foo")])); | ||
const expectedResponse = { mocked: true }; | ||
transform.mockReturnValue(expectedResponse); | ||
|
||
const result = getArrayOption(schema); | ||
const response = getArrayOption(schema); | ||
|
||
expect(result).toEqual({ | ||
type: "array", | ||
requiresArg: false, | ||
default: [], | ||
}); | ||
}); | ||
|
||
it("should transform TArray with description to yargs option", () => { | ||
const schema = Type.Array(Type.String(), { description: "foo" }); | ||
const expectedOverwrites = { | ||
choices: ["foo"], | ||
} satisfies Options; | ||
|
||
const result = getArrayOption(schema); | ||
|
||
expect(result).toEqual({ | ||
type: "array", | ||
requiresArg: true, | ||
description: "foo", | ||
}); | ||
expect(isUnionLiteral).not.toBeCalled(); | ||
expect(tUnionToTuple).not.toBeCalled(); | ||
expect(transform).toBeCalledWith("array", schema, expectedOverwrites); | ||
expect(response).toEqual(expectedResponse); | ||
}); | ||
|
||
it("should transform TArray with override to yargs option", () => { | ||
const schema = Type.Array(Type.String()); | ||
const overwrite: Options = { | ||
requiresArg: false, | ||
alias: "aliased", | ||
}; | ||
|
||
const result = getArrayOption(schema, overwrite); | ||
it("should take union of literals to tuple of literals as value of choices then call transform to transform", () => { | ||
const schema = Type.Array( | ||
Type.Union([Type.Literal("foo"), Type.Literal(10), Type.Literal(true)]), | ||
); | ||
const expectedResponse = { mocked: true }; | ||
isUnionLiteral.mockReturnValueOnce(true); | ||
tUnionToTuple.mockReturnValueOnce(["foo", 10, true]); | ||
transform.mockReturnValue(expectedResponse); | ||
|
||
const response = getArrayOption(schema); | ||
|
||
const expectedOverwrites = { | ||
choices: ["foo", 10, true], | ||
} satisfies Options; | ||
|
||
expect(isUnionLiteral).toBeCalledWith(schema.items); | ||
expect(tUnionToTuple).toBeCalledWith(schema.items); | ||
expect(transform).toBeCalledWith("array", schema, expectedOverwrites); | ||
expect(response).toEqual(expectedResponse); | ||
}); | ||
|
||
expect(result).toEqual({ | ||
type: "array", | ||
requiresArg: false, | ||
it("should call transform with overwrites", () => { | ||
const schema = Type.Array( | ||
Type.Union([Type.Literal("foo"), Type.Literal(10), Type.Literal(true)]), | ||
); | ||
const overwrites = { | ||
alias: "aliased", | ||
}); | ||
}); | ||
choices: ["foo", "bar"], | ||
} satisfies Options; | ||
const expectedResponse = { mocked: true, ...overwrites }; | ||
isUnionLiteral.mockReturnValueOnce(true); | ||
tUnionToTuple.mockReturnValueOnce(["foo", 10, true]); | ||
transform.mockReturnValue(expectedResponse); | ||
|
||
it("should detect if it is optional", () => { | ||
const schema = Type.Optional(Type.Array(Type.String())); | ||
const response = getArrayOption(schema, overwrites); | ||
|
||
const result = getArrayOption(schema); | ||
const expectedOverwrites = overwrites satisfies Options; | ||
|
||
expect(result).toEqual({ | ||
type: "array", | ||
requiresArg: false, | ||
}); | ||
expect(isUnionLiteral).toBeCalledWith(schema.items); | ||
expect(tUnionToTuple).toBeCalledWith(schema.items); | ||
expect(transform).toBeCalledWith("array", schema, expectedOverwrites); | ||
expect(response).toEqual(expectedResponse); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,66 +1,45 @@ | ||
import { | ||
type Static, | ||
type TArray, | ||
type TLiteral, | ||
type TSchema, | ||
type TUnion, | ||
TypeGuard, | ||
type UnionToTuple, | ||
} from "@sinclair/typebox"; | ||
import type { Options } from "yargs"; | ||
|
||
function getLiteralValue(schema: TLiteral): string { | ||
return schema.const.toString(); | ||
} | ||
|
||
function getValue(schema: TSchema): string | undefined { | ||
if (TypeGuard.IsLiteral(schema)) return getLiteralValue(schema); | ||
|
||
return undefined; | ||
} | ||
|
||
function getUnionValues(schema: TUnion): string[] | undefined { | ||
const values: string[] = []; | ||
|
||
const isAllValid = schema.anyOf.every(item => { | ||
const value = getValue(item); | ||
if (!value) return false; | ||
import { isUnionLiteral } from "@/helpers/is-union-literal"; | ||
import { tUnionToTuple } from "@/helpers/t-union-to-tuple"; | ||
|
||
values.push(value); | ||
import { transform } from "./transform"; | ||
|
||
return true; | ||
}); | ||
type GetChoices<T extends TSchema> = T extends TLiteral | ||
? [Static<T>] | ||
: T extends TUnion<TLiteral[]> | ||
? UnionToTuple<Static<T>> | ||
: never; | ||
|
||
if (!isAllValid) return undefined; | ||
|
||
return values; | ||
} | ||
|
||
function getChoices(schema: TSchema): string[] | undefined { | ||
if (TypeGuard.IsLiteral(schema)) return [getLiteralValue(schema)]; | ||
if (TypeGuard.IsUnion(schema)) return getUnionValues(schema); | ||
function getChoices<T extends TSchema>(schema: T): GetChoices<T>; | ||
function getChoices(schema: TSchema) { | ||
if (TypeGuard.IsLiteral(schema)) return [schema.const]; | ||
if (isUnionLiteral(schema)) return tUnionToTuple(schema); | ||
|
||
return undefined; | ||
} | ||
|
||
export function getArrayOption( | ||
schema: TArray, | ||
override: Options = {}, | ||
): Options { | ||
const hasDefaultValue = schema.default !== undefined; | ||
const options = { | ||
type: "array" as const, | ||
requiresArg: !TypeGuard.IsOptional(schema) && !hasDefaultValue, | ||
export function getArrayOption<S extends TArray, O extends Options = object>( | ||
schema: S, | ||
overwrites: O = {} as never, | ||
) { | ||
const choices = getChoices(schema.items) as GetChoices<S["items"]>; | ||
const mergedOverwrites = { | ||
// @ts-expect-error We expect this type to be calculated, but seems it's too | ||
// long for ts. If it been proved to be not nessesary, we can remove this | ||
// type inference. | ||
choices, | ||
...overwrites, | ||
}; | ||
|
||
if (hasDefaultValue) | ||
Object.assign(options, { default: schema.default as unknown[] }); | ||
if (schema.description) | ||
Object.assign(options, { description: schema.description }); | ||
|
||
const choices = getChoices(schema.items); | ||
|
||
if (choices) Object.assign(options, { choices }); | ||
|
||
Object.assign(options, override); | ||
|
||
return options; | ||
return transform("array", schema, mergedOverwrites); | ||
} |
Oops, something went wrong.