- 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 objecttestContext.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) 로 변환한다.