Skip to content

Commit

Permalink
Revision 0.32.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sinclairzx81 committed Nov 29, 2023
1 parent 55a4bba commit d2955b3
Showing 1 changed file with 118 additions and 98 deletions.
216 changes: 118 additions & 98 deletions src/value/convert/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,45 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/

import { IsArray, IsObject, IsDate, IsUndefined, IsString, IsNumber, IsBoolean, IsBigInt, IsSymbol } from '../guard/guard'
import { Clone } from '../clone/clone'
import { Check } from '../check/check'
import { Deref } from '../deref/deref'
import * as Types from '../../type/index'
import { Clone } from '../clone/index'
import { Check } from '../check/index'
import { Deref } from '../deref/index'

// --------------------------------------------------------------------------
import { TObject as IsObjectType } from '../../type/guard/type'
import { Kind } from '../../type/symbols/index'
import { Composite } from '../../type/composite/index'

import type { TSchema } from '../../type/schema/index'
import type { TArray } from '../../type/array/index'
import type { TBigInt } from '../../type/bigint/index'
import type { TBoolean } from '../../type/boolean/index'
import type { TDate } from '../../type/date/index'
import type { TInteger } from '../../type/integer/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TLiteral } from '../../type/literal/index'
import type { TNull } from '../../type/null/index'
import type { TNumber } from '../../type/number/index'
import type { TObject } from '../../type/object/index'
import type { TRecord } from '../../type/record/index'
import type { TRef } from '../../type/ref/index'
import type { TThis } from '../../type/recursive/index'
import type { TTuple } from '../../type/tuple/index'
import type { TUnion } from '../../type/union/index'
import type { TString } from '../../type/string/index'
import type { TSymbol } from '../../type/symbol/index'
import type { TUndefined } from '../../type/undefined/index'

// ------------------------------------------------------------------
// Errors
// --------------------------------------------------------------------------
// ------------------------------------------------------------------
export class ValueConvertUnknownTypeError extends Error {
constructor(public readonly schema: Types.TSchema) {
constructor(public readonly schema: TSchema) {
super('Unknown type')
}
}
// --------------------------------------------------------------------------
// ------------------------------------------------------------------
// Conversions
// --------------------------------------------------------------------------
// ------------------------------------------------------------------
function IsStringNumeric(value: unknown): value is string {
return IsString(value) && !isNaN(value as any) && !isNaN(parseFloat(value))
}
Expand Down Expand Up @@ -70,9 +93,9 @@ function IsDateTimeStringWithoutTimeZone(value: unknown): value is string {
function IsDateString(value: unknown): value is string {
return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\d$/i.test(value)
}
// --------------------------------------------------------------------------
// ------------------------------------------------------------------
// Convert
// --------------------------------------------------------------------------
// ------------------------------------------------------------------
function TryConvertLiteralString(value: unknown, target: string) {
const conversion = TryConvertString(value)
return conversion === target ? conversion : value
Expand All @@ -85,16 +108,14 @@ function TryConvertLiteralBoolean(value: unknown, target: boolean) {
const conversion = TryConvertBoolean(value)
return conversion === target ? conversion : value
}
function TryConvertLiteral(schema: Types.TLiteral, value: unknown) {
if (typeof schema.const === 'string') {
return TryConvertLiteralString(value, schema.const)
} else if (typeof schema.const === 'number') {
return TryConvertLiteralNumber(value, schema.const)
} else if (typeof schema.const === 'boolean') {
return TryConvertLiteralBoolean(value, schema.const)
} else {
return Clone(value)
}
// prettier-ignore
function TryConvertLiteral(schema: TLiteral, value: unknown) {
return (
IsString(schema.const) ? TryConvertLiteralString(value, schema.const) :
IsNumber(schema.const) ? TryConvertLiteralNumber(value, schema.const) :
IsBoolean(schema.const) ? TryConvertLiteralBoolean(value, schema.const) :
Clone(value)
)
}
function TryConvertBoolean(value: unknown) {
return IsValueTrue(value) ? true : IsValueFalse(value) ? false : value
Expand All @@ -117,85 +138,82 @@ function TryConvertNull(value: unknown) {
function TryConvertUndefined(value: unknown) {
return IsString(value) && value === 'undefined' ? undefined : value
}
// ------------------------------------------------------------------
// note: this function may return an invalid dates for the regex
// tests above. Invalid dates will however be checked during the
// casting function and will return a epoch date if invalid.
// Consider better string parsing for the iso dates in future
// revisions.
// ------------------------------------------------------------------
// prettier-ignore
function TryConvertDate(value: unknown) {
// --------------------------------------------------------------------------
// note: this function may return an invalid dates for the regex tests
// above. Invalid dates will however be checked during the casting function
// and will return a epoch date if invalid. Consider better string parsing
// for the iso dates in future revisions.
// --------------------------------------------------------------------------
return IsDate(value)
? value
: IsNumber(value)
? new Date(value)
: IsValueTrue(value)
? new Date(1)
: IsValueFalse(value)
? new Date(0)
: IsStringNumeric(value)
? new Date(parseInt(value))
: IsTimeStringWithoutTimeZone(value)
? new Date(`1970-01-01T${value}.000Z`)
: IsTimeStringWithTimeZone(value)
? new Date(`1970-01-01T${value}`)
: IsDateTimeStringWithoutTimeZone(value)
? new Date(`${value}.000Z`)
: IsDateTimeStringWithTimeZone(value)
? new Date(value)
: IsDateString(value)
? new Date(`${value}T00:00:00.000Z`)
: value
}
// --------------------------------------------------------------------------
return (
IsDate(value) ? value :
IsNumber(value) ? new Date(value) :
IsValueTrue(value) ? new Date(1) :
IsValueFalse(value) ? new Date(0) :
IsStringNumeric(value) ? new Date(parseInt(value)) :
IsTimeStringWithoutTimeZone(value) ? new Date(`1970-01-01T${value}.000Z`) :
IsTimeStringWithTimeZone(value) ? new Date(`1970-01-01T${value}`) :
IsDateTimeStringWithoutTimeZone(value) ? new Date(`${value}.000Z`) :
IsDateTimeStringWithTimeZone(value) ? new Date(value) :
IsDateString(value) ? new Date(`${value}T00:00:00.000Z`) :
value
)
}
// ------------------------------------------------------------------
// Default
// --------------------------------------------------------------------------
// ------------------------------------------------------------------
export function Default(value: any) {
return value
}
// --------------------------------------------------------------------------
// ------------------------------------------------------------------
// Convert
// --------------------------------------------------------------------------
function TArray(schema: Types.TArray, references: Types.TSchema[], value: any): any {
// ------------------------------------------------------------------
function TArray(schema: TArray, references: TSchema[], value: any): any {
if (IsArray(value)) {
return value.map((value) => Visit(schema.items, references, value))
}
return value
}
function TBigInt(schema: Types.TBigInt, references: Types.TSchema[], value: any): unknown {
function TBigInt(schema: TBigInt, references: TSchema[], value: any): unknown {
return TryConvertBigInt(value)
}
function TBoolean(schema: Types.TBoolean, references: Types.TSchema[], value: any): unknown {
function TBoolean(schema: TBoolean, references: TSchema[], value: any): unknown {
return TryConvertBoolean(value)
}
function TDate(schema: Types.TDate, references: Types.TSchema[], value: any): unknown {
function TDate(schema: TDate, references: TSchema[], value: any): unknown {
return TryConvertDate(value)
}
function TInteger(schema: Types.TInteger, references: Types.TSchema[], value: any): unknown {
function TInteger(schema: TInteger, references: TSchema[], value: any): unknown {
return TryConvertInteger(value)
}
function TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value: any): unknown {
// prettier-ignore
return (schema.allOf.every(schema => Types.TypeGuard.TObject(schema)))
? Visit(Types.Type.Composite(schema.allOf as Types.TObject[]), references, value)
: Visit(schema.allOf[0], references, value)
// prettier-ignore
function TIntersect(schema: TIntersect, references: TSchema[], value: any): unknown {
const allObjects = schema.allOf.every(schema => IsObjectType(schema))
if(allObjects) return Visit(Composite(schema.allOf as TObject[]), references, value)
return Visit(schema.allOf[0], references, value) // todo: fix this
}
function TLiteral(schema: Types.TLiteral, references: Types.TSchema[], value: any): unknown {
function TLiteral(schema: TLiteral, references: TSchema[], value: any): unknown {
return TryConvertLiteral(schema, value)
}
function TNull(schema: Types.TNull, references: Types.TSchema[], value: any): unknown {
function TNull(schema: TNull, references: TSchema[], value: any): unknown {
return TryConvertNull(value)
}
function TNumber(schema: Types.TNumber, references: Types.TSchema[], value: any): unknown {
function TNumber(schema: TNumber, references: TSchema[], value: any): unknown {
return TryConvertNumber(value)
}
function TObject(schema: Types.TObject, references: Types.TSchema[], value: any): unknown {
if (IsObject(value))
return Object.getOwnPropertyNames(schema.properties).reduce((acc, key) => {
return value[key] !== undefined ? { ...acc, [key]: Visit(schema.properties[key], references, value[key]) } : { ...acc }
}, value)
return value
}
function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[], value: any): unknown {
// prettier-ignore
function TObject(schema: TObject, references: TSchema[], value: any): unknown {
const isConvertable = IsObject(value)
if(!isConvertable) return value
return Object.getOwnPropertyNames(schema.properties).reduce((value, key) => {
return !IsUndefined(value[key])
? ({ ...value, [key]: Visit(schema.properties[key], references, value[key]) })
: ({ ...value })
}, value)
}
function TRecord(schema: TRecord<any, any>, references: TSchema[], value: any): unknown {
const propertyKey = Object.getOwnPropertyNames(schema.patternProperties)[0]
const property = schema.patternProperties[propertyKey]
const result = {} as Record<string, unknown>
Expand All @@ -204,30 +222,32 @@ function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[], v
}
return result
}
function TRef(schema: Types.TRef<any>, references: Types.TSchema[], value: any): unknown {
function TRef(schema: TRef<any>, references: TSchema[], value: any): unknown {
return Visit(Deref(schema, references), references, value)
}
function TString(schema: Types.TString, references: Types.TSchema[], value: any): unknown {
function TString(schema: TString, references: TSchema[], value: any): unknown {
return TryConvertString(value)
}
function TSymbol(schema: Types.TSymbol, references: Types.TSchema[], value: any): unknown {
function TSymbol(schema: TSymbol, references: TSchema[], value: any): unknown {
return IsString(value) || IsNumber(value) ? Symbol(value) : value
}
function TThis(schema: Types.TThis, references: Types.TSchema[], value: any): unknown {
function TThis(schema: TThis, references: TSchema[], value: any): unknown {
return Visit(Deref(schema, references), references, value)
}
function TTuple(schema: Types.TTuple<any[]>, references: Types.TSchema[], value: any): unknown {
if (IsArray(value) && !IsUndefined(schema.items)) {
return value.map((value, index) => {
return index < schema.items!.length ? Visit(schema.items![index], references, value) : value
})
}
return value
}
function TUndefined(schema: Types.TUndefined, references: Types.TSchema[], value: any): unknown {
// prettier-ignore
function TTuple(schema: TTuple<any[]>, references: TSchema[], value: any): unknown {
const isConvertable = IsArray(value) && !IsUndefined(schema.items)
if(!isConvertable) return value
return value.map((value, index) => {
return (index < schema.items!.length)
? Visit(schema.items![index], references, value)
: value
})
}
function TUndefined(schema: TUndefined, references: TSchema[], value: any): unknown {
return TryConvertUndefined(value)
}
function TUnion(schema: Types.TUnion, references: Types.TSchema[], value: any): unknown {
function TUnion(schema: TUnion, references: TSchema[], value: any): unknown {
for (const subschema of schema.anyOf) {
const converted = Visit(subschema, references, value)
if (Check(subschema, references, converted)) {
Expand All @@ -236,13 +256,10 @@ function TUnion(schema: Types.TUnion, references: Types.TSchema[], value: any):
}
return value
}
function Visit(schema: Types.TSchema, references: Types.TSchema[], value: any): unknown {
function Visit(schema: TSchema, references: TSchema[], value: any): unknown {
const references_ = IsString(schema.$id) ? [...references, schema] : references
const schema_ = schema as any
switch (schema[Types.Kind]) {
// ------------------------------------------------------
// Structural
// ------------------------------------------------------
switch (schema[Kind]) {
case 'Array':
return TArray(schema_, references_, value)
case 'BigInt':
Expand Down Expand Up @@ -283,14 +300,17 @@ function Visit(schema: Types.TSchema, references: Types.TSchema[], value: any):
return Default(value)
}
}
// --------------------------------------------------------------------------
// ------------------------------------------------------------------
// Convert
// --------------------------------------------------------------------------
// ------------------------------------------------------------------
/** Converts any type mismatched values to their target type if a reasonable conversion is possible. */
export function Convert<T extends Types.TSchema>(schema: T, references: Types.TSchema[], value: unknown): unknown
export function Convert<T extends TSchema>(schema: T, references: TSchema[], value: unknown): unknown
/** Converts any type mismatched values to their target type if a reasonable conversion is possible. */
export function Convert<T extends Types.TSchema>(schema: T, value: unknown): unknown
export function Convert<T extends TSchema>(schema: T, value: unknown): unknown
/** Converts any type mismatched values to their target type if a reasonable conversion is possible. */
// prettier-ignore
export function Convert(...args: any[]) {
return args.length === 3 ? Visit(args[0], args[1], args[2]) : Visit(args[0], [], args[1])
return args.length === 3
? Visit(args[0], args[1], args[2])
: Visit(args[0], [], args[1])
}

0 comments on commit d2955b3

Please sign in to comment.