DEV Community

loading...

Create complex type using object spread

mistlog
・2 min read

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

Discussion (0)