Skip to content

Commit

Permalink
add readonly combinator
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti committed Mar 3, 2017
1 parent e56f931 commit 7d0293f
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

- **New Feature**
- add `partial` combinator
- add `readonly` combinator
- add `never` type
- **Breaking Changes**
- remove `undefined` as valid value for `maybe` combinator
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ import * as t from 'io-ts'
| literal | `'s'` | `t.literal('s')` |
| maybe | `A | null` | `t.maybe(A)` |
| partial | `Partial<{ name: string }>` | `t.partial({ name: t.string })` |
| readonly | `Readonly<{ name: string }>` | `t.readonly({ name: t.string })` |
| dictionaries | `{ [key: A]: B }` | `t.dictionary(A, B)` |
| refinement || `t.refinement(A, predicate)` |
| interface | `{ name: string }` | `t.interface({ name: t.string })` |
Expand Down
18 changes: 16 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ export const number = new Type<number>(
(v, c) => typeof v === 'number' ? success(v) : failure<number>(v, c)
)

export const Integer = refinement(number, n => n % 1 === 0, 'Integer')

export const boolean = new Type<boolean>(
'boolean',
(v, c) => typeof v === 'boolean' ? success(v) : failure<boolean>(v, c)
Expand Down Expand Up @@ -172,8 +174,6 @@ export function refinement<RT extends Any>(type: RT, predicate: Predicate<TypeOf
)
}

export const Integer = refinement(number, n => n % 1 === 0, 'Integer')

//
// recursive types
//
Expand Down Expand Up @@ -524,6 +524,20 @@ export function tuple<RTS extends Array<Any>>(types: RTS, name?: string): TupleT
)
}

export class ReadonlyType<RT extends Any> extends Type<Readonly<TypeOf<RT>>> {
constructor(name: string, validate: Validate<Readonly<TypeOf<RT>>>, public readonly type: RT) {
super(name, validate)
}
}

export function readonly<RT extends Any>(type: RT, name?: string): ReadonlyType<RT> {
return new ReadonlyType(
name || `Readonly<${getTypeName(type)}>`,
(v, c) => type.validate(v, c).map(x => Object.freeze(x)),
type
)
}

export {
nullType as null,
undefinedType as undefined,
Expand Down
27 changes: 27 additions & 0 deletions test/readonly.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as assert from 'assert'
import * as t from '../src/index'
import {
assertSuccess,
assertFailure
} from './helpers'

describe('readonly', () => {

it('should succeed validating a valid value', () => {
const T = t.readonly(t.interface({ a: t.number }))
assertSuccess(t.validate({ a: 1 }, T))
})

it('should fail validating an invalid value', () => {
const T = t.readonly(t.interface({ a: t.number }))
assertFailure(t.validate({}, T), [
'Invalid value undefined supplied to : Readonly<{ a: number }>/a: number'
])
})

it('should freeze the value', () => {
const T = t.readonly(t.interface({ a: t.number }))
t.validate({ a: 1 }, T).map(x => assert.ok(Object.isFrozen(x)))
})

})

0 comments on commit 7d0293f

Please sign in to comment.