From 95be9d8d4acb09ce64559f2e3e027c16eb1d5207 Mon Sep 17 00:00:00 2001 From: Pascal Perrenoud Date: Thu, 14 Aug 2025 17:31:04 +0200 Subject: [PATCH] Dump WIP --- index.ts | 16 +++++------ package.json | 5 ++-- src/read_type.ts | 62 +++++++++++++----------------------------- test/read_type.test.ts | 32 +++++++++------------- 4 files changed, 42 insertions(+), 73 deletions(-) diff --git a/index.ts b/index.ts index 60b65d2..f20cf2f 100644 --- a/index.ts +++ b/index.ts @@ -1,14 +1,13 @@ -import {type Static, type TObject, type TProperties} from '@sinclair/typebox' -import {Value} from '@sinclair/typebox/value' +import {z, type ZodObject, type ZodRawShape} from 'zod' import logger from 'log' import {read_object} from './src/read_type' const log = logger('config') -export {Type, type Static, type StaticDecode} from '@sinclair/typebox' // Re-export Type so users can describe config -export {Value} from '@sinclair/typebox/value' +// TODO : export {Type, type Static, type StaticDecode} from '@sinclair/typebox' // Re-export Type so users can describe config +// TODO : export {Value} from '@sinclair/typebox/value' -export async function parse(scheme: TObject): Promise> | null> { +export async function parse(scheme: ZodObject): Promise> | null> { log.info("Parse configuration from env") log.debug("Read configuration from env") @@ -17,12 +16,13 @@ export async function parse(scheme: TObject): Promise< // This check is kind of a duplicate, is it useful ? log.debug("Validate config against scheme") - const config_parsed = Value.Check(scheme, config.data) - if (config_parsed) { + const config_parsed = await scheme.safeParseAsync(config.data) + if (config_parsed.success) { log.trace("Config is valid") - return config.data + return config_parsed.data } else { log.warn("Invalid config, check failed") + log.debug("Error :", config_parsed.error) return null } } diff --git a/package.json b/package.json index d8e7139..09f2b4b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "dependencies": { "@sinclair/typebox": "^0.34.33", - "log": "git+https://git.pband.ch/typescript/log.git" + "log": "git+https://git.pband.ch/typescript/log.git", + "zod": "^3.25.60" }, "devDependencies": { "@types/bun": "^1.2.15", @@ -12,12 +13,10 @@ "tmp": "^0.2.3", "typescript": "^5.8.3" }, - "scripts": { "test": "bun test", "check": "tsc --noEmit" }, - "name": "config", "description": "Parse env to create a configuration from a description", "version": "1.0.0", diff --git a/src/read_type.ts b/src/read_type.ts index cc254a7..99d2ede 100644 --- a/src/read_type.ts +++ b/src/read_type.ts @@ -1,13 +1,4 @@ -import { - OptionalKind, - Type, - type Static, - type TDate, - type TObject, - type TProperties, - type TSchema, type TString -} from '@sinclair/typebox' -import {Value} from '@sinclair/typebox/value' +import z, {type $ZodObject, $ZodType} from 'zod/v4/core' import logger from 'log' import type {Ok} from './helpers' @@ -17,24 +8,23 @@ import {read_env} from './read_env' const log = logger('config:read_type') // Read each TypeBox type from env -export async function read_object(scheme: TObject, base_name: string = "") : Promise>>> { +export async function read_object(scheme: T, base_name: string = "") : Promise>> { log.debug("Read object from env") if (base_name.length !== 0) base_name = base_name + "_" - const data: Static> = Value.Create(scheme) + const data: z.infer = Object.create({}) - for (const key in scheme.properties) { + for (const key in scheme.shape) { const sub_key = base_name + key.toUpperCase() - - // Get sub-scheme - const sub_scheme = scheme.properties[key] + const sub_scheme = scheme.shape[key] + const sub_type = sub_scheme.def.type as string // Read value let value: Ok; - if (sub_scheme.type === 'object') { - value = await read_object(sub_scheme as unknown as TObject, sub_key) - } else if (sub_scheme.type === 'Date') { - value = await read_date(sub_scheme as unknown as TDate, sub_key) + if (sub_type === 'object') { + value = await read_object(sub_scheme as any, sub_key) + //} else if (sub_type === 'Date') { + //value = await read_date(sub_scheme as any, sub_key) } else { value = await read_type(sub_scheme, sub_key) } @@ -52,37 +42,23 @@ export async function read_object(scheme: TObject, bas return {ok: true, data} } -export async function read_type(scheme: T, name: string) : Promise | undefined>> { +export async function read_type(scheme: T, name: string) : Promise | undefined>> { let value = await read_env(name) if (!value.ok) return value - if (value.data === undefined && scheme[OptionalKind] !== undefined) return {ok: true, data: undefined} + if (value.data === undefined && scheme.isOptional()) return {ok: true, data: undefined} try { - const data = Value.Parse(scheme, value.data) - return {ok: true, data} + const data = await scheme.safeParseAsync(value.data) + if (data.success) return {ok: true, data: data.data} + else { + log.warn(`Parsing of '${name}' failed`) + log.debug('Error :', data.error) + return {ok: false} + } } catch (e) { log.warn(`Parsing of key '${name}' failed (invalid value : '${value.data}')`) log.debug('Error :', e) return {ok: false} } } - -export async function read_date(scheme: TDate, name: string): Promise | undefined>> { - let string_scheme: TString; - if (scheme[OptionalKind] !== undefined) string_scheme = Type.Optional(Type.String()) - else string_scheme = Type.String() - - const value = await read_type(string_scheme, name) - if (!value.ok) return value - if (value.data === undefined) return {ok: true, data: undefined} - - const data = new Date(value.data) - const checked = Value.Check(scheme, data) - if (!checked) { - log.warn(`Invalid date for '${name}' : ${value.data}`) - return {ok: false} - } - - return {ok: true, data} -} diff --git a/test/read_type.test.ts b/test/read_type.test.ts index 382dbf3..fd434c6 100644 --- a/test/read_type.test.ts +++ b/test/read_type.test.ts @@ -1,9 +1,10 @@ import {expect, test, describe, beforeEach, afterEach} from 'bun:test' -import {type TDate, type TSchema, Type} from "@sinclair/typebox" +import {type TSchema, Type} from "@sinclair/typebox" import {type FileResult, fileSync} from 'tmp' import * as fs from 'node:fs/promises' +import {z} from 'zod/v4' -import {read_date, read_object, read_type} from '../src/read_type' +import {read_object, read_type} from '../src/read_type' import {parse} from '..' beforeEach(() => process.env = {}) @@ -21,6 +22,16 @@ async function testing(value: string | undefined, scheme: T, } } +test('tweak', async () => { + const ts = '2025-06-11 09:36:12' + process.env['NAME'] = 'coucou' + process.env['TS'] = ts + process.env['PORT'] = '212' + const scheme = z.object({name: z.string(), port: z.number(), ts: z.date()}) + const res = await read_object(scheme) + expect(res).toEqual({ok: true, data: {name: 'coucou', port: 212, ts: new Date(ts)}}) +}) + // Most important function : read_object describe('Object', () => { test('basic', async () => { @@ -219,22 +230,5 @@ describe('Enum', () => { }) // Read specific types -describe('Date', () => { - async function testing(value: string | undefined, scheme: T, should_be_successful: boolean, data?: any) { - const key = 'DB_PORT' - if (value !== undefined) process.env[key] = value - - const res = await read_date(scheme, key) - - if (should_be_successful) { - expect(res).toEqual({ok: true, data}) - } else { - expect(res).toEqual({ok: false}) - } - } - - test('basic', () => testing('2025-06-08 22:40:17', Type.Date(), true, new Date('2025-06-08 22:40:17'))) - test('optional', () => testing(undefined, Type.Optional(Type.Date()), true)) -}) describe.todo('Array') describe.todo('Tuple') \ No newline at end of file