| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- package main
- import (
- "fmt"
- "io"
- "sort"
- "strings"
- )
- func emitZod(w io.Writer, schemas []Schema, aliases []Alias) error {
- if _, err := fmt.Fprintln(w, zodHeader); err != nil {
- return err
- }
- for _, a := range sortAliases(aliases) {
- if _, err := fmt.Fprintf(w, "export const %sSchema = %s;\n", a.Name, zodTypeExpr(a.Underlying)); err != nil {
- return err
- }
- if _, err := fmt.Fprintf(w, "export type %s = z.infer<typeof %sSchema>;\n\n", a.Name, a.Name); err != nil {
- return err
- }
- }
- for _, s := range sortSchemas(schemas) {
- if _, err := fmt.Fprintf(w, "export const %sSchema = z.object({\n", s.Name); err != nil {
- return err
- }
- fields := append([]Field(nil), s.Fields...)
- sort.SliceStable(fields, func(i, j int) bool { return fields[i].JSONName < fields[j].JSONName })
- for _, f := range fields {
- line := fmt.Sprintf(" %s: %s,\n", quoteIfNeeded(f.JSONName), zodExpr(f))
- if _, err := fmt.Fprint(w, line); err != nil {
- return err
- }
- }
- if _, err := fmt.Fprintln(w, "});"); err != nil {
- return err
- }
- if _, err := fmt.Fprintf(w, "export type %s = z.infer<typeof %sSchema>;\n\n", s.Name, s.Name); err != nil {
- return err
- }
- }
- return nil
- }
- func zodExpr(f Field) string {
- expr := zodTypeExpr(f.Type)
- expr = applyZodValidations(expr, f.Type, f.Validate)
- if f.Optional {
- expr += ".optional()"
- }
- return expr
- }
- func zodTypeExpr(t TypeRef) string {
- switch t.Kind {
- case KindString:
- return "z.string()"
- case KindBool:
- return "z.boolean()"
- case KindInt:
- return "z.number().int()"
- case KindNumber:
- return "z.number()"
- case KindAny, KindUnknown:
- return "z.unknown()"
- case KindRaw:
- return "z.unknown()"
- case KindArray:
- return "z.array(" + zodTypeExpr(*t.Element) + ")"
- case KindMap:
- return "z.record(" + zodTypeExpr(*t.Key) + ", " + zodTypeExpr(*t.Value) + ")"
- case KindRef:
- if t.Name == "nullable" {
- return zodTypeExpr(*t.Inner) + ".nullable()"
- }
- return "z.lazy(() => " + t.Name + "Schema)"
- }
- return "z.unknown()"
- }
- func applyZodValidations(expr string, t TypeRef, rules []ValidateRule) string {
- for _, r := range rules {
- switch r.Name {
- case "required":
- continue
- case "omitempty":
- continue
- case "gte":
- if t.Kind == KindInt || t.Kind == KindNumber {
- expr += fmt.Sprintf(".min(%s)", r.Param)
- }
- case "lte":
- if t.Kind == KindInt || t.Kind == KindNumber {
- expr += fmt.Sprintf(".max(%s)", r.Param)
- }
- case "gt":
- if t.Kind == KindInt || t.Kind == KindNumber {
- expr += fmt.Sprintf(".gt(%s)", r.Param)
- }
- case "lt":
- if t.Kind == KindInt || t.Kind == KindNumber {
- expr += fmt.Sprintf(".lt(%s)", r.Param)
- }
- case "min":
- if t.Kind == KindString {
- expr += fmt.Sprintf(".min(%s)", r.Param)
- } else if t.Kind == KindInt || t.Kind == KindNumber {
- expr += fmt.Sprintf(".min(%s)", r.Param)
- }
- case "max":
- if t.Kind == KindString {
- expr += fmt.Sprintf(".max(%s)", r.Param)
- } else if t.Kind == KindInt || t.Kind == KindNumber {
- expr += fmt.Sprintf(".max(%s)", r.Param)
- }
- case "url":
- expr += ".url()"
- case "email":
- expr += ".email()"
- case "oneof":
- values := strings.Fields(r.Param)
- quoted := make([]string, 0, len(values))
- for _, v := range values {
- quoted = append(quoted, fmt.Sprintf("'%s'", v))
- }
- expr = fmt.Sprintf("z.enum([%s])", strings.Join(quoted, ", "))
- }
- }
- return expr
- }
- func quoteIfNeeded(name string) string {
- if name == "" {
- return "''"
- }
- for i, r := range name {
- if r >= 'a' && r <= 'z' {
- continue
- }
- if r >= 'A' && r <= 'Z' {
- continue
- }
- if r == '_' || r == '$' {
- continue
- }
- if i > 0 && r >= '0' && r <= '9' {
- continue
- }
- return "'" + name + "'"
- }
- return name
- }
- const zodHeader = `// Code generated by tools/openapigen. DO NOT EDIT.
- import { z } from 'zod';`
|