Yup

Yup is a schema builder for runtime value parsing and validation. Define a schema, transform a value to match, assert the shape of an existing value, or both. Yup schema are extremely expressive and allow modeling complex, interdependent validations, or value transformation.

@gqloom/yup provides integration of GQLoom with Yup to weave Yup Schema into GraphQL Schema.

Installation

npm
yarn
pnpm
bun
npm install @gqloom/core yup @gqloom/yup

Also, we need to declare metadata from GQLoom for Yup in the project:

import 'yup' import { type GQLoomMetadata } from "@gqloom/yup" declare module "yup" { export interface CustomSchemaMetadata extends GQLoomMetadata {} }

Defining simple scalars

Yup Schema can be used as a silk in GQLoom using yupSilk:

import { number, string, boolean } from "yup" import { yupSilk } from "../src" const StringScalar = yupSilk(string()) const BooleanScalar = yupSilk(boolean()) const FloadtScalar = yupSilk(number()) const IntScalar = yupSilk(number().integer())

Resolver

In order to use Yup Schema as a Silk, we need to wrap yupSilk around it, and a lot of wrapping can be a bit cumbersome in development, so @gqloom/yup provides re-exported resolver and operation constructors to simplify the process. The resolver, query, mutation, and field imported from @gqloom/yup will automatically wrap yupSilk internally so that in most cases we can use Yup Schema directly.

import { string } from "yup" import { resolver, query } from "../src" export const HelloResolver = resolver({ hello: query(string(), () => "Hello, World!"), })

Defining objects

We can use Yup to define objects and use them as silk to use:

import { string, boolean, object, number } from "yup" export const Cat = object({ name: string().required(), age: number().integer().required(), loveFish: boolean(), }).label("Cat")

Names and more metadata

Defining names for objects

Usinglabel()

import { string, boolean, object, number } from "yup" export const Cat = object({ name: string().required(), age: number().integer().required(), loveFish: boolean(), }).label("Cat")

In the above code, we have defined the name for the object using label so that the object will have the name Cat in the generated GraphQL Schema.

UsingcollectNames

import { string, boolean, object, number } from "yup" import { collectNames } from "../src" export const Cat = object({ name: string().required(), age: number().integer().required(), loveFish: boolean(), }) collectNames({ Cat })

In the above code, we are using the collectNames function to define names for objects. The collectNames function accepts an object whose key is the name of the object and whose value is the object itself.

import { string, boolean, object, number } from "yup" import { collectNames } from "../src" export const { Cat } = collectNames({ Cat: object({ name: string().required(), age: number().integer().required(), loveFish: boolean(), }), })

In the code above, we use the collectNames function to define the names for the objects and deconstruct the returned objects into Cat and export them.

UsingasObjectType metadata

import { string, boolean, object, number } from "yup" export const Cat = object({ name: string().required(), age: number().integer().required(), loveFish: boolean(), }).meta({ asObjectType: { name: "Cat" } })

In the above code, we have used meta function in Yup Schema to define the name for the object. Here, we have defined the name asObjectType metadata and set it to { name: “Cat” } so that in the generated GraphQL Schema, the object will have the name Cat.

Add more metadata

We can use the meta function in Yup Schema to add more metadata, such as description, deprecationReason, extensions and so on.

import { string, boolean, object, number } from "yup" export const Cat = object({ name: string().required(), age: number().integer().required(), loveFish: boolean(), }).meta({ asObjectType: { name: "Cat", description: "A cute cat" } })

In the above code, we have added description metadata to the Cat object so that in the generated GraphQL Schema, the object will have the description A cute cat:

"""A cute cat""" type Cat { name: String! age: Int! loveFish: Boolean }

We can also use the asField attribute in the metadata to add metadata to the field, such as description, type, and so on:

import { string, boolean, object, number } from "yup" import { GraphQLInt } from "graphql" export const Cat = object({ name: string().required(), age: number().meta({ asField: { type: () => GraphQLInt, description: "How old is the cat" }, }), loveFish: boolean(), }).meta({ asObjectType: { name: "Cat", description: "A cute cat" } })

In the above code, we added type and description metadata to the age field and ended up with the following GraphQL Schema:

"""A cute cat""" type Cat { name: String! """How old is the cat""" age: Int loveFish: Boolean }

Declaring Interfaces

We can also use the asObjectType function to declare interfaces, for example:

import { string, object, number } from "yup" const Fruit = object({ name: string().required(), color: string().required(), prize: number() .required() .meta({ description: "How much do you want to win?" }), }) .meta({ description: "Fruit Interface" }) .label("Fruit") const Orange = object({ name: string().required(), color: string().required(), prize: number().required(), }) .meta({ asObjectType: { interfaces: [Fruit] } }) .label("Orange")

In the code above, we declared the Orange object as an implementation of the Fruit interface using the interfaces attribute in asObjectType.

Omitting fields

We can also omit fields by setting the type to null using the asField attribute, for example:

import { string, boolean, object, number } from "yup" export const Cat = object({ name: string().required(), age: number() .integer() .meta({ asField: { description: "How old is the cat" } }), loveFish: boolean(), }).meta({ asObjectType: { name: "Cat", description: "A cute cat" } })

The following GraphQL Schema will be generated:

type Dog { name: String }

Defining Union Types

Use union from @gqloom/yup to define union types, for example:

import { object, string, number } from "yup" import { union } from "@gqloom/yup" const Cat = object({ name: string(). required(), color: string().required(), }).label("Cat") const Dog = object({ name: string().required(), height: number().required(), }).label("Dog") const Animal = union([Cat, Dog]).label("Animal")

In the above code, we used the union function to define the Cat and Dog objects as members of the Animal union type.

Defining enumerated types

Usingoneof()

We can use string().oneof() to define enumerated types, for example:

import { string } from "yup" const Fruit = string() .oneOf(["apple", "banana", "orange"]) .label("Fruit") .meta({ asEnumType: { description: "Some fruits you might like", valuesConfig: { apple: { description: "Apple is red" }, banana: { description: "Banana is yellow" }, orange: { description: "Orange is orange" }, }, }, })

Usingenum

We can also use enum to define enumeration types, for example:

import { mixed } from "yup" enum FruitEnum { apple, banana, orange, } const Fruit = mixed() .oneOf(Object.values(FruitEnum) as FruitEnum[]) .label("Fruit") .meta({ asEnumType: { enum: FruitEnum, description: "Some fruits you might like", valuesConfig: { apple: { description: "Apple is red" }, banana: { description: "Banana is yellow" }, orange: { description: "Orange is orange" }, }, }, })

Custom Type Mappings

To accommodate more Yup types, we can extend GQLoom to add more type mappings to it.

First we use YupWeaver.config to define the type mapping configuration. Here we import GraphQLDateTime from graphql-scalars, and when we encounter a date type, we map it to the matching GraphQL scalar.

import { GraphQLDateTime } from "graphql-scalars" import { YupWeaver } from "@gqloom/yup" export const yupWeaverConfig = YupWeaver.config({ presetGraphQLType: (description) => { switch (description.type) { case "date": return GraphQLDateTime } }, })

Configurations are passed into the weave function when weaving the GraphQL Schema:

import { weave } from "@gqloom/yup" export const schema = weave(yupWeaverConfig, HelloResolver)

Default Type Mappings

The following table lists the default mappings between Yup types and GraphQL types in GQLoom:

Yup typesGraphQL types
string()GraphQLString
number()GraphQLFloat
number().integer()GraphQLInt
boolean()GraphQLBoolean
object()GraphQLObjectType
array()GraphQLList
union()GraphQLUnionType
string().oneof(["Value1"])GraphQLEnumType