diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..0f705fb --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,2 @@ +[test] +preload = "./test/init.ts" diff --git a/package.json b/package.json index 1c2a704..b7ade4c 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,9 @@ "test": "bun test", "typecheck": "tsc --noEmit" }, - "dependencies": { - "log": "git+https://git.pband.ch/typescript/log.git" + "log": "git+https://git.pband.ch/typescript/log.git", + "yup": "^1.7.0" }, "devDependencies": { "@eslint/js": "^9.35.0", @@ -17,15 +17,15 @@ "eslint": "^9.35.0", "globals": "^16.4.0", "jiti": "^2.5.1", + "logger-console": "git+https://git.pband.ch/typescript/logger-console.git", "prettier": "^3.6.2", + "tmp": "^0.2.5", "typescript": "^5.9.2", "typescript-eslint": "^8.43.0" }, - "name": "config", "description": "Parse configuration from env or files", "version": "1.0.0", - "author": "Pascal Perrenoud ", "type": "module", "main": "./src/index.ts", diff --git a/src/helpers.ts b/src/helpers.ts index b73984f..088127f 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1 +1 @@ -export type Ok = {ok: true, data: T} | {ok: false} +export type Ok = {ok: true, data: T} | {ok?: false} diff --git a/src/index.ts b/src/index.ts index 3fbfcd6..7bedb1c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,27 @@ import logger from 'log' +import * as yup from 'yup' -import {Ok} from './helpers' +import * as parsing from './parsing' +import type {Ok} from './helpers' -// TODO : re-export types used to describe schema +export * as yup from 'yup' const log = logger('config') -export async function parse(schema: S): Promise> { - log.info("Parse configuration from env") +export async function parse>(schema: yup.ObjectSchema): Promise> { + log.info("Parse from env") - // TODO : Read config from env + log.trace("Start parsing") + const config = await parsing.object(schema) + if (!config.ok) return config - // TODO : maybe double check config + log.trace("double-check") + const res = await schema.isValid(config.data, {strict: true}) + if (!res) { + log.error("Double-check failed") + log.debug('Config', config.data) + return {ok: false} + } - return {ok: false} + return config } diff --git a/src/parsing.ts b/src/parsing.ts new file mode 100644 index 0000000..50a49d3 --- /dev/null +++ b/src/parsing.ts @@ -0,0 +1,62 @@ +import * as yup from 'yup' +import logger from 'log' + +import {read_env} from './env' +import type {Ok} from './helpers' + +const log = logger('config:parsing') + +export async function object>(schema: yup.ObjectSchema, base_name: string = ""): Promise> { + log.debug("Object") + if (base_name.length !== 0) base_name = base_name + "_" + + // @ts-expect-error Ugly hack with type S + const data: S = {} + + for (const key in schema.fields) { + const sub_key = base_name + key.toUpperCase() + + const sub_scheme = schema.fields[key] + let value: Ok + + // TODO : If array, add a transform + if (sub_scheme.describe().type === 'object') { + value = await object(sub_scheme, sub_key) + } else { + value = await generic(sub_scheme, sub_key) + } + + if (!value.ok) return value + if (value.data === undefined) continue + + data[key] = value.data + } + + return {ok: true, data} +} + +export async function generic(scheme: yup.AnySchema, key: string): Promise> { + log.debug('Generic', scheme.describe().type) + + const value = await read_env(key) + if (!value.ok) return value + + if (value.data === undefined) { + const def = scheme.getDefault() + if (def !== undefined) return {ok: true, data: def} + else if (scheme.spec.optional) return {ok: true, data: undefined} + + log.warn('Missing value for', key) + return {} + } + + if (!(await scheme.isValid(value.data))) { + log.warn('Invaid value for', key) + log.debug('Value:', value.data) + return {} + } + + const res = scheme.cast(value.data) + + return {ok: true, data: res} +}