0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

effect-tsで入力チェック - Schema

Last updated at Posted at 2025-02-11

入力チェックのやり方のメモ

effect-ts : Schema

単純な入力チェック

単体チェックNG

import { describe, it } from 'vitest'
import { Either, Schema, ParseResult } from 'effect'

describe('effect-ts 入力検証 - person', () => {
  const NameSchema = Schema.String.annotations({
    arbitrary: () => (fc) => fc.constantFrom('山田 太郎', 'Alice Johnson')
  })
  const AgeSchema = Schema.Number.pipe(Schema.between(1, 80), Schema.int())

  const Person = Schema.Struct({
    name: NameSchema,
    age: AgeSchema
  })

  const decode_Person = Schema.decodeEither(Person)

  it('シンプルNG - decode', () => {
    const testData: any = {
      name: null,
      age: 100
    }

    const result = decode_Person(testData, { errors: 'all' })

    if (Either.isLeft(result)) {
      console.error('入力エラー')
      console.error(ParseResult.TreeFormatter.formatErrorSync(result.left))
    } else {
      console.log('問題なし')
    }
  })
})

実行結果

入力エラー
{ readonly name: string; readonly age: between(1, 80) & int }
├─ ["name"]
  └─ Expected string, actual null
└─ ["age"]
   └─ between(1, 80) & int
      └─ From side refinement failure
         └─ between(1, 80)
            └─ Predicate refinement failure
               └─ Expected a number between 1 and 80, actual 100

単体チェックOK

import { describe, it } from 'vitest'
import { Either, Schema, ParseResult } from 'effect'

describe('effect-ts 入力検証 - person', () => {
  const NameSchema = Schema.String.annotations({
    arbitrary: () => (fc) => fc.constantFrom('山田 太郎', 'Alice Johnson')
  })
  const AgeSchema = Schema.Number.pipe(Schema.between(1, 80), Schema.int())

  const Person = Schema.Struct({
    name: NameSchema,
    age: AgeSchema
  })

  const decode_Person = Schema.decodeEither(Person)

  it('シンプルOK - decode', () => {
    const testData: any = {
      name: 'test name',
      age: 50
    }

    const result = decode_Person(testData, { errors: 'all' })

    if (Either.isLeft(result)) {
      console.error('入力エラー')
      console.error(ParseResult.TreeFormatter.formatErrorSync(result.left))
    } else {
      console.log('問題なし')
    }
  })
})

実行結果

問題なし

テストデータ作成(入力データチェック)

import { describe, it } from 'vitest'
import { Schema, Arbitrary, FastCheck } from 'effect'

describe('effect-ts 入力検証 - person', () => {
  const NameSchema = Schema.String.annotations({
    arbitrary: () => (fc) => fc.constantFrom('山田 太郎', 'Alice Johnson')
  })
  const AgeSchema = Schema.Number.pipe(Schema.between(1, 80), Schema.int())

  const Person = Schema.Struct({
    name: NameSchema,
    age: AgeSchema
  })

  const testDataGenerator_Person = Arbitrary.make(Person)

  it('テストデータ作成 - Arbitrary', () => {
    const result = FastCheck.sample(testDataGenerator_Person, 10)
    console.log(result)
  })
})

実行結果

[
  { name: '山田 太郎', age: 74 },
  { name: 'Alice Johnson', age: 3 },
  { name: '山田 太郎', age: 3 },
  { name: 'Alice Johnson', age: 76 },
  { name: 'Alice Johnson', age: 6 },
  { name: 'Alice Johnson', age: 3 },
  { name: 'Alice Johnson', age: 4 },
  { name: '山田 太郎', age: 2 },
  { name: 'Alice Johnson', age: 76 },
  { name: '山田 太郎', age: 54 }
]

複雑な入力チェック

チェックNG

import { describe, it } from 'vitest'
import { Either, Schema, ParseResult } from 'effect'

describe('effect-ts 入力検証 - myform', () => {
  const Password = Schema.Trim.pipe(Schema.minLength(2))

  const MyForm = Schema.Struct({
    password: Password,
    confirm_password: Password
  }).pipe(
    Schema.filter((input) => {
      if (input.password !== input.confirm_password) {
        return {
          path: ['confirm_password'],
          message: '同じパスワードを入力してください'
        }
      }
    })
  )

  const decode_MyForm = Schema.decodeEither(MyForm)

  it('相関チェック - decode', () => {
    const testData: any = {
      password: '12345',
      confirm_password: '1234'
    }

    const result = decode_MyForm(testData, { errors: 'all' })

    if (Either.isLeft(result)) {
      console.error('入力エラー')
      console.error(ParseResult.TreeFormatter.formatErrorSync(result.left))
    } else {
      console.log('問題なし')
    }
  })
})

実行結果

入力エラー
{ { readonly password: minLength(2); readonly confirm_password: minLength(2) } | filter }
└─ Predicate refinement failure
   └─ ["confirm_password"]
      └─ 同じパスワードを入力してください

テストデータ作成(入力データチェック)

import { describe, it } from 'vitest'
import { Schema, Arbitrary, FastCheck } from 'effect'

describe('effect-ts 入力検証 - myform', () => {
  const Password = Schema.Trim.pipe(Schema.minLength(2))

  const MyForm = Schema.Struct({
    password: Password,
    confirm_password: Password
  }).pipe(
    Schema.filter((input) => {
      if (input.password !== input.confirm_password) {
        return {
          path: ['confirm_password'],
          message: '同じパスワードを入力してください'
        }
      }
    })
  )

  const testDataGenerator_MyForm = Arbitrary.make(MyForm)

  it('テストデータ作成 - Arbitrary', () => {
    const result = FastCheck.sample(testDataGenerator_MyForm, 10)
    console.log(result)
  })
})

実行結果

[
  { password: '~{', confirm_password: '~{' },
  { password: '$#', confirm_password: '$#' },
  { password: 'z&', confirm_password: 'z&' },
  { password: '&&', confirm_password: '&&' },
  { password: '%#', confirm_password: '%#' },
  { password: '&&', confirm_password: '&&' },
  { password: 'z$', confirm_password: 'z$' },
  { password: '%#', confirm_password: '%#' },
  { password: '#!', confirm_password: '#!' },
  { password: 'e:', confirm_password: 'e:' }
]

二つの組み合わせ

MyForm の pipe以後のチェック処理が短絡してなくなっている気がする。
ここらへんを全部出す方法は調査中

チェックNG

import { describe, it } from 'vitest'
import { Either, Schema, ParseResult } from 'effect'

describe('effect-ts 入力検証 - myform と person', () => {
  const Password = Schema.Trim.pipe(Schema.minLength(2))

  const NameSchema = Schema.String.annotations({
    arbitrary: () => (fc) => fc.constantFrom('山田 太郎', 'Alice Johnson')
  })
  const AgeSchema = Schema.Number.pipe(Schema.between(1, 80), Schema.int())

  const Person = Schema.Struct({
    name: NameSchema,
    age: AgeSchema
  })

  const MyForm = Schema.Struct({
    password: Password,
    confirm_password: Password
  }).pipe(
    Schema.filter((input) => {
      if (input.password !== input.confirm_password) {
        return {
          path: ['confirm_password'],
          message: '同じパスワードを入力してください'
        }
      }
    })
  )

  const PersonForm = Schema.extend(Person, MyForm)

  const decode_PersonForm = Schema.decodeEither(PersonForm)

  it('チェックの合体 - decode', () => {
    const testData: any = {
      name: null,
      age: 100,
      password: '12345',
      confirm_password: '1234'
    }

    const result = decode_PersonForm(testData, { errors: 'all' })

    if (Either.isLeft(result)) {
      console.error('入力エラー')
      console.error(ParseResult.TreeFormatter.formatErrorSync(result.left))
    } else {
      console.log('問題なし')
    }
  })
})

実行結果

入力エラー
{ { readonly name: string; readonly age: between(1, 80) & int; readonly password: minLength(2); readonly confirm_password: minLength(2) } | filter }
└─ From side refinement failure
   └─ { readonly name: string; readonly age: between(1, 80) & int; readonly password: minLength(2); readonly confirm_password: minLength(2) }
      ├─ ["name"]
        └─ Expected string, actual null
      └─ ["age"]
         └─ between(1, 80) & int
            └─ From side refinement failure
               └─ between(1, 80)
                  └─ Predicate refinement failure
                     └─ Expected a number between 1 and 80, actual 100

テストデータ作成(入力データチェック)

import { describe, it } from 'vitest'
import { Schema, Arbitrary, FastCheck } from 'effect'

describe('effect-ts 入力検証 - myform と person', () => {
  const Password = Schema.Trim.pipe(Schema.minLength(2))

  const NameSchema = Schema.String.annotations({
    arbitrary: () => (fc) => fc.constantFrom('山田 太郎', 'Alice Johnson')
  })
  const AgeSchema = Schema.Number.pipe(Schema.between(1, 80), Schema.int())

  const Person = Schema.Struct({
    name: NameSchema,
    age: AgeSchema
  })

  const MyForm = Schema.Struct({
    password: Password,
    confirm_password: Password
  }).pipe(
    Schema.filter((input) => {
      if (input.password !== input.confirm_password) {
        return {
          path: ['confirm_password'],
          message: '同じパスワードを入力してください'
        }
      }
    })
  )

  const PersonForm = Schema.extend(Person, MyForm)

  const testDataGenerator_PersonForm = Arbitrary.make(PersonForm)

  it('テストデータ作成 - Arbitrary', () => {
    const result = FastCheck.sample(testDataGenerator_PersonForm, 10)
    console.log(result)
  })
})

実行結果

[
  {
    name: 'Alice Johnson',
    age: 4,
    password: '$"',
    confirm_password: '$"'
  },
  {
    name: 'Alice Johnson',
    age: 23,
    password: '8\f{',
    confirm_password: '8\f{'
  },
  {
    name: 'Alice Johnson',
    age: 76,
    password: 'y{',
    confirm_password: 'y{'
  },
  {
    name: '山田 太郎',
    age: 3,
    password: '|reft',
    confirm_password: '|reft'
  },
  {
    name: 'Alice Johnson',
    age: 69,
    password: 'y&',
    confirm_password: 'y&'
  },
  { name: '山田 太郎', age: 3, password: '|#', confirm_password: '|#' },
  { name: '山田 太郎', age: 51, password: '!%', confirm_password: '!%' },
  {
    name: '山田 太郎',
    age: 2,
    password: 'xkey"',
    confirm_password: 'xkey"'
  },
  { name: '山田 太郎', age: 6, password: '~#', confirm_password: '~#' },
  { name: '山田 太郎', age: 56, password: '&"', confirm_password: '&"' }
]
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?