created: 2022-07-04T03:25:00.010Z

class-validatorとclass-transformerを使ってコマンドラインオプションを受け取る

nodejsのコマンドラインパーサーは色々あるが、class-validatorclass-transformer を使ってパースしてみたら、新しく既存のコマンドラインパーサーを使うよりも覚えることも少なくて型安全に引数が受け取れてよかった。

受け取る引数をこんな感じでクラス定義する。

import { IsOptional, IsBoolean, IsString } from "class-validator";

export class Option {
  @IsOptional()
  @IsString()
  tableNamePattern?: string;

  @IsOptional()
  @IsBoolean()
  dryrun?: boolean;
  
  // メソッドもかける
  isToIgnore() {
    ....
  }
}

実装

パースする処理も20行ほどで済む。

import { plainToClass, ClassConstructor } from "class-transformer";
import { validateSync } from "class-validator";
import { camelCase } from "change-case"
import { formatErrors } from './utility';

export const loadOption = <T>(Cls: ClassConstructor<T>, v: unknown) => {
  const obj = plainToClass(Cls, v);
  const errors = validateSync(obj as any);
  if (errors.length) {
    throw OptionError(formatErrors("Faliled to load options.", errors));
  } else {
    return obj as T;
  }
};

export const parseArgs = (argv: string[]) => {
  const entry = [];
  for (const opt of argv) {
    const [, name, param] = opt.match(/--([\w-]+)=(\S+)/) || [];
    if (name?.length && param?.length) {
      entry.push([camelCase(name), param]);
      continue
    }
    const [, flag] = opt.match(/--([\w-]+)/) || [];
    if (flag?.length) {
      entry.push([camelCase(flag), true]);
      continue
    }
  }
  return Object.fromEntries(entry);
};


export class OptionError extends Error {};