- Published on
yup docs 에 대한 정리
- Authors
- Name
- 길재훈
yup 에 대한 정리
react-hook-form
을 사용하면서, yup
을 resolver
로 사용하고 있다.yup
을 사용하면서 기껏해야 yup.string
및 yup.email
, yup.objact
등등...
yup
에서 제공하고 있는 많은 기능중, 한정적으로 사용하고 있다는 생각이 들어 내용 정리차, blog
에 기록해두려 한다.
yup 이란?
다음은 docs
에 적혀져 있는 문구이다.
Yup
은runtime
에 값 분석 및 유효성검사를 하기 위한schema builder
이다.
이말은, schema
를 작성후, 값을 평가하여, 해당 값이 정확하게 맞아 떨어지는지를 검사하고 평가한다.
이러한 평가하는 방식은, input
사용시 꼭 필요한 기능이다. 그러면서 다음처럼 같이 설명해준다.
Yup schema
는 매우 표현력이 좋으며, 값 변환 및 의존성있는 값들의 평가 같은 복잡한modeling
을 허용한다.
위의말은, 유연하게 사용할수 있는 method
들이 많으며, 그로인해 유효성검사를
쉽게 할수 있다 정도로 이해하면 될것 같다.
Schema 란?
Schema
는 type
이 디자인된 형태라고 보면된다. Schema
를 통해, input
을 조작할 수 있다.
이러한 Schema
는 method chian
형태로, 각 기능들을 나열하여 처리 가능하다. 아래는 Docs
에 나와 있는 예시이다.
const num = number().cast('1') // 1
const obj = object({
firstName: string().lowercase().trim(),
})
.json()
.camelCase()
.cast('{"first_name": "jAnE "}') // { firstName: 'jane' }
위의 예시는 나열되어 있는 예시는, json.parser
를
진행하고, object
를 cacmelCase
로 만들라고 하는 명령이다.
cast
는 어떠한 값을 넣으면, 위의 나열된 method chain
으로 변환해주는
함수라고 생각하면 된다.
실제로 위의 값으로 JSON
객체를 넣었는데, 결과값으로, Object
를 반환하는것을 볼 수 있다.
Yup
은 이렇게 input
사용시 발생할 수 있는 문제들을 해결하기 위해 여러 method
들을 제공하며, typescript
역시 지원해 편리한 사용이 가능하다.
docs
에서는 custom transrom
을 만들어 처리도 가능하다고 말한다.
const reversedString = string()
.transform((currentValue) => currentValue.split('').reverse().join(''))
.cast('dlrow olleh') // "hello world"
여기서, transform
을 사용해 안쪽에 함수를 전달해주면, Yup
에서
직접 만든 method
를 사용하여 조작 가능하다.
다른 예시도 많으므로, Docs 에서 살펴보는것이 좋을듯 싶다.
Typescript integration
Yup
은 정적 Typescript interface
생성이 가능하다. 다음을 보자.
import * as yup from 'yup'
const personSchema = yup.object({
firstName: yup.string().defined(),
nickName: yup.string().default('').nullable(),
sex: yup
.mixed()
.oneOf(['male', 'female', 'other'] as const)
.defined(),
email: yup.string().nullable().email(),
birthDate: yup.date().nullable().min(new Date(1900, 0, 1)),
})
interface Person extends yup.InferType<typeof personSchema> {
// using interface instead of type generally gives nicer editor feedback
}
interface Person
에 yup.InferType
을 사용하여, type
을 추론한후 확장하여 지정한다.
또한 Schema
에 default
값을 줄수 있다고도 되어있다.
import { string } from 'yup'
const value: string = string().default('hi').validate(undefined)
// vs
const value: string | undefined = string().validate(undefined)
Yup
은 typescript
를 지원한다고 했으므로, 역시나 interface
를 지정하여, 해당 interface
를 yup
에 넘겨 처리할 수 도 있다.
import { object, number, string, ObjectSchema } from 'yup';
interface Person {
name: string;
age?: number;
sex: 'male' | 'female' | 'other' | null;
}
// will raise a compile-time type error if the schema does not produce a valid Person
const schema: ObjectSchema<Person> = object({
name: string().defined(),
age: number().optional(),
sex: string<'male' | 'female' | 'other'>().nullable().defined();
});
// ❌ errors:
// "Type 'number | undefined' is not assignable to type 'string'."
const badSchema: ObjectSchema<Person> = object({
name: number(),
});
API
API
는 다음의 module
형식을 따른다고 한다.
// core schema
import { mixed, string, number, boolean, bool, date, object, array, ref, lazy } from 'yup'
// Classes
import {
Schema,
MixedSchema,
StringSchema,
NumberSchema,
BooleanSchema,
DateSchema,
ArraySchema,
ObjectSchema,
} from 'yup'
// Types
import type { InferType, ISchema, AnySchema, AnyObjectSchema } from 'yup'
reach
reach
는 nested schemas
중에 제공된 path
를 기반으로, 그 값을 찾는다.
import { reach } from 'yup'
let schema = object({
nested: object({
arr: array(object({ num: number().max(4) })),
}),
})
reach(schema, 'nested.arr.num')
reach(schema, 'nested.arr[].num')
reach(schema, 'nested.arr[1].num')
reach(schema, 'nested["arr"][1].num')
ref
ref
는 필드의 자속 혹은 형제를 참조하는 field
를 만들 수 있다.
아래를 보도록 하자.
import { ref, object, string } from 'yup'
let schema = object({
baz: ref('foo.bar'),
foo: object({
bar: string(),
}),
x: ref('$x'),
})
schema.cast({ foo: { bar: 'boom' } }, { context: { x: 5 } })
// => { baz: 'boom', x: 5, foo: { bar: 'boom' } }
위의 예를 보면, baz
는 ref
를 통해, 자신의 형제 필드의 자손인 foo.bar
의 값을 참조하는것을 볼 수 있다.
여기서, $x
의 의미가 중요한다, Yup
에서는 $
기호를 이용하여, 컨텍스트의 값을 참조 할 수 있다.
위의 예시를 보면, { context: { x: 5 } }
를 통해, 추가정보를를 전달하기 위해 context
에 x
값을 넣었고, schema
에서는 context
의 x
를 사용하기 위해 $
를 사용하여 참조하고 있다.
context
schema
검증시 추가 정보를 전달하기 위한 객체이다.context
는 schema
검증시, 검증 함수에 전달되고, 해당 검증 함수에서 "$"
를 사용해서 context
의 field
를 참조한다.
context
는 다음의 proerties
로 이루어져 있다.
"value", "path", "label
ref
의 중요한 부분중 하나는, 자기 자신의 field
를 참조할 수 있다는 것이다.
이러한 부분은 circuler dependencies
가 발생할 수 있으니, 조심스럽게 사용해야 한다.
lazy
유효성 검사 / 캐스트 시간에 평가되는 스키마
Docs
에서는 이렇게 말하고 있다.
이는 나중에 평가되는 schema
라고 해서 lazy
라고 명명되어진것 같다.
이 method
는 polymorphic(다형성)
및 array
같은 Tree
를 가진 Schema
에서 재귀적으로 생성하는데 유용하다고 되어 있다.
사실 그렇게까지는 와닿지는 않은 방식이라, 써보면서 익혀야 될것 같다.
let node = object({
id: number(),
child: yup.lazy(() => node.default(undefined)),
})
let renderable = yup.lazy((value) => {
switch (typeof value) {
case 'number':
return number()
case 'string':
return string()
default:
return mixed()
}
})
let renderables = array().of(renderable)
ValidationError
이름을 보면 알겠지만, 유효성 검사 오류를 지정해주는 함수이다. 유효성 검사가 실패했을때 error
를 던진다. Docs
에서는 다음처럼 이루어져 있다고 한다.
- name: "ValidationError"
- type: 실패된
test이름
또는type
을 지정한다. - value: 테스트된
field
의value
값이다. - params ?:
test inpu
들이다, 에를 들어,max value
,regex
같은... - errors:
error message
들의 배열이다. - inner:
집계 오류인 경우
, 유효성 검사chain
초기에 발생하는,ValidataionErrors
의 배열이다.abortEarly
옵션이false
일때, 발생한 각 오류를 검사할 수 있다. 이 오류에는 각 내부 오류의 모든 메시지가 포함된다.
Schema
schema
는 bastract base class(추상 기반 class)
이다.schema
는 schema type
에 기본 methods
및 properties
를 제공한다.
Schema.clone(): Schema
Schema
를 deep copy
한다. Schema
복제는 상태 변경과 함께 새 스키마를 반환하기 위해 내부적으로 사용된다고 한다
Schema.label(label: string): Schema
error message
안의 사용된 key name
을 overrinding
한다.
Schema.meta(metadata: object): Schema
metadata
객체를 추가한다 schema
와 함께 data
를 저장할때 유용하다 cast
객체 자신에게 포함되지 않는, data
를 저장할때 유용하다
Schema.describe(options?: ResolveOptions): SchemaDescription
직렬화된 description
객체안의 schema
의 세부정보 모음이다.
(like meta, labels, active tests)
Docs
에서의 예제는 다음과 같다
const schema = object({
name: string().required(),
})
const description = schema.describe()
이렇게 하면 description
변수에는 schema
에 설정한 세부정보를 담은
객체를 가진다.
하지만, dynamic component
같은 경우는, 정확한 schema
설명을 리턴하기 위한 더 많은 context
를 서술해야 한다.
import { ref, object, string, boolean } from 'yup'
let schema = object({
isBig: boolean(),
count: number().when('isBig', {
is: true,
then: (schema) => schema.min(5),
otherwise: (schema) => schema.min(0),
}),
})
schema.describe({ value: { isBig: true } })
위의 when()
은 isBig
의 값이 true
이면, count
는 min
값은 5
이고, 아니면 0
이다. 로 해석된다.
이렇게 describe
함수를 하용할때, 값이 dynamic
하게 변경되는 값이면,context
를 작성하여, 처리해주어야 한다고 서술되어 있다.
해당 부분의
docs
를 보면서 개념적이해를 하고 있는 상황이라 실제로 써보면서 조금더 살펴보아야 할것 같다.
Docs
에서는 아래의 코드를 부여주면서 description types
라고 한다. 참고로, schema type
에 의존하여 약간씩 달라질 수 있다고 한다.
interface SchemaDescription {
type: string
label?: string
meta: object | undefined
oneOf: unknown[]
notOneOf: unknown[]
default?: unknown
nullable: boolean
optional: boolean
tests: Array<{ name?: string; params: ExtraParams | undefined }>
// Present on object schema descriptions
fields: Record<string, SchemaFieldDescription>
// Present on array schema descriptions
innerType?: SchemaFieldDescription
}
type SchemaFieldDescription = SchemaDescription | SchemaRefDescription | SchemaLazyDescription
interface SchemaRefDescription {
type: 'ref'
key: string
}
interface SchemaLazyDescription {
type: string
label?: string
meta: object | undefined
}
어허,, 이부분은 추후 더 살펴보아야 할것 같다..
Schema.concat(schema: Schema): Schema
2개의 schema 를 결합하여 새로운 schema
를 만든다 동일한 유형의 schema
만 concatenated
한다
concat
은 schema
를 재정의한다는 것에서 merge
기능이 아니라고 말한다.
import * as Yup from 'yup'
const schema1 = Yup.object().shape({
name: Yup.string().required(),
age: Yup.number().integer().min(18).max(99),
})
const schema2 = Yup.object().shape({
email: Yup.string().email().required(),
password: Yup.string().matches(/^[a-zA-Z0-9]{3,30}$/),
})
const mergedSchema = schema1.concat(schema2)
Schema.validate(value: any, options?: object): Promise<InferType<Schema>, ValidationError>
input value
의 유효성을 평가하고 리턴한다. 리턴될 값은 평가된 값 혹은 throwing error
이다.
이 method
는 비동기적이고, promise object
를 반환한다. 이 promise object
는 value
와 함께 이행(fullfilled
) 되거나,ValidationError
와 함께 거부(reject
) 된다.
value = await schema.validate({ name: 'jimmy', age: 24 })
options
는 좀더 구체적으로 조작하기위해 제공한다.
interface Options {
// when true, parsing is skipped an the input is validated "as-is"
strict: boolean = false
// Throw on the first error or collect and return all
abortEarly: boolean = true
// Remove unspecified keys from objects
stripUnknown: boolean = false
// when `false` validations will be performed shallowly
recursive: boolean = true
// External values that can be provided to validations and conditionals
context?: object
}
위의 제공되는 Options
를 제공한다.
Schma.validateSync(value: any, options?: object): InferType<Scehma>
동기적으로 validations
를 실행한다. 이는 결과를 반환하거나, ValidationError
를 throw
한다
options
는 위의 validate
와 동일하다
동기적 validation
은 aync test
가 없는 경우에만 작동한다.
예를들어 Promise
를 반환하는 test
같은경우 다음과 같이 작동한다.
let schema = number().test('is-42', "this isn't the number i want", (value) =>
Promise.resolve(value != 42)
)
schema.validateSync(42) // throws Error
다음은 Promise
를 반환하지 않는 test
이다.
let schema = number().test('is-42', "this isn't the number i want", (value) => value != 42)
schema.validateSync(23) // throws ValidationError
Schema.validateAt, ValidationError
중첩된 경로를 통해 검증하지만, 결과 schema
를 유효성 대상으로 사용한다.
let schema = object({
foo: array().of(
object({
loose: boolean(),
bar: string().when('loose', {
is: true,
otherwise: (schema) => schema.strict(),
}),
})
),
})
let rootValue = {
foo: [{ bar: 1 }, { bar: 1, loose: true }],
}
await schema.validateAt('foo[0].bar', rootValue) // => ValidationError: must be a string
await schema.validateAt('foo[1].bar', rootValue) // => '1'
Schema.validateSyncAt
이는 validateAt
의 동기적 버전이다.
Schema.isValid
주어진 value
가 schema
와 일치하면 true
를 리턴한다. isValid
는 asunchronous
이므로, promise
객체를 반환한다.
Schema.isValidSync
isValid
의 동기적
버전이다.
Schema.cast
주어진 value
와 Schema
와 강제로 일치하기 위해 시도한다. 예를 들어, number()
타입을 사용할때 '5'
는 5
로 변환한다.
cast
가 실패되면 일반적으로 null
을 반환한다. 그러나, 유효하지않은 string
이라면 NaN
같은 결과를 리턴할수도 있다.
설정할 수 있는 값들 역시 같이 제공한다.
interface CastOptions<TContext extends {}> {
// Remove undefined properties from objects
stripUnknown: boolean = false
// Throws a TypeError if casting doesn't produce a valid type
// note that the TS return type is inaccurate when this is `false`, use with caution
assert?: boolean = true
// External values that used to resolve conditions and references
context?: TContext
}
Schema.isType
전달된 value
에 대해서 type check
를 한다. 일치한다면 true
를 반환하며, value
를 cast
하지는 않는다.
모든 Schema
타입을 체크하는데 isType
을 사용해야한다.
Schema.strip
출력 object
에서 제거할 schema
를 표시한다. 오직, 중첩된 schema
에서 작동한다.
let schema = object({
useThis: number(),
notThis: string().strip(),
})
schema.cast({ notThis: 'foo', useThis: 4 }) // => { useThis: 4 }
Docs
에서는 strip
이 적용된 schema
는 타입이 naver
으로 추론되므로, 모든 유형에서 제거된다고 한다.
let schema = object({
useThis: number(),
notThis: string().strip(),
})
InferType<typeof schema> /*
{
useThis?: number | undefined
}
*/
Schema.default
value
가 undefined
일때, default value
를 설정한다.default
는 변형이 실행된 이후에 생성된다.
이는 유효성검사 이전에 지정된 안전한 default
를 보장하는데 도움이 된다.
이 default value
는 사용될때마다, cloned
되어, object
및 array
사용시 크기가 클 경우 성능 저하가 발생될수 있다.
이 overhead
를 방지하기 위해 새로운 default
를 반환하는 function
을 통해 값을 전달하는 방식도 존재한다.
그 값이 크지 않다면 기본값으로 설정해도 되지만, 객체의 크기가 클경우에는 function
으로 대체해서 처리하는것이 좋을듯 싶다
default
속성은 null
도 비어있는 값으로 간주한다. 그래서, 해당 필드가 누락된다면 null
이 아니라 ""
로 설정한다.
yup.string.default('nothing')
yup.object.default({ number: 5 }) // object will be cloned every time a default is needed
yup.object.default(() => ({ number: 5 })) // this is cheaper
yup.date.default(() => new Date()) // also helpful for defaults that change over time
Schema.getDefault
default value
를 설정한 값을 찾는데 사용된다.
Schema.nullable
null
을 가리킨다. nullable()
없으면 null
은 다른유형으로 취급된다. Schema.isType
으로 check
할때 실패할것이다.
const schema = number().nullable()
schema.cast(null) // null
InferType<typeof schema> // number | null
Schema.nonNullable
nullable()
과 반대된다. Schema
를 에서 유효한 유형의 값들로 부터 null
을 제거한다
Schma
는 기본적으로 null
을 허용하지 않는다
const schema = number().nonNullable()
schema.cast(null) // TypeError
InferType<typeof schema> // number
Schema.defined
Schema
에 대한 value
을 반드시 정의해야 한다 모든 field
값들은 undefined
외의 모든 값은 이 요구사항에 충족한다.
이 말은 값은 반드시 지정되어야 하지만, null
값은 허용됨을 말한다.
const schema = string().defined()
schema.cast(undefined) // TypeError
InferType<typeof schema> // string
Schema.optional
defined()
의 반대이다. 제공되는 타입의 value
는 undefined
를 허용한다
const schema = string().optional()
schema.cast(undefined) // undefined
InferType<typeof schema> // string | undefined
Schema.required
Schema
에 required
를 표시한다면, 값으로써 null
, undefined
를 허용하지 않는다.
이는 defeind
와는 다르다. defined
는 null
을 허용하지만, undefined
는 허용하지 않는다.
그러므로, required
는 optional
과 nallable
의 반대되는 method
이다.
Docs
에서string().required
일때,null
,undefined
뿐만 아니라""
빈 문자열도 허용하지 않으므로, 주의하라고 설명해준다.
Schema.notRequired
notRequired()
를 표시하면, schema.nullable().optional()
처럼 동작한다.
Schema.typeError
type check
가 실패될때, error message
를 정의한다. ${value}
그리고 ${type}
은 message
인자 안에서 사용할 수 있다.
Schema.oneOf
오직, 값 집합의 값들만 허용한다. 이는 마치 배열처럼 동작하며, 해당 배열의 값을 isValid
함수를 통해 확인가능하다.
검증시 확인된 resolved values
를 get
할때, ${value}
보간 및 refs
또는 ref
가 있다면, ${resolved}
보간은 message
인자안에서 보간으로 사용될 수 있다.
undefined
는 arrayOfValues
에 포함되지 않는 경우에도 검증에 실패되지 않는다.
만약, undefined
를 원치 않는다면, Schema.required
를 사용할 수 있다.
let schema = yup.mixed().oneOf(['jimmy', 42])
await schema.isValid(42) // => true
await schema.isValid('jimmy') // => true
await schema.isValid(new Date()) // => false
Schema.notOneOf
이는 oneOf()
에 반대되는 method
이다. 값의 집합에 있는 값은 허용하지 않는다.
let schema = yup.mixed().notOneOf(['jimmy', 42])
await schema.isValid(42) // => false
await schema.isValid(new Date()) // => true
Schema.when
형제 또는 형제의 자손 field
를 기반으로 한 schema
를 조정하는데 사용된다. 마치 case
문처럼 처리가 가능하며, 작동은 다음과 같다
when
에는 object literal
혹은 함수
를 넣을 수 있다.
object
에 is
라는 key
에는 value
또는 matcher function
을 제공한다. 이로써, key
에서 제공되는 값과 형제 및 형제의 자손 field
와 같아면, then
이 실행되고, 그렇지 않다면 otherwise
가 실행된다.
또한, is
는 (엄격한 동등 연산자)===
로 작동하며, 다른 방식을 원한다면 함수를 통해 설정가능하다
예) is: (value) => value == true
이는 다른 field
에 대한 의존하여 값이 설정될때 사용된다.
input value
대신에 context
에 연관된 property
를 지정할때는 $
를 prefix
로 사용할 수 있다.
context
를 사용하고 싶다면, validate
및 cast
를 사용하여 지정해야 한다.
let schema = object({
isBig: boolean(),
count: number()
.when('isBig', {
is: true, // alternatively: (val) => val == true
then: (schema) => schema.min(5),
otherwise: (schema) => schema.min(0),
})
.when('$other', ([other], schema) => (other === 4 ? schema.max(6) : schema)),
})
await schema.validate(value, { context: { other: 4 } }) // context 를 사용한 예
만약, dependant key
를 더 지정하고 싶다면, 배열을 사용한다. 이때, 배열안에 들어간 값은 &&
처럼 사용된다.
let schema = object({
isSpecial: boolean(),
isBig: boolean(),
count: number().when(['isBig', 'isSpecial'], {
is: true, // alternatively: (isBig, isSpecial) => isBig && isSpecial
then: (schema) => schema.min(5),
otherwise: (schema) => schema.min(0),
}),
})
await schema.validate({
isBig: true,
isSpecial: true,
count: 10,
})
다르게 작성도 가능한데, function
을 사용해서 schema
를 리턴한다.
let schema = yup.object({
isBig: yup.boolean(),
count: yup.number().when('isBig', ([isBig], schema) => {
return isBig ? schema.min(5) : schema.min(0)
}),
})
await schema.validate({ isBig: false, count: 4 })
function
의 첫번쩨 인자는, 제공된 key
에대한 값의 배열이며, 두번째 인자는 현재 schema
를 제공한다.
Schema.test
필드값을
test
하는 메소드
object
가 casing
된 이후에 테스트가 실행된다. 이러한 test
를 custom
하게 제공하는 기능을 제공한다.
yup
에서는 비동기식으로 유효성검사가 이루어지기에, 모든 테스트가
비동기식으로 실행된다.
비동기는 굉장히 유용한 기능이지만, 단점은, 실행순서를 보장할 수 없다. 그래서, 동기식으로 test
하는법 역시 존재한다.
모든 테스트는 name
, error message
, 현재 값이 유효하면 그때, true
를 반환하고, 그렇지 않으면 false
및 validationError
를 반환하는 함수를 제공해야 한다.
만약, 비동기식 이라면 true
, false
혹은 validationError
를 반환하는 promise
를 반환한다.
message
인수의 경우 ${param}
구문을 사용하여 지정된 경우 특정값을 보간하는 문자열을 제공할 수 있다.
기본적으로 모든 테스트 메시지는 중첩된 스키마에서 중요한 경로 값을 전달한다고 한다.
다음을 보자.
let jimmySchema = string().test(
'is-jimmy',
'${path} is not Jimmy',
(value, context) => value === 'jimmy'
)
// or make it async by returning a promise
let asyncJimmySchema = string()
.label('First name')
.test(
'is-jimmy',
({ label }) => `${label} is not Jimmy`, // a message can also be a function
async (value, testContext) => (await fetch('/is-jimmy/' + value)).responseText === 'true'
)
await schema.isValid('jimmy') // => true
await schema.isValid('john') // => false
이 test
함수는 2번째 인자의 특별한 context value
와 함께 호출된다. 이는 metadata
로 유용하게 사용된다.
이 test context
는 arrow function
이 아닌 function
을 사용할경우 this
가 향하는 객체는 test context
가 된다.
arrow function
에는this
참조는lexical context
를 그대로 계승받는다. 일반 함수와는 다르게 동적이지 않고 정적으로 향하므로,this
를 통해 접근이 불가하다.실제
function
은bind
함수를 통해, 객체 바인딩이 가능하지만,arrow function
은 이를 지원하지 않는다.이러한 연유로
arrow function
에서는this
에 대한 참조로 접근하지 않는듯 하다.
textContext
는 다음의 property
를 갖는다.
textContext.path
: 현재 유효성검사하는 경로textContext.schema
:test
실행하는데 확인된schema object
testContext.options
:options
객체는validate()
혹은isValid()
와 함께 호출된다.testContext.originalValue
:test
중인original value
=testContext.createError(Object: { path: String, message: String, params: Object})
: 리턴할validation error
를 생성한다. 이는 동적으로setting
하기에 유용하다. 만약 생략된다면current path
,default message
가 사용된다.
Schema.test(options: object): Schema
Schema
를 검증하는 메소드
이는 Schema
를 검증하는데 사용된다. 이를 위해서는 options
를 사용하여 설정가능하다.
options
는 다음과 같다.
Options = {
// unique name identifying the test
name: string;
// test function, determines schema validity
test: (value: any) => boolean;
// the validation error message
message: string;
// values passed to message for interpolation
params: ?object;
// mark the test as exclusive, meaning only one test of the same name can be active at once
exclusive: boolean = false;
}
여기서 exclusive
가 눈에 띄는데, 이는 다음과 같다.
Exclusive Test
: 스키마 객체의 모든 필드가 검증 규칙을 통과해야 한다. 이중 하나라도 검증이 실패하면 전체 검증이 실패한다.Non-Exclusive Test
: 스키마 객체의 각 필드는 개별적으로 검증되며, 검증 규칙 중 하나라도 실패하더라도 다른 필드들은 여전히 검증된다.
Schema.transform
Schema
객체의 값을 변환하는데 사용되는 메서드이다. 이는 validate
메서드가 호출되기 전에 값을 변환한다.
transform
메서드는 인수를 받는데, currentValue
는 schema 객체
의 값이며, originalValue
는 원래의 값을 가진 인수이다.
이는 다음처럼 사용가능하다.
const schema = yup.object().shape({
name: yup.string().required(),
age: yup.number().required(),
})
const transformedSchema = schema.transform(function (obj, originalValue) {
if (obj.age < 0) {
return { ...obj, age: 0 } // age 값이 음수일 경우 0으로 변환
}
return obj // 변환하지 않음
})
위는 age
필드의 값이 음수일 경우 age
는 0
이 되고,
그렇지 않으면 obj
그대로를 반환한다.
이러한 방식대로 처리 가능하다.
mixed
mixed
이는 Schema
로 부터 상속받는 타입으로, 모든 타입에 일치한다.
이에 대한 타입을 보면 mixed extends {}
로 any
또는 unknown
대신에 사용된다.
typescript
에서 {}
는 null
혹은 undefined
를 제외한 모든 타입을 의미한다.
import { mixed, InferType } from 'yup';
let schema = mixed().nullable();
schema.validateSync('string'); // 'string';
schema.validateSync(1); // 1;
schema.validateSync(new Date()); // Date;
InferType<typeof schema>; // {} | undefined
InferType<typeof schema.nullable().defined()>; // {} | null
밑의 예는 Docs
에서 설명해주는 code
이다.
import { mixed, InferType } from 'yup'
let objectIdSchema = yup
.mixed((input): input is ObjectId => input instanceof ObjectId)
.transform((value: any, input, ctx) => {
if (ctx.isType(value)) return value
return new ObjectId(value)
})
await objectIdSchema.validate(ObjectId('507f1f77bcf86cd799439011')) // ObjectId("507f1f77bcf86cd799439011")
await objectIdSchema.validate('507f1f77bcf86cd799439011') // ObjectId("507f1f77bcf86cd799439011")
InferType<typeof objectIdSchema> // ObjectId
위는 mixed
타입을 가지며, mixed
타입의 값이 ObjectId
값이 맞으면, value
를 리턴하고, 그렇지 않으면 ObjectId
로 만들어 리턴한다.
mixed
에서는 함수를 인자로 받아 유효성 검사를 수행할 수 있다. 이때 반환된 값은 해당 Schema
에 부합하는지 여부를 검사하고, true
혹은 false
를 반환한다.
const schema = yup.mixed((value) => {
// 유효성 검사 로직
return true // 유효한 값인 경우 true 반환
})
또는, 타입가드 함수를 전달할수도 있다.
const schema = yup.mixed((input): input is MyType => {
// 타입 가드 로직
return true // 입력값이 MyType인 경우 true 반환
})
mixed
에서 이부분을 사용하여, type guard
를 실행해, 해당 타입임을 검증한다. mixed
에서 이부분을 사용하여, type guard
를 실행해, 해당 타입임을 검증한다.
여기서는 transform
을 사용하여 검증이전에 값을 변경하고, 검증이 이루어진다검증이 이루어진다.
위의 인자은 다음과 같다.
value
: 변환할 값input
: 변환이전의 값ctx
: Yup 컴텍스트 객체
ctx.isType
은 Yup
에서 isType
을 가져온것이다. isType
은 mix
값이 ObjextId
라면, value
가 ObjextId
인지 확인한다.
이러한 로직을 통해, 위의 code
가 제대로 검증처리가 됨을 알 수 있다.
string
이는 string
타입을 검사하는 로직이다. 내부적으로 cast
가 실행되고 이는 toString
과 같다
string.required
빈 문자열도 누락되는점을 제외하고는 mixed().required()
와 동일하다.
string.length
문자열 값의 length
을 요구되도록 설정한다. ${length}
보간은 message
인자에서 사용될수 있다
string.min
string value
의 값을 최소 length
로 제한한다. ${min}
보간은 message
인자에서 사용될수 있다
string.max
string value
의 값을 최대 length
로 제한한다. ${max}
보간은 message
인자에서 사용될수 있다
string.matches
임의의 정규식을 사용하여, value
와 일치하는지 검사한다
let schema = string().matches(/(hi|bye)/)
await schema.isValid('hi') // => true
await schema.isValid('nope') // => false
string.matches
matches
와 같지만 추가적 options
를 추가할 수 있다. excludeEmptyString
은 ""
빈문자열일때, 패턴 검사를 건너띄고 유효한 값으로 처리한다.
이는 빈문자열일 수 있는 상황에서 유효하게 사용될 것이다.
let schema = string().matches(/(hi|bye)/, { excludeEmptyString: true })
await schema.isValid('') // => true
string.email
email
값인지 확인한다. 이는 HTML spec
에서 지정한 regex
와 같다.
하지만, eamil
에 대해서는 워낙에 다양하기에 꼭 맞아 떨아진다고 보장하기 어렵다.
이러한 경우 처리할 수 있도록 아래의 방식을 제안한다.
yup.addMethod(yup.string, 'email', function validateEmail(message) {
return this.matches(myEmailRegex, {
message,
name: 'email',
excludeEmptyString: true,
})
})
이부분에 대해서 addMethod
를 통해 email
의 양식을 평가할 수 있는, regexp
를 만든다.
string.url
URL
에 대한 regex
를 검증한다.
string.uuid
UUID
에 대한 regex
를 검증한다.
string.ensure
undefined
그리고 null
값을 빈 문자열로 변환하고, default
로 설정한다.
string.trim
선행 및 후행의 빈 문자열을 제거한다. 만약, strinc()
이면 값이 잘리는지 확인만 한다.
string.lowercase
lowercase
로 변환한다. 만약 strict()
이면, lowcase
인지만 확인한다.
string.uppercase
uppercase
로 변환한다. 만약 strict()
이면, uppercase
인지만 확인한다.
number
number schema
를 정의한다 주의할점은 parseFloat
을 통해 cast
한다는 것이다. 그러므로, 실패시 NaN
을 리턴할 수 도 있다.
let schema = yup.number()
await schema.isValid(10) // => true
number.min
최소값을 설정한다. 최소값을 설정한다. ${min}
보간은 message
인자에서 사용될수 있다
number.max
최대값을 설정한다. 최대값을 설정한다. ${max}
보간은 message
인자에서 사용될수 있다
number.lessThan
값이 max
보다 적어야만 한다 ${less}
보간은 message
인자에서 사용될수 있다
number.moreThan
값이 min
보다 적어야만 한다 ${more}
보간은 message
인자에서 사용될수 있다
number.positive
value
는 반드시 positive number
이어야 한다.
number.negative
value
는 반드시 negative number
이어야 한다
number.integer
value
는 integer
이어야 한다
number.round
value
는 Math
의 method
를 통해 실수값을 조정한다. (default 는 round
이다.)
date
Date Schema
를 정의한다.
let schema = yup.date()
await schema.isValid(new Date()) // => true
default
로 변경되는 cast
로직은 date
를 사용한다. 이는 Date
생성자를 통해 만들어지며, 이는 ISO date
문자열로 구문분석을 시도한다.
실패시 invalied Date
를 반환한다.
date.min
최소 date
값을 설정한다.
date.max
최대 date
값을 설정한다.
array
array
스키마를 정의한다.
let schema = yup.array().of(yup.number().min(2))
await schema.isValid([2, 3]) // => true
await schema.isValid([1, -24]) // => false
schema.cast(['2', '3']) // => [2, 3]
위는 array
의 값이 2
보다 크면 ture
, 아니면 false
이다. subtype
스키마를 편의상 array
생성자를 거쳐 사용할수 있다
array().of(yup.number())
// or
array(yup.number())
Array
는 기본 캐스팅
동작없이 작동한다.
array.of
array element
의 schema
를 지정한다. of()
는 선택적이고, 제외하면 해당 contents
의 유효성을 검사하지 않는다.
array.json
input
된 값을, JSON.parse
을 사용하여 string
으로 구분분석한다.
array.length
Array
를 통해 특정 length
요구사항을 설정한다. ${length}
보간은 message
인자에서 사용할 수 있다.
array.min
Array
의 최소 length
값을 설정한다. ${min}
보간은 message
인자에서 사용할 수 있다.
array.max
Array
의 최대 length
값을 설정한다. ${max}
보간은 message
인자에서 사용할 수 있다.
array.ensure
값이 배열인지 보장한다. default
는 []
으로 설정하고, null
그리고 undefined
는 비어있는 arrya
로 변환한다.
비어있지 않고, 배열이 아닌 값은 array
로 wrapper
한다.
array().ensure().cast(null) // => []
array().ensure().cast(1) // => [1]
array().ensure().cast([1]) // => [1]
array.compact
falsy
값은 array
에서 제거한다. 또한, rejector
라는 함수를 제거하여, 제거할 value
를 설정할 수 도 있다.
array().compact().cast(['', 1, 0, 4, false, null]) // => [1, 4]
array()
.compact(function (v) {
return v == null
})
.cast(['', 1, 0, 4, false, null]) // => ['', 1, 0, 4, false]
tuple
tuple
은 array
에 length
를 고정한다. 각 항목에는 고유한 타입이 있다
import { tuple, string, number, InferType } from 'yup'
let schema = tuple([string().label('name'), number().label('age').positive().integer()])
await schema.validate(['James', 3]) // ['James', 3]
await schema.validate(['James', -24]) // => ValidationError: age must be a positive number
InferType<typeof schema> // [string, number] | undefined
tuple
은 default casting
이 없다.
object
object
스키마를 정의한다. isValid
안에 전달된 옵션들은 child schemas
에게도 역시 전달된다.
yup.object({
name: string().required(),
age: number().required().positive().integer(),
email: string().email(),
website: string().url(),
})
object schema default
기본적으로 object
내부에 field
에 default
로 set
되어 있다면, 기본값으로 설정되어 있다.
schema.default()
로 하면 해당 값이 이미 설정되어 있다.
const schema = object({
name: string().default(''),
})
schema.default() // -> { name: '' }
이러한 방식은 큰 객체를 만들때, 매우 도움이될것이다. 하지만, 그렇지 않은 경우가 존재한다고 한다.
바로 required
인 field
는 실패할수 있기 때문이다. 그러므로, 이러한 부분을 잘 확인하고 적용해야 한다.
const schema = object({
id: string().required(),
names: object({
first: string().required(),
}),
})
schema.isValid({ id: 1 }) // false! names.first is required
object.shape
object key
와 해당 keys
에 대한 sechma
를 말한다.
다음을 보도록 하자.
object({
a: string(),
b: number(),
}).shape({
b: string(),
c: number(),
})
위의 예시는 마치 Objec.assign
처럼 작동한다. 이는 다음의 결과값을 반환한다.
object({
a: string(),
b: string(),
c: number(),
})
object.json
JSON.parse
를 사용하여 JSON
객체를 구문분석하기 위해 시도한다.
object.concat
SchemaB
를 받아서, 새로운 Schema
를 만든다. 이때, object shape
는 shallowly merg
된다.
즉, 같은 field
가 있을경우, schemaB
로 덮어씌어진다.
object.pick
하위집합에 있는 field
를 선택하여, 새로운 schema
를 만든하위집합에 있는 field
를 선택하여, 새로운 schema
를 만든다
const person = object({
age: number().default(30).required(),
name: string().default('pat').required(),
color: string().default('red').required(),
})
const nameAndAge = person.pick(['name', 'age'])
nameAndAge.getDefault() // => { age: 30, name: 'pat'}
object.omit
하위집합에 있는 field
를 제거한 새로운 schema
를 만든다.
const person = object({
age: number().default(30).required(),
name: string().default('pat').required(),
color: string().default('red').required(),
})
const nameAndAge = person.omit(['color'])
nameAndAge.getDefault() // => { age: 30, name: 'pat'}
object.from
지정한 key
를 새로운 key
로 변환한다. 만약, alias
가 true
일때, old key
는 남아있는다.
let schema = object({
myProp: mixed(),
Other: mixed(),
})
.from('prop', 'myProp')
.from('other', 'Other', true)
schema.cast({ prop: 5, other: 6 }) // => { myProp: 5, other: 6, Other: 6 }
object.noUnknown
정의되지 않은 field
가 입력값에 존재하는 경우 해당 field
를 무시하고 유효성 검사를 수행한다.
onlyKnownKeys
매개변수는 검증 스키마가 정의되지 않은 필드가 입력값에 존재하는 경우에 대한 처리방식을 지정한다.
true
인 경우, 검증 스키마에 정의되지 않은 필드가 입력값에 존재하면 해당 필드를 무시하고 유효성 검사를 수행한다.
flase
인 경우, 검증 스키마에 정의되지 않은 필드가 입력값에 존재하면 유효성 검사를 실패한다.
message
매개변수는 실패시 반환하는 error
메시지이다.
object.noUnknown()
메서드는 object.shape()
와 함께 사용하여 객체의 유효성 검사를 수행할 때 유용하게 사용된다.
import * as yup from 'yup'
const schema = yup.object().shape({
name: yup.string().required(),
age: yup.number().required(),
})
const data = {
name: 'John Doe',
age: 30,
address: '123 Main St.', // 검증 스키마에 없는 필드
}
schema
.noUnknown()
.validate(data)
.then((valid) => console.log(valid))
.catch((err) => console.error(err))
object.camelCase
keys
들을 camelCase
로 변환시킨다.
object.constantCase
keys
를 전부 대분자 스네이크케이스(CONSTNAT_CASE
) 로 변환한다.