Effect-TS / effect

An ecosystem of tools for building production-grade applications in TypeScript.

Home Page:https://effect.website

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Schema-based config descriptions

vecerek opened this issue · comments

What is the problem this feature would solve?

Right now, if there is a more complex config, for example a file containing some JSON configuration, the caller has to first load the config, then parse it:

import { Schema } from "@effect/schema"
import { Config, Effect } from "effect"

const MyJsonSchema = Schema.parseJson(
  Schema.Struct({
    a: Schema.String
  })
)

const parseJsonConfig = Schema.decode(MyJsonSchema, { errors: "all" })

Effect.gen(function* () {
  const jsonConfig = yield* Config.string("MY_JSON_CONFIG")
  const parsedConfig = yield* parseJsonConfig(jsonConfig)
  // ...
})

What is the feature you are proposing to solve the problem?

I'd like to have an API that combines the loading of the config with its parsing. Something like:

// config module

// for simplicity's sake, otherwise it could be a dual API
export declare const schema: <const C extends string, A>(
  name: C,
  schema: Schema.Schema<A, string>,
) => Config.Config<A>

and then at the call site:

import { Schema } from "@effect/schema"
import * as Config from "./config.js"

const MyJsonSchema = Schema.parseJson(
  Schema.Struct({
    a: Schema.String
  })
)

Effect.gen(function* () {
  const parsedConfig = yield* Config.schema("MY_JSON_CONFIG", MyJsonSchema)
  // ...
})

What alternatives have you considered?

No response

Here's a naive implementation:

import * as Schema from "@effect/schema/Schema"
import { formatErrorSync } from "@effect/schema/TreeFormatter"
import { Config, ConfigError, Either } from "effect"
import { flow } from "effect/Function"

export const schema = <const C extends string, A>(
  name: C,
  schema: Schema.Schema<A, string>,
): Config.Config<A> =>
  Config.string(name).pipe(
    Config.mapOrFail(
      flow(
        Schema.decodeEither(schema),
        Either.mapLeft((error) =>
          ConfigError.InvalidData([name], formatErrorSync(error)),
        ),
      ),
    ),
  )

There's already an open issue for this :P
#2346

Closing as duplicated of #2346