Skip to content

Commit

Permalink
fix: fix nested object schema type inference
Browse files Browse the repository at this point in the history
  • Loading branch information
ASafaeirad committed Apr 13, 2024
1 parent da8e0cc commit b4d9675
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 24 deletions.
39 changes: 39 additions & 0 deletions src/Config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Config } from './Config';
import type { Equals, Expect } from './types';

describe('Config', () => {
it('should parse nested object', () => {
const config = new Config({
s: Config.string(),
n: Config.number().require(),
foo: Config.object({
foo1: Config.string().require(),
foo2: Config.object({ foo3: Config.boolean() }),
}),
})
.parse({
s: 's',
n: 0,
foo: { foo1: 'foo1', foo2: { foo3: false } },
})
.getAll();

expect(config).toEqual({
s: 's',
n: 0,
foo: { foo1: 'foo1', foo2: { foo3: false } },
});

// eslint-disable-next-line @typescript-eslint/no-unused-vars
type Test = Expect<
Equals<
typeof config,
{
s: string | undefined;
n: number;
foo: { foo1: string; foo2: { foo3: boolean | undefined } };
}
>
>;
});
});
14 changes: 6 additions & 8 deletions src/Config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import type { InferType } from './InferType';
import type { Schema, SchemaOptions } from './Schema';
import { BooleanSchema, NumberSchema, StringSchema } from './Schema';
import { ObjectSchema } from './Schema/ObjectSchema';
import type { SchemaWithDefaultOptions } from './Schema/SchemaOptions';

type RequiredSchema<T extends Schema> = T & { isRequired: true };
import type { InferSchema, Prettify, RequiredSchema } from './types';

export class Config<TSchema extends Record<string, Schema>> {
private value!: InferType<TSchema>;
private value!: InferSchema<TSchema>;

constructor(private schema: TSchema) {}

Expand All @@ -18,7 +16,7 @@ export class Config<TSchema extends Record<string, Schema>> {
s.key = key;
s.setValue(this.value[key]);
s.validate();
this.value[key as keyof InferType<TSchema>] = s.parse();
this.value[key as keyof InferSchema<TSchema>] = s.parse();
});

return this;
Expand Down Expand Up @@ -55,10 +53,10 @@ export class Config<TSchema extends Record<string, Schema>> {
}

public get<TKey extends keyof TSchema>(key: TKey) {
return this.value[key] as InferType<TSchema>[TKey];
return this.value[key] as Prettify<InferSchema<TSchema>[TKey]>;
}

public getAll(): InferType<TSchema> {
return this.value as any;
public getAll() {
return this.value as Prettify<InferSchema<TSchema>>;
}
}
13 changes: 0 additions & 13 deletions src/InferType.ts

This file was deleted.

6 changes: 4 additions & 2 deletions src/Schema/BooleanSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { Schema } from './Schema';
import type { SchemaOptions } from './SchemaOptions';

export class BooleanSchema<TInput = any> extends Schema<TInput, boolean> {
private falseRegex = /false/i;
#type = 'boolean'; // eslint-disable-line no-unused-private-class-members
#falseRegex = /false/i;

constructor(options: SchemaOptions<boolean> = {}) {
super({
...options,
typeConstructor: n => {
if (typeof n === 'string' && this.falseRegex.test(n)) return false;
if (typeof n === 'string' && this.#falseRegex.test(n)) return false;

return Boolean(n);
},
Expand Down
2 changes: 2 additions & 0 deletions src/Schema/NumberSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class MaxNumberGuard implements Guard<number> {
}

export class NumberSchema<TInput = any> extends Schema<TInput, number> {
#type = 'number'; // eslint-disable-line no-unused-private-class-members

constructor(options: SchemaOptions<number> = {}) {
super({ ...options, typeConstructor: Number, type: 'number' });
}
Expand Down
2 changes: 2 additions & 0 deletions src/Schema/ObjectSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class ObjectGuard implements Guard<Record<string, Schema>> {
}

export class ObjectSchema<TInput = any> extends Schema<TInput, TInput> {
#type = 'object'; // eslint-disable-line no-unused-private-class-members

constructor(schema: Record<string, Schema>) {
super({
typeConstructor: x => x,
Expand Down
2 changes: 2 additions & 0 deletions src/Schema/StringSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Schema } from './Schema';
import type { SchemaOptions } from './SchemaOptions';

export class StringSchema<TInput = any> extends Schema<TInput, string> {
#type = 'string'; // eslint-disable-line no-unused-private-class-members

constructor(options: SchemaOptions<string> = {}) {
super({ ...options, typeConstructor: String, type: 'string' });
}
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './Config';
export type { InferType } from './InferType';
export type { InferSchema } from './types';
32 changes: 32 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { Schema, StringSchema } from './Schema';
import type { ObjectSchema } from './Schema/ObjectSchema';

export type Prettify<T> = {
[K in keyof T]: T[K];
} & {}; // eslint-disable-line @typescript-eslint/ban-types

export type InferObjectSchema<T extends ObjectSchema> = T extends ObjectSchema<
infer G
>
? G extends Record<string, Schema>
? Prettify<InferSchema<G>>
: never
: never;

export type InferSchema<T extends Record<string, Schema>> = {
[K in keyof T]: T[K] extends ObjectSchema
? InferObjectSchema<T[K]>
: T[K]['isRequired'] extends true
? NonNullable<T[K]['value']>
: T[K]['value'];
};

export type Expect<T extends true> = T;

export type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends <
T,
>() => T extends Y ? 1 : 2
? true
: false;

export type RequiredSchema<T extends Schema> = T & { isRequired: true };

0 comments on commit b4d9675

Please sign in to comment.