DEV Community

mistlog
mistlog

Posted on

Create complex type using object spread

use typetype: https://github.com/mistlog/typetype

The url parser example:

export type function parseURL = (text) => ^{ if (parseProtocol<text> extends [infer protocol, infer rest]) { return { protocol, ...parseAuthority<rest> } } else { return never } } type function parseProtocol = (text) => ^{ if(text extends `${infer protocol}://${infer rest}`) { return [ protocol, rest ] } else { return never } } type function parseUserInfo = (text) => ^{ if(text extends `${infer username}:${infer password}`) { return { username, password } } else { return { username: text } } } type function parseAuthority = (text) => ^{ if(text extends `${infer authority}@${infer rest}`) { return { authority: parseUserInfo<authority>, ...parseHost<rest> } } else { return { authority: null, rest: text } } } type function parseHost = (text) => ^{ if(text extends `${infer name}:${infer port}`) { return ^{ if(parsePort<port> extends never) { return never } else { return { name, port } } } } else { return { name: text } } } type function parsePort = (text) => ^{ if(isNumberString<text> extends true) { return text } else { return never } } type function isNumberString = (text) => ^{ if(text extends "") { return never } else { return _isNumberString<text> } } type function _isNumberString = (text) => ^{ /* the end of recursion: each char of text is digit, no more chars to inspect */ if(text extends "") { return true } else if(text extends `${infer digit}${infer rest}`) { return ^{ if(digit extends Digit) { return _isNumberString<rest> } else { return false } } } else { return false } } type Digit = union ["0" , "1" , "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9"] 
Enter fullscreen mode Exit fullscreen mode

Then, the generated type:

export type parseURL<text> = parseProtocol<text> extends [infer protocol, infer rest] ? object$assign<{}, [{ protocol: protocol; }, parseAuthority<rest>]> : never; type parseProtocol<text> = text extends `${infer protocol}://${infer rest}` ? [protocol, rest] : never; type parseUserInfo<text> = text extends `${infer username}:${infer password}` ? { username: username; password: password; } : { username: text; }; type parseAuthority<text> = text extends `${infer authority}@${infer rest}` ? object$assign<{}, [{ authority: parseUserInfo<authority>; }, parseHost<rest>]> : { authority: null; rest: text; }; type parseHost<text> = text extends `${infer name}:${infer port}` ? parsePort<port> extends never ? never : { name: name; port: port; } : { name: text; }; type parsePort<text> = isNumberString<text> extends true ? text : never; type isNumberString<text> = text extends "" ? never : _isNumberString<text>; type _isNumberString<text> = text extends "" ? true : text extends `${infer digit}${infer rest}` ? digit extends Digit ? _isNumberString<rest> : false : false; type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"; 
Enter fullscreen mode Exit fullscreen mode

test it!

import { parseURL } from "./url-parser-2"; import { Test } from "ts-toolbelt" const { checks, check } = Test type url = `http://admin:123456@github.com:8080`; type result = parseURL<url> checks([ check<result, { name: "github.com"; port: "8080"; authority: { username: "admin"; password: "123456"; }; protocol: "http"; }, Test.Pass>(), ]) 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)