Skip to content

Commit 3bda3ec

Browse files
committed
feat: add t.opaque
1 parent 4407abd commit 3bda3ec

File tree

6 files changed

+72
-1
lines changed

6 files changed

+72
-1
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ The validation errors are detailed. Adapted from the brilliant work in `flow-run
4242
- [`t.readonlyArray(t.number())`](#treadonlyarraytnumber)
4343
- [`t.object(properties)`](#tobjectproperties)
4444
- [`t.object({ required?, optional?, exact? })`](#tobject-required-optional-exact-)
45+
- [`t.opaque<DateString>(() => t.string())`](#topaquedatestring--tstring)
4546
- [`t.readonly(objectType)`](#treadonlyobjecttype)
4647
- [`t.merge(...objectTypes)`](#tmergeobjecttypes)
4748
- [`t.mergeInexact(...objectTypes)`](#tmergeinexactobjecttypes)
@@ -386,9 +387,13 @@ PersonType.assert({ name: 1 }) // error
386387
PersonType.assert({ name: 'dude', age: 'old' }) // error
387388
```
388389

390+
### `t.opaque<DateString>(() => t.string())`
391+
392+
A validator that requires the value to be a string, but presents the type as `DateString` (for instance with `export opaque type DateString = string`)
393+
389394
### `t.readonly(objectType)`
390395

391-
Use `t.readOnly(t.object(...))` or `t.readOnly(t.merge(...))` etc. Doesn't require the object to be frozen, just allows the extracted type to be readonly.
396+
Use `t.readonly(t.object(...))` or `t.readonly(t.merge(...))` etc. Doesn't require the object to be frozen, just allows the extracted type to be readonly.
392397

393398
### `t.merge(...objectTypes)`
394399

src/index.js.flow

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import NumberType from './types/NumberType'
1414
import NumericLiteralType from './types/NumericLiteralType'
1515
import ObjectType from './types/ObjectType'
1616
import ObjectTypeProperty from './types/ObjectTypeProperty'
17+
import OpaqueType from './types/OpaqueType'
1718
import oneOf from './oneOf'
1819
import PrimitiveLiteralType from './types/PrimitiveLiteralType'
1920
import RecordType from './types/RecordType'
@@ -47,6 +48,7 @@ export {
4748
NumericLiteralType,
4849
ObjectType,
4950
ObjectTypeProperty,
51+
OpaqueType,
5052
oneOf,
5153
PrimitiveLiteralType,
5254
RecordType,
@@ -68,6 +70,7 @@ export {
6870

6971
declare export function any(): Type<any>
7072
declare export function unknown(): Type<mixed>
73+
declare export function opaque<T>(type: () => Type<any>): OpaqueType<T>
7174

7275
declare export function array<T>(elementType: Type<T>): Type<T[]>
7376
declare export function readonlyArray<T>(

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import NumberType from './types/NumberType'
1212
import NumericLiteralType from './types/NumericLiteralType'
1313
import ObjectType from './types/ObjectType'
1414
import ObjectTypeProperty from './types/ObjectTypeProperty'
15+
import OpaqueType from './types/OpaqueType'
1516
import oneOf from './oneOf'
1617
import PrimitiveLiteralType from './types/PrimitiveLiteralType'
1718
import RecordType from './types/RecordType'
@@ -45,6 +46,7 @@ export {
4546
NumericLiteralType,
4647
ObjectType,
4748
ObjectTypeProperty,
49+
OpaqueType,
4850
oneOf,
4951
PrimitiveLiteralType,
5052
RecordType,
@@ -66,6 +68,8 @@ export {
6668

6769
export const any = (): Type<any> => new AnyType()
6870
export const unknown = (): Type<unknown> => new UnknownType()
71+
export const opaque = <T>(type: () => Type<any>): OpaqueType<T> =>
72+
new OpaqueType<T>(type)
6973

7074
export const array = <T>(elementType: Type<T>): Type<T[]> =>
7175
new ArrayType(elementType)

src/types/OpaqueType.js.flow

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @flow
2+
3+
import Type from './Type'
4+
5+
declare class OpaqueType<T> extends Type<T> {
6+
type: () => Type<any>;
7+
8+
constructor(type: () => Type<any>): void;
9+
}
10+
11+
export default OpaqueType

src/types/OpaqueType.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Type from './Type'
2+
import Validation, { IdentifierPath } from '../Validation'
3+
import RuntimeTypeErrorItem from '../errorReporting/RuntimeTypeErrorItem'
4+
5+
export default class TypeReference<T> extends Type<T> {
6+
typeName = 'TypeReference'
7+
readonly type: () => Type<any>
8+
9+
constructor(type: () => Type<any>) {
10+
super()
11+
this.type = type
12+
}
13+
14+
resolveType(): Type<any> {
15+
return this.type().resolveType()
16+
}
17+
18+
*errors(
19+
validation: Validation,
20+
path: IdentifierPath,
21+
input: any
22+
): Iterable<RuntimeTypeErrorItem> {
23+
yield* this.type().errors(validation, path, input)
24+
}
25+
26+
accepts(input: any): input is T {
27+
return this.type().accepts(input)
28+
}
29+
30+
get acceptsSomeCompositeTypes(): boolean {
31+
return this.type().acceptsSomeCompositeTypes
32+
}
33+
34+
toString(): string {
35+
return this.type().toString()
36+
}
37+
}

test/opaque.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as t from '../src'
2+
import { expect } from 'chai'
3+
4+
describe(`t.opaque`, function() {
5+
const DateString: t.OpaqueType<any> = t.opaque(() => t.string())
6+
7+
it(`works`, function() {
8+
DateString.assert('foo')
9+
expect(DateString.accepts('foo')).to.be.true
10+
})
11+
})

0 commit comments

Comments
 (0)