Skip to content

Commit

Permalink
Merge pull request #591 from streamich/json-type
Browse files Browse the repository at this point in the history
JSON Type improvements
  • Loading branch information
streamich authored Apr 23, 2024
2 parents 3fbba64 + ac273dd commit 549e754
Show file tree
Hide file tree
Showing 19 changed files with 185 additions and 172 deletions.
4 changes: 2 additions & 2 deletions src/json-type/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ Type builder for JSON and MessagePack.
```ts
import {t} from 'json-joy/lib/json-type';

t.String(); // { __t: 'str' }
t.String({const: 'add'}); // { __t: 'str', const: 'add' }
t.String(); // { kind: 'str' }
t.String({const: 'add'}); // { kind: 'str', const: 'add' }

const type = t.Object([
t.Field('collection', t.Object([
Expand Down
2 changes: 1 addition & 1 deletion src/json-type/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* Define basic types, for example, a string:
*
* ```ts
* t.String(); // { __t: 'str' }
* t.String(); // { kind: 'str' }
* ```
*
* Define complex types:
Expand Down
36 changes: 18 additions & 18 deletions src/json-type/schema/SchemaBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,23 @@ export class SchemaBuilder {
public Boolean(options?: NoT<BooleanSchema>): BooleanSchema;
public Boolean(a?: string | NoT<BooleanSchema>, b?: NoT<BooleanSchema> | void): BooleanSchema {
if (typeof a === 'string') return this.Boolean({id: a, ...(b || {})});
return {__t: 'bool', ...(a || {})};
return {kind: 'bool', ...(a || {})};
}

public Number(options?: NoT<NumberSchema>): NumberSchema {
return {__t: 'num', ...options};
return {kind: 'num', ...options};
}

public String(id: string, options?: NoT<StringSchema>): StringSchema;
public String(options?: NoT<StringSchema>): StringSchema;
public String(a?: string | NoT<StringSchema>, b?: NoT<StringSchema>): StringSchema {
if (typeof a === 'string') return this.String({id: a, ...(b || {})});
return {__t: 'str', ...(a || {})};
return {kind: 'str', ...(a || {})};
}

public Binary<T extends Schema>(type: T, options: Optional<BinarySchema> = {}): BinarySchema {
return {
__t: 'bin',
kind: 'bin',
type,
...options,
};
Expand All @@ -108,7 +108,7 @@ export class SchemaBuilder {
c?: Omit<NoT<ArraySchema<T>>, 'id' | 'type'>,
): ArraySchema<T> {
if (typeof a === 'string') return this.Array(b as T, {id: a, ...(c || {})});
return {__t: 'arr', ...(b as Omit<NoT<ArraySchema<T>>, 'id' | 'type'>), type: a as T};
return {kind: 'arr', ...(b as Omit<NoT<ArraySchema<T>>, 'id' | 'type'>), type: a as T};
}

/**
Expand All @@ -127,11 +127,11 @@ export class SchemaBuilder {
): ConstSchema<
string extends V ? never : number extends V ? never : boolean extends V ? never : any[] extends V ? never : V
> {
return {__t: 'const', value: value as any, ...options};
return {kind: 'const', value: value as any, ...options};
}

public Tuple<T extends Schema[]>(...types: T): TupleSchema<T> {
return {__t: 'tup', types};
return {kind: 'tup', types};
}

public fields<F extends ObjectFieldSchema<any, any>[]>(...fields: ObjectSchema<F>['fields']): F {
Expand All @@ -158,7 +158,7 @@ export class SchemaBuilder {
typeof first === 'object' &&
(first as NoT<ObjectSchema<F>>).fields instanceof Array
)
return {__t: 'obj', ...(first as NoT<ObjectSchema<F>>)};
return {kind: 'obj', ...(first as NoT<ObjectSchema<F>>)};
if (args.length >= 1 && args[0] instanceof Array)
return this.Object({fields: args[0] as F, ...(args[1] as Optional<ObjectSchema<F>>)});
return this.Object({fields: args as F});
Expand All @@ -171,7 +171,7 @@ export class SchemaBuilder {
options: Omit<NoT<ObjectFieldSchema<K, V>>, 'key' | 'type' | 'optional'> = {},
): ObjectFieldSchema<K, V> {
return {
__t: 'field',
kind: 'field',
key,
type,
...options,
Expand All @@ -185,7 +185,7 @@ export class SchemaBuilder {
options: Omit<NoT<ObjectFieldSchema<K, V>>, 'key' | 'type' | 'optional'> = {},
): ObjectOptionalFieldSchema<K, V> {
return {
__t: 'field',
kind: 'field',
key,
type,
...options,
Expand All @@ -200,7 +200,7 @@ export class SchemaBuilder {
options: Omit<NoT<ObjectFieldSchema<K, V>>, 'key' | 'type' | 'optional'> = {},
): ObjectFieldSchema<K, V> {
return {
__t: 'field',
kind: 'field',
key,
type,
...options,
Expand All @@ -214,7 +214,7 @@ export class SchemaBuilder {
options: Omit<NoT<ObjectFieldSchema<K, V>>, 'key' | 'type' | 'optional'> = {},
): ObjectOptionalFieldSchema<K, V> {
return {
__t: 'field',
kind: 'field',
key,
type,
...options,
Expand All @@ -223,42 +223,42 @@ export class SchemaBuilder {
}

public Map<T extends Schema>(type: T, options?: Omit<NoT<MapSchema<T>>, 'type'>): MapSchema<T> {
return {__t: 'map', type, ...options};
return {kind: 'map', type, ...options};
}

public Any(options: NoT<AnySchema> = {}): AnySchema {
return {
__t: 'any',
kind: 'any',
...options,
};
}

public Ref<T extends TType = any>(ref: string): RefSchema<T> {
return {
__t: 'ref',
kind: 'ref',
ref: ref as string & T,
};
}

public Or<T extends Schema[]>(...types: T): OrSchema<T> {
return {
__t: 'or',
kind: 'or',
types,
discriminator: ['num', -1],
};
}

public Function<Req extends Schema, Res extends Schema>(req: Req, res: Res): FunctionSchema<Req, Res> {
return {
__t: 'fn',
kind: 'fn',
req,
res,
};
}

public Function$<Req extends Schema, Res extends Schema>(req: Req, res: Res): FunctionStreamingSchema<Req, Res> {
return {
__t: 'fn$',
kind: 'fn$',
req,
res,
};
Expand Down
40 changes: 24 additions & 16 deletions src/json-type/schema/__tests__/SchemaBuilder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,64 @@ import {s} from '..';

describe('string', () => {
test('can create a string type', () => {
expect(s.String()).toEqual({__t: 'str'});
expect(s.String()).toEqual({kind: 'str'});
});

test('can create a named a string type', () => {
expect(s.String('UserName')).toEqual({
__t: 'str',
kind: 'str',
id: 'UserName',
});
});

test('can add custom metadata', () => {
expect(s.String('validator', {meta: {regex: true}})).toEqual({
kind: 'str',
id: 'validator',
meta: {regex: true},
});
});
});

describe('object', () => {
test('can create an empty object using shorthand', () => {
expect(s.obj).toEqual({__t: 'obj', fields: []});
expect(s.obj).toEqual({kind: 'obj', fields: []});
});

test('can create an empty object using default syntax', () => {
expect(s.Object()).toEqual({__t: 'obj', fields: []});
expect(s.Object()).toEqual({kind: 'obj', fields: []});
});

test('can create an empty object using fields-first syntax', () => {
expect(s.Object()).toEqual({__t: 'obj', fields: []});
expect(s.Object()).toEqual({kind: 'obj', fields: []});
});

test('can create a named empty object using fields-first syntax', () => {
expect(s.Object([])).toEqual({__t: 'obj', fields: []});
expect(s.Object([])).toEqual({kind: 'obj', fields: []});
});

test('can create a named empty object using default syntax', () => {
expect(s.Object({fields: []})).toEqual({__t: 'obj', fields: []});
expect(s.Object({fields: []})).toEqual({kind: 'obj', fields: []});
});

test('can specify types', () => {
const type = s.Object([s.prop('id', s.String('UserId')), s.prop('name', s.str)]);
expect(type).toEqual({
__t: 'obj',
kind: 'obj',
fields: [
{
__t: 'field',
kind: 'field',
key: 'id',
type: {
__t: 'str',
kind: 'str',
id: 'UserId',
},
},
{
__t: 'field',
kind: 'field',
key: 'name',
type: {
__t: 'str',
kind: 'str',
},
},
],
Expand All @@ -61,20 +69,20 @@ describe('object', () => {

describe('map', () => {
test('can create an simple object using shorthand', () => {
expect(s.map).toEqual({__t: 'map', type: {__t: 'any'}});
expect(s.map).toEqual({kind: 'map', type: {kind: 'any'}});
});

test('can define a map', () => {
expect(s.Map(s.Boolean())).toEqual({__t: 'map', type: {__t: 'bool'}});
expect(s.Map(s.Boolean())).toEqual({kind: 'map', type: {kind: 'bool'}});
});
});

describe('or', () => {
test('can create an "or" type', () => {
const type = s.Or(s.str, s.num);
expect(type).toEqual({
__t: 'or',
types: [{__t: 'str'}, {__t: 'num'}],
kind: 'or',
types: [{kind: 'str'}, {kind: 'num'}],
discriminator: ['num', -1],
});
});
Expand Down
30 changes: 15 additions & 15 deletions src/json-type/schema/__tests__/type.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {ObjectSchema, s} from '..';

test('can generate any type', () => {
const address: ObjectSchema = {
__t: 'obj',
kind: 'obj',
title: 'User address',
description: 'Various address fields for user',
fields: [...s.Object(s.prop('street', s.String()), s.prop('zip', s.String())).fields],
Expand All @@ -18,45 +18,45 @@ test('can generate any type', () => {
);

expect(userType).toMatchObject({
__t: 'obj',
kind: 'obj',
fields: [
{
key: 'id',
type: {
__t: 'num',
kind: 'num',
format: 'i',
},
},
{
key: 'alwaysOne',
type: {
__t: 'const',
kind: 'const',
value: 1,
},
},
{
key: 'name',
type: {
__t: 'str',
kind: 'str',
},
},
{
key: 'address',
type: {
__t: 'obj',
kind: 'obj',
title: 'User address',
description: 'Various address fields for user',
fields: [
{
key: 'street',
type: {
__t: 'str',
kind: 'str',
},
},
{
key: 'zip',
type: {
__t: 'str',
kind: 'str',
},
},
],
Expand All @@ -65,21 +65,21 @@ test('can generate any type', () => {
{
key: 'timeCreated',
type: {
__t: 'num',
kind: 'num',
},
},
{
key: 'tags',
type: {
__t: 'arr',
kind: 'arr',
type: {
__t: 'or',
kind: 'or',
types: [
{
__t: 'num',
kind: 'num',
},
{
__t: 'str',
kind: 'str',
},
],
},
Expand All @@ -88,9 +88,9 @@ test('can generate any type', () => {
{
key: 'elements',
type: {
__t: 'map',
kind: 'map',
type: {
__t: 'str',
kind: 'str',
},
},
},
Expand Down
Loading

0 comments on commit 549e754

Please sign in to comment.