1525 lines
50 KiB
TypeScript
1525 lines
50 KiB
TypeScript
import { describe, it } from "@effect/vitest"
|
|
import { deepStrictEqual } from "@effect/vitest/utils"
|
|
import { Config, ConfigProvider, Duration, Effect, Option, pipe, Redacted, Result, Schema, SchemaIssue } from "effect"
|
|
import * as assert from "node:assert"
|
|
|
|
async function assertSuccess<T>(config: Config.Config<T>, provider: ConfigProvider.ConfigProvider, expected: T) {
|
|
const r = await config.parse(provider).pipe(
|
|
Effect.result,
|
|
Effect.runPromise
|
|
)
|
|
assert.deepStrictEqual(r, Result.succeed(expected))
|
|
}
|
|
|
|
async function assertFailure<T>(config: Config.Config<T>, provider: ConfigProvider.ConfigProvider, message: string) {
|
|
const r = await config.parse(provider).pipe(
|
|
Effect.mapError((e) => e.cause.message),
|
|
Effect.result,
|
|
Effect.runPromise
|
|
)
|
|
assert.deepStrictEqual(r, Result.fail(message))
|
|
}
|
|
|
|
describe("Config", () => {
|
|
it("a config is an Effect and can be yielded", () => {
|
|
const provider = ConfigProvider.fromEnv({ env: { STRING: "value" } })
|
|
const result = Effect.runSync(Effect.provide(
|
|
Config.schema(Schema.Struct({ STRING: Schema.String })),
|
|
ConfigProvider.layer(provider)
|
|
))
|
|
deepStrictEqual(result, { STRING: "value" })
|
|
})
|
|
|
|
describe("schema", () => {
|
|
it("should not leak any information about the value", async () => {
|
|
const provider = ConfigProvider.fromUnknown({})
|
|
await assertFailure(
|
|
Config.schema(Schema.Redacted(Schema.Literal("secret")), "a"),
|
|
provider,
|
|
`Invalid data <redacted>
|
|
at ["a"]`
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("constructors", () => {
|
|
it("fail", async () => {
|
|
await assertFailure(
|
|
Config.fail(
|
|
new Schema.SchemaError(new SchemaIssue.Forbidden(Option.none(), { message: "failure message" }))
|
|
),
|
|
ConfigProvider.fromUnknown({}),
|
|
`failure message`
|
|
)
|
|
})
|
|
|
|
it("succeed", async () => {
|
|
const provider = ConfigProvider.fromUnknown({})
|
|
await assertSuccess(Config.succeed(1), provider, 1)
|
|
})
|
|
|
|
it("string", async () => {
|
|
const provider = ConfigProvider.fromUnknown({ a: "value" })
|
|
await assertSuccess(Config.string("a"), provider, "value")
|
|
await assertFailure(
|
|
Config.string("b"),
|
|
provider,
|
|
`Expected string, got undefined
|
|
at ["b"]`
|
|
)
|
|
})
|
|
|
|
it("nonEmptyString", async () => {
|
|
const provider = ConfigProvider.fromUnknown({ a: "value", b: "" }, { preserveEmptyStrings: true })
|
|
await assertSuccess(Config.nonEmptyString("a"), provider, "value")
|
|
await assertFailure(
|
|
Config.nonEmptyString("b"),
|
|
provider,
|
|
`Expected a value with a length of at least 1, got ""
|
|
at ["b"]`
|
|
)
|
|
})
|
|
|
|
it("number", async () => {
|
|
const provider = ConfigProvider.fromUnknown({ a: "1", c: "c", d: "Infinity" })
|
|
await assertSuccess(Config.number("a"), provider, 1)
|
|
await assertSuccess(Config.number("d"), provider, Infinity)
|
|
await assertFailure(
|
|
Config.number("b"),
|
|
provider,
|
|
`Expected string | "Infinity" | "-Infinity" | "NaN", got undefined
|
|
at ["b"]`
|
|
)
|
|
})
|
|
|
|
it("finite", async () => {
|
|
const provider = ConfigProvider.fromUnknown({ a: "1", b: "a", c: "Infinity" })
|
|
await assertSuccess(Config.finite("a"), provider, 1)
|
|
await assertFailure(
|
|
Config.finite("b"),
|
|
provider,
|
|
`Expected a string representing a finite number, got "a"
|
|
at ["b"]`
|
|
)
|
|
await assertFailure(
|
|
Config.finite("c"),
|
|
provider,
|
|
`Expected a string representing a finite number, got "Infinity"
|
|
at ["c"]`
|
|
)
|
|
})
|
|
|
|
it("int", async () => {
|
|
const provider = ConfigProvider.fromUnknown({ a: "1", b: "1.2" })
|
|
await assertSuccess(Config.int("a"), provider, 1)
|
|
await assertFailure(
|
|
Config.int("b"),
|
|
provider,
|
|
`Expected an integer, got 1.2
|
|
at ["b"]`
|
|
)
|
|
})
|
|
|
|
it("literal", async () => {
|
|
const provider = ConfigProvider.fromUnknown({ a: "L" })
|
|
await assertSuccess(Config.literal("L", "a"), provider, "L")
|
|
await assertFailure(
|
|
Config.literal("-", "a"),
|
|
provider,
|
|
`Expected "-", got "L"
|
|
at ["a"]`
|
|
)
|
|
})
|
|
|
|
it("literals", async () => {
|
|
const provider = ConfigProvider.fromUnknown({ a: "production", b: "staging" })
|
|
await assertSuccess(Config.literals(["development", "production"], "a"), provider, "production")
|
|
await assertFailure(
|
|
Config.literals(["development", "production"], "b"),
|
|
provider,
|
|
`Expected "development" | "production", got "staging"
|
|
at ["b"]`
|
|
)
|
|
})
|
|
|
|
it("literals (numbers)", async () => {
|
|
const provider = ConfigProvider.fromUnknown({ a: "1", b: "3" })
|
|
await assertSuccess(Config.literals([1, 2], "a"), provider, 1)
|
|
await assertFailure(
|
|
Config.literals([1, 2], "b"),
|
|
provider,
|
|
`Expected "1" | "2", got "3"
|
|
at ["b"]`
|
|
)
|
|
})
|
|
|
|
it("date", async () => {
|
|
const provider = ConfigProvider.fromUnknown({ a: "2021-01-01", b: "invalid" })
|
|
await assertSuccess(Config.date("a"), provider, new Date("2021-01-01"))
|
|
await assertFailure(
|
|
Config.date("b"),
|
|
provider,
|
|
`Expected a valid date, got Invalid Date
|
|
at ["b"]`
|
|
)
|
|
})
|
|
|
|
it("redacted", async () => {
|
|
const provider = ConfigProvider.fromUnknown({
|
|
a: "value"
|
|
})
|
|
|
|
await assertSuccess(Config.redacted("a"), provider, Redacted.make("value"))
|
|
await assertFailure(
|
|
Config.redacted("failure"),
|
|
provider,
|
|
`Invalid data <redacted>
|
|
at ["failure"]`
|
|
)
|
|
})
|
|
|
|
it("url", async () => {
|
|
const provider = ConfigProvider.fromUnknown({
|
|
a: "https://example.com"
|
|
})
|
|
|
|
await assertSuccess(Config.url("a"), provider, new URL("https://example.com"))
|
|
await assertFailure(
|
|
Config.url("failure"),
|
|
provider,
|
|
`Expected string, got undefined
|
|
at ["failure"]`
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("combinators", () => {
|
|
it("map", async () => {
|
|
const config = Config.schema(Schema.String)
|
|
|
|
await assertSuccess(
|
|
Config.map(config, (value) => value.toUpperCase()),
|
|
ConfigProvider.fromUnknown("value"),
|
|
"VALUE"
|
|
)
|
|
await assertSuccess(
|
|
pipe(config, Config.map((value) => value.toUpperCase())),
|
|
ConfigProvider.fromUnknown("value"),
|
|
"VALUE"
|
|
)
|
|
})
|
|
|
|
it("mapOrFail", async () => {
|
|
const config = Config.schema(Schema.String)
|
|
const f = (s: string) =>
|
|
s === ""
|
|
? Effect.fail(
|
|
new Config.ConfigError(
|
|
new Schema.SchemaError(new SchemaIssue.InvalidValue(Option.some(s), { message: "empty" }))
|
|
)
|
|
)
|
|
: Effect.succeed(s.toUpperCase())
|
|
|
|
await assertSuccess(
|
|
Config.mapOrFail(config, f),
|
|
ConfigProvider.fromUnknown("value"),
|
|
"VALUE"
|
|
)
|
|
await assertFailure(
|
|
Config.mapOrFail(config, f),
|
|
ConfigProvider.fromUnknown("", { preserveEmptyStrings: true }),
|
|
`empty`
|
|
)
|
|
})
|
|
|
|
it("orElse", async () => {
|
|
const config = Config.orElse(Config.string("a"), () => Config.finite("b"))
|
|
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "value" }),
|
|
"value"
|
|
)
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromUnknown({ b: "1" }),
|
|
1
|
|
)
|
|
})
|
|
|
|
describe("all", () => {
|
|
it("tuple", async () => {
|
|
const config = Config.all([Config.nonEmptyString("a"), Config.finite("b")])
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "a", b: "1" }), ["a", 1])
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "", b: "1" }, { preserveEmptyStrings: true }),
|
|
`Expected a value with a length of at least 1, got ""
|
|
at ["a"]`
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "a", b: "b" }),
|
|
`Expected a string representing a finite number, got "b"
|
|
at ["b"]`
|
|
)
|
|
})
|
|
|
|
it("iterable", async () => {
|
|
const config = Config.all(new Set([Config.nonEmptyString("a"), Config.finite("b")]))
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "a", b: "1" }), ["a", 1])
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "", b: "1" }, { preserveEmptyStrings: true }),
|
|
`Expected a value with a length of at least 1, got ""
|
|
at ["a"]`
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "a", b: "b" }),
|
|
`Expected a string representing a finite number, got "b"
|
|
at ["b"]`
|
|
)
|
|
})
|
|
|
|
it("struct", async () => {
|
|
const config = Config.all({ a: Config.nonEmptyString("b"), c: Config.finite("d") })
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ b: "b", d: "1" }), { a: "b", c: 1 })
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ b: "", d: "1" }, { preserveEmptyStrings: true }),
|
|
`Expected a value with a length of at least 1, got ""
|
|
at ["b"]`
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ b: "b", d: "b" }),
|
|
`Expected a string representing a finite number, got "b"
|
|
at ["d"]`
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("withDefault", () => {
|
|
it("value", async () => {
|
|
const defaultValue = 0
|
|
const config = Config.finite("a").pipe(Config.withDefault(defaultValue))
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "1" }), 1)
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({}), defaultValue)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "value" }),
|
|
`Expected a string representing a finite number, got "value"
|
|
at ["a"]`
|
|
)
|
|
})
|
|
|
|
it("redacted", async () => {
|
|
const defaultValue = Redacted.make("default")
|
|
const config = Config.redacted("a").pipe(Config.withDefault(defaultValue))
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "value" }), Redacted.make("value"))
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({}), defaultValue)
|
|
})
|
|
|
|
it("uses default for empty env strings", async () => {
|
|
const config = Config.string("a").pipe(Config.withDefault("default"))
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "" } }), "default")
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { a: "" }, preserveEmptyStrings: true }),
|
|
""
|
|
)
|
|
})
|
|
|
|
it("uses default for empty env numbers", async () => {
|
|
const config = Config.number("a").pipe(Config.withDefault(0))
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "" } }), 0)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { a: "" }, preserveEmptyStrings: true }),
|
|
`Expected a string representing a finite number, got ""
|
|
at ["a"]
|
|
Expected "Infinity" | "-Infinity" | "NaN", got ""
|
|
at ["a"]`
|
|
)
|
|
})
|
|
|
|
it("struct", async () => {
|
|
const defaultValue = { a: "a", c: 0 }
|
|
const config = Config.all({ a: Config.nonEmptyString("b"), c: Config.finite("d") }).pipe(
|
|
Config.withDefault(defaultValue)
|
|
)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ b: "b", d: "1" }), { a: "b", c: 1 })
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ b: "b" }), defaultValue)
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ d: "1" }), defaultValue)
|
|
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ b: "", d: "1" }, { preserveEmptyStrings: true }),
|
|
`Expected a value with a length of at least 1, got ""
|
|
at ["b"]`
|
|
)
|
|
})
|
|
|
|
it("does not recover from invalid union values", async () => {
|
|
const config = Config.logLevel("LOG_LEVEL").pipe(Config.withDefault("Info"))
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({}), "Info")
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ LOG_LEVEL: "debug" }),
|
|
`Expected "All" | "Fatal" | "Error" | "Warn" | "Info" | "Debug" | "Trace" | "None", got "debug"
|
|
at ["LOG_LEVEL"]`
|
|
)
|
|
})
|
|
|
|
it("does not recover from filter failures", async () => {
|
|
const schema = Schema.String.check(
|
|
Schema.makeFilter((s) =>
|
|
s === "a" ? undefined : new SchemaIssue.InvalidValue(Option.none(), { message: `must be "a"` })
|
|
)
|
|
)
|
|
const config = Config.schema(schema, "a").pipe(Config.withDefault("fallback"))
|
|
|
|
// missing key -> default
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({}), "fallback")
|
|
// valid present value -> parsed
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "a" }), "a")
|
|
// present value that fails the refinement must fail, not use the default
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "b" }),
|
|
`must be "a"
|
|
at ["a"]`
|
|
)
|
|
})
|
|
|
|
it("array", async () => {
|
|
const config = Config.schema(Schema.Array(Schema.String), "a").pipe(Config.withDefault(["default"]))
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "value" } }), ["value"])
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "" } }), ["default"])
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "" }, preserveEmptyStrings: true }), [])
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: {} }), ["default"])
|
|
})
|
|
|
|
it("schema containers", async () => {
|
|
const provider = ConfigProvider.fromEnv({ env: {} })
|
|
|
|
await assertSuccess(
|
|
Config.schema(Schema.Struct({ value: Schema.String }), "a").pipe(Config.withDefault({ value: "default" })),
|
|
provider,
|
|
{ value: "default" }
|
|
)
|
|
await assertSuccess(
|
|
Config.schema(Schema.Struct({ value: Schema.optionalKey(Schema.String) }), "a").pipe(
|
|
Config.withDefault({ value: "default" })
|
|
),
|
|
provider,
|
|
{ value: "default" }
|
|
)
|
|
await assertSuccess(
|
|
Config.schema(Schema.Struct({}), "a").pipe(Config.withDefault({ value: "default" })),
|
|
provider,
|
|
{ value: "default" }
|
|
)
|
|
await assertSuccess(
|
|
Config.schema(Schema.Record(Schema.String, Schema.String), "a").pipe(
|
|
Config.withDefault({ value: "default" })
|
|
),
|
|
provider,
|
|
{ value: "default" }
|
|
)
|
|
await assertSuccess(
|
|
Config.schema(Schema.Tuple([Schema.String]), "a").pipe(Config.withDefault(["default"])),
|
|
provider,
|
|
["default"]
|
|
)
|
|
await assertSuccess(
|
|
Config.schema(Schema.ReadonlySet(Schema.String), "a").pipe(Config.withDefault(new Set(["default"]))),
|
|
provider,
|
|
new Set(["default"])
|
|
)
|
|
await assertSuccess(
|
|
Config.schema(Schema.ReadonlyMap(Schema.String, Schema.String), "a").pipe(
|
|
Config.withDefault(new Map([["default", "value"]]))
|
|
),
|
|
provider,
|
|
new Map([["default", "value"]])
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("option", () => {
|
|
it("value", async () => {
|
|
const config = Config.finite("a").pipe(Config.option)
|
|
const stringConfig = Config.string("a").pipe(Config.option)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "1" }), Option.some(1))
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({}), Option.none())
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "" } }), Option.none())
|
|
await assertSuccess(
|
|
stringConfig,
|
|
ConfigProvider.fromEnv({ env: { a: "" }, preserveEmptyStrings: true }),
|
|
Option.some("")
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "value" }),
|
|
`Expected a string representing a finite number, got "value"
|
|
at ["a"]`
|
|
)
|
|
})
|
|
|
|
it("struct", async () => {
|
|
const config = Config.all({ a: Config.nonEmptyString("b"), c: Config.finite("d") }).pipe(
|
|
Config.option
|
|
)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ b: "b", d: "1" }), Option.some({ a: "b", c: 1 }))
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ b: "b" }), Option.none())
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ d: "1" }), Option.none())
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ b: "", d: "1" }), Option.none())
|
|
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ b: "", d: "1" }, { preserveEmptyStrings: true }),
|
|
`Expected a value with a length of at least 1, got ""
|
|
at ["b"]`
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("nested", () => {
|
|
describe("fromUnknown", () => {
|
|
it("nested", async () => {
|
|
const config = Config.string().pipe(Config.nested("a"))
|
|
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "value" }),
|
|
"value"
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({}),
|
|
`Expected string, got undefined
|
|
at ["a"]`
|
|
)
|
|
})
|
|
|
|
it("name + nested", async () => {
|
|
const config = Config.string("a").pipe(Config.nested("b"))
|
|
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromUnknown({ b: { a: "value" } }),
|
|
"value"
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({}),
|
|
`Expected string, got undefined
|
|
at ["b"]["a"]`
|
|
)
|
|
})
|
|
|
|
it("name + nested + nested", async () => {
|
|
const config = Config.string("a").pipe(Config.nested("b"), Config.nested("c"))
|
|
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromUnknown({ c: { b: { a: "value" } } }),
|
|
"value"
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ c: { b: {} } }),
|
|
`Expected string, got undefined
|
|
at ["c"]["b"]["a"]`
|
|
)
|
|
})
|
|
|
|
it("all", async () => {
|
|
const config = Config.all({
|
|
host: Config.string("host"),
|
|
port: Config.number("port")
|
|
}).pipe(Config.nested("database"))
|
|
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromUnknown({ database: { host: "localhost", port: "5432" } }),
|
|
{ host: "localhost", port: 5432 }
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({}),
|
|
`Expected string, got undefined
|
|
at ["database"]["host"]`
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("fromEnv", () => {
|
|
it("nested", async () => {
|
|
const config = Config.string().pipe(Config.nested("a"))
|
|
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { a: "value" } }),
|
|
"value"
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: {} }),
|
|
`Expected string, got undefined
|
|
at ["a"]`
|
|
)
|
|
})
|
|
|
|
it("name + nested", async () => {
|
|
const config = Config.string("a").pipe(Config.nested("b"))
|
|
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { "b_a": "value" } }),
|
|
"value"
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: {} }),
|
|
`Expected string, got undefined
|
|
at ["b"]["a"]`
|
|
)
|
|
})
|
|
|
|
it("name + nested + nested", async () => {
|
|
const config = Config.string("a").pipe(Config.nested("b"), Config.nested("c"))
|
|
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { "c_b_a": "value" } }),
|
|
"value"
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { "c_b": "value" } }),
|
|
`Expected string, got undefined
|
|
at ["c"]["b"]["a"]`
|
|
)
|
|
})
|
|
|
|
it("all", async () => {
|
|
const config = Config.all({
|
|
host: Config.string("host"),
|
|
port: Config.number("port")
|
|
}).pipe(Config.nested("database"))
|
|
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { "database_host": "localhost", "database_port": "5432" } }),
|
|
{ host: "localhost", port: 5432 }
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: {} }),
|
|
`Expected string, got undefined
|
|
at ["database"]["host"]`
|
|
)
|
|
})
|
|
|
|
it("config nested and provider nested compose lookup but not error paths", async () => {
|
|
const config = Config.string("host").pipe(Config.nested("database"))
|
|
const provider = ConfigProvider.fromEnv({
|
|
env: { app_database_host: "localhost" }
|
|
}).pipe(ConfigProvider.nested("app"))
|
|
|
|
await assertSuccess(config, provider, "localhost")
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: {} }).pipe(ConfigProvider.nested("app")),
|
|
`Expected string, got undefined
|
|
at ["database"]["host"]`
|
|
)
|
|
})
|
|
|
|
it("provider nested over orElse keeps the logical error path", async () => {
|
|
const provider = ConfigProvider.fromEnv({ env: { app_port: "abc" } }).pipe(
|
|
ConfigProvider.orElse(ConfigProvider.fromEnv({ env: {} })),
|
|
ConfigProvider.nested("app")
|
|
)
|
|
|
|
await assertFailure(
|
|
Config.number("port"),
|
|
provider,
|
|
`Expected a string representing a finite number, got "abc"
|
|
at ["port"]
|
|
Expected "Infinity" | "-Infinity" | "NaN", got "abc"
|
|
at ["port"]`
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("unwrap", () => {
|
|
it("plain object", async () => {
|
|
const config = Config.unwrap({
|
|
a: Config.schema(Schema.String, "a2")
|
|
})
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a2: "value" }), { a: "value" })
|
|
})
|
|
|
|
it("nested", async () => {
|
|
const config = Config.unwrap({
|
|
a: {
|
|
b: Config.schema(Schema.String, "b2")
|
|
}
|
|
})
|
|
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromUnknown({ b2: "value" }),
|
|
{ a: { b: "value" } }
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("Config built-in schemas", () => {
|
|
it("Boolean", async () => {
|
|
const provider = ConfigProvider.fromUnknown({
|
|
a: "true",
|
|
b: "false",
|
|
c: "yes",
|
|
d: "no",
|
|
e: "on",
|
|
f: "off",
|
|
g: "1",
|
|
h: "0",
|
|
i: "y",
|
|
j: "n",
|
|
failure: "value"
|
|
})
|
|
|
|
await assertSuccess(Config.boolean("a"), provider, true)
|
|
await assertSuccess(Config.boolean("b"), provider, false)
|
|
await assertSuccess(Config.boolean("c"), provider, true)
|
|
await assertSuccess(Config.boolean("d"), provider, false)
|
|
await assertSuccess(Config.boolean("e"), provider, true)
|
|
await assertSuccess(Config.boolean("f"), provider, false)
|
|
await assertSuccess(Config.boolean("g"), provider, true)
|
|
await assertSuccess(Config.boolean("h"), provider, false)
|
|
await assertSuccess(Config.boolean("i"), provider, true)
|
|
await assertSuccess(Config.boolean("j"), provider, false)
|
|
await assertFailure(
|
|
Config.boolean("failure"),
|
|
provider,
|
|
`Expected "true" | "yes" | "on" | "1" | "y" | "false" | "no" | "off" | "0" | "n", got "value"
|
|
at ["failure"]`
|
|
)
|
|
})
|
|
|
|
it("Duration", async () => {
|
|
const provider = ConfigProvider.fromUnknown({
|
|
a: "1000 millis",
|
|
b: "1 second",
|
|
c: "Infinity",
|
|
d: "-Infinity",
|
|
failure: "value"
|
|
})
|
|
|
|
await assertSuccess(Config.duration("a"), provider, Duration.millis(1000))
|
|
await assertSuccess(Config.duration("b"), provider, Duration.seconds(1))
|
|
await assertSuccess(Config.duration("c"), provider, Duration.infinity)
|
|
await assertSuccess(Config.duration("d"), provider, Duration.negativeInfinity)
|
|
await assertFailure(
|
|
Config.duration("failure"),
|
|
provider,
|
|
`Invalid Duration string: value
|
|
at ["failure"]`
|
|
)
|
|
})
|
|
|
|
it("Port", async () => {
|
|
const provider = ConfigProvider.fromUnknown({
|
|
a: "8080",
|
|
failure: "-1"
|
|
})
|
|
|
|
await assertSuccess(Config.port("a"), provider, 8080)
|
|
await assertFailure(
|
|
Config.port("failure"),
|
|
provider,
|
|
`Expected a value between 1 and 65535, got -1
|
|
at ["failure"]`
|
|
)
|
|
})
|
|
|
|
it("LogLevel / logLevel", async () => {
|
|
const provider = ConfigProvider.fromUnknown({
|
|
a: "Info",
|
|
failure_1: "info",
|
|
failure_2: "value"
|
|
})
|
|
|
|
await assertSuccess(Config.logLevel("a"), provider, "Info")
|
|
await assertFailure(
|
|
Config.logLevel("failure_1"),
|
|
provider,
|
|
`Expected "All" | "Fatal" | "Error" | "Warn" | "Info" | "Debug" | "Trace" | "None", got "info"
|
|
at ["failure_1"]`
|
|
)
|
|
await assertFailure(
|
|
Config.logLevel("failure_2"),
|
|
provider,
|
|
`Expected "All" | "Fatal" | "Error" | "Warn" | "Info" | "Debug" | "Trace" | "None", got "value"
|
|
at ["failure_2"]`
|
|
)
|
|
})
|
|
|
|
describe("Record", () => {
|
|
it("from record", async () => {
|
|
const schema = Config.Record(Schema.String, Schema.String)
|
|
const config = Config.schema(schema, "OTEL_RESOURCE_ATTRIBUTES")
|
|
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromUnknown({
|
|
OTEL_RESOURCE_ATTRIBUTES: {
|
|
"service.name": "my-service",
|
|
"service.version": "1.0.0",
|
|
"custom.attribute": "value"
|
|
}
|
|
}),
|
|
{
|
|
"service.name": "my-service",
|
|
"service.version": "1.0.0",
|
|
"custom.attribute": "value"
|
|
}
|
|
)
|
|
})
|
|
|
|
it("from string", async () => {
|
|
const schema = Config.Record(Schema.String, Schema.String)
|
|
const config = Config.schema(schema, "OTEL_RESOURCE_ATTRIBUTES")
|
|
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromEnv({
|
|
env: {
|
|
OTEL_RESOURCE_ATTRIBUTES: "service.name=my-service,service.version=1.0.0,custom.attribute=value"
|
|
}
|
|
}),
|
|
{
|
|
"service.name": "my-service",
|
|
"service.version": "1.0.0",
|
|
"custom.attribute": "value"
|
|
}
|
|
)
|
|
})
|
|
|
|
it("options", async () => {
|
|
const schema = Config.Record(Schema.String, Schema.String, { separator: "&", keyValueSeparator: "==" })
|
|
const config = Config.schema(schema, "OTEL_RESOURCE_ATTRIBUTES")
|
|
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromEnv({
|
|
env: {
|
|
OTEL_RESOURCE_ATTRIBUTES: "service.name==my-service&service.version==1.0.0&custom.attribute==value"
|
|
}
|
|
}),
|
|
{
|
|
"service.name": "my-service",
|
|
"service.version": "1.0.0",
|
|
"custom.attribute": "value"
|
|
}
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("fromEnv", () => {
|
|
it("path argument", async () => {
|
|
await assertSuccess(
|
|
Config.schema(Schema.String, "a"),
|
|
ConfigProvider.fromEnv({ env: { a: "value" } }),
|
|
"value"
|
|
)
|
|
await assertSuccess(
|
|
Config.schema(Schema.String, ["a", "b"]),
|
|
ConfigProvider.fromEnv({ env: { "a_b": "value" } }),
|
|
"value"
|
|
)
|
|
await assertSuccess(
|
|
Config.schema(Schema.UndefinedOr(Schema.String)),
|
|
ConfigProvider.fromEnv({ env: {} }),
|
|
undefined
|
|
)
|
|
await assertSuccess(
|
|
Config.schema(Schema.UndefinedOr(Schema.String), "a"),
|
|
ConfigProvider.fromEnv({ env: {} }),
|
|
undefined
|
|
)
|
|
})
|
|
|
|
describe("leafs and containers", () => {
|
|
it("node can be both leaf and object", async () => {
|
|
const schema = Schema.Struct({ a: Schema.Number })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1", "a_b": "2" } }), { a: 1 })
|
|
})
|
|
|
|
it("node can be both leaf and array", async () => {
|
|
const schema = Schema.Struct({ a: Schema.Number })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1", "a_0": "2" } }), { a: 1 })
|
|
})
|
|
|
|
it("if a node can be both object and array, it should be an object", async () => {
|
|
const schema = Schema.Struct({ a: Schema.Struct({ b: Schema.Number }) })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1", "a_b": "2", "a_0": "3" } }), {
|
|
a: { b: 2 }
|
|
})
|
|
})
|
|
})
|
|
|
|
it("Null", async () => {
|
|
const schema = Schema.Null
|
|
const config = Config.schema(schema, "a")
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "null" } }), null)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: {} }),
|
|
`Expected "null", got undefined
|
|
at ["a"]`
|
|
)
|
|
})
|
|
|
|
it("String", async () => {
|
|
const schema = Schema.String
|
|
const config = Config.schema(schema, "a")
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "a" } }), "a")
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: {} }),
|
|
`Expected string, got undefined
|
|
at ["a"]`
|
|
)
|
|
})
|
|
|
|
it("Number", async () => {
|
|
const schema = Schema.Number
|
|
const config = Config.schema(schema, "a")
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1" } }), 1)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: {} }),
|
|
`Expected string | "Infinity" | "-Infinity" | "NaN", got undefined
|
|
at ["a"]`
|
|
)
|
|
})
|
|
|
|
it("Finite", async () => {
|
|
const schema = Schema.Finite
|
|
const config = Config.schema(schema, "a")
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1" } }), 1)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: {} }),
|
|
`Expected string, got undefined
|
|
at ["a"]`
|
|
)
|
|
})
|
|
|
|
it("Int", async () => {
|
|
const schema = Schema.Int
|
|
const config = Config.schema(schema, "a")
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1" } }), 1)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: {} }),
|
|
`Expected string, got undefined
|
|
at ["a"]`
|
|
)
|
|
})
|
|
|
|
it("Boolean", async () => {
|
|
const schema = Schema.Boolean
|
|
const config = Config.schema(schema, "a")
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "true" } }), true)
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "false" } }), false)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: {} }),
|
|
`Expected "true" | "false", got undefined
|
|
at ["a"]`
|
|
)
|
|
})
|
|
|
|
describe("Struct", () => {
|
|
it("required properties", async () => {
|
|
const schema = Schema.Struct({ a: Schema.Number })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1" } }), { a: 1 })
|
|
})
|
|
|
|
it("optionalKey properties", async () => {
|
|
const schema = Schema.Struct({ a: Schema.optionalKey(Schema.Number) })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1" } }), { a: 1 })
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: {} }), {})
|
|
})
|
|
|
|
it("optional properties", async () => {
|
|
const config = Config.schema(
|
|
Schema.Struct({ a: Schema.optional(Schema.Number) })
|
|
)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1" } }), { a: 1 })
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: {} }), {})
|
|
})
|
|
|
|
it("literal property", async () => {
|
|
const schema = Schema.Struct({ a: Schema.Literals(["b", "c"]) })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "b" } }), { a: "b" })
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "c" } }), { a: "c" })
|
|
})
|
|
|
|
it("array property", async () => {
|
|
const schema = Schema.Struct({ a: Schema.Array(Schema.Number) })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "" }, preserveEmptyStrings: true }), { a: [] })
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1" } }), { a: [1] })
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a_0: "1" } }), { a: [1] })
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a_0: "1", a_1: "2" } }), { a: [1, 2] })
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1", a_0: "2" } }), { a: [1] })
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: {} }),
|
|
`Missing key
|
|
at ["a"]`
|
|
)
|
|
})
|
|
})
|
|
|
|
it("Record(String, Finite)", async () => {
|
|
const schema = Schema.Record(Schema.String, Schema.Finite)
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1" } }), { a: 1 })
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1", b: "2" } }), { a: 1, b: 2 })
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { a: "1", b: "value" } }),
|
|
`Expected a string representing a finite number, got "value"
|
|
at ["b"]`
|
|
)
|
|
})
|
|
|
|
describe("Tuple", () => {
|
|
it("empty", async () => {
|
|
const schema = Schema.Struct({ a: Schema.Tuple([]) })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "" }, preserveEmptyStrings: true }), { a: [] })
|
|
})
|
|
|
|
it("ensure array", async () => {
|
|
const schema = Schema.Struct({ a: Schema.Tuple([Schema.Number]) })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1" } }), { a: [1] })
|
|
})
|
|
|
|
it("required elements", async () => {
|
|
const schema = Schema.Struct({ a: Schema.Tuple([Schema.String, Schema.Finite]) })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a_0: "a", a_1: "2" } }), { a: ["a", 2] })
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { a: "a" } }),
|
|
`Missing key
|
|
at ["a"][1]`
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { a_0: "a", a_1: "value" } }),
|
|
`Expected a string representing a finite number, got "value"
|
|
at ["a"][1]`
|
|
)
|
|
})
|
|
})
|
|
|
|
it("Array(Finite)", async () => {
|
|
const schema = Schema.Struct({ a: Schema.Array(Schema.Finite) })
|
|
const config = Config.schema(schema)
|
|
|
|
// ensure array
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1,2,3" } }), { a: [1, 2, 3] })
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a_0: "1", a_1: "2" } }), { a: [1, 2] })
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { a: "a", a_0: "1" } }),
|
|
`Expected a string representing a finite number, got "a"
|
|
at ["a"][0]`
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { a_0: "1", a_2: "2" } }),
|
|
`Expected string, got undefined
|
|
at ["a"][1]`
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { a_0: "1", a_1: "value" } }),
|
|
`Expected a string representing a finite number, got "value"
|
|
at ["a"][1]`
|
|
)
|
|
})
|
|
|
|
describe("Union", () => {
|
|
describe("Literals", () => {
|
|
it("string", async () => {
|
|
const schema = Schema.Struct({ a: Schema.Literals(["a", "b"]) })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "a" } }), { a: "a" })
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "b" } }), { a: "b" })
|
|
})
|
|
})
|
|
|
|
it("inclusive", async () => {
|
|
const schema = Schema.Union([
|
|
Schema.Struct({ a: Schema.String }),
|
|
Schema.Struct({ b: Schema.Number })
|
|
])
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "a" } }), { a: "a" })
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { b: "1" } }), { b: 1 })
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "a", b: "1" } }), { a: "a" })
|
|
})
|
|
|
|
it("exclusive", async () => {
|
|
const schema = Schema.Union([
|
|
Schema.Struct({ a: Schema.String }),
|
|
Schema.Struct({ b: Schema.Number })
|
|
], { mode: "oneOf" })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "a" } }), { a: "a" })
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { b: "1" } }), { b: 1 })
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { a: "a", b: "1" } }),
|
|
`Expected exactly one member to match the input {"a":"a","b":"1"}`
|
|
)
|
|
})
|
|
|
|
it("number | string", async () => {
|
|
const schema = Schema.Union([Schema.Number, Schema.String])
|
|
const config = Config.schema(schema, "a")
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1" } }), 1)
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "a" } }), "a")
|
|
})
|
|
|
|
it("string | number", async () => {
|
|
const schema = Schema.Union([Schema.String, Schema.Number])
|
|
const config = Config.schema(schema, "a")
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1" } }), 1)
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "a" } }), "a")
|
|
})
|
|
})
|
|
|
|
it("Suspend", async () => {
|
|
interface A {
|
|
readonly a: string
|
|
readonly as: ReadonlyArray<A>
|
|
}
|
|
const schema = Schema.Struct({
|
|
a: Schema.String,
|
|
as: Schema.Array(Schema.suspend((): Schema.Codec<A> => schema))
|
|
})
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { a: "1", as: "" }, preserveEmptyStrings: true }),
|
|
{ a: "1", as: [] }
|
|
)
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { a: "1", as_0_a: "2", as_0_as: "" }, preserveEmptyStrings: true }),
|
|
{
|
|
a: "1",
|
|
as: [{ a: "2", as: [] }]
|
|
}
|
|
)
|
|
})
|
|
|
|
it("Redacted(Int)", async () => {
|
|
const schema = Schema.Redacted(Schema.Int)
|
|
const config = Config.schema(schema, "a")
|
|
|
|
await assertSuccess(config, ConfigProvider.fromEnv({ env: { a: "1" } }), Redacted.make(1))
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: {} }),
|
|
`Invalid data <redacted>
|
|
at ["a"]`
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromEnv({ env: { a: "1.1" } }),
|
|
`Invalid data <redacted>
|
|
at ["a"]`
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("fromUnknown", () => {
|
|
it("path argument", async () => {
|
|
await assertSuccess(
|
|
Config.schema(Schema.String, []),
|
|
ConfigProvider.fromUnknown("value"),
|
|
"value"
|
|
)
|
|
await assertSuccess(
|
|
Config.schema(Schema.String, "a"),
|
|
ConfigProvider.fromUnknown({ a: "value" }),
|
|
"value"
|
|
)
|
|
await assertSuccess(
|
|
Config.schema(Schema.String, ["a", "b"]),
|
|
ConfigProvider.fromUnknown({ a: { b: "value" } }),
|
|
"value"
|
|
)
|
|
})
|
|
|
|
it("Undefined", async () => {
|
|
const schema = Schema.Undefined
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown(undefined), undefined)
|
|
await assertFailure(config, ConfigProvider.fromUnknown("a"), `Expected undefined, got "a"`)
|
|
})
|
|
|
|
it("Null", async () => {
|
|
const schema = Schema.Null
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown("null"), null)
|
|
await assertFailure(config, ConfigProvider.fromUnknown("a"), `Expected "null", got "a"`)
|
|
})
|
|
|
|
it("String", async () => {
|
|
const schema = Schema.String
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown("value"), "value")
|
|
await assertFailure(config, ConfigProvider.fromUnknown({}), `Expected string, got undefined`)
|
|
})
|
|
|
|
it("Number", async () => {
|
|
const schema = Schema.Number
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown("1"), 1)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown("a"),
|
|
`Expected a string representing a finite number, got "a"
|
|
Expected "Infinity" | "-Infinity" | "NaN", got "a"`
|
|
)
|
|
})
|
|
|
|
it("Finite", async () => {
|
|
const schema = Schema.Finite
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown("1"), 1)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown("a"),
|
|
`Expected a string representing a finite number, got "a"`
|
|
)
|
|
})
|
|
|
|
it("Int", async () => {
|
|
const schema = Schema.Int
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown("1"), 1)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown("a"),
|
|
`Expected a string representing a finite number, got "a"`
|
|
)
|
|
})
|
|
|
|
it("Boolean", async () => {
|
|
const schema = Schema.Boolean
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown("true"), true)
|
|
await assertSuccess(config, ConfigProvider.fromUnknown("false"), false)
|
|
await assertFailure(config, ConfigProvider.fromUnknown("a"), `Expected "true" | "false", got "a"`)
|
|
})
|
|
|
|
describe("Struct", () => {
|
|
it("required properties", async () => {
|
|
const schema = Schema.Struct({ a: Schema.Finite })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "1" }), { a: 1 })
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({}),
|
|
`Missing key
|
|
at ["a"]`
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "value" }),
|
|
`Expected a string representing a finite number, got "value"
|
|
at ["a"]`
|
|
)
|
|
})
|
|
|
|
it("optionalKey properties", async () => {
|
|
const schema = Schema.Struct({ a: Schema.optionalKey(Schema.Number) })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "1" }), { a: 1 })
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({}), {})
|
|
})
|
|
|
|
it("optional properties", async () => {
|
|
const config = Config.schema(
|
|
Schema.Struct({ a: Schema.optional(Schema.Number) })
|
|
)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "1" }), { a: 1 })
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({}), {})
|
|
})
|
|
|
|
it("literal property", async () => {
|
|
const schema = Schema.Struct({ a: Schema.Literals(["b", "c"]) })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "b" }), { a: "b" })
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "c" }), { a: "c" })
|
|
})
|
|
|
|
it("array property", async () => {
|
|
const schema = Schema.Struct({ a: Schema.Array(Schema.Number) })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "" }),
|
|
`Missing key
|
|
at ["a"]`
|
|
)
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "" }, { preserveEmptyStrings: true }),
|
|
{ a: [] }
|
|
)
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "1" }), { a: [1] })
|
|
})
|
|
})
|
|
|
|
it("Record(String, Finite)", async () => {
|
|
const schema = Schema.Record(Schema.String, Schema.Finite)
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "1" }), { a: 1 })
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "1", b: "2" }), { a: 1, b: 2 })
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "1", b: "value" }),
|
|
`Expected a string representing a finite number, got "value"
|
|
at ["b"]`
|
|
)
|
|
})
|
|
|
|
describe("Tuple", () => {
|
|
it("ensure array", async () => {
|
|
const schema = Schema.Tuple([Schema.Number])
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown(["1"]), [1])
|
|
await assertSuccess(config, ConfigProvider.fromUnknown("1"), [1])
|
|
})
|
|
|
|
it("required elements", async () => {
|
|
const schema = Schema.Tuple([Schema.String, Schema.Finite])
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown(["a", "2"]), ["a", 2])
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown(["a"]),
|
|
`Missing key
|
|
at [1]`
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown(["a", "value"]),
|
|
`Expected a string representing a finite number, got "value"
|
|
at [1]`
|
|
)
|
|
})
|
|
})
|
|
|
|
it("Array(Finite)", async () => {
|
|
const schema = Schema.Array(Schema.Finite)
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown(["1"]), [1])
|
|
// ensure array
|
|
await assertSuccess(config, ConfigProvider.fromUnknown("1"), [1])
|
|
await assertSuccess(config, ConfigProvider.fromUnknown(["1", "2"]), [1, 2])
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown(["1", "value"]),
|
|
`Expected a string representing a finite number, got "value"
|
|
at [1]`
|
|
)
|
|
})
|
|
|
|
describe("Union", () => {
|
|
describe("Literals", () => {
|
|
it("string", async () => {
|
|
const schema = Schema.Literals(["a", "b"])
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown("a"), "a")
|
|
await assertSuccess(config, ConfigProvider.fromUnknown("b"), "b")
|
|
})
|
|
})
|
|
|
|
it("inclusive", async () => {
|
|
const schema = Schema.Union([
|
|
Schema.Struct({ a: Schema.String }),
|
|
Schema.Struct({ b: Schema.Number })
|
|
])
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "a" }), { a: "a" })
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ b: "1" }), { b: 1 })
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "a", b: "1" }), { a: "a" })
|
|
})
|
|
|
|
it("exclusive", async () => {
|
|
const schema = Schema.Union([
|
|
Schema.Struct({ a: Schema.String }),
|
|
Schema.Struct({ b: Schema.Number })
|
|
], { mode: "oneOf" })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "a" }), { a: "a" })
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ b: "1" }), { b: 1 })
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "a", b: "1" }),
|
|
`Expected exactly one member to match the input {"a":"a","b":"1"}`
|
|
)
|
|
})
|
|
|
|
it("number | string", async () => {
|
|
const schema = Schema.Union([Schema.Number, Schema.String])
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown("1"), 1)
|
|
await assertSuccess(config, ConfigProvider.fromUnknown("a"), "a")
|
|
})
|
|
|
|
it("string | number", async () => {
|
|
const schema = Schema.Union([Schema.String, Schema.Number])
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown("1"), 1)
|
|
await assertSuccess(config, ConfigProvider.fromUnknown("a"), "a")
|
|
})
|
|
})
|
|
|
|
it("Suspend", async () => {
|
|
interface A {
|
|
readonly a: string
|
|
readonly as: ReadonlyArray<A>
|
|
}
|
|
const schema = Schema.Struct({
|
|
a: Schema.String,
|
|
as: Schema.Array(Schema.suspend((): Schema.Codec<A> => schema))
|
|
})
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "1", as: [] }), { a: "1", as: [] })
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "1", as: [{ a: "2", as: [] }] }), {
|
|
a: "1",
|
|
as: [{ a: "2", as: [] }]
|
|
})
|
|
})
|
|
|
|
it("Redacted(Int)", async () => {
|
|
const schema = Schema.Struct({ a: Schema.Redacted(Schema.Int) })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(config, ConfigProvider.fromUnknown({ a: "1" }), { a: Redacted.make(1) })
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({}),
|
|
`Missing key
|
|
at ["a"]`
|
|
)
|
|
await assertFailure(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "1.1" }),
|
|
`Invalid data <redacted>
|
|
at ["a"]`
|
|
)
|
|
})
|
|
|
|
it("URL", async () => {
|
|
const schema = Schema.Struct({ a: Schema.URL })
|
|
const config = Config.schema(schema)
|
|
|
|
await assertSuccess(
|
|
config,
|
|
ConfigProvider.fromUnknown({ a: "https://example.com" }),
|
|
{ a: new URL("https://example.com") }
|
|
)
|
|
})
|
|
})
|
|
})
|