Claude
Skills
Sign in
Back

zog

Included with Lifetime
$97 forever

Zog schema validation library: schema definition, parsing, validation, error handling, HTTP/JSON/env integration, custom tests, and transforms. Invoke whenever task involves any interaction with Zog — writing schemas, parsing input, validating structs, handling errors, or integrating with HTTP handlers.

Productivity

What this skill does


# Zog

Zod-inspired schema validation for Go. Declarative schema builder with runtime parsing and validation. Import as
`z "github.com/Oudwins/zog"`.

<critical>

- All fields are **optional by default** (opposite of Zod)
- Schema keys in `z.Shape{}` must match **struct field names**, not input data keys — use struct tags (`json`, `form`,
  `zog`) for input key mapping
- All schemas return `ZogIssueList`
- Check errors with `len(errs) > 0`, not `errs != nil`
- Zog **panics on schema misconfiguration** (destination type mismatch, missing struct fields) but never on invalid
  input data
- Do not depend on test execution order — tests may run in parallel in future versions

</critical>

## Parse vs Validate

Two entry points, same schemas:

- `schema.Parse(data, &dest, ...opts)` — coerces untyped input into destination. Use at IO boundaries (HTTP, JSON, env)
- `schema.Validate(&value, ...opts)` — validates existing Go values. No coercion. More efficient when data is already
  typed

**Key difference:** `Validate` treats zero values as missing when `Required()` is set. Use `z.Ptr()` + `.NotNil()` to
distinguish "not provided" from "zero value":

```go
// Parse can distinguish 0 from missing
z.Int().Required().Parse(0, &dest) // ok

// Validate cannot — use pointer
z.Ptr(z.Int()).NotNil().Validate(&valPtr) // nil = missing, *0 = valid
```

**Prefer `Validate` when data is already typed.** Use `Parse` when accepting external input that needs coercion.

## Schema Types

### Primitives

All primitive `.Parse()` / `.Validate()` return `ZogIssueList`.

**String:**

- `z.String()` — base string schema
- `.Trim()` — transform: trims whitespace
- `.Min(n)`, `.Max(n)`, `.Len(n)` — length validators
- `.Email()`, `.URL()`, `.UUID()`, `.IPv4()` — format validators
- `.Match(regex)` — regex match
- `.Contains(s)`, `.ContainsUpper()`, `.ContainsDigit()`, `.ContainsSpecial()` — content validators
- `.HasPrefix(s)`, `.HasSuffix(s)` — prefix/suffix validators
- `.OneOf([]string{...})` — enum-like validation (replaces Zod's `z.Enum()`)
- `.Not()` — negates the next test

**Numbers:**

- `z.Int()`, `z.Int32()`, `z.Int64()`, `z.Float32()`, `z.Float64()` — numeric schemas
- `.GT(n)`, `.GTE(n)`, `.LT(n)`, `.LTE(n)`, `.EQ(n)` — comparison validators
- `.OneOf([]T{...})` — enum-like validation
- `.Not()` — negates the next test

**Bool:**

- `z.Bool()` — boolean schema
- `.True()`, `.False()`, `.EQ(v)` — value validators

**Time:**

- `z.Time()` — validates `time.Time`
- `.After(t)`, `.Before(t)`, `.Is(t)` — temporal validators
- `z.Time(z.Time.Format(layout))` — parse strings using layout (default: `time.RFC3339`). Coercion only works with
  `Parse()`, not `Validate()`

### Complex Types

**Struct:**

```go
schema := z.Struct(z.Shape{
    "name": z.String().Required(),
    "age":  z.Int().GT(0),
})
```

- `.Pick("key1", map[string]bool{"a": true})` — shallow copy with only specified fields
- `.Omit("key1", map[string]bool{"a": true})` — shallow copy without specified fields
- `.Extend(z.Shape{...})` — shallow copy with additional fields
- `.Merge(other1, other2)` — merge schemas, last wins on conflict
- Structs cannot be `Required()` or `Optional()` — use `z.Ptr(z.Struct(...))` for optional structs

**Slice:**

```go
schema := z.Slice(z.String().Required())
```

- `.Min(n)`, `.Max(n)`, `.Length(n)` — size validators
- `.Contains(val)` — element presence validator
- `.Not()` — negates the next test

**Pointer:**

```go
z.Ptr(z.String()) // pointer to string
```

- `.NotNil()` — equivalent to `Required()` for other types

**Boxed:**

```go
z.Boxed[BoxType, InnerType](innerSchema, unboxFunc, boxFunc)
```

Wraps a schema with custom box/unbox logic for types like `sql.NullString`, `driver.Valuer`, or custom wrappers. The
`unboxFunc` extracts the inner value; `boxFunc` creates the wrapper from validated value (can be `nil` if boxing not
needed).

### Custom Primitive Schemas

For named types based on primitives:

```go
type Env string
z.StringLike[Env]().OneOf([]Env{"prod", "dev"})
```

Available: `z.StringLike[T]()`, `z.IntLike[T]()`, `z.FloatLike[T]()`, `z.UintLike[T]()`, `z.BoolLike[T]()`

### CustomFunc

Quick validation for non-primitive types without defining a full schema:

```go
z.CustomFunc(func(valPtr *uuid.UUID, ctx z.Ctx) bool {
    return valPtr.IsValid()
}, z.Message("invalid uuid"))
```

Does not support coercion.

## Generic Schema Methods

Available on all schema types:

- `.Required()` — field must be present and non-zero
- `.Optional()` — field can be absent (default behavior)
- `.Default(val)` / `.DefaultFunc(fn)` — use this value when input is zero. Takes priority over `Required()`. Tests
  still run
- `.Catch(val)` / `.CatchFunc(fn)` — on any error, set destination to this value and stop execution
- `.Test(z.Test{...})` — complex custom test (like Zod's `superRefine`)
- `.TestFunc(fn, ...opts)` — simple custom test (like Zod's `refine`)
- `.Transform(func(valPtr *T, ctx z.Ctx) error)` — post-validation in-place mutation

Execution order: nil check → default/required → coerce → validation loop (tests + transforms in order) → catch on error

## Struct Tags

Schema keys match struct field names by default. Use struct tags for input key mapping:

- `json` — JSON input
- `form` — form data
- `query` — query params
- `env` — environment variables
- `zog` — catch-all, works for any input

Priority: `json`/`form`/`query`/`env` → `zog` → schema field name

```go
type User struct {
    Name     string `zog:"first-name"`
    LastName string `query:"last_name" json:"last-name"`
}
```

## Custom Tests

**Simple (refine-like):** Return `bool` — Zog creates the issue:

```go
z.String().TestFunc(func(data *string, ctx z.Ctx) bool {
    return *data == "expected"
}, z.Message("must be expected"))
```

For structs/slices, the parameter is `any` — cast manually:

```go
z.Struct(z.Shape{...}).TestFunc(func(dataPtr any, ctx z.Ctx) bool {
    user := dataPtr.(*User)
    return user.Name != ""
})
```

**Complex (superRefine-like):** Add issues manually via context:

```go
z.String().Test(z.Test{
    Func: func(val any, ctx z.Ctx) {
        s := val.(string)
        if !isValid(s) {
            ctx.AddIssue(ctx.Issue().SetMessage("invalid value"))
        }
    },
})
```

**Reusable tests:** Wrap in functions returning `z.Test[T]`:

```go
func MinWords(n int, opts ...z.TestOption) z.Test[any] {
    options := []z.TestOption{z.Message(fmt.Sprintf("must have at least %d words", n))}
    options = append(options, opts...)
    return z.TestFunc(func(val any, ctx z.Ctx) bool {
        s := val.(*string)
        return len(strings.Fields(*s)) >= n
    }, options...)
}
```

## Transforms

Post-validation mutation via pointer. Runs in declaration order with tests:

```go
z.String().Min(3).Transform(func(valPtr *string, ctx z.Ctx) error {
    *valPtr = strings.ToLower(*valPtr)
    return nil
})
```

Struct transforms receive `any` — cast to struct pointer:

```go
z.Struct(z.Shape{...}).Transform(func(dataPtr any, ctx z.Ctx) error {
    user := dataPtr.(*User)
    user.FullName = user.First + " " + user.Last
    return nil
})
```

Returning an error from a transform stops execution and produces an issue.

## Preprocess

Transform input **before** parsing. Pure function — creates a copy:

```go
z.Preprocess(func(data any, ctx z.Ctx) (any, error) {
    s, ok := data.(string)
    if !ok {
        return nil, fmt.Errorf("expected string, got %T", data)
    }
    return strings.Split(s, ","), nil
}, z.Slice(z.String().Email().Required()))
```

**Footgun:** With `Validate()`, the `data` argument is a pointer to the value, not the raw input.

## Error Handling

### ZogIssue

```go
type ZogIssue struct {
    Code    zconst.ZogIssueCode // issue identifier (e.g., "required", "min", "email")
    Path    []string            // location in data structure (nil for root primitives)
    Value   any                 // input value that caused the issue
    Dtype   string              // destination type
   
Files: 2
Size: 18.0 KB
Complexity: 24/100
Category: Productivity

Related in Productivity