diff --git a/.changeset/hot-dryers-push.md b/.changeset/hot-dryers-push.md new file mode 100644 index 0000000000..a0e58c99e2 --- /dev/null +++ b/.changeset/hot-dryers-push.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add Config.literal diff --git a/packages/effect/src/Config.ts b/packages/effect/src/Config.ts index 76a6154e3f..b0ad9554e1 100644 --- a/packages/effect/src/Config.ts +++ b/packages/effect/src/Config.ts @@ -151,6 +151,21 @@ export const number: (name?: string) => Config = internal.number */ export const integer: (name?: string) => Config = internal.integer +/** + * Constructs a config for a literal value. + * + * @example + * import { Config } from "effect" + * + * const config = Config.literal("http", "https")("PROTOCOL") + * + * @since 2.0.0 + * @category constructors + */ +export const literal: >(...literals: Literals) => ( + name?: string +) => Config = internal.literal + /** * Constructs a config for a `LogLevel` value. * diff --git a/packages/effect/src/internal/config.ts b/packages/effect/src/internal/config.ts index 5cd0864f82..b68ecf491c 100644 --- a/packages/effect/src/internal/config.ts +++ b/packages/effect/src/internal/config.ts @@ -255,6 +255,30 @@ export const integer = (name?: string): Config.Config => { return name === undefined ? config : nested(config, name) } +/** @internal */ +export type LiteralValue = string | number | boolean | null | bigint + +/** @internal */ +export const literal = >(...literals: Literals) => +( + name?: string +): Config.Config => { + const valuesString = literals.map(String).join(", ") + const config = primitive(`one of (${valuesString})`, (text) => { + const found = literals.find((value) => String(value) === text) + if (found === undefined) { + return Either.left( + configError.InvalidData( + [], + `Expected one of (${valuesString}) but received ${text}` + ) + ) + } + return Either.right(found) + }) + return name === undefined ? config : nested(config, name) +} + /** @internal */ export const logLevel = (name?: string): Config.Config => { const config = mapOrFail(string(), (value) => { diff --git a/packages/effect/test/Config.test.ts b/packages/effect/test/Config.test.ts index 1b9a6d2fad..d75819ea66 100644 --- a/packages/effect/test/Config.test.ts +++ b/packages/effect/test/Config.test.ts @@ -93,6 +93,35 @@ describe("Config", () => { }) }) + describe("literal", () => { + it("name = undefined", () => { + const config = Config.array(Config.literal("a", "b")(), "ITEMS") + assertSuccess(config, [["ITEMS", "a"]], ["a"]) + assertFailure( + config, + [["ITEMS", "value"]], + ConfigError.InvalidData(["ITEMS"], "Expected one of (a, b) but received value") + ) + }) + + it("name != undefined", () => { + const config = Config.literal("a", 0, -0.3, BigInt(5), false, null)("LITERAL") + assertSuccess(config, [["LITERAL", "a"]], "a") + assertSuccess(config, [["LITERAL", "0"]], 0) + assertSuccess(config, [["LITERAL", "-0.3"]], -0.3) + assertSuccess(config, [["LITERAL", "5"]], BigInt(5)) + assertSuccess(config, [["LITERAL", "false"]], false) + assertSuccess(config, [["LITERAL", "null"]], null) + + assertFailure(config, [], ConfigError.MissingData(["LITERAL"], "Expected LITERAL to exist in the provided map")) + assertFailure( + config, + [["LITERAL", "value"]], + ConfigError.InvalidData(["LITERAL"], "Expected one of (a, 0, -0.3, 5, false, null) but received value") + ) + }) + }) + describe("date", () => { it("name = undefined", () => { const config = Config.date()