Skip to content
This repository has been archived by the owner on Sep 14, 2023. It is now read-only.

Approach to Branded Types & RPC-related Validation #96

Closed
harrysolovay opened this issue Jun 12, 2022 · 5 comments
Closed

Approach to Branded Types & RPC-related Validation #96

harrysolovay opened this issue Jun 12, 2022 · 5 comments

Comments

@harrysolovay
Copy link
Contributor

Do U.HexString and comparable branded types buy us much in terms of safety / DX?

@harrysolovay harrysolovay changed the title Think Through Approach to Branded Types Approach to Branded Types & RPC-related Validation Jun 12, 2022
@tjjfvi
Copy link
Contributor

tjjfvi commented Jun 13, 2022

Yes, branded types give us much better DX, not just because of safety, but because they describe what things are. string is impenetrable and near-useless, but MultiAddressString is much less so.

However, I think we should rework our approach to branded types.

type Branded<T, Brand extends PropertyKey, V = undefined> = T & Record<Brand, V>;

declare const _hex: unique symbol;
export type Hex<T extends Uint8Array = Uint8Array> = Branded<string, typeof _hex, T>;

declare const _encoded: unique symbol;
export type Encoded<T> = Branded<Uint8Array, typeof _encoded, T>;

declare const _hash: unique symbol;
export type Hash<T = unknown, K extends HasherKind = HasherKind> = Branded<Uint8Array, typeof _hash, [T, K]>

export type HexEncoded<T> = Hex<Encoded<T>;
export type HexHash<T = unknown, K extends HasherKind = HasherKind> = Hex<Hash<T, K>>;

With this, instead of manually creating a branded MultiAddressString, we can simply use HexEncoded<MultiAddress>. We can also have things like

export function encodeHex<T>($t: Codec<T>, value: T): HexEncoded<T> {
  ...
}
export function decodeHex<T>($t: Codec<T>, hex: HexEncoded<T>): T {
  ...
}

And then type-safely call encodeHex($multiAddress, multiAddress) or decodeHex($multiAddress, multiAddressStr).

@harrysolovay
Copy link
Contributor Author

I like this approach of integrating branded types into our conversion utils. Nice. That signature––HexEncoded<MultiAddress>––is beautiful.

Why the T extends Uint8Array in the Hex type params?

@harrysolovay
Copy link
Contributor Author

harrysolovay commented Jun 13, 2022

Also, I'm not sure if the branded types belong in utils/branded.ts. How would you feel about moving branded types into a top-level branded.ts file? Or better yet, into a new_types/mod.ts? We could include validations/conversions in this file as well. Perhaps the hex utils belong in here too.

@tjjfvi
Copy link
Contributor

tjjfvi commented Jun 13, 2022

Why the T extends Uint8Array in the Hex type params?

Because, ultimately, the data contained in a hex string is a Uint8Array. If that Uint8Array itself represents data, then we can narrow it further, like we do with Hex<Encoded<T>> and Hex<Hash<T>>.

It also opens up the possibility of signatures like this in our utils/hex.ts:

export function encode<T extends Uint8Array>(array: T): Hex<T> {
  ...
}
export function decode<T extends Uint8Array>(hex: Hex<T>): T {
  ...
}

Also, I'm not sure if the branded types belong in utils/branded.ts. How would you feel about moving branded types into a top-level branded.ts file? Or better yet, into a new_types/mod.ts? We could include validations/conversions in this file as well. Perhaps the hex utils belong in here too.

Perhaps they should be moved in with primitives (or whatever it is renamed to)?

@harrysolovay
Copy link
Contributor Author

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants