diff --git a/packages/ts-sdk/src/test/util.test.ts b/packages/ts-sdk/src/test/util.test.ts index 639def60..089ebd9a 100644 --- a/packages/ts-sdk/src/test/util.test.ts +++ b/packages/ts-sdk/src/test/util.test.ts @@ -1,5 +1,5 @@ import test from 'ava' -import { UrlFromString } from '../util' +import { DateFromUnknown, UrlFromString } from '../util' import { isRight, Either } from 'fp-ts/lib/Either' function rightAnd(e: Either, validation: (res: T) => void) { @@ -41,11 +41,51 @@ test('UrlFromString success cases', (t) => { test('UrlFromString failure cases', (t) => { const url1 = UrlFromString.decode('NOT_A_URL') - t.false(isRight(UrlFromString.decode(url1))) + t.false(isRight(url1)) const url2 = UrlFromString.decode(409) - t.false(isRight(UrlFromString.decode(url2))) + t.false(isRight(url2)) const url3 = UrlFromString.decode({}) - t.false(isRight(UrlFromString.decode(url3))) + t.false(isRight(url3)) +}) + +test('DateFromUnknown success cases', (t) => { + const d1 = DateFromUnknown.decode(new Date(0)) + t.true( + rightAnd(d1, (d) => { + t.is(d.valueOf(), 0) + t.is(d.getUTCFullYear(), 1970) + t.is(DateFromUnknown.encode(d), '1970-01-01T00:00:00.000Z') + }), + ) + + const d2 = DateFromUnknown.decode(0) + t.true( + rightAnd(d2, (d) => { + t.is(d.valueOf(), 0) + t.is(d.getUTCFullYear(), 1970) + t.is(DateFromUnknown.encode(d), '1970-01-01T00:00:00.000Z') + }), + ) + + const d4 = DateFromUnknown.decode('1970-01-01T00:00:00.000Z') + t.true( + rightAnd(d4, (d) => { + t.is(d.valueOf(), 0) + t.is(d.getUTCFullYear(), 1970) + t.is(DateFromUnknown.encode(d), '1970-01-01T00:00:00.000Z') + }), + ) +}) + +test('DateFromUnknown failure cases', (t) => { + const d1 = DateFromUnknown.decode('NOT_A_DATE') + t.false(isRight(d1)) + + const d2 = DateFromUnknown.decode(undefined) + t.false(isRight(d2)) + + const d4 = DateFromUnknown.decode({ some: 'thing' }) + t.false(isRight(d4)) }) diff --git a/packages/ts-sdk/src/types.ts b/packages/ts-sdk/src/types.ts index 4b45ce5f..9609a74a 100644 --- a/packages/ts-sdk/src/types.ts +++ b/packages/ts-sdk/src/types.ts @@ -1,6 +1,5 @@ import * as t from 'io-ts' -import { DateFromISOString } from 'io-ts-types' -import { UrlFromString } from './util' +import { UrlFromString, DateFromUnknown } from './util' function arrayOrOneOf(literalStrings: string[]) { const [one, two, ...r] = literalStrings @@ -87,7 +86,7 @@ export const DocmapThing = t.intersection([ t.partial({ // TODO use DateFromString for better parsing: // https://github.com/gcanti/io-ts/blob/dedb64e05328417ecd3d87e00008d9e72130374a/index.md#custom-types - published: DateFromISOString, + published: DateFromUnknown, id: t.string, doi: t.string, type: t.union([t.array(t.string), t.string]), // TODO this Type can be more specific ('web-page', 'preprint', etc) @@ -137,12 +136,12 @@ export const Docmap = t.intersection([ ]), publisher: DocmapPublisher, // TODO: required contents of these date strings, - created: DateFromISOString, + created: DateFromUnknown, }), t.partial({ steps: t.record(t.string, DocmapStep), 'first-step': t.string, - updated: DateFromISOString, + updated: DateFromUnknown, }), ]) diff --git a/packages/ts-sdk/src/util.ts b/packages/ts-sdk/src/util.ts index 078c8650..38425ec7 100644 --- a/packages/ts-sdk/src/util.ts +++ b/packages/ts-sdk/src/util.ts @@ -27,3 +27,26 @@ export const UrlFromString: UrlFromStringC = new t.Type( ), String, ) + +/** Date from Anything + * + * based on example there: + * https://github.com/gcanti/io-ts/blob/master/index.md#custom-types + */ + +export type DateFromUnknownC = t.Type + +export const DateFromUnknown: DateFromUnknownC = new t.Type( + 'DateFromUnknown', + (input: unknown): input is Date => input instanceof Date, + (input, context) => { + if (typeof input === 'string' || typeof input === 'number' || input instanceof Date) { + const date = new Date(input) + if (!isNaN(date.getTime())) { + return t.success(date) + } + } + return t.failure(input, context, 'Invalid date-like input') + }, + (date: Date) => date.toISOString(), +)