入力チェックのやり方のメモ
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: '&"' }
]