Skip to content

Commit e676703

Browse files
authored
feat: Support constant type (#312)
1 parent 9c20513 commit e676703

File tree

4 files changed

+113
-0
lines changed

4 files changed

+113
-0
lines changed

docs/api/types.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,38 @@ schema({
103103
});
104104
```
105105

106+
## `constant`
107+
108+
Creates a constant value. Useful for creating discriminated unions with the `oneOf` type.
109+
110+
**Parameters:**
111+
112+
| Name | Type | Attribute |
113+
| ------------------ | ---------------- | --------- |
114+
| `value` | `TValue` | required |
115+
| `options` | `GenericOptions` | optional |
116+
| `options.required` | `boolean` | optional |
117+
118+
**Example:**
119+
120+
```ts
121+
import { schema, types } from 'papr';
122+
123+
schema({
124+
shape: types.oneOf([
125+
types.object({
126+
type: types.constant('circle' as const, { required: true }),
127+
radius: types.number({ required: true }),
128+
}),
129+
types.object({
130+
type: types.constant('rectangle' as const, { required: true }),
131+
width: types.number({ required: true }),
132+
length: types.number({ required: true }),
133+
}),
134+
]),
135+
});
136+
```
137+
106138
## `date`
107139

108140
Creates a date type.

src/__tests__/schema.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,8 @@ describe('schema', () => {
482482
binaryRequired: types.binary({ required: true }),
483483
booleanOptional: types.boolean(),
484484
booleanRequired: types.boolean({ required: true }),
485+
constantOptional: types.constant(TEST_ENUM.FOO as const),
486+
constantRequired: types.constant(TEST_ENUM.FOO as const, { required: true }),
485487
dateOptional: types.date(),
486488
dateRequired: types.date({ required: true }),
487489
enumOptional: types.enum([...Object.values(TEST_ENUM), null]),
@@ -589,6 +591,12 @@ describe('schema', () => {
589591
booleanRequired: {
590592
type: 'boolean',
591593
},
594+
constantOptional: {
595+
enum: ['foo'],
596+
},
597+
constantRequired: {
598+
enum: ['foo'],
599+
},
592600
createdAt: {
593601
bsonType: 'date',
594602
},
@@ -689,6 +697,7 @@ describe('schema', () => {
689697
'arrayRequired',
690698
'binaryRequired',
691699
'booleanRequired',
700+
'constantRequired',
692701
'dateRequired',
693702
'enumRequired',
694703
'numberRequired',
@@ -718,6 +727,8 @@ describe('schema', () => {
718727
binaryRequired: Binary;
719728
booleanOptional?: boolean;
720729
booleanRequired: boolean;
730+
constantOptional?: TEST_ENUM.FOO;
731+
constantRequired: TEST_ENUM.FOO;
721732
dateOptional?: Date;
722733
dateRequired: Date;
723734
enumOptional?: TEST_ENUM | null;
@@ -815,6 +826,8 @@ describe('schema', () => {
815826
binaryRequired: types.binary({ required: true }),
816827
booleanOptional: types.boolean({ required: false }),
817828
booleanRequired: types.boolean({ required: true }),
829+
constantOptional: types.constant(TEST_ENUM.FOO as const, { required: false }),
830+
constantRequired: types.constant(TEST_ENUM.FOO as const, { required: true }),
818831
dateOptional: types.date({ required: false }),
819832
dateRequired: types.date({ required: true }),
820833
enumOptional: types.enum([...Object.values(TEST_ENUM), null]),
@@ -922,6 +935,12 @@ describe('schema', () => {
922935
booleanRequired: {
923936
type: 'boolean',
924937
},
938+
constantOptional: {
939+
enum: ['foo'],
940+
},
941+
constantRequired: {
942+
enum: ['foo'],
943+
},
925944
createdAt: {
926945
bsonType: 'date',
927946
},
@@ -1022,6 +1041,7 @@ describe('schema', () => {
10221041
'arrayRequired',
10231042
'binaryRequired',
10241043
'booleanRequired',
1044+
'constantRequired',
10251045
'dateRequired',
10261046
'enumRequired',
10271047
'numberRequired',
@@ -1051,6 +1071,8 @@ describe('schema', () => {
10511071
binaryRequired: Binary;
10521072
booleanOptional?: boolean;
10531073
booleanRequired: boolean;
1074+
constantOptional?: TEST_ENUM.FOO;
1075+
constantRequired: TEST_ENUM.FOO;
10541076
dateOptional?: Date;
10551077
dateRequired: Date;
10561078
enumOptional?: TEST_ENUM | null;

src/__tests__/types.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,29 @@ describe('types', () => {
393393
});
394394
});
395395

396+
describe('constant', () => {
397+
test('default', () => {
398+
const value = types.constant('foo' as const);
399+
400+
expect(value).toEqual({
401+
enum: ['foo'],
402+
});
403+
expectType<'foo' | undefined>(value);
404+
});
405+
406+
test('required', () => {
407+
const value = types.constant('foo' as const, { required: true });
408+
409+
expect(value).toEqual({
410+
$required: true,
411+
enum: ['foo'],
412+
});
413+
expectType<'foo'>(value);
414+
// @ts-expect-error `value` should not be undefined
415+
expectType<typeof value>(undefined);
416+
});
417+
});
418+
396419
describe('object', () => {
397420
test('default', () => {
398421
const value = types.object({

src/types.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,16 @@ function array<Item, Options extends ArrayOptions>(
136136
} as unknown as GetType<NonNullable<Item>[], Options>;
137137
}
138138

139+
function constant<Value, Options extends GenericOptions>(
140+
value: Value,
141+
options?: Options
142+
): GetType<Value, Options> {
143+
return {
144+
...(options?.required ? { $required: true } : {}),
145+
enum: [value],
146+
} as unknown as GetType<Value, Options>;
147+
}
148+
139149
function enumType<Enum, Options extends GenericOptions>(
140150
values: Enum[],
141151
options?: Options
@@ -324,6 +334,32 @@ export default {
324334
*/
325335
boolean: createSimpleType<boolean>('boolean'),
326336

337+
/**
338+
* Creates a constant value. Useful for creating discriminated unions with the `oneOf` type.
339+
*
340+
* @param value {TValue}
341+
* @param [options] {GenericOptions}
342+
* @param [options.required] {boolean}
343+
*
344+
* @example
345+
* import { schema, types } from 'papr';
346+
*
347+
* schema({
348+
* shape: types.oneOf([
349+
* types.object({
350+
* type: types.constant('circle' as const, { required: true }),
351+
* radius: types.number({ required: true }),
352+
* }),
353+
* types.object({
354+
* type: types.constant('rectangle' as const, { required: true }),
355+
* width: types.number({ required: true }),
356+
* length: types.number({ required: true }),
357+
* }),
358+
* ]),
359+
* });
360+
*/
361+
constant,
362+
327363
/**
328364
* Creates a date type.
329365
*

0 commit comments

Comments
 (0)