A
arktype•2mo ago
afonso

Multiple error messages

I'm trying to migrate a zod schema to arktype. This is the zod schema I have:
import { z } from 'zod'

export const createPasswordSchema = z
.object({
// According to the SOC 2 Logical Access policy, the password must meet the following requirements:
// Length: At least 12 characters.
// Complexity: Must include a mix of uppercase and lowercase letters, numbers, and symbols.
password: z
.string()
// 1. At least 12 characters
.min(12, 'length')
// 2. At least one lowercase letter
.regex(/[a-z]/, 'lowercase')
// 3. At least one uppercase letter
.regex(/[A-Z]/, 'uppercase')
// 4. At least one digit
.regex(/\d/, 'number')
// 5. At least one symbol
.regex(/[^A-Za-z0-9]/, 'special'),

confirmation: z.string(),
})
// 6. Match with confirmation
.refine(({ password, confirmation }) => password === confirmation, {
message: 'noMatch',
})
import { z } from 'zod'

export const createPasswordSchema = z
.object({
// According to the SOC 2 Logical Access policy, the password must meet the following requirements:
// Length: At least 12 characters.
// Complexity: Must include a mix of uppercase and lowercase letters, numbers, and symbols.
password: z
.string()
// 1. At least 12 characters
.min(12, 'length')
// 2. At least one lowercase letter
.regex(/[a-z]/, 'lowercase')
// 3. At least one uppercase letter
.regex(/[A-Z]/, 'uppercase')
// 4. At least one digit
.regex(/\d/, 'number')
// 5. At least one symbol
.regex(/[^A-Za-z0-9]/, 'special'),

confirmation: z.string(),
})
// 6. Match with confirmation
.refine(({ password, confirmation }) => password === confirmation, {
message: 'noMatch',
})
If any of these checks fail, Superforms will provide me with a $allErrors for the password field that will be an array like ['lenght', 'uppercase'], which in turn I can use in my i18n layer. I'm trying to achieve the same behaviour with Arktype, but I can't have a different error being reported, or even trim the error message down to a word, for my i18n layer. This is what I have so far, which I know it does not work:
export const createPasswordSchema = type({
password: type.string
.atLeastLength(12)
.describe('length')
.matching(/[a-z]/)
.describe('lowercase')
.matching(/[A-Z]/)
.describe('uppercase')
.matching(/\d/)
.describe('number')
.matching(/[^A-Za-z0-9]/)
.describe('special'),
confirmation: type.string,
}).narrow(({ password, confirmation }, context) => {
if (password !== confirmation) {
return context.reject('noMatch')
}
return true
})
export const createPasswordSchema = type({
password: type.string
.atLeastLength(12)
.describe('length')
.matching(/[a-z]/)
.describe('lowercase')
.matching(/[A-Z]/)
.describe('uppercase')
.matching(/\d/)
.describe('number')
.matching(/[^A-Za-z0-9]/)
.describe('special'),
confirmation: type.string,
}).narrow(({ password, confirmation }, context) => {
if (password !== confirmation) {
return context.reject('noMatch')
}
return true
})
any thoughts on how I can achieve this?
7 Replies
ssalbdivad
ssalbdivad•2mo ago
My first thought would be to configure message instead of describe so that it will replace the entire output: https://arktype.io/docs/configuration#errors Let me circle back tomorrow and take a look at mapping the errors by field to the list of messages. It's really useful for me to have context on i18n/forms integrations like this so I know what utilities to provide on ArkErrors. There's lots of introspectability there currently but if there's way I can organize it so it integrates more easily into existing solutions like this I'm all for it
ArkType
ArkType Docs
TypeScript's 1:1 validator, optimized from editor to runtime
afonso
afonsoOP•2mo ago
we currently use this approach to do something like this with our i18n layer:
{#each error.messages as type, index (index)}
<Text context="app" type="caption1" color="danger"> {$i18n(`onboarding.acceptInvitation.errors.${type}`)}
</Text>
{/each}
{#each error.messages as type, index (index)}
<Text context="app" type="caption1" color="danger"> {$i18n(`onboarding.acceptInvitation.errors.${type}`)}
</Text>
{/each}
ssalbdivad
ssalbdivad•2mo ago
Oh wow this shows me how little I know about Svelte syntax haha but I do still want to think about mapping to i18n codes... if I haven't taken a stab at some mapping by Monday please ping me!
afonso
afonsoOP•2mo ago
here's the ping you requested @ssalbdivad 🙌
ssalbdivad
ssalbdivad•2mo ago
Hey sorry for the delay! It seems like something like this should work:
import { type } from "arktype"

export const createPasswordSchema = type({
password: type.string
.atLeastLength({ rule: 12, meta: { problem: "length" } })
.matching({ rule: /[a-z]/.source, meta: { problem: "lowercase" } })
.matching({ rule: /[A-Z]/.source, meta: { problem: "uppercase" } })
.matching({ rule: /\d/.source, meta: { problem: "number" } })
.matching({ rule: /[^A-Za-z0-9]/.source, meta: { problem: "special" } }),
confirmation: type.string.narrow(
(s, context) =>
s === (context.root as any).password ||
context.reject({ problem: "noMatch" })
)
})

const out = createPasswordSchema({
password: "f",
confirmation: "ff"
})

if (out instanceof type.errors) {
// {
// confirmation: ["noMatch"],
// password: ["uppercase", "special", "number", "length"]
// }
console.log(out.flatProblemsByPath)
}
import { type } from "arktype"

export const createPasswordSchema = type({
password: type.string
.atLeastLength({ rule: 12, meta: { problem: "length" } })
.matching({ rule: /[a-z]/.source, meta: { problem: "lowercase" } })
.matching({ rule: /[A-Z]/.source, meta: { problem: "uppercase" } })
.matching({ rule: /\d/.source, meta: { problem: "number" } })
.matching({ rule: /[^A-Za-z0-9]/.source, meta: { problem: "special" } }),
confirmation: type.string.narrow(
(s, context) =>
s === (context.root as any).password ||
context.reject({ problem: "noMatch" })
)
})

const out = createPasswordSchema({
password: "f",
confirmation: "ff"
})

if (out instanceof type.errors) {
// {
// confirmation: ["noMatch"],
// password: ["uppercase", "special", "number", "length"]
// }
console.log(out.flatProblemsByPath)
}
Would definitely be interested in building in an API for making this a bit easier since I can see how this is a useful pattern for i18n.
afonso
afonsoOP•2mo ago
wonderful! let me know if there's anything I can do to help
ssalbdivad
ssalbdivad•4w ago
Actually if you want to create an issue with this example and my recommended workaround, that would be a good place to track this work and to potentially propose APIs that would solve the problem more elegantly

Did you find this page helpful?