0.1.5 • Published 1 year ago

ts-union-parser v0.1.5

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

ts-union-parser

A CLI tool for generating parsers from discriminated union TypeScript types.

Version Downloads/week

Use case

Suppose you have the following types defining the shape of data you'll be sending over a WebSocket connection:

type MessageRequest = {
  type: 'request';
  item: 'state' | 'user-count';
}

type MessageState = {
  type: 'state';
  state: State;
}

type MessageUserCount = {
  type: 'user-count';
  count: number;
}

type MessageError = {
  type: 'error';
  message?: string;
}

type State = {
  connections: number;
  status: 'ok' | 'err';
}

/**
 * This is a "discriminated union". TypeScript can narrow the type of a `Message`
 * using the `type` field, which is unique for each of the four types of the union. 
 */
type Message = MessageRequest | MessageState | MessageUserCount | MessageError;

When a WebSocket message is received, we often want to parse it and validate that it is the correct shape before attempting to perform any logic using the (potentially ill-formed) data. This script will generate a parser function that does this parsing and validation step. The output given the types above is:

export function parser(data: string): Message {
  const _data = JSON.parse(data);
  if (typeof _data !== 'object') {
    throw new Error('Parsed data is not an object');
  }
  if (!_data.hasOwnProperty('type')) {
    throw new Error('Parsed data does not have "type" field.');
  }
  switch (_data['type']) {
    case 'request':
      if (isMessageRequest(_data)) return _data as MessageRequest;
    case 'state':
      if (isMessageState(_data)) return _data as MessageState;
    case 'user-count':
      if (isMessageUserCount(_data)) return _data as MessageUserCount;
    case 'error':
      if (isMessageError(_data)) return _data as MessageError;
    default:
      throw new Error(
        'Parsed data does not contain valid discriminator value.'
      );
  }
}
function isMessageRequest(data: any): data is MessageRequest {
  return (
    typeof data === 'object' &&
    data['type'] === 'request' &&
    (data['item'] === 'state' || data['item'] === 'user-count')
  );
}
function isMessageState(data: any): data is MessageState {
  return (
    typeof data === 'object' &&
    data['type'] === 'state' &&
    isState(data['state'])
  );
}
function isState(data: any): data is State {
  return (
    typeof data === 'object' &&
    typeof data['connections'] === 'number' &&
    (data['status'] === 'ok' || data['status'] === 'err')
  );
}
function isMessageUserCount(data: any): data is MessageUserCount {
  return (
    typeof data === 'object' &&
    data['type'] === 'user-count' &&
    typeof data['count'] === 'number'
  );
}
function isMessageError(data: any): data is MessageError {
  return (
    typeof data === 'object' &&
    data['type'] === 'error' &&
    (data.hasOwnProperty('message')
      ? typeof data['message'] === 'string'
      : true)
  );
}