Skip to content

Commit eeae664

Browse files
authored
Merge pull request #592 from streamich/json-type-2
JSON Type further improvements
2 parents 549e754 + 1233650 commit eeae664

File tree

10 files changed

+263
-79
lines changed

10 files changed

+263
-79
lines changed

src/json-type/schema/__tests__/SchemaBuilder.spec.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,6 @@ describe('string', () => {
1111
id: 'UserName',
1212
});
1313
});
14-
15-
test('can add custom metadata', () => {
16-
expect(s.String('validator', {meta: {regex: true}})).toEqual({
17-
kind: 'str',
18-
id: 'validator',
19-
meta: {regex: true},
20-
});
21-
});
2214
});
2315

2416
describe('object', () => {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {s} from '..';
2+
3+
describe('metadata', () => {
4+
test('can add custom metadata', () => {
5+
expect(s.String('validator', {meta: {regex: true}})).toEqual({
6+
kind: 'str',
7+
id: 'validator',
8+
meta: {regex: true},
9+
});
10+
});
11+
});
12+
13+
describe('deprecations', () => {
14+
test('can deprecate a type', () => {
15+
const schema = s.String('validator', {
16+
deprecated: {},
17+
});
18+
expect(schema).toEqual({
19+
kind: 'str',
20+
id: 'validator',
21+
deprecated: {},
22+
});
23+
});
24+
25+
test('can deprecate a type with a message', () => {
26+
const schema = s.String('validator', {
27+
deprecated: {
28+
description: 'Use the new type',
29+
},
30+
});
31+
expect(schema).toEqual({
32+
kind: 'str',
33+
id: 'validator',
34+
deprecated: {
35+
description: 'Use the new type',
36+
},
37+
});
38+
});
39+
});

src/json-type/schema/schema.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ export interface TType<Value = unknown> extends Display, Partial<Identifiable> {
2020
* List of example usages of this type.
2121
*/
2222
examples?: TExample<Value>[];
23+
24+
/**
25+
* A flag that indicates that this type is deprecated. When a type is
26+
* deprecated, it should not be used in new code, and existing code should be
27+
* updated to use a non-deprecated type.
28+
*/
29+
deprecated?: {
30+
/**
31+
* A message that explains why the type is deprecated, and what to use
32+
* instead.
33+
*/
34+
description?: string;
35+
};
2336
}
2437

2538
/**
@@ -132,7 +145,7 @@ export interface BinarySchema<T extends TType = any> extends TType, WithValidato
132145
/** Type of value encoded in the binary data. */
133146
type: T;
134147
/** Codec used for encoding the binary data. */
135-
format?: 'json' | 'cbor' | 'msgpack' | 'ion';
148+
format?: 'json' | 'cbor' | 'msgpack' | 'resp3' | 'ion' | 'bson' | 'ubjson' | 'bencode';
136149
}
137150

138151
/**
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`can print a type 1`] = `
4+
"obj
5+
├─ "id": "The id of the object"
6+
│ └─ str
7+
├─ "tags": "Always use tags"
8+
│ └─ arr "Tags"
9+
│ └─ str
10+
├─ "optional"?:
11+
│ └─ any
12+
├─ "booleanProperty":
13+
│ └─ bool
14+
├─ "numberProperty":
15+
│ └─ num
16+
├─ "binaryProperty":
17+
│ └─ bin
18+
│ └─ any
19+
├─ "arrayProperty":
20+
│ └─ arr
21+
│ └─ any
22+
├─ "objectProperty":
23+
│ └─ obj
24+
│ └─ "id":
25+
│ └─ str
26+
├─ "unionProperty":
27+
│ └─ or
28+
│ ├─ str
29+
│ ├─ num
30+
│ └─ const → null
31+
├─ "enumAsConst"?:
32+
│ └─ or
33+
│ ├─ const → "a"
34+
│ ├─ const → "b"
35+
│ └─ const → "c"
36+
├─ "refField"?:
37+
│ └─ ref → [refId]
38+
├─ "und"?:
39+
│ └─ const → undefined
40+
├─ "operation":
41+
│ └─ obj
42+
│ ├─ "type":
43+
│ │ └─ const "Always use replace" → "replace"
44+
│ ├─ "path":
45+
│ │ └─ str
46+
│ └─ "value":
47+
│ └─ any
48+
├─ "binaryOperation":
49+
│ └─ bin
50+
│ └─ tup "Should always have 3 elements"
51+
│ ├─ const "7 is the magic number" → 7
52+
│ ├─ str
53+
│ └─ any
54+
├─ "map":
55+
│ └─ map
56+
│ └─ num
57+
├─ "simpleFn1":
58+
│ └─ fn
59+
│ ├─ req: any
60+
│ └─ res: any
61+
├─ "simpleFn2":
62+
│ └─ fn$
63+
│ ├─ req: any
64+
│ └─ res: any
65+
└─ "function":
66+
└─ fn
67+
├─ req: obj
68+
│ └─ "id":
69+
│ └─ str
70+
└─ res: obj
71+
└─ "name":
72+
└─ str"
73+
`;

src/json-type/type/__tests__/toString.spec.ts

Lines changed: 4 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -36,62 +36,11 @@ test('can print a type', () => {
3636
.options({format: 'cbor'}),
3737
),
3838
t.prop('map', t.Map(t.num)),
39+
t.prop('simpleFn1', t.fn),
40+
t.prop('simpleFn2', t.fn$),
41+
t.prop('function', t.Function(t.Object(t.prop('id', t.str)), t.Object(t.prop('name', t.str)))),
3942
)
4043
.options({unknownFields: true});
4144
// console.log(type + '');
42-
expect(type + '').toMatchInlineSnapshot(`
43-
"obj { unknownFields = !t }
44-
├─ "id": { description = "The id of the object" }
45-
│ └─ str { validator = [ "id", "uuid" ] }
46-
├─ "tags": { title = "Always use tags" }
47-
│ └─ arr { title = "Tags" }
48-
│ └─ str
49-
├─ "optional"?:
50-
│ └─ any
51-
├─ "booleanProperty":
52-
│ └─ bool
53-
├─ "numberProperty":
54-
│ └─ num { format = "f64", gt = 3.14 }
55-
├─ "binaryProperty":
56-
│ └─ bin { format = "cbor" }
57-
│ └─ any
58-
├─ "arrayProperty":
59-
│ └─ arr
60-
│ └─ any
61-
├─ "objectProperty":
62-
│ └─ obj
63-
│ └─ "id":
64-
│ └─ str { ascii = !t, min = 3, max = 128 }
65-
├─ "unionProperty":
66-
│ └─ or { discriminator = [ "?", [ "==", !n, [ "$", "" ] ], 2, [ "?", [ "==", [ "type", [ "$", "" ] ], "number" ], 1, 0 ] ] }
67-
│ ├─ str
68-
│ ├─ num
69-
│ └─ const { description = "" } → null
70-
├─ "enumAsConst"?:
71-
│ └─ or { discriminator = [ "?", [ "==", "c", [ "$", "" ] ], 2, [ "?", [ "==", "b", [ "$", "" ] ], 1, 0 ] ] }
72-
│ ├─ const → "a"
73-
│ ├─ const → "b"
74-
│ └─ const → "c"
75-
├─ "refField"?:
76-
│ └─ ref → [refId]
77-
├─ "und"?:
78-
│ └─ const → undefined
79-
├─ "operation":
80-
│ └─ obj
81-
│ ├─ "type":
82-
│ │ └─ const { title = "Always use replace" } → "replace"
83-
│ ├─ "path":
84-
│ │ └─ str
85-
│ └─ "value":
86-
│ └─ any
87-
├─ "binaryOperation":
88-
│ └─ bin { format = "cbor" }
89-
│ └─ tup { description = "Should always have 3 elements" }
90-
│ ├─ const { description = "7 is the magic number" } → 7
91-
│ ├─ str
92-
│ └─ any
93-
└─ "map":
94-
└─ map
95-
└─ num"
96-
`);
45+
expect(type + '').toMatchSnapshot();
9746
});

src/json-type/type/classes/AbstractType.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as schema from '../../schema';
22
import {RandomJson} from '../../../json-random';
33
import {Printable} from '../../../util/print/types';
4-
import {stringify} from '../../../json-text/stringify';
54
import {ValidatorCodegenContext, ValidatorCodegenContextOptions} from '../../codegen/validator/ValidatorCodegenContext';
65
import {JsonTypeValidator, ValidationPath} from '../../codegen/validator/types';
76
import {
@@ -297,9 +296,10 @@ export abstract class AbstractType<S extends schema.Schema> implements BaseType<
297296
}
298297

299298
protected toStringOptions(): string {
300-
const options = this.getOptions();
301-
if (Object.keys(options).length === 0) return '';
302-
return stringify(options);
299+
const options = this.getOptions() as schema.Display;
300+
const title = options.title || options.intro || options.description;
301+
if (!title) return '';
302+
return JSON.stringify(title);
303303
}
304304

305305
public toString(tab: string = ''): string {

src/json-type/type/classes/FunctionType.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ const fnNotImplemented: schema.FunctionValue<any, any> = async () => {
1111
throw new Error('NOT_IMPLEMENTED');
1212
};
1313

14+
const toStringTree = (tab: string = '', type: FunctionType<Type, Type> | FunctionStreamingType<Type, Type>) => {
15+
return printTree(tab, [
16+
(tab) => 'req: ' + type.req.toString(tab + ' '),
17+
(tab) => 'res: ' + type.res.toString(tab + ' '),
18+
]);
19+
};
20+
1421
type FunctionImpl<Req extends Type, Res extends Type, Ctx = unknown> = (
1522
req: ResolveType<Req>,
1623
ctx: Ctx,
@@ -87,10 +94,7 @@ export class FunctionType<Req extends Type, Res extends Type> extends AbstractTy
8794
}
8895

8996
public toString(tab: string = ''): string {
90-
return (
91-
super.toString(tab) +
92-
printTree(tab, [(tab) => 'req: ' + this.req.toString(tab), (tab) => 'res: ' + this.res.toString(tab)])
93-
);
97+
return super.toString(tab) + toStringTree(tab, this);
9498
}
9599
}
96100

@@ -176,9 +180,6 @@ export class FunctionStreamingType<Req extends Type, Res extends Type> extends A
176180
}
177181

178182
public toString(tab: string = ''): string {
179-
return (
180-
super.toString(tab) +
181-
printTree(tab, [(tab) => 'req: ' + this.req.toString(tab), (tab) => 'res: ' + this.res.toString(tab)])
182-
);
183+
return super.toString(tab) + toStringTree(tab, this);
183184
}
184185
}

src/json-type/type/classes/ObjectType.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type {TypeSystem} from '../../system/TypeSystem';
2222
import type {json_string} from '@jsonjoy.com/util/lib/json-brand';
2323
import type * as ts from '../../typescript/types';
2424
import type {TypeExportContext} from '../../system/TypeExportContext';
25+
import type {ExcludeFromTuple, PickFromTuple} from '../../../util/types';
2526

2627
const augmentWithComment = (
2728
type: schema.Schema | schema.ObjectFieldSchema,
@@ -132,12 +133,34 @@ export class ObjectType<F extends ObjectFieldType<any, any>[] = ObjectFieldType<
132133
return options as any;
133134
}
134135

135-
public getField(key: string): ObjectFieldType<string, Type> | undefined {
136+
public getField<K extends keyof schema.TypeOf<schema.ObjectSchema<SchemaOfObjectFields<F>>>>(
137+
key: K,
138+
): ObjectFieldType<string, Type> | undefined {
136139
return this.fields.find((f) => f.key === key);
137140
}
138141

139142
public extend<F2 extends ObjectFieldType<any, any>[]>(o: ObjectType<F2>): ObjectType<[...F, ...F2]> {
140-
return new ObjectType([...this.fields, ...o.fields]);
143+
const type = new ObjectType([...this.fields, ...o.fields]) as ObjectType<[...F, ...F2]>;
144+
type.system = this.system;
145+
return type;
146+
}
147+
148+
public omit<K extends keyof schema.TypeOf<schema.ObjectSchema<SchemaOfObjectFields<F>>>>(
149+
key: K,
150+
): ObjectType<ExcludeFromTuple<F, ObjectFieldType<K extends string ? K : never, any>>> {
151+
const type = new ObjectType(this.fields.filter((f) => f.key !== key) as any);
152+
type.system = this.system;
153+
return type;
154+
}
155+
156+
public pick<K extends keyof schema.TypeOf<schema.ObjectSchema<SchemaOfObjectFields<F>>>>(
157+
key: K,
158+
): ObjectType<PickFromTuple<F, ObjectFieldType<K extends string ? K : never, any>>> {
159+
const field = this.fields.find((f) => f.key === key);
160+
if (!field) throw new Error('FIELD_NOT_FOUND');
161+
const type = new ObjectType([field] as any);
162+
type.system = this.system;
163+
return type;
141164
}
142165

143166
public validateSchema(): void {
@@ -554,7 +577,6 @@ if (${rLength}) {
554577
}
555578

556579
public toString(tab: string = ''): string {
557-
const {kind, fields, ...rest} = this.getSchema();
558580
return (
559581
super.toString(tab) +
560582
printTree(

0 commit comments

Comments
 (0)