API Reference

k.issue

Emit a validation issue with a machine-readable code, checked against the codes your config declares.

Beta

Custom issue codes are new and still settling. k.issue and the validation.issueCodes option may change while v2 is in beta, so pin your version if you depend on them.

Emit a single Zod validation issue carrying a custom machine-readable code. The code is surfaced verbatim in the errors[].code field of Kizuna's ValidationErrorSchema.

The code is checked against the validation.issueCodes you declared on defineConfig, so a typo is a compile error.

Zod's types restrict ctx.addIssue() to its built-in issue union, so a custom code needs a cast at every call site. k.issue holds that cast.

Parameters

k.issue<Input>(
    ctx: z.core.$RefinementCtx<Input>,
    issue: { code: string; message: string; input: Input },
): void
ParameterTypeDescription
ctxz.core.$RefinementCtx<Input>The refinement context from .superRefine().
issue.codeCodes | BuiltinIssueCodeOne of the validation.issueCodes declared on defineConfig, or a built-in Zod code. Surfaced in errors[].code.
issue.messagestringHuman-readable description of the failure.
issue.inputInputThe value that failed validation.

Example

routes/contacts.ts
import { z } from 'zod';
import { isValidPhoneNumber } from 'libphonenumber-js';

const CreateContactSchema = z.object({
    phone: z.string().superRefine((value, ctx) => {
        if (isValidPhoneNumber(value)) return;
        k.issue(ctx, {
            code: 'invalid_phone_number',
            message: 'Invalid phone number',
            input: value,
        });
    }),
});

A request whose body fails this refinement returns 400 with the custom code in the response:

{
    "type": "about:blank",
    "title": "Bad Request",
    "status": 400,
    "detail": "Request validation failed",
    "errors": [
        {
            "code": "invalid_phone_number",
            "path": ["phone"],
            "message": "Invalid phone number"
        }
    ]
}

Typed codes on the client

By default errors[].code is typed as ValidationIssueCode, so the built-in Zod codes are suggested in autocomplete and any custom string is accepted, but your invalid_phone_number is not suggested (it lives only inside the refinement callback, where TypeScript can't see it).

To make the client suggest your custom codes, declare them under validation.issueCodes on defineConfig and regenerate the fetch client:

k.ts
import { defineConfig } from 'kizunajs';
import { tags } from './src/tags';
import { routes } from './src/routes';

export default defineConfig({
    tags,
    routes,
    validation: {
        issueCodes: ['invalid_phone_number'],
    },
});
src/lib/api-client.ts
import { createClient } from './api-client.generated';

export const apiClient = createClient({
    baseUrl: 'http://localhost:3000',
});

Now errors[].code on a 400 response is widened to ValidationIssueCode | 'invalid_phone_number', so invalid_phone_number shows up in autocomplete when you read or compare it:

lib/contacts.ts
const result = await apiClient.contacts.createContact({
    body: {
        phone: 'nope',
    },
});
if (result.status === 400 && isValidationError(result.body)) {
    for (const issue of result.body.errors) {
        if (issue.code === 'invalid_phone_number') {
            // ^ autocompleted, type-checked
        }
    }
}

Each API has its own config, so codes stay scoped to it.

See isValidationError for how clients consume these codes.

On this page