// File: index
# Introduction
Zog 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. Zog schemas are extremely expressive and allow modeling complex, interdependent validations, or value transformations.
Killer Features:
- Concise yet expressive schema interface, equipped to model simple to complex data models
- **[Zod](https://github.com/colinhacks/zod)-like API**, use method chaining to build schemas in a typesafe manner
- **Extensible**: add your own Tests and Schemas
- **Rich errors** with detailed context, make debugging a breeze
- **Fast**: Zog is one of the fastest Go validation libraries. We are just behind the goplayground/validator for most of the [govalidbench](https://github.com/Oudwins/govalidbench/tree/master) benchmarks.
- **Built-in coercion** support for most types
- Zero dependencies!
- **Four Helper Packages**
- **zenv**: parse environment variables
- **zhttp**: parse http forms & query params
- **zjson**: parse json
- **i18n**: Opinionated solution to good i18n zog errors
- **Agent skill**: So you can write as little boilerplate as possible yourself.
> **API Stability:**
>
> - I will consider the API stable when we reach v1.0.0
> - However, I believe very little API changes will happen from the current implementation. The APIs most likely to change are the **data providers** (please don't make your own if possible use the helpers whose APIs will not change meaningfully) and the z.Ctx most other APIs should remain the same. I could be wrong but I don't expect many breaking changes.
> - Although we want to keep breaking changes to a minimum, Zog is still in version 0 and will have breaking changes in the minor versions as per semver. So please be careful when upgrading minor versions.
---
// File: getting-started
# Getting Started
#### **Optional: Install the AI agent skill**
Zog includes an agent skill with usage guidance, common mistakes, and best practices.
Install it via `npx skills add https://github.com/Oudwins/zog` or by copying it from the skills directory
#### **1 Install**
```bash
go get github.com/Oudwins/zog
```
#### **2 Create a user schema and its struct**
```go
import (
z "github.com/Oudwins/zog"
)
type User struct {
Name string
Age int
}
var userSchema = z.Struct(z.Shape{
// its very important that schema keys like "name" match the struct field name NOT the input data
"name": z.String().Min(3, z.Message("Override default message")).Max(10),
"age": z.Int().GT(18),
})
```
#### **3 Validate your schema**
**Using [schema.Parse()](https://zog.dev/core-concepts/parsing)**
```go
func main() {
u := User{}
m := map[string]string{
"name": "Zog",
"age": "", // won't return an error because fields are optional by default
}
errs := userSchema.Parse(m, &u)
if errs != nil {
// handle errors -> see Errors section
for _, issue := range errs {
fmt.Printf("%s: %s\n", issue.Path, issue.Message)
}
}
u.Name // "Zog"
// note that this might look weird but we didn't say age was required so Zog just skipped the empty string and we are left with the uninitialized int
// If we need 0 to be a valid value for age we can use a pointer to an int which will be nil if the value was not present in the input data
u.Age // 0
}
```
**Using [schema.Validate()](https://zog.dev/core-concepts/validate)**
```go
func main() {
u := User{
Name: "Zog",
Age: 0, // wont return an error because fields are optional by default otherwise it will error
}
errs := userSchema.Validate(&u)
if errs != nil {
// handle errors -> see Errors section
}
}
```
#### **4. Its easy to use with http & json**
The [zhttp package](https://zog.dev/packages/zhttp) has you covered for JSON, Forms and Query Params, just do:
```go
import (
zhttp "github.com/Oudwins/zog/zhttp"
)
err := userSchema.Parse(zhttp.Request(r), &user)
```
If you are receiving json some other way you can use the [zjson package](https://zog.dev/packages/zjson)
```go
import (
zjson "github.com/Oudwins/zog/zjson"
)
err := userSchema.Parse(zjson.Decode(bytes.NewReader(jsonBytes)), &user)
```
#### **5. Or to validate your environment variables**
The [zenv package](https://zog.dev/packages/zenv) has you covered, just do:
```go
import (
zenv "github.com/Oudwins/zog/zenv"
)
err := envSchema.Parse(zenv.NewDataProvider(), &envs)
```
#### **6. You can also parse individual fields**
```go
var t = time.Time
// All schemas now return ZogIssueList
errs := Time().Required().Parse("2020-01-01T00:00:00Z", &t)
```
#### **7 Transform Data without limits**
```go
var dest []string
schema := z.Preprocess(func(data any, ctx z.Ctx) ([]string, error) {
s := data.(string) // don't do this, actually check the type
return strings.Split(s, ","), nil
}, z.Slice(z.String().Trim().Email().Required()))
errs := schema.Parse("foo@bar.com,bar@foo.com", &dest) // dest = [foo@bar.com bar@foo.com]
```
---
// File: preprocess
# Preprocess
Just like in Zod you can use preprocess function to transform the data before it is validated. This is also useful for things like type coercion. **Preprocess functions are PURE functions**. They take in data and return new data. This is the function signature:
```go
func Preprocess[F any, T any](fn func(data F, ctx Ctx) (out T, err error), schema ZogSchema) *PreprocessSchema[F, T]
// Usage:
// Note that even if preprocess takes a generic [F]rom type, my recommendation is to always set that to any unless you are 100% sure that the input data will always be of a specific type. Since if you are using this schema with schema.Parse() the input data can be anything.
z.Preprocess(func(data any, ctx z.ctx) (any, error) {
s, ok := data.(string)
if !ok {
return nil, fmt.Errorf("expected string but got %T", data)
}
return strings.split(s, ","), nil
}, z.Slice(z.String())))
```
You can use `z.Preprocess` for things like trimming whitespace, splitting strings, etc. Here is an example of splitting a string into a slice of strings:
> **FOOTGUNS**.
> _parse vs validate_: z.Preprocess can run for both `schema.Parse()` and `schema.Validate()`, in each case the data argument will be different!. For `schema.Parse()` the data argument is the value you are parsing (i.e the input data). For `schema.Validate()` the data argument is the pointer to the value you are validating.
> _Pure Functions_: Since preprocess functions are pure functions. They create copies of the data. So be careful when using them with large data structures if you are concerned about performance.
---
// File: custom-schemas
# Custom Schemas
> Please read the [Anatomy of a Schema](/core-concepts/anatomy-of-schema) page before continuing.
Currently Zog plans to support three different ways of creating custom schemas. Although this is subject to change and some of these are not yet implemented so keep an eye out on this page as it gets updated, [more details on my thoughts here](https://github.com/Oudwins/zog/discussions/132).
1. Generics on Primitive Schemas for custom strings, numbers, booleans, etc...
2. Custom Schemas Interface you can implement to create a 100% custom schema (Not yet implemented)
3. A system by which you can define a schema for a custom type or interface that after some transformation can become a normal zog schema.
## Creating Custom Schemas for Primitive Types
This is quite simple to do for the supported primitive types (string, number, boolean). For complete list of options see the [reference](/reference) page. Here is an example:
```go
// definition in your code
type Env string
const (
Prod Env = "prod"
Dev Env = "env"
)
type Status int
const (
Active Status = 1
Inactive Status = 0 // default value
)
func EnvSchema() *StringSchema[Env] {
return z.StringLike[Env]().OneOf([]Env{Prod, Dev})
}
// usage
type S struct {
Environment Env
Status Status
}
schema := z.Struct(
z.Shape{
"Environment": EnvSchema(), // All string methods will now be typed to Env type
"Status": z.IntLike[Status]().OneOf([]Status{Active, Inactive}),
},
)
```
## Quick and Dirty Custom Schema
Sometimes you may want to create a custom schema for a type that is not a primitive and you don't want to go through the process of defining everything needed to create a full schema. You just want to run a validation inside Zog. Zog supports a simple way to do this using the `CustomFunc` function which looks like this:
```go
// fn signature
func CustomFunc[T any](fn func(valPtr *T, ctx z.Ctx) bool, opts ...z.TestOption) *z.Custom[T]
```
Usage is very similar to the `schema.TestFunc()` function:
```go
user := z.Struct(z.Shape{
"uuid": z.CustomFunc(func(valPtr *uuid.UUID, ctx z.Ctx) bool {
return (*valPtr).IsValid()
}, z.Message("invalid uuid"))
})
```
> **Limitations**
>
> - CustomFunc doesn't support type coercion yet. You can still use it with parse but it will not be able to coerce the type.
> **Why is valPtr a pointer?**
> Mainly for performance reasons. It is faster in almost every case to pass a pointer to the value than the value itself. This is specially true if the value is a large struct.
---
// File: custom-tests
# Custom Tests
> Please read the [Anatomy of a Schema](/core-concepts/anatomy-of-schema) page before continuing.
## Simple Custom Tests - Aka Zod's `refine`
All schemas contain the `TestFunc()` method which can be used to create a simple custom test in a similar way to Zod's `refine` method. The `TestFunc()` method takes a `ValidateFunc` as an argument. This is a function that takes the data as input and returns a boolean indicating if it is valid or not. If you return `false` from the function Zog will create a [ZogIssue](/errors). For example:
```go
z.String().TestFunc(func(data *string, ctx z.Ctx) bool { // notice that here Zog already knows you need to pass a *string to the test.
return *data == "test"
})
```
Test funcs for structs and slices instead receive a pointer to the data to avoid copying large data structures. For example:
```go
z.Struct(z.Shape{
"name": z.String(),
}).TestFunc(func(dataPtr any, ctx z.Ctx) bool { // notice that here we have to cast the dataPtr because no inference for struct types
user := dataPtr.(*User)
return user.Name == "test"
})
```
> **Pro tip**
> It is very likely that you may want to set custom messages or paths, you can do that like with any other tests with `TestOptions`. For more on this checkout the [Anatomy of a Schema](/core-concepts/anatomy-of-schema#test-options) page.
## Complex Custom Tests - Aka Zod's `superRefine`
For complex tests you can use the `schema.TestFunc()` method but it is recommended that you use the `schema.Test()` method as it provides more flexibility. Use the [zog context](/advanced/context) for this—here's an example that executes a DB call to verify a user's session:
```go
sessionSchema := z.String().Test(z.Test{
Func: func (val any, ctx z.Ctx) {
session := val.(string)
if !sessionStore.IsValid(session) {
// This ctx.Issue() is a shortcut to creating Zog issues that are aware of the current schema context. Basically this means that it will prefil some data like the path, value, etc. for you.
ctx.AddIssue(ctx.Issue().SetMessage("Invalid session"))
return
}
if sessionStore.HasExpired(session) {
// But you can also just use the normal z.Issue{} struct if you want to.
ctx.AddIssue(z.Issue{
Message: "Session expired",
Path: "session",
Value: val,
})
return
}
if sessionStore.IsRevoked(session) {
ctx.AddIssue(ctx.Issue().SetMessage("Session revoked"))
return
}
// etc
}
})
```
## Making Reusable Tests
In general, I recommend you wrap your reusable tests in a function. Here are examples for both simple and complex tests:
```go
// Notice how we can pass default values to the test which can then be overriden by the function called. This is super nice if you need it!
func MySimpleTest(opts ...z.TestOption) z.Test[any] {
options := []TestOption{
Message("Default message, can be overriden"),
}
options = append(options, opts...)
return z.TestFunc(
func (val any, ctx z.Ctx) bool {
user := val.(*User)
return user.Name == "test" // or any other validation
},
...options
)
}
func MyComplexTest() z.Test[*string] {
return z.Test{
Func: func (valPtr *string, ctx z.Ctx) {
// complex test here
}
}
}
```
---
// File: faq
# FAQ
## My code panics / zog doesn't work
Please make sure that you are using Zog properly. The expectation is that if Zog causes a panic, you are doing something wrong.
So before reporting an issue please check these common mistakes:
### 1. ❌ Not passing a pointer to the struct you are parsing into
```go
payload := models.UserPayload{}
errMap := models.UserSchema.Parse(zhttp.Request(c.Request), payload) // ❌ Incorrect: note how the "payload" is not being passed as a pointer, you must pass `&payload` as the pointer to `payload`
```
#### ✅ Correct usage
```go
payload := models.UserPayload{}
errMap := models.UserSchema.Parse(zhttp.Request(c.Request), &payload) // ✅ Correct: passing the pointer to `payload`
```
### 2. ❌ Not defining your schema properly
```go
type Name struct {
FirstName string `json:"first_name" zog:"first_name"` // The zog/json tags define the INPUT (e.g. JSON) key, not the schema key. The schema only matches Go struct field names.
LastName string `json:"last_name" zog:"last_name"`
}
data := new(Name)
var schema = z.Struct(z.Shape{
"first_name": z.String().Required(z.Message("First name is required")), // ❌ Incorrect: schema keys must match the Go struct field name (FirstName), not the JSON/input key
"last_name": z.String().Required(z.Message("Last name is required")), // ❌ Same issue: this refers to the JSON key, but the struct field is LastName
})
```
The schema operates purely on the _Go struct shape_. It does not know or care about the source of the input data (JSON, form data, etc.).
Struct tags (`json`, `zog`) are _only used to map_ input keys into struct fields.
Schema keys must always correspond to Go struct field names (e.g. `FirstName`, `LastName`), not input keys like `first_name`.
#### ✅ Correct usage
```go
type Name struct {
FirstName string `json:"first_name" zog:"first_name"`
LastName string `json:"last_name" zog:"last_name"`
}
data := new(Name)
var schema = z.Struct(z.Shape{
"FirstName": z.String().Required(z.Message("First name is required")), // ✅ Correct: schema key matches Go struct field name
"LastName": z.String().Required(z.Message("Last name is required")), // ✅ Correct: schema key matches Go struct field name
})
```
## Error: `"Struct is missing expected schema key: {some_key}"`
This error is telling you that you are defining your schema keys incorrectly. For example:
```go
type Name struct {
FirstName string `json:"first_name" zog:"first_name"` // zog struct tag is used to define the name of the field in the input data (i.e json key) not the name of the schema key (common mistake)
LastName string `json:"last_name" zog:"last_name"`
}
data := new(Name)
var schema = z.Struct(z.Shape{
"first_name": z.String().Required(z.Message("First name is required")), // ❌ Incorrect: here you are telling zog that your struct should have a `first_name` field, but this is incorrect because the struct has a `FirstName` field. The key here should be "firstName" or "FirstName" (both are valid)
"last_name": z.String().Required(z.Message("Last name is required")), // ❌ Same issue here: the struct has a `LastName` field, so the key here should be "lastName" or "LastName"
})
```
This comes from a misunderstanding of how the zog struct tag works.
The zog struct tag is used to _define the name of the field in the input data_ (i.e json key), **not** the name of the schema key. The schema is only aware of what the struct looks like, and **it is not** aware of the source of the input data.
## Why does zog have an internals package?
Please see the [internals](/packages/internals) page for more information.
## Self referencing / Cyclic schemas
Zog currently does not support this. But it is planned.
---
// File: advanced/context
# Zog Context
## What is context?
Zog uses a `z.Ctx` interface to pass around information related to a specific `schema.Parse()` or `schema.Validate()` call. Currently use of the parse context is quite limited but it will be expanded upon in the future. The context interface currently looks like this:
```go
type Ctx interface {
// Get a value from the context
Get(key string) any
// Adds an issue to the schema execution.
AddIssue(e *ZogIssue)
// Returns a new issue with the current schema context's data prefilled
/*
Usage:
func MyCustomTestFunc(val any, ctx z.Ctx) {
if reason1 {
ctx.AddIssue(ctx.Issue().SetMessage("Reason 1"))
} else if reason2 {
ctx.AddIssue(ctx.Issue().SetMessage("Reason 2"))
} else {
ctx.AddIssue(ctx.Issue().SetMessage("Reason 3"))
}
}
*/
Issue() *ZogIssue
}
```
### Uses of context
#### Create issues manually
In any Zog function that has access to the context you may add issues manually. This is useful for many reasons. But mostly for creating [complex custom tests](/custom-tests), check the [custom tests](/custom-tests) section for more information.
#### Pass custom data to functions
Here is an example with a custom test
```go
nameSchema := z.String().Min(3).Test(func(data *string, ctx z.Ctx) (bool) {
b := ctx.Get("is_valid").(bool)
return b
})
nameSchema.Parse("Michael Jackson", &dest, z.WithCtxValue("is_valid", true))
```
#### Change the issue formatter for this execution
This might be useful for localization, or for changing the error messages for one specific execution.
```go
nameSchema := z.String().Min(3)
nameSchema.Parse(data, &dest, z.WithIssueFormatter(MyCustomErrorMessageFormatter))
```
---
// File: advanced/configuration
# Configuration
Inside the `conf` package zog provides a bunch of global configuration options. If you override these options you can **change the default behavior of all zog schemas.**
## Coercion
During [parsing](/core-concepts/parsing) zog will attempt to coerce the data into the correct type. For example if you have a `float64` field and the data is a `string` that contains a number with a comma as the decimal separator, zog will attempt to convert this to a `float64`. Zog provides a set of default coercer functions for each type, but you can override these globally.
Lets go through an example of overriding the `float64` coercer function, because we want to support floats that use a comma as the decimal separator.
```go
import (
// import the conf package
"github.com/Oudwins/zog/conf"
)
// we override the coercer function for float64
conf.Coercers.Float64 = func(data any) (any, error) {
str, ok := data.(string)
// identify the case we want to override
if !ok && strings.Contains(str, ",") {
return MyCustomFloatCoercer(str)
}
// fallback to the original function
return conf.DefaultCoercers.Float64(data)
}
```
## Error Formatting
For information on configuring error formatting globally please refer to the [errors page](/errors/custom-messages#5-configure-issue-messages-globally).
---
// File: advanced/performance
# Performance
Zog is one of the fastest validation libraries for Go as per the [govalidbench](https://github.com/Oudwins/govalidbench) benchmarks. But to get the most performance out of zog there is some things you can do as a user:
## When possible use global schemas
One thing that makes Zog very performant is that once you have built a schema you can reuse it as many times as you want. So if you are able it is recommended not to build a new schema for each request. The difference in speed and allocations can be very drastic specially for large schemas. For reference this is the same benchmark of a large struct (as of v0.17.2) reusing the schema vs creating a new one for each request:
```bash
# Reusing the schema
BenchmarkStructComplexSuccess/Success-12 330733 3626 ns/op 451 B/op 28 allocs/op
BenchmarkStructComplexSuccessParallel/Success-12 1000000 1294 ns/op 476 B/op 28 allocs/op
BenchmarkStructComplexFailure/Error-12 124696 8428 ns/op 1395 B/op 59 allocs/op
BenchmarkStructComplexFailureParallel/Error-12 465020 3047 ns/op 1410 B/op 57 allocs/op
# Creating a new schema for each execution
BenchmarkStructComplexCreateSuccess/Success-12 119824 8757 ns/op 9702 B/op 114 allocs/op
BenchmarkStructComplexCreateSuccessParallel/Success-12 294409 4607 ns/op 9905 B/op 114 allocs/op
BenchmarkStructComplexCreateFailure/Error-12 80371 14069 ns/op 10652 B/op 145 allocs/op
BenchmarkStructComplexCreateFailureParallel/Error-12 192896 6993 ns/op 10793 B/op 144 allocs/op
```
If you need to build the schema on the fly and need the best performance possible I recommend you look into using `sync.Pool` to reuse the schemas.
## Use `schema.Validate` instead of `schema.Parse` when possible
For the moment parsing is slower because it needs to unmarshal the data into a map then parse it into the struct. I have quite a few ideas on how to improve `Parse` and hopefully make it as efficient as `Validate` but it will take some time. So unless you need the features that `Parse` provides I recommend you use `Validate`.
For more information on the differences read [parsing vs validation](/core-concepts/parsing-vs-validation).
## Collect issues for reuse
One of the most expensive operations in Zog is the generation of issues. We store a lot of information for the issues which is great for debugging and for generating rich custom err0r messages but can cause many allocations and be slow. A great way to mitigate this is to let Zog know that you are done using an issue. This way Zog will reuse those structs which will put less preassure on the GC. To do this Zog provides a few utility functions under the z.Issues name space:
```go
// Collects a ZogIssueMap to be reused by Zog. This will "free" the issues in the map. This can help make Zog more performant by reusing issue structs.
func (i *issueHelpers) CollectMap(issues ZogIssueMap)
// Collects a ZogIssueList to be reused by Zog. This will "free" the issues in the list. This can help make Zog more performant by reusing issue structs.
func (i *issueHelpers) CollectList(issues ZogIssueList)
// Collects a ZogIssue to be reused by Zog. This will "free" the issue. This can help make Zog more performant by reusing issue structs.
func (i *issueHelpers) Collect(issue *ZogIssue)
```
---
// File: core-concepts/1-anatomy-of-schema
# Anatomy of the Zog Schema
A zog schema is an interface implemented by multiple custom structs that represent a set of `validation` and `transformation` logic for a variable of a given type. For example:
```go
stringSchema := z.String().Trim().Min(3).Required() // A zog schema that represents a required string which will first be trimmed then a test to ensure it has 3+ characters will be ran.
userSchema := z.Struct(z.Shape{"name": stringSchema}) // a zog schema that represents a user struct. Also yes I know that z.Schema might be confusing but think of it as the schema for the struct not a ZogSchema
```
**The string schema, for example, looks something like this:**
```go
type stringSchema struct {
isRequired bool // optional. Defaults to FALSE
defaultValue string // optional. if the input value is a "zero value" it will be replaced with this. Tests will still run on this value.
catchValue string // optional. If this is set it will "catch" any errors, set the destination value to this value and exit
testOrTransformation TestOrTransformation // This is the test or transformation that will be applied to the data.
}
```
## Required, Default and Catch
`schema.Required()` is a boolean that indicates if the field is required. If it is required and the data is a zero value the schema will return a [ZogIssue](/errors).
`schema.Default(value)` sets a default value for the field. If the data is a zero value it will be replaced with this value, this takes priority over required. Tests will still run on this value.
`schema.Catch(value)` sets a catch value. If this is set it will "catch" any errors or ZogIssues with the catch value. Meaning it will set the destination value to the catch value and exit. When this is triggered, no matter what error triggers it code will automatically exit. For more information checkout the [parsing execution structure](/core-concepts/parsing#parsing-execution-structure).
## Tests
> A test is what zog calls a "validator". It is a struct that represents an individual validation. For example for the String schema `z.String()` the method `Min(3)` generates a test that checks if the string is at least 3 characters long. You can view all the default tests that come with each [schema type here.](/reference)
### Test Options
You can configure tests with `TestOptions` which modify a test in some manner. Here are some examples:
```go
z.String().Min(3, z.Message("String must be at least 3 characters long")) // This sets the message that Zogissues will have if the validation fails
z.String().Min(3, z.IssueCode("min_3")) // This sets the issue code that Zogissues will have if the validation fails
z.String().Min(3, z.IssuePath("name")) // This sets the issue path that Zogissues will have if the validation fails
```
### Creating Custom Tests
You are also free to create custom tests and pass them to the `schema.Test()` and `schema.TestFunc()` methods. For more details on this checkout the [Creating Custom Tests](/custom-tests) page.
## Transforms
Transforms is a list of function that are applied to the data at any point. You can think of it like a `pipeline` of transformations for a specific schema. This is the function signature:
```go
// Transforms are generic functions that take a pointer to the data as input. For primitive types you won't have to typecast but for complex types it will just be a any type and you will have to manually typecast it.
type Transform[T any] func(dataPtr T, ctx Ctx) error
```
As you can see the function takes a pointer to the data as input. This is to allow the function to modify the data.
```go
type User struct {
Phone string
AreaCode string
}
z.Struct(z.Shape{
"phone": z.String().Test(...).Transform(func (valPtr *string, ctx z.Ctx) error{
*valPtr = strings.ReplaceAll(*valPtr, " ", "") // remove all spaces
return nil
}),
}).Transform(func(dataPtr any, ctx z.Ctx) error {
user := dataPtr.(*User)
user.AreaCode = user.Phone[:3]
user.Phone = user.Phone[3:]
return nil
})
```
---
// File: core-concepts/2-parsing-vs-validation
# Parsing vs Validation
Zog supports two main ways of processing data, both of which support the exact same schemas and can be used interchangeably without modifying the schema:
- [`Schema.Parse(data, &dest, ...options)`](/core-concepts/parsing) - Parses the data into the destination pointer and returns a [ZogIssueList](/errors) if any.
- [`Schema.Validate(&data, ...options)`](/core-concepts/validate) - Validates the data and returns a [ZogIssueList](/errors/overview#zogissuelist) if any.
> **You are probably wondering** > [What is the difference?](#what-is-the-difference) > [Which should I use?](#which-should-i-use)
## What is the difference?
There is only one difference between the two:
- For [Schema.Parse(data, &dest, ...options)](/core-concepts/parsing) you must provide data that Zog will parse into the destination structure. For example, if you use one of the helper packages like [zog-json](/packages/zjson) zog will unmarshal the json into the destination structure.
- For [Schema.Validate(&data, ...options)](/core-concepts/validate) you are expected to have already parsed the data into the final structure you want and are now just validating that it is correct.
**Okay, but what does this mean in practice?**
It means that Parse will handle things like type coercion, zero value checking, etc... for you. Whereas Validate will not. For example:
```go
data := "2024-01-01"
var dest time.Time
z.Time().Parse(data, &dest) // dest will be 2024-01-01 00:00:00 +0000 UTC
z.Time().Validate(&data) // Error: string is not a valid time
```
It also means that Validate cannot know if a value was provided by the user or if it was set by a default value. Therefore it will consider zero values as invalid when the schema is required. For example:
```go
var dest int
z.Int().Required().Parse(0, &dest) // dest will be 0
val := 0
z.Int().Required().Validate(&val) // will return a required issue
// To fix this you can use a pointer:
var valPtr *int
z.Ptr(z.Int()).NotNil().Validate(&valPtr) // will return a not nil issue
*valPtr = 0
z.Ptr(z.Int()).NotNil().Validate(&valPtr) // No issues will be returned
```
## Which should I use?
If you can, use [`Schema.Validate(&data, ...options)`](/core-concepts/validate) as it is more efficient. But if you need the type coercion or zero value checking without pointers feel free to use [`Schema.Parse(data, &dest, ...options)`](/core-concepts/parsing).
## Validation Execution Structure
Generally speaking when executing `schema.Validate()` Zog will follow a very similar execution structure to the one described in [Parsing Execution Structure](/core-concepts/parsing/#parsing-execution-structure).
---
// File: core-concepts/3-parsing
# Parsing
## What is schema.Parse()?
To validate and parse your data into a destination pointer you can use the `schema.Parse()` function. The function signature looks like this:
```go
schema.Parse(data, &dest, options...)
```
This works with any Zog Schema:
```go
// string
var dest string
z.String().Min(3).Parse("test", &dest)
// structs
var dest User
z.Struct(z.Shape{"name": z.String().Min(3)}).Parse(map[string]any{"name": "test"}, &dest)
```
Under the hood Zog follows the [Parsing Execution Structure](#parsing-execution-structure) and does a bunch of things under the hood to make sure your data is parsed correctly. Such as checking for zero values, coercing types, etc...
## Parsing Struct Tags
By default zog will use the schema field name as the key for the parsed value. For example:
```go
type User struct {
Name string `zog:"name"`
}
z.Struct(z.Shape{"name": z.String()}).Parse(map[string]any{"name": "test"}, &User{}) // note zog will fetch the value as map[name]
type User struct {
Name string `zog:"name"`
}
z.Struct(z.Shape{"Name": z.String()}).Parse(map[string]any{"name": "test"}, &User{}) // note zog will fetch the value as map[Name]
```
However, this is not always practical. For example, you may have data coming in in kebab case and you want to parse it into camel case. For this Zog supports a few struct tags:
- `json` tag which works for JSON input data
- `form` tag which works for form input data
- `query` tag which works for query string input data
- `env` tag which works for environment variables see [zenv](/packages/zenv) for more info
- `zog` a catch all tag which works for any input data
> The priority of keys is as follows:
> `json` || `form` || `query` || `env` -> `zog` -> schema field name
You can mix and match without issue:
```go
type User struct {
Name string `zog:"first-name"`
LastName string `query:"last_name" json:"last-name"`
}
z.Struct(z.Shape{"name": z.String(), "lastName": z.String()}).Parse(map[string]any{"first-name": "test", "lastName": "Doe"}, &User{})
```
## Coercion
Zog will attempt to coerce the data into the correct type. For example if you have a `z.Int()` schema and you pass in a `"1"` it will be coerced into an `int` type. This behaviour, like almost everything else in Zog, can be customized. Zog provides two main ways to customize coercion:
- [Global Coercion](/advanced/configuration#coercion) - Change the default coercion behaviour for all schemas. More details on this in the [configuration page](/advanced/configuration#coercion).
- [Per schema coercers](#per-schema-coercers) - Define custom coercion functions for your schemas.
#### Per schema coercers
You can define custom coercion functions for your schemas by using the `z.WithCoercer()` schema option.
```go
z.String(z.WithCoercer(func(data any) (any, error) {
return "test", nil // now the result will be "test" no matter the input
})).Parse("abc", &dest) // dest will be "test"
```
## Parsing Execution Structure
Parsing execution structure is quite simple. It just does roughly this:
1. Check for nil value
- If nil take into account if schema has default value or is required
2. Coerce value to the correct type otherwise
3. Run validation loop
- If a test fails, an issue is added
- If you return an error from a transform, the execution stops and that error is returned.
- If catch is active any error triggers it and stops the execution.

---
// File: core-concepts/4-parsing-results
# Parsing Results Examples
Under the hood Zog follows the [Parsing Execution Structure](/core-concepts/parsing/#parsing-execution-structure) and does a bunch of things under the hood to make sure your data is parsed correctly. Such as checking for zero values, coercing types, etc...
Some of this might not be obvious so here is a summary table that breaksdown expected results of calling schema.Parse() on various inputs:
import ParsingResultsTable from "./parsing-table.tsx";
{e[0].Message}
} // display only the first error if e, ok := errs["password"]; ok {{e[0].Message}
} } ``` **PS:** If you are using go html templates & tailwindcss you might be interesting in my port of [tailwind-merge to go.](https://github.com/Oudwins/tailwind-merge-go) --- // File: examples-of-use/rest-apis # Using Zog in a REST API Zog providers two helper functions called `z.Issues.SanitizeMap(issueMap)` and `z.Issues.SanitizeList(issueList)` that will return a map of strings of the issue messages (stripping out the internal error). So, if you do not mind sending issue messages to your users in the same form zog returns them, you can do something like this: ```go // If you want to use Parse errs := schema.Parse(zhttp.Request(r), &userFormData) // if you want to use Validate userFormData := unmarshalUserFormData(r.body) // any way you want to unmarshal is fine errs := schema.Validate(&userFormData) if errs != nil { sanitized := z.Issues.SanitizeMap(errs) // sanitize will be map[string][]string // for example: // {"name": []string{"min length is 5", "max length is 10"}, "email": []string{"is not a valid email"}} // ... marshal sanitized to json and send to the user } ``` --- // File: experimental/custom-schemas # Custom Schemas > **⚠️ Experimental API**: This feature is experimental and subject to breaking changes. Use with caution. Create fully custom schemas by implementing the `EXPERIMENTAL_PUBLIC_ZOG_SCHEMA` interface. This gives you complete control over parsing, validation, and type coercion. ## The Interface Implement these four methods: ```go type EXPERIMENTAL_PUBLIC_ZOG_SCHEMA interface { Process(ctx *internals.SchemaCtx) // Called during Parse() Validate(ctx *internals.SchemaCtx) // Called during Validate() GetType() zconst.ZogType // Return schema type identifier SetCoercer(c CoercerFunc) // Optional: set type coercion function } ``` ## Key Concepts ### Process vs Validate - **`Process(ctx *internals.SchemaCtx)`**: Handles parsing. You must: - Coerce `ctx.Data` to your target type - Assign the value to `ctx.ValPtr` (always a pointer) - Run validation and add issues if validation fails - **`Validate(ctx *internals.SchemaCtx)`**: Handles validation. The value is already at `ctx.ValPtr`, just validate it. ### SchemaCtx Essentials - **`ctx.Data`**: Input data (only during `Process()`) - **`ctx.ValPtr`**: Pointer to destination value (always a pointer) - **`ctx.AddIssue(issue)`**: Add a validation error - **`ctx.Issue()`**: Create a new issue with context pre-filled - **`ctx.IssueFromCoerce(err)`**: Create an issue from a coercion error ## Example Here's a complete example for a custom string schema: ```go import ( "fmt" z "github.com/Oudwins/zog" p "github.com/Oudwins/zog/pkgs/internals" "github.com/Oudwins/zog/zconst" ) type MinLengthSchema struct { minLength int errorMsg string coercer z.CoercerFunc } func (s *MinLengthSchema) Process(ctx *internals.SchemaCtx) { // Optional: handle coercion if s.coercer != nil { coerced, err := s.coercer(ctx.Data) if err != nil { ctx.AddIssue(ctx.IssueFromCoerce(err)) return } ctx.Data = coerced } // Type assertion ptr, ok := ctx.ValPtr.(*string) if !ok { ctx.AddIssue(ctx.IssueFromCoerce( fmt.Errorf("expected *string, got %T", ctx.ValPtr))) return } val, ok := ctx.Data.(string) if !ok { ctx.AddIssue(ctx.IssueFromCoerce( fmt.Errorf("expected string, got %T", ctx.Data))) return } // Assign value *ptr = val // Validate if len(val) < s.minLength { issue := ctx.Issue().SetMessage(s.errorMsg) ctx.AddIssue(issue) } } func (s *MinLengthSchema) Validate(ctx *internals.SchemaCtx) { ptr, ok := ctx.ValPtr.(*string) if !ok { return } if len(*ptr) < s.minLength { issue := ctx.Issue().SetMessage(s.errorMsg) ctx.AddIssue(issue) } } func (s *MinLengthSchema) GetType() zconst.ZogType { return zconst.TypeString } func (s *MinLengthSchema) SetCoercer(c z.CoercerFunc) { s.coercer = c } // Usage schema := z.Struct(z.Shape{ "name": z.Use(&MinLengthSchema{ minLength: 5, errorMsg: "name must be at least 5 characters", }), }) ``` ## Using Custom Schemas Wrap your implementation with `z.Use()`: ```go customSchema := &MyCustomSchema{...} schema := z.Use(customSchema) ``` Use it anywhere a Zog schema is expected: ```go // In structs z.Struct(z.Shape{"id": z.Use(customSchema)}) // In slices z.Slice(z.Use(customSchema)) // In pointers z.Ptr(z.Use(customSchema)) ``` ## Type Coercion Set a coercer to handle type conversion: ```go type CoercerFunc func(original any) (value any, err error) customSchema.SetCoercer(func(original any) (value any, err error) { if i, ok := original.(int); ok { return strconv.Itoa(i), nil } return nil, fmt.Errorf("cannot convert %T to string", original) }) ``` The coercer is called during `Process()` before type assertion. ## Important Notes - **Experimental**: API may change in future versions - **Type Safety**: You're responsible for proper type assertions - **Error Handling**: Always use `ctx.AddIssue()`, don't panic - **ValPtr**: Always a pointer to the destination value For simpler validation needs, consider `z.CustomFunc()` instead. --- // File: experimental/zss # ZSS - Zog Schema Specification **ZSS (Zog Schema Specification)** is an intermediate, structured representation of Zog schemas. It serves as a bridge between Zog's runtime schema definitions and other schema formats. > **⚠️ Experimental API**: ZSS is an experimental intermediate format that is still in flux. The structure and API are subject to breaking changes. Use with caution. ## Purpose ZSS is designed to be an intermediate format that can be used to generate other schema formats such as: - **JSON Schema** (planned) - **OpenAPI/Swagger** specifications (planned) - **TypeScript types** (planned) - Other target formats as needed By converting Zog schemas to ZSS first, we can generate multiple output formats from a single, well-defined intermediate representation. ## Structure A ZSS document consists of: - **`ZSSDocument`**: The root container with version information and a root schema - **`ZSSSchema`**: Represents individual schema nodes with: - `Kind`: The schema type (string, number, bool, time, slice, struct, ptr, etc.) - `Processors`: Validation tests and transformers - `Fields`: Struct fields, keyed by field name - `Element`: The nested schema for pointers, slices, preprocessors, and boxed schemas - `Key` / `Value`: Map key and value schemas - `GoTypes`: Go type metadata (when exhaustive metadata is enabled) - `Required`, `DefaultValue`, `CatchValue`: Schema constraints and defaults ## Usage Convert a Zog schema to ZSS using `EXPERIMENTAL_TO_ZSS()`: ```go import ( z "github.com/Oudwins/zog" zss "github.com/Oudwins/zog/pkgs/zss/core" ) schema := z.String().Min(5) // Convert to ZSS zssDoc := z.EXPERIMENTAL_TO_ZSS(schema) // Access the root schema rootSchema := zssDoc.Root ``` ## Exhaustive Metadata ZSS supports an "exhaustive metadata" mode that includes additional information in the schema output. This mode must be explicitly enabled at build time. ### Enabling Exhaustive Metadata To enable exhaustive metadata, build your application with the `zogmeta` build tag: ```bash go build -tags zogmeta ``` Or when running tests: ```bash go test -tags zogmeta ``` ### What's Included When exhaustive metadata is enabled, ZSS includes: - **`GoTypes`**: Array of `ZSSGoType` containing: - `PkgPath`: Package path (empty for built-in types) - `Name`: Type name (may be empty for unnamed types) - `Display`: Full type string representation - **`Format`**: For `TimeSchema`, the format string specified via `z.Time(z.Time.Format(...))` is included in the ZSS output. **Note**: The format is only included in ZSS when exhaustive metadata is enabled. - **Custom Messages**: Custom messages set via `z.Message()` are included in the ZSS `ZSSTest.Message` field. **Note**: Custom messages are only included in ZSS output when exhaustive metadata is enabled. For generic schemas like `PreprocessSchema[F, T]` and `BoxedSchema[B, T]`, multiple type parameters are captured in the `GoTypes` array. ### Example ```go // With exhaustive metadata enabled schema := z.Time(z.Time.Format(time.RFC3339)) schema := z.String().Min(5, z.Message("Custom validation message")) zssDoc := z.EXPERIMENTAL_TO_ZSS(schema) // Format and custom messages will be included in zssDoc ``` ## Notes - The ZSS format is versioned via the `$schema` field - Currently, only version `0.0.1` is defined - The format may change significantly before stabilization --- // File: experimental/recursive-schemas # Recursive Schemas > **⚠️ Experimental API**: This feature is experimental and subject to breaking changes. Use with caution. Define schemas that reference themselves recursively, enabling validation of recursive data structures like linked lists, trees, and nested hierarchies. ## Overview Recursive schemas solve the problem of defining schemas for data structures that contain references to themselves. Without recursive schemas, you cannot define a schema for a struct that contains a field of its own type. ## Basic Usage Use `EXPERIMENTAL_RECURSIVE` to create a recursive schema: ```go import z "github.com/Oudwins/zog" type Node struct { Value int Self *Node } var nodeSchema = z.EXPERIMENTAL_RECURSIVE(func(self z.RecursiveSchema[*z.PointerSchema]) *z.PointerSchema { return z.Ptr(z.Struct(z.Shape{ "value": z.Int().Required(), "self": self(), })) }) ``` The `self` parameter is a function that returns a schema representing the recursive type. Call `self()` wherever you need the recursive reference. ### Updating Recursive Schemas You can modify the recursive schema using updater functions. This is mostly intended to be used to clone a schema and apply additional constraints to it. If you make changes to the schema without cloning it you can land on undefined behavior land. Which maybe means this API should change and clone automatically for you. ```go var nodeSchema = z.EXPERIMENTAL_RECURSIVE(func(self z.RecursiveSchema[*z.PointerSchema]) *z.PointerSchema { return z.Ptr(z.Struct(z.Shape{ "value": z.Int().Required(), "self": self(func(original *z.PointerSchema) *z.PointerSchema { // Modify the original schema return original }), })) }) ``` ## Important Notes - **Experimental**: API may change in future versions - **Lazy Initialization**: The recursive schema is only materialized when first accessed, so first call will be slightly slower - **Thread-Safe**: Safe for concurrent use across multiple goroutines - **Type Safety**: Ensure your Go struct matches the schema structure - **Nil Handling**: Recursive references can be `nil` (use `z.Ptr()` for optional fields) --- // File: migrations/0.21-to-0.22 # Migrating from 0.21 to 0.22 This guide helps you migrate from the dual return type system (ZogIssueList for primitives, ZogIssueMap for complex schemas) to the unified ZogIssueList return type. ## What's changed? 1. `ZogIssueMap` was removed and all schemas now return `ZogIssueList` 2. `ZogIssue.Path` is now a slice of strings instead of a string (You can still get the previous path string via `Issues.FlattenPath(issue.Path)` or `issue.PathString()`). Now root errors have a `nil` path instead of an empty string as the path. ## Why This Change? Previously, Zog had two different return types: - **Primitive schemas** (String, Int, Bool, Time) returned `ZogIssueList` - **Complex schemas** (Struct, Slice, Pointer) returned `ZogIssueMap` The main reason for this change is that I wanted to provide many ways to format the many issue messages a schema might generate. Locking you into a `map[path][]issue` was unnecesarily restrictive. The new system is heavily inspired by [Zod v4's error formatting](https://zod.dev/error-formatting) ## Quick Migration ### Error Checking ```go // Before (complex schemas): err := userSchema.Parse(data, &user) if err != nil { // handle errors } // After (all schemas): err := userSchema.Parse(data, &user) if len(err) > 0 { // handle errors } ``` ### Message sanitization ```go // before messages = z.Issues.Sanitize(err) // After messages = z.Issues.Flatten(err) // or one of the other strategies ``` ### Getting the First Error ```go // Before: first := errs["$first"][0] // After (option 1 - direct): first := errs[0] ``` ### Creating previous error map If you have extensive code using the old map format, you can use the conversion helper: ```go errs := userSchema.Parse(data, &user) errsMap := z.Issues.GroupByFlattenedPath(errs) // Now use errsMap like before if nameErrs, ok := errsMap["name"]; ok { // ... } ``` > **Note**: Before using this consider if `Issues.Flattened` or another one of the formatting strategies is not a better fit ## Method Return Type Changes | Schema | Before | After | | ------------------------------ | ----------------- | ------------------ | | `StringSchema.Parse()` | `ZogIssueList` | `ZogIssueList` | | `StringSchema.Validate()` | `ZogIssueList` | `ZogIssueList` | | `NumberSchema.Parse()` | `ZogIssueList` | `ZogIssueList` | | `NumberSchema.Validate()` | `ZogIssueList` | `ZogIssueList` | | `BoolSchema.Parse()` | `ZogIssueList` | `ZogIssueList` | | `BoolSchema.Validate()` | `ZogIssueList` | `ZogIssueList` | | `TimeSchema.Parse()` | `ZogIssueList` | `ZogIssueList` | | `TimeSchema.Validate()` | `ZogIssueList` | `ZogIssueList` | | **`StructSchema.Parse()`** | **`ZogIssueMap`** | **`ZogIssueList`** | | **`StructSchema.Validate()`** | **`ZogIssueMap`** | **`ZogIssueList`** | | **`SliceSchema.Parse()`** | **`ZogIssueMap`** | **`ZogIssueList`** | | **`SliceSchema.Validate()`** | **`ZogIssueMap`** | **`ZogIssueList`** | | **`PointerSchema.Parse()`** | **`ZogIssueMap`** | **`ZogIssueList`** | | **`PointerSchema.Validate()`** | **`ZogIssueMap`** | **`ZogIssueList`** | | **`BoxedSchema.Parse()`** | **`ZogIssueMap`** | **`ZogIssueList`** | | **`BoxedSchema.Validate()`** | **`ZogIssueMap`** | **`ZogIssueList`** | --- // File: packages/zhttp # zhttp For Zog provides a built in helper package called `zhttp` that helps parse JSON, Forms or Query Params. Helps parse a request into a struct by using the Content-Type header to infer the type of the request. Example usage below: ```go import ( z "github.com/Oudwins/zog" "github.com/Oudwins/zog/zhttp" ) var userSchema = z.Struct(z.Shape{ "name": z.String().Required(), "age": z.Int().Required().GT(18), }) func handlePostRequest(w http.ResponseWriter, r *http.Request) { user := struct { Name string Age int } // if using json (i.e json Content-Type header): errs := userSchema.Parse(zhttp.Request(r), &user) // if using form data (i.e Content-Type header = application/x-www-form-urlencoded) errs := userSchema.Parse(zhttp.Request(r), &user) // if using multipart form data you are expected to parse the form yourself before using it with zhttp. See this article on why/how to correctly parse multipart form data: https://medium.com/@owlwalks/dont-parse-everything-from-client-multipart-post-golang-9280d23cd4ad // After that you can just use it as normal errs := userSchema.Parse(zhttp.Request(r), &user) // if using query params (i.e no http Content-Type header) errs := userSchema.Parse(zhttp.Request(r), &user) if errs != nil { // ... } user.Name // defined user.Age // defined } ``` > **WARNING** The `zhttp` package does NOT currently support parsing into any data type that is NOT a struct. ## Behaviour on unmarshal errors If the json, form or query params are not valid, a top-level `ZogIssue` will be generated with one of the following issue codes: `IssueCodeInvalidJSON`, `IssueCodeZHTTPInvalidForm` or `IssueCodeZHTTPInvalidQuery`, and the schema will not be run. ## Complex Forms If you need to parse complex forms or query params such as those parsed by packages like [qs](https://www.npmjs.com/package/qs), for example: ```js assert.deepEqual(qs.parse("foo[bar]=baz"), { foo: { bar: "baz", }, }); ``` zhttp does not currently support these types of forms (see [issue #8](https://github.com/Oudwins/zog/issues/8)). However I suggest you try using the [form go package](https://github.com/go-playground/form) which supports this type of parsing. You can integrate the library with zhttp by overriding the `zhttp.Config.Parsers.Form` function. > **WARNING**: This depends on `DataProviders` which are not yet documented and may change in the future. I encourage you to avoid doing this unless you really need to. --- // File: packages/zjson # zjson A very small package for using Zog schemas to parse json into structs. It exports a single function `Decode` which takes in an `io.Reader` or an `io.ReaderCloser` and returns the necessary structures for Zog to parse the json into a struct. This package is used by the `zhttp` package. ```go import ( "bytes" z "github.com/Oudwins/zog" "github.com/Oudwins/zog/parsers/zjson" ) var userSchema = z.Struct(z.Shape{ "name": z.String().Required(), "age": z.Int().Required().GT(18), }) type User struct { Name string Age int } func ParseJson(json []byte) { var user User errs := userSchema.Parse(zjson.Decode(bytes.NewReader(json)), &user) if errs != nil { // handle errors } user.Name // defined user.Age // defined } ``` > **WARNING** The `zjson` package does NOT currently support parsing into any data type that is NOT a struct. ## Behaviour on unmarshal errors If the json is not valid, a top-level `ZogIssue` will be generated with the `IssueCodeInvalidJSON` issue code and the schema will not be run. --- // File: packages/zenv # zenv `zenv` helps validate environment variables. Since `os.Getenv` does not perform any validation or type coercion this is a great way to ensure you didn't forget to set an environment variable. Which we have all done at some point.... ```go import ( z "github.com/Oudwins/zog" "github.com/Oudwins/zog/zenv" ) var envSchema = z.Struct(z.Shape{ "PORT": z.Int().GT(1000).LT(65535).Default(3000), "DB": z.Struct(z.Shape{ "Host": z.String().Default("localhost"), "User": z.String().Default("root"), "Pass": z.String().Default("root"), }), }) var Env = struct { PORT int // zog will automatically coerce the PORT env to an int DB struct { Host string `env:"DB_HOST"` // we specify the `env` or the `zog` tag to tell zog to parse the field from the DB_HOST environment variable. See parsing for more info on struct tags User string `zog:"DB_USER"` Pass string `env:"DB_PASS"` } }{} // Init our typesafe env vars, panic if any envs are missing func Init() { errs := envSchema.Parse(zenv.NewDataProvider(), &Env) if errs != nil { log.Fatal(errs) } } // if you want to always panic on error var Env = parse() func Parse() env { var e env errs := envSchema.Parse(zenv.NewDataProvider(), &e) if errs != nil { fmt.Println("FAILURE TO PARSE ENV VARIABLES") log.Fatal(z.Issues.SanitizeMap(errs)) } return e } ``` --- // File: packages/i18n # i18n Zog has built in support for i18n with two types of language support: First Party Languages (maintained by Zog maintainers and guaranteed to be up to date): - `English` - `Spanish` Community maintained Languages (translations may lag behind Zog's updates): - `Azerbaijani` (by [@aykhans](https://github.com/aykhans)) - `Japanese` (by [@sawada-naoya](https://github.com/sawada-naoya)) You can add your own custom languages or even make a package for a new language very easily. We encourage you to submit pull requests to update error message translations if you find any issues or want to contribute a new language. > **NOTE**: I recommend you also read the [errors](/errors) page which will give you a bunch of options for [creating custom error messages](/errors/custom-messages) which you can use in conjunction with i18n. ## Changing the default language for error messages Lets imagine you are building an application that is only in Spanish. You can change the default language for error messages like this: ```go import ( "github.com/Oudwins/zog/conf" // import the zog configuration package "github.com/Oudwins/zog/i18n/es" // import the built in spanish translations ) // override the default error map conf.DefaultErrMsgMap = es.Map // now all errors will be in spanish! ``` This will be a little bit harder if you want to change the language to one that is not built in by default into Zog. But it is still very easy to do. You just have to implement your own LangMap. Checkout the spanish or english language maps copy one of those and translate the error messages to your language. And after, **how about publishing it as a package and make it available to everyone?** ## Supporting Multiple Languages This is what the `i18n` package is for. It allows you to support multiple languages in a simple and easy way. Below is an example of how to make Zog support both English and Spanish errors and configure the language for each [parsing execution](/core-concepts/parsing#parsing-execution-structure). Again, if you need to support languages not built in to Zog you will have to implement your own LangMaps. ```go // Somewhere when you start your app import ( "github.com/Oudwins/zog/i18n" // import the i18n library "github.com/Oudwins/zog/i18n/en" "github.com/Oudwins/zog/i18n/es" "github.com/Oudwins/zog/i18n/ja" // import any of the supported language maps or build your own ) i18n.SetLanguagesErrsMap(map[string]i18n.LangMap{ "es": es.Map, "en": en.Map, "ja": ja.Map, }, "es", // default language i18n.WithLangKey("langKey"), // (optional) default lang key is "lang" and is stored in i18n.LangKey ) // Now when we parse schema.Parse(data, &dest, z.WithCtxValue("langKey", "es")) // get spanish errors schema.Parse(data, &dest, z.WithCtxValue("langKey", "en")) // get english errors schema.Parse(data, &dest, z.WithCtxValue("langKey", "ja")) // get japanese errors schema.Parse(data, &dest) // get default lang errors (spanish in this case) ``` --- // File: packages/zconst # zconst `zconst` is a helper package that provides constants for common use cases such as error codes, Zog Types and more. Every constant here is just a string so using `zconst` is completely optional. This is the entire code of the zconst package as of version 0.11.0: ```go const ( ZogTag = "zog" ) // Map used to format errors in Zog. Both ZogType & ZogIssueCode are just strings type LangMap = map[ZogType]map[ZogIssueCode]string type ZogType = string const ( TypeString ZogType = "string" TypeNumber ZogType = "number" TypeBool ZogType = "bool" TypeTime ZogType = "time" TypeSlice ZogType = "slice" TypeStruct ZogType = "struct" ) type ZogIssueCode = string const ( IssueCodeCustom ZogIssueCode = "custom" // all IssueCodeRequired ZogIssueCode = "required" // all IssueCodeNotNil ZogIssueCode = "not_nil" // all (technically only applies to pointers) IssueCodeCoerce ZogIssueCode = "coerce" // all // all. Applied when other errror code is not implemented. Required to be implemented for every zog type! IssueCodeFallback ZogIssueCode = "fallback" IssueCodeEQ ZogIssueCode = "eq" // number, time, string IssueCodeOneOf ZogIssueCode = "one_of_options" // string or number IssueCodeMin ZogIssueCode = "min" // string, slice IssueCodeMax ZogIssueCode = "max" // string, slice IssueCodeLen ZogIssueCode = "len" // string, slice IssueCodeContains ZogIssueCode = "contained" // string, slice // number only IssueCodeLTE ZogIssueCode = "lte" // number IssueCodeLT ZogIssueCode = "lt" // number IssueCodeGTE ZogIssueCode = "gte" // number IssueCodeGT ZogIssueCode = "gt" // number // string only IssueCodeEmail ZogIssueCode = "email" IssueCodeUUID ZogIssueCode = "uuid" IssueCodeMatch ZogIssueCode = "match" IssueCodeURL ZogIssueCode = "url" IssueCodeHasPrefix ZogIssueCode = "prefix" IssueCodeHasSuffix ZogIssueCode = "suffix" IssueCodeContainsUpper ZogIssueCode = "contains_upper" IssueCodeContainsLower ZogIssueCode = "contains_lower" IssueCodeContainsDigit ZogIssueCode = "contains_digit" IssueCodeContainsSpecial ZogIssueCode = "contains_special" // time only IssueCodeAfter ZogIssueCode = "after" IssueCodeBefore ZogIssueCode = "before" // bool only IssueCodeTrue ZogIssueCode = "true" IssueCodeFalse ZogIssueCode = "false" // ZHTTP ERRORS IssueCodeZHTTPInvalidJSON ZogIssueCode = "invalid_json" // invalid json body IssueCodeZHTTPInvalidForm ZogIssueCode = "invalid_form" // invalid form data IssueCodeZHTTPInvalidQuery ZogIssueCode = "invalid_query" // invalid query params ) ``` --- // File: packages/internals # Internals Those astute among you may have noticed that `zog` has an `internals` package. You may be right in thinking that this is a bit weird as standard practice in golang is to name the package for internal code `internal` which allows golang to not export that code to user space. And you would be right. However, `zog` takes a different approach. Our `internals` package holds code that is not meant for user space just like a typical `internal` package. Its not recommended that you use it as code inside it may have breaking changes at any time. However you can if you need or want to. This is a nice way for us to experiment with API's and allow you to build things on top of experimental code. Often time a feature will be hidden inside the internals package for a long time before it is promoted to the main package. --- // File: philosophy/panics # Zog Panics ## When does Zog panic? Zog follows [TigerStyle](https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/TIGER_STYLE.md) asserts. It panics when something in its fundamental assumptions is broken. In practice this means that Zog will never panic if the input data is wrong but it will panic if you configure it wrong. Most of the time "configured it wrong" means that you have made a mistake in your schema definition which puts Zog into an invalid state and results in a schema that can never succeed. ## Types of Panics > If you find a panic that is not listed here, please report it as a bug! ### Schema Definition Errors ```go var schema = z.Struct(z.Schema{ "name": z.String().Required(), }) // This struct is a valid destination for the schema type User struct { Name string Age int // age will be ignored since it is not a field in the schema } // this struct is not a valid structure for the schema. It is missing the name field. // This will cause Zog to panic in both Parse and Validate mode type User2 struct { Email string `zog:"name"` // using struct tag here DOES NOT WORK. This is not the purpose of the struct tag. Age int } schema.Parse(map[string]any{"name": "zog"}, &User{}) // this will panic even if input data is valid. Because the destination is not a valid structure for the schema schema.Validate(&User2{}) // This will panic because the structure does not match the schema ``` ### Type Cast Errors There are multiple ways in which a type cast error can occur. For example: ###### 1 Destination/Validation value is not a pointer ```go var schema = z.Struct(z.Schema{ "name": z.String().Required(), }) var dest User schema.Parse(map[string]any{"name": "zog"}, dest) // This will panic because dest is not a pointer schema.Validate(dest) // This will panic because dest is not a pointer // Fix this by using a pointer schema.Parse(map[string]any{"name": "zog"}, &dest) schema.Validate(&dest) ``` > This can only really happen on complex schemas since those are not fully typesafe. Primitive schemas are typesafe and won't let you pass a non-pointer value. ###### 2 Destination/Validation value is not a valid type for the schema ```go type MyString string type User struct { Age MyString } var schema = z.Struct(z.Schema{ "age": z.String().Required(), }) val := User{ Age: MyString("1"), } schema.Validate(&val) // This will panic because the schema is expecting a string but the value is of type MyString ``` Same thing will happen if you incorrectly set the type in a z.Custom schema: ```go type User struct { ID uuid.UUID } var schema = z.Struct(z.Schema{ "id": z.Custom(func (ptr *string, ctx z.Ctx) bool { // Zog can't convert a UUID to a string so this will panic return true }), }) val := User{ ID: uuid.New(), } schema.Validate(&val) // This will panic because the schema is expecting a string but the value is of type uuid.UUID ``` Another common example is when you forget to use z.Ptr. ```go type User struct { Friends *[]Friend } // This is incorrect! var schema = z.Struct(z.Schema{ "friends": z.Slice(z.Struct(z.Schema{ "name": z.String().Required(), })), }) // This is correct! var schema2 = z.Struct(z.Schema{ "friends": z.Ptr(z.Slice(z.Struct(z.Schema{ "name": z.String().Required(), }))), }) ``` ###### 3 The coercer returns a value of the wrong type > Only applicable to `schema.Parse()` ```go var schema = z.Struct(z.Schema{ "name": z.String(z.WithCoercer(func (v any, ctx z.Ctx) (any, error) { return 1, nil // we are returning an int but the schema is expecting a string })).Required(), }) val := User{ Name: "zog", } schema.Parse(map[string]any{"name": "zog"}, &val) // This will panic because the coercer is returning an int but the schema is expecting a string ``` --- // File: philosophy/core-design-decisions # Core Design Decisions - All fields optional by default. Same as graphql - When parsing into structs, private fields are ignored (same as stdlib json.Unmarshal) - Errors returned by you (for example in a `Preprocess` or `Transform` function) can be the ZogIssue interface or an error. If you return an error, it will be wrapped in a ZogIssue. ZogIssue is just a struct that wraps around an error and adds a message field which is text that can be shown to the user. For more on this see [Errors](/errors) - You should not depend on test execution order. They might run in parallel in the future > **A WORD OF CAUTION. [ZOG & PANICS](/philosophy/panics)** > In general Zog will never panic if the input data is wrong but it will panic if you configure it wrong. For example: > > - In parse mode Zog will never panic due to invalid input data but will always panic if invalid destination is passed to the `Parse` function. if the destination does not match the schema in terms of types or fields. > - In validate mode Zog will panic if the expected types or fields are not present in the structure you are validating. ```go var schema = z.Struct(z.Shape{ "name": z.String().Required(), }) // This struct is a valid destination for the schema type User struct { Name string Age int // age will be ignored since it is not a field in the schema } // this struct is not a valid structure for the schema. It is missing the name field. // This will cause Zog to panic in both Parse and Validate mode type User2 struct { Email string Age int } schema.Parse(map[string]any{"name": "zog"}, &User{}) // this will panic even if input data is valid. Because the destination is not a valid structure for the schema schema.Validate(&User2{}) // This will panic because the structure does not match the schema ``` ## Limitations Most of these things are issues we would like to address in future versions. - Unsupported schemas: - `z.Map()` - `zhttp` does not support parsing into any data type that is not a struct - Schema & pick, omit, etc are not really typesafe. i.e `z.Struct(z.Shape{"name"})` name is not typesafe - structs and slices don't support catch, and structs don't support default values - It is not recommended to use very deeply nested schemas since that requires a lot of reflection and can have a negative impact on performance --- // File: philosophy/changes-from-zod # Changes from Zod - Zog is Zod inspired, we adhere to the Zod API whenever possible but there are significant differences because: 1. Go is statically typed and does not allow optional function params 2. I have chosen to make Zog prioritize idiomatic Golang over the Zod API. Meaning some of the schemas & tests (validation rules) have changed names, `z.Array()` is `z.Slice()`, `z.String().StartsWith()` is `z.String().HasPrefix` (to follow the std lib). Etc. 3. When I felt like a Zod method name would be confusing for Golang devs I changed it - Some other changes: - The refine & superRefine methods for providing a custom validation function is renamed to `schema.TestFunc` & `schema.Test()` - schemas are optional by default (in zod they are required) - The `z.Enum()` type from zod is removed in favor of `z.String().OneOf()` and is only supported for strings and numbers - `string().regex` is renamed to `z.String().Match()` as that is in line with the regexp methods from the standard library (i.e `regexp.Match` and `regexp.MatchString()`) --- // File: reference # Reference ## Generic Zog Schema Methods These are methods that can generally be called on any schema type (Some exceptions might exist). ```go schema.Test(test) // create a custom test schema.TestFunc(fn) // create a custom test from a function schema.Required() // marks field as required schema.Optional() // marks field as optional schema.Default(value) // sets default value for field schema.DefaultFunc(fn) // sets default value for field using a function schema.Catch(value) // sets catch value for field schema.CatchFunc(fn) // sets catch value for field using a function schema.Transform(func(valPtr *T or any, ctx z.Ctx) (any, error)) // adds a transformation function to the schema. This is useful for things like trimming strings, etc. // VALIDATION METHODS schema.Parse(data, destPtr) // parses the data into the destination schema.Validate(dataPtr) // validates the data structure directly. This is a pointer to a struct, slice, string, int, etc... ``` ## Options Utility functions used to configure schemas, executions, tests, etc... ### Test Options These are options that can be passed to any test. For more on this checkout the [Anatomy of a Schema](/core-concepts/anatomy-of-schema#test-options) page. ```go z.Message() // sets the issue message for messages generated by the tests z.MessageFunc(fn) // sets the issue message for messages generated by the tests. This is a function that takes the data as input and returns a string z.IssueCode() // sets the issue code for messages generated by the tests z.IssuePath() // sets the issue path for messages generated by the tests ``` ### Schema Options These are options that can be passed to schemas when creating them. ```go z.WithCoercer(fn) // sets the coercer for the schema. Only does anything if using schema.Parse() ``` ### Execution Options These are options that can be passed to schema.Parse() & schema.Validate(). They configure the execution behaviour of the validation. ```go z.WithIssueFormatter(fn) // sets the issue formatter for the execution. This is used to format the issues messages during execution. z.WithCtxValue(key, val) // sets a value in the execution context. This is useful for passing values to tests or post transforms. ``` ## Schema Types ```go // Primitives. Calling .Parse() on these will return []ZogIssue z.String() z.Int() z.Int32() z.Int64() z.Float32() z.Float64() z.Bool() z.Time() // Custom Primitive Schemas z.StringLike[T]() z.IntLike[T]() z.FloatLike[T]() z.UintLike[T]() z.BoolLike[T]() // Complex Types. Calling .Parse() on these will return ZogIssueList (same as primitives). Each issue has a Path field ([]string) indicating where the issue occurred. Root-level issues have a nil Path. z.Struct(z.Shape{ "name": z.String(), }) z.Slice(z.String()) z.Ptr(z.String()) // pointer to string z.Boxed[B, T](schema, unboxFunc, boxFunc) // boxed type wrapper ``` ### Utility Schemas ```go z.Preprocess() // Usage: z.Preprocess(func(data any, ctx z.ctx) (any, error) { s := data.(string) return strings.split(s, ","), nil }, z.slice(z.string()))) ``` ### Primitive Types #### String ```go // Transforms z.String().Trim() // trims the value of whitespace // Tests / Validations z.String().Test() // custom test z.String().Min(5) // validates min length z.String().Max(10) // validates max length z.String().Len(5) // validates length z.String().Email() // validates email z.String().URL() // validates url z.String().IPv4() // validates IPv4 address z.String().UUID() // validates uuid v4 z.String().Match(regex) // matches a regex z.String().Contains(substring) // validates string contains substring z.String().ContainsUpper() // validates string contains uppercase letter z.String().ContainsDigit() // validates string contains digit z.String().ContainsSpecial() // validates string contains special character z.String().HasPrefix(prefix) // validates string has prefix z.String().HasSuffix(suffix) // validates string has suffix z.String().OneOf([]string{"a", "b", "c"}) // validates string is one of the values. Similar to zod enums // Utilities z.String().Not() // Negates the next test/validation ``` #### Numbers / Ints & Floats ```go // Supported out of the box (see custom schemas for comparable types): z.Int() z.Int32() z.Int64() z.Float32() z.Float64() // Tests / Validators z.Int().GT(n) // validates int is greater than n z.Float().GTE(n) // validates float is greater than or equal to n z.Int().LT(n) // validates int is less than n z.Float().LTE(n) // validates float is less than or equal to n z.Int().EQ(n) // validates int is equal to n z.Float().OneOf([]float64{1.0, 2.0, 3.0}) // validates float is one of the values. Similar to zod enums // Utilities z.Int().Not() // Negates the next test/validation z.Float().Not() // Negates the next test/validation ``` #### Booleans ```go // Tests / Validators z.Bool().True() // validates bool is true z.Bool().False() // validates bool is false z.Bool().EQ(true) // validates bool is equal to true ``` ### Times & Dates Use Time to validate `time.Time` instances ```go // Tests / Validators z.Time().After(time.Now()) // validates time is after now z.Time().Before(time.Now()) // validates time is before now z.Time().Is(time.Now()) // validates time is equal to now // Schema Options z.Time(z.Time.Format(time.RFC3339)) // If input is a string, it will be parsed as a time.Time using the provided layout. time.RFC3339 is the default. Keep in mind this coercion only works when using Parse() ``` ### Complex Types #### Structs > Note structs cannot be required or optional. They just pass through to the underlying ZogSchemas for their fields. If you need to express that a struct might exist and if it does it must be valid, you can use a pointer. i.e `z.Ptr(z.Struct(z.Shape{...}))` ```go // usage s := z.Struct(z.Shape{ "name": String().Required(), "age": Int().Required(), }) // UTILITIES schema.Pick("key1", map[string]bool{"a": true, "b": false}) // creates a new shallow copy of the schema with only the specified fields. It supports both string keys and map[string]bool schema.Omit("key1", map[string]bool{"a": true, "b": false}) // creates a new shallow copy of the schema omitting the specified fields. It supports both string keys and map[string]bool schema.Extend(z.Shape{"a": z.String()}) // creates a new shallow copy of the schema with the additional fields schema.Merge(otherSchema, otherSchema2) // merges two or more schemas into a new schema. Last schema takes precedence for conflicting keys // Tests / Validators // None right now ``` #### Slices ```go // usage schema := z.Slice(String()) // Tests / Validators z.Slice(Int()).Min(5) // validates slice has at least 5 elements z.Slice(Float()).Max(5) // validates slice has at most 5 elements z.Slice(Bool()).Length(5) // validates slice has exactly 5 elements z.Slice(String()).Contains("foo") // validates slice contains the element "foo" // Utilities z.Slice(String()).Not() // Negates the next test/validation ``` #### Pointers ```go z.Ptr(z.String()) // validates pointer to string z.Ptr(z.Slice(z.String())) // validates pointer to slice of strings // Tests / Validators z.Ptr(z.String()).NotNil() // Validates pointer is not nil. This is equivalent to Required() for other types ``` #### Boxed Types Boxed schemas allow you to wrap any Zog schema with custom box/unbox logic. This is useful for working with types that wrap primitive values (like `sql.NullString`) or implementing custom value extraction patterns (like the `driver.Valuer` interface). ```go // Function signatures type UnboxFunc[B any, T any] func(data B, ctx Ctx) (T, error) type CreateBoxFunc[T any, B any] func(data T, ctx Ctx) (B, error) z.Boxed[B, T](schema ZogSchema, unboxFunc UnboxFunc[B, T], boxFunc CreateBoxFunc[T, B]) *BoxedSchema[B, T] ``` **Parameters:** - `B`: The boxed type (the wrapper type) - `T`: The inner type (the type that the schema validates) - `schema`: The Zog schema to validate the inner type - `unboxFunc`: Function to extract the inner value from the box - `boxFunc`: Function to create a new box from the validated inner value **Usage Examples:** ```go // // // Example 1: driver.Valuer pattern // // type StringValuer interface { Value() (string, error) } schema := z.Boxed( z.String().Min(3), func(b StringValuer, ctx z.Ctx) (string, error) { return b.Value() }, func(s string, ctx z.Ctx) (StringValuer, error) { return myStringValuer{v: s}, nil }, // you can pass nil here if you don't need to box values. ) var valuer StringValuer schema.Parse("hello", &valuer) // valuer.Value() will be "hello" valuer = createValuer("hello2") schema.Validate(&valuer) // valuer.Value() will be "hello2" // Example 2: Nullable pattern (like sql.NullString) type NullString struct { String string Valid bool } schema := z.Boxed( z.String().Min(3), func(ns NullString, ctx z.Ctx) (string, error) { if !ns.Valid { return "", errors.New("null string is not valid") } return ns.String, nil }, func(s string, ctx z.Ctx) (NullString, error) { return NullString{String: s, Valid: true}, nil }, ) // Example 3: Omittable pattern type Omittable[T any] interface { Value() T IsSet() bool } schema := z.Boxed( z.Ptr(z.String().Min(3)), func(o Omittable[string], ctx z.Ctx) (*string, error) { if o.IsSet() { val := o.Value() return &val, nil } return nil, nil }, func(s *string, ctx z.Ctx) (Omittable[string], error) { return createOmittable(s), nil }, ) ``` **Parse vs Validate:** - **Parse**: Accepts raw data, box values (`B`), or pointers to boxes (`*B`). If the input is a box, it unboxes it first before processing. The value will be boxed back into the original type if a `boxFunc` is provided. - **Validate**: Validates an existing box value, applies transformations, and re-boxes the result back into the original box if a `boxFunc` is provided. **Notes:** - Transforms, defaults, catch values, and all other schema features work normally and propagate back to the box - Boxed schemas can be nested inside Struct schemas - The `boxFunc` is called after validation/transformation to create the final boxed value