Skip to content

Commit

Permalink
Add support for Config module, closes #2346 (#2816)
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti committed May 22, 2024
1 parent f8038ca commit e376641
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/hungry-eyes-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/schema": patch
---

Add support for `Config` module, closes #2346
18 changes: 18 additions & 0 deletions packages/schema/src/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import * as boolean_ from "effect/Boolean"
import type { Brand } from "effect/Brand"
import * as cause_ from "effect/Cause"
import * as chunk_ from "effect/Chunk"
import * as config_ from "effect/Config"
import * as configError_ from "effect/ConfigError"
import * as data_ from "effect/Data"
import * as duration_ from "effect/Duration"
import * as Effect from "effect/Effect"
Expand Down Expand Up @@ -47,6 +49,7 @@ import * as util_ from "./internal/util.js"
import * as ParseResult from "./ParseResult.js"
import * as pretty_ from "./Pretty.js"
import type * as Serializable from "./Serializable.js"
import * as TreeFormatter from "./TreeFormatter.js"

/**
* @since 1.0.0
Expand Down Expand Up @@ -8056,3 +8059,18 @@ export class BooleanFromUnknown extends transform(
static override annotations: (annotations: Annotations.Schema<boolean>) => typeof BooleanFromUnknown = super
.annotations
}

/**
* @category Config validations
* @since 1.0.0
*/
export const Config = <A>(name: string, schema: Schema<A, string>): config_.Config<A> => {
const decodeEither_ = decodeEither(schema)
return config_.string(name).pipe(
config_.mapOrFail((a) =>
decodeEither_(a).pipe(
either_.mapLeft((error) => configError_.InvalidData([], TreeFormatter.formatErrorSync(error)))
)
)
)
}
59 changes: 59 additions & 0 deletions packages/schema/test/Schema/Config/Config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as Schema from "@effect/schema/Schema"
import type * as Config from "effect/Config"
import * as ConfigError from "effect/ConfigError"
import * as ConfigProvider from "effect/ConfigProvider"
import * as Effect from "effect/Effect"
import * as Exit from "effect/Exit"
import { describe, expect, it } from "vitest"

/**
* Asserts that loading a configuration with invalid data fails with the expected error.
*
* @param config - The configuration to load.
* @param map - The map of configuration values.
* @param error - The expected error.
*/
const assertFailure = <A>(
config: Config.Config<A>,
map: ReadonlyArray<readonly [string, string]>,
error: ConfigError.ConfigError
) => {
const configProvider = ConfigProvider.fromMap(new Map(map))
const result = Effect.runSync(Effect.exit(configProvider.load(config)))
expect(result).toStrictEqual(Exit.fail(error))
}

/**
* Asserts that loading a configuration with valid data succeeds and returns the expected value.
*
* @param config - The configuration to load.
* @param map - The map of configuration values.
* @param a - The expected value.
*/
const assertSuccess = <A>(
config: Config.Config<A>,
map: ReadonlyArray<readonly [string, string]>,
a: A
) => {
const configProvider = ConfigProvider.fromMap(new Map(map))
const result = Effect.runSync(Effect.exit(configProvider.load(config)))
expect(result).toStrictEqual(Exit.succeed(a))
}

describe("Config", () => {
it("should validate the configuration schema correctly", () => {
const config = Schema.Config("A", Schema.NonEmpty)
assertSuccess(config, [["A", "a"]], "a")
assertFailure(config, [], ConfigError.MissingData(["A"], `Expected A to exist in the provided map`))
assertFailure(
config,
[["A", ""]],
ConfigError.InvalidData(
["A"],
`NonEmpty
└─ Predicate refinement failure
└─ Expected NonEmpty (a non empty string), actual ""`
)
)
})
})

0 comments on commit e376641

Please sign in to comment.