Last active
September 25, 2021 16:06
-
-
Save kaave/90be8a8fe95fd72eb5c72ead64b7d1ba to your computer and use it in GitHub Desktop.
My Branded Type Pattern
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as Hex from './hex'; | |
const red = Hex.create('#f00'); | |
const nonBrandedRed = '#f00'; | |
function stdoutOnlyHex(hex: Hex) { | |
console.log(hex); | |
} | |
stdoutOnlyHex(red); | |
stdoutOnlyHex(nonBrandedRed); // Error |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { GraphQLScalarType, Kind } from 'graphql'; | |
import type { GraphQLScalarTypeConfig } from 'graphql'; | |
import { hex } from '.'; | |
import type { Hex } from '.'; | |
function generateHex() { | |
/* cspell: disable-next-line */ | |
return `#${Math.floor(Math.random() * 255).toString(16)}${Math.floor(Math.random() * 255).toString(16)}${Math.floor( | |
Math.random() * 255, | |
).toString(16)}`; | |
} | |
const hexCore: GraphQLScalarTypeConfig<string, Hex> = { | |
name: 'Hex', | |
// value sent to the client | |
serialize: (v: Hex) => v, | |
// value from the client | |
parseValue: v => hex(v), | |
// ast value is always in string format | |
parseLiteral: ast => (ast.kind === Kind.STRING ? hex(ast.value) : null), | |
}; | |
export const GraphQLUuid = new GraphQLScalarType(hexCore); | |
export const MockGraphQLUuid = () => new GraphQLScalarType({ ...hexCore, name: generateHex() }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as Hex from '.'; | |
describe('Hex', () => { | |
describe('is', () => { | |
it('0-9a-zA-Z は Valid', () => { | |
expect(Hex.is('#000')).toBe(true); | |
expect(Hex.is('#0f0')).toBe(true); | |
expect(Hex.is('#9f0')).toBe(true); | |
expect(Hex.is('#FFF')).toBe(true); | |
}); | |
it('全角は Invalid', () => { | |
expect(Hex.is('#000')).toBe(false); | |
expect(Hex.is('#0f0')).toBe(false); | |
expect(Hex.is('#9f0')).toBe(false); | |
expect(Hex.is('#FFF')).toBe(false); | |
expect(Hex.is('#FFF')).toBe(false); | |
}); | |
it('3, 6桁以外は Invalid', () => { | |
expect(Hex.is('#00')).toBe(false); | |
expect(Hex.is('#0000')).toBe(false); | |
expect(Hex.is('#00000')).toBe(false); | |
expect(Hex.is('#0000000')).toBe(false); | |
expect(Hex.is('#00000000')).toBe(false); | |
expect(Hex.is('#000000000')).toBe(false); | |
}); | |
it('範囲外 は Invalid', () => { | |
expect(Hex.is('#g00')).toBe(false); | |
expect(Hex.is('#00G')).toBe(false); | |
}); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// validation は is という命名で統一 | |
export function is(possibleHex: string): possibleHex is Hex { | |
return /^#([\da-fA-F]{2}){3}$/.test(possibleHex) || /^#([\da-fA-F]{1}){3}$/.test(possibleHex); | |
} | |
// factory は create という命名で統一 | |
export function create(from: string): Hex { | |
if (is(from)) { | |
return from as Hex; // ここの cast は許容するため、場合によっては Linter を disabled する | |
} | |
throw new TypeError(`Invalid Hex format: [${from}]`); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 型そのものは global にしたいので ambience | |
// 衝突に重々注意する | |
// `__${typename}Brand` という key を readonly の unique symbol で使用する | |
// https://basarat.gitbook.io/typescript/main-1/nominaltyping | |
declare type Hex = `#{string}` & { readonly __hexBrand: unique symbol }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment