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"]
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";
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>(),
])
Top comments (0)