UNPKG

@atomiqlabs/sdk

Version:

atomiq labs SDK for cross-chain swaps between smart chains and bitcoin

139 lines (116 loc) 5.36 kB
import {InputSchema, InputSchemaField} from "./ApiTypes"; // Errors function invalidInput(path: string, expected: string, value: unknown): Error { const actualType = value == null ? String(value) : Array.isArray(value) ? "array" : typeof(value); return new Error(`Invalid input "${path}", expected ${expected}, got ${actualType}`); } function missingInput(path: string): Error { return new Error(`Missing required input "${path}"`); } function invalidAllowedValue(path: string, allowedValues: readonly (string | number | bigint)[], value: string | number | bigint): Error { return new Error( `Invalid input "${path}", expected one of: ${allowedValues.map(val => val.toString()).join(", ")}, got ${value.toString()}` ); } // Parsers function parseNumber(path: string, value: unknown): number { let result: number; if(typeof(value) === "number") { result = value; } else if(typeof(value) === "string") { const trimmedValue = value.trim(); if(trimmedValue === "") throw invalidInput(path, "finite number", value); result = Number(trimmedValue); } else throw invalidInput(path, "finite number", value); if(!Number.isFinite(result)) throw invalidInput(path, "finite number", value); return result; } function parseBigInt(path: string, value: unknown): bigint { if(typeof(value) === "bigint") return value; if(typeof(value) === "number") { if(!Number.isSafeInteger(value)) throw invalidInput(path, "safe integer", value); return BigInt(value); } if(typeof(value) === "string") { const trimmedValue = value.trim(); if(!/^[+-]?\d+$/.test(trimmedValue)) throw invalidInput(path, "integer", value); return BigInt(trimmedValue); } throw invalidInput(path, "integer", value); } function parseBoolean(path: string, value: unknown): boolean { Boolean(value); if(typeof(value) === "boolean") return value; if(value === "true") return true; if(value === "false") return false; throw invalidInput(path, "boolean", value); } // Object type check function isObject(value: unknown): value is Record<string, unknown> { return value != null && typeof(value) === "object" && !Array.isArray(value); } // Allowed values for enums function applyAllowedValues<T extends string | number | bigint>(path: string, value: T, allowedValues?: readonly T[]): T { if(allowedValues != null && !allowedValues.includes(value)) { throw invalidAllowedValue(path, allowedValues, value); } return value; } function parseField<T>(field: InputSchemaField<T>, value: unknown, path: string): T { switch(field.type) { case "string": { if(typeof(value) !== "string") throw invalidInput(path, "string", value); return applyAllowedValues(path, value, field.allowedValues as readonly string[] | undefined) as T; } case "number": { const parsedValue = parseNumber(path, value); return applyAllowedValues(path, parsedValue, field.allowedValues as readonly number[] | undefined) as T; } case "bigint": { const parsedValue = parseBigInt(path, value); return applyAllowedValues(path, parsedValue, field.allowedValues as readonly bigint[] | undefined) as T; } case "boolean": return parseBoolean(path, value) as T; case "array": { if(!Array.isArray(value)) throw invalidInput(path, "array", value); if(field.items == null) return [...value] as T; return value.map((item, index) => parseField(field.items as InputSchemaField, item, `${path}[${index}]`)) as T; } case "object": { if(!isObject(value)) throw invalidInput(path, "object", value); if(field.properties == null) return {...value} as T; return _parseApiInput(field.properties as InputSchema<T>, value, path); } default: throw new Error(`Unsupported input schema type for "${path}": ${(field as InputSchemaField).type}`); } } function _parseApiInput<TInput>(inputSchema: InputSchema<TInput>, rawInput: unknown, parentPath: string = ""): TInput { if(!isObject(rawInput)) { throw invalidInput(parentPath || "input", "object", rawInput); } const parsedInput: Partial<TInput> = {}; for(const key in inputSchema) { const field = inputSchema[key]; const value = rawInput[key]; const path = parentPath === "" ? key : `${parentPath}.${key}`; if(value == null) { if(field.required) throw missingInput(path); continue; } parsedInput[key] = parseField(field as InputSchemaField<TInput[typeof key]>, value, path); } return parsedInput as TInput; } /** * Parses raw input values according to the endpoint schema. * * This accepts untyped transport input such as HTTP query params, JSON request bodies, * or CLI arguments and returns the normalized callback input type. * * @category API */ export function parseApiInput<TInput>(inputSchema: InputSchema<TInput>, rawInput: unknown): TInput { return _parseApiInput(inputSchema, rawInput); }