Skip to content

feat: boxed schema#201

Merged
Oudwins merged 13 commits intomasterfrom
feat/boxed-values
Dec 7, 2025
Merged

feat: boxed schema#201
Oudwins merged 13 commits intomasterfrom
feat/boxed-values

Conversation

@Oudwins
Copy link
Owner

@Oudwins Oudwins commented Dec 7, 2025

This PR adds support for boxed schema which should allow you to use any/all optional types with Zog!

Summary by CodeRabbit

  • New Features

    • Added boxed schema support to wrap/unwrap external types so schemas validate/parse inner values while propagating transforms/defaults back to boxed values; works with primitives, pointers, structs, slices, maps and nested schemas.
  • Tests

    • Added extensive unit tests covering parsing, validation, error paths, transforms, defaults, nullable/omittable patterns, pointer scenarios, and nested compositions.
  • Documentation

    • Added docs and examples demonstrating boxed types, common patterns, and Parse vs Validate behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 7, 2025

Warning

Rate limit exceeded

@Oudwins has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 4 minutes and 21 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 047a860 and 14a2e0a.

📒 Files selected for processing (1)
  • boxedSchema.go (1 hunks)

Walkthrough

Adds a generic BoxedSchema wrapper that boxes/unboxes values around an inner ZogSchema, implements Parse/Validate flows, forwards type/coercer behavior to the inner schema, integrates with execution context and issue handling, and includes extensive tests and documentation for boxed patterns and examples.

Changes

Cohort / File(s) Summary
Implementation
boxedSchema.go
Adds generic BoxedSchema[B, T], UnboxFunc and CreateBoxFunc types, Boxed() constructor, public Parse() and Validate() methods, internal process()/validate() logic, and getType()/setCoercer() forwarding to the inner schema.
Tests — Parse
boxedSchema_parse_test.go
New parse tests covering primitives, pointers, valuer patterns, slices/structs/maps, transforms, defaults, nullable/omittable patterns, nested schemas, and error paths (unbox/box/catch).
Tests — Validate
boxedSchema_validate_test.go
New validation tests with helper boxed types and valuer mocks; covers primitive/composite boxing, interface-based validation, pointer schemas, transforms/defaults, required/optional/catch semantics, and nested/composite scenarios.
Docs & Examples
boxedSchema_docs_examples_test.go, docs/docs/reference.md
Adds documentation and example tests for Boxed types, documents z.Boxed[B, T], UnboxFunc, CreateBoxFunc, Parse vs Validate behavior, and patterns (driver.Valuer, sql.NullString, Omittable).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus areas:
    • boxedSchema.go: generics usage, type-safety, and context/issue propagation in process() and validate().
    • Error mapping: conversion of unbox/box errors into issues and interaction with catch semantics.
    • Tests: validate correctness of value propagation back into boxed types and pointer/nullable behaviors.
    • Documentation: ensure examples match exported signatures and compile.

Possibly related PRs

  • refactor: types #136 — Related coercion/type API changes that intersect with BoxedSchema.setCoercer and issue/issue-map types used by the implementation.

Poem

🐇 I burrow through bytes, then gently unbind,
I lift out the truth, then tuck it behind,
Parsers hop in, validators hum,
Errors are chewed, defaults become,
I box every value and leave joy behind 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.61% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: boxed schema' directly corresponds to the main feature being added—a new BoxedSchema wrapper that enables optional type support in the Zog schema validation library.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e2bd77 and 2c60776.

📒 Files selected for processing (3)
  • boxedSchema.go (1 hunks)
  • boxedSchema_parse_test.go (1 hunks)
  • boxedSchema_validate_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
boxedSchema_parse_test.go (10)
boxedSchema.go (1)
  • Boxed (20-22)
string.go (1)
  • String (83-91)
boxedSchema_validate_test.go (18)
  • StringBox (13-15)
  • BoolBox (25-27)
  • IntBox (29-31)
  • Float64Box (33-35)
  • TimeBox (37-39)
  • StringValuer (55-57)
  • IntValuer (59-61)
  • SliceBox (41-43)
  • UserBox (50-52)
  • BoxedUser (45-48)
  • SliceValuer (63-65)
  • NullString (102-105)
  • StringPtrBox (519-521)
  • IntPtrBox (575-577)
  • StringValuerBox (21-23)
  • ContainerStruct (811-814)
  • ValuerBox (899-901)
  • ContainerWithSlice (911-914)
boolean.go (1)
  • Bool (34-42)
numbers.go (2)
  • Int (93-101)
  • Float64 (63-71)
internals/tests.go (2)
  • GT (236-247)
  • Required (114-121)
time.go (1)
  • Time (37-45)
slices.go (1)
  • Slice (44-53)
struct.go (2)
  • Struct (42-46)
  • Shape (35-35)
pointers.go (1)
  • Ptr (31-35)
boxedSchema_validate_test.go (9)
time.go (1)
  • Time (37-45)
string.go (1)
  • String (83-91)
boxedSchema.go (1)
  • Boxed (20-22)
boolean.go (1)
  • Bool (34-42)
numbers.go (2)
  • Int (93-101)
  • Float64 (63-71)
internals/tests.go (2)
  • GT (236-247)
  • Required (114-121)
slices.go (1)
  • Slice (44-53)
struct.go (2)
  • Struct (42-46)
  • Shape (35-35)
pointers.go (1)
  • Ptr (31-35)
🔇 Additional comments (16)
boxedSchema.go (4)

1-22: LGTM! Clean generic type definitions and constructor.

The type definitions, interface compliance check, and constructor are well-structured. The generic type parameters B (box type) and T (inner type) are clearly documented through usage.


24-38: LGTM! Proper resource management in Parse method.

The deferred Free() calls ensure proper cleanup of pooled resources (errs, ctx, path, sctx). The execution flow correctly applies options before processing.


40-54: LGTM! Validate method follows same pattern as Parse.

Consistent resource management and option handling. Correctly passes *dest as data and dest as valPtr.


130-136: LGTM! Correctly delegates to inner schema.

Both getType() and setCoercer() appropriately forward to the wrapped schema.

boxedSchema_validate_test.go (6)

12-105: LGTM! Well-designed test fixtures covering diverse boxing scenarios.

The type definitions provide good coverage:

  • Struct-based boxes (StringBox, IntBox, etc.)
  • Interface-based Valuer patterns
  • Error-returning implementations for testing error paths
  • Nullable pattern mirroring sql.NullString

These serve as effective shared fixtures for the test suite.


111-201: LGTM! Thorough primitive type validation tests.

Tests cover all major primitive types with both success and failure scenarios. The assertions verify both the error state and that original values are preserved appropriately.


207-337: LGTM! Good coverage of interface-based boxing and complex types.

The Valuer pattern tests demonstrate flexibility for database/sql-like interfaces. Complex type tests (Slice, Struct) verify nested schema handling.


343-513: LGTM! Critical error handling and catch propagation tests.

The tests verify:

  • Unbox errors are properly surfaced
  • Nullable patterns work as expected
  • Catch values correctly propagate back to the boxed type

These are essential for the BoxedSchema's reliability.


519-805: LGTM! Comprehensive tests for value modification scenarios.

Excellent coverage of:

  • Pointer schemas with nil/NotNil handling
  • Transform propagation back to boxed types
  • Default value propagation
  • Required vs Optional semantics
  • Interaction between Required and Catch

These tests validate the core value-flow mechanics of BoxedSchema.


811-938: LGTM! Essential nested schema composition tests.

The tests for BoxedSchema inside Struct validate critical composition scenarios including catch value propagation, transform propagation, and validation failures in nested contexts. The ContainerWithSlice test with interface-based boxing is a good real-world scenario.

boxedSchema_parse_test.go (6)

1-104: LGTM! Solid primitive type parsing tests.

Good coverage of parsing raw data into boxed types, including the time parsing test which verifies string-to-time coercion works through the BoxedSchema.


110-181: LGTM! Important input path coverage.

These tests validate the type switch logic in process() handles both B and *B inputs correctly, ensuring the unboxing path works for all input forms.


187-358: LGTM! Comprehensive interface and complex type parsing tests.

Good coverage of Valuer-pattern parsing and complex types (Slice, Struct). The error handling tests ensure both unbox and box errors are properly surfaced during parsing.


364-578: LGTM! Real-world pattern and pointer parsing tests.

The nullable pattern tests (lines 364-412) are particularly valuable for database integration scenarios. Pointer schema parsing with nil handling (lines 518-578) ensures optional field semantics work correctly during parsing.


584-791: LGTM! Transform, default, and required parsing tests.

Excellent coverage of value modification during parsing:

  • Trim transform properly propagates back to boxed output
  • Default values work with nil input
  • Required fails on zero values while Optional allows them
  • Required + Catch interaction works correctly

797-926: LGTM! Nested schema parsing tests complete the coverage.

The BoxedSchema-inside-Struct parsing tests validate composition scenarios including:

  • Raw data to nested boxed fields
  • Box input to nested boxed fields
  • Catch and Transform propagation in nested contexts
  • Slice with inner catch in boxed context

This provides confidence that BoxedSchema integrates correctly with other schema types.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Dec 7, 2025

Deploying zog with  Cloudflare Pages  Cloudflare Pages

Latest commit: 047a860
Status: ✅  Deploy successful!
Preview URL: https://58a32925.zog-3a0.pages.dev
Branch Preview URL: https://feat-boxed-values.zog-3a0.pages.dev

View logs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
docs/docs/reference.md (1)

223-318: Excellent documentation with minor formatting issue.

The Boxed Types documentation is comprehensive and includes clear examples that match the test implementations. The function signatures, parameters, and usage patterns are well-explained.

Minor formatting cleanup:

The code examples use hard tabs instead of spaces (lines 253-305). While this doesn't affect functionality, it's flagged by the markdown linter. Consider replacing tabs with spaces for consistency:

 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 },
+    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 },
 )

Apply this pattern to all code examples in the section (lines 253-305).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c60776 and b79a073.

📒 Files selected for processing (2)
  • boxedSchema_docs_examples_test.go (1 hunks)
  • docs/docs/reference.md (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
boxedSchema_docs_examples_test.go (3)
boxedSchema.go (1)
  • Boxed (20-22)
string.go (1)
  • String (83-91)
pointers.go (1)
  • Ptr (31-35)
🪛 markdownlint-cli2 (0.18.1)
docs/docs/reference.md

253-253: Hard tabs
Column: 1

(MD010, no-hard-tabs)


257-257: Hard tabs
Column: 1

(MD010, no-hard-tabs)


258-258: Hard tabs
Column: 1

(MD010, no-hard-tabs)


259-259: Hard tabs
Column: 1

(MD010, no-hard-tabs)


270-270: Hard tabs
Column: 1

(MD010, no-hard-tabs)


271-271: Hard tabs
Column: 1

(MD010, no-hard-tabs)


275-275: Hard tabs
Column: 1

(MD010, no-hard-tabs)


276-276: Hard tabs
Column: 1

(MD010, no-hard-tabs)


277-277: Hard tabs
Column: 1

(MD010, no-hard-tabs)


278-278: Hard tabs
Column: 1

(MD010, no-hard-tabs)


279-279: Hard tabs
Column: 1

(MD010, no-hard-tabs)


280-280: Hard tabs
Column: 1

(MD010, no-hard-tabs)


281-281: Hard tabs
Column: 1

(MD010, no-hard-tabs)


282-282: Hard tabs
Column: 1

(MD010, no-hard-tabs)


283-283: Hard tabs
Column: 1

(MD010, no-hard-tabs)


284-284: Hard tabs
Column: 1

(MD010, no-hard-tabs)


290-290: Hard tabs
Column: 1

(MD010, no-hard-tabs)


291-291: Hard tabs
Column: 1

(MD010, no-hard-tabs)


295-295: Hard tabs
Column: 1

(MD010, no-hard-tabs)


296-296: Hard tabs
Column: 1

(MD010, no-hard-tabs)


297-297: Hard tabs
Column: 1

(MD010, no-hard-tabs)


298-298: Hard tabs
Column: 1

(MD010, no-hard-tabs)


299-299: Hard tabs
Column: 1

(MD010, no-hard-tabs)


300-300: Hard tabs
Column: 1

(MD010, no-hard-tabs)


301-301: Hard tabs
Column: 1

(MD010, no-hard-tabs)


302-302: Hard tabs
Column: 1

(MD010, no-hard-tabs)


303-303: Hard tabs
Column: 1

(MD010, no-hard-tabs)


304-304: Hard tabs
Column: 1

(MD010, no-hard-tabs)


305-305: Hard tabs
Column: 1

(MD010, no-hard-tabs)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (6)
boxedSchema_docs_examples_test.go (5)

15-42: LGTM!

The Omittable pattern implementation is clean and correctly handles both set and unset cases.


48-74: Test logic is sound.

The test properly exercises both Parse and Validate flows for the driver.Valuer pattern, assuming myStringValuer is defined elsewhere in the codebase.


76-110: LGTM!

The nullable pattern test is self-contained and thoroughly tests both Parse and Validate flows with proper assertions.


120-161: Comprehensive test coverage.

The test thoroughly validates all four scenarios (Parse/Validate × set/unset) with correct assertions for the Omittable pattern.


10-13: The myStringValuer type is defined in boxedSchema_validate_test.go in the same package, so there is no compilation error. Test files in the same package have access to all package-level symbols, including those defined in other test files. The createValuer function will compile and run correctly.

Likely an incorrect or invalid review comment.

docs/docs/reference.md (1)

83-83: LGTM!

The Boxed schema type is properly documented in the Schema Types section with a clear comment.

@Oudwins Oudwins merged commit 6bb4304 into master Dec 7, 2025
8 checks passed
@Oudwins Oudwins deleted the feat/boxed-values branch December 7, 2025 16:34
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
boxedSchema.go (1)

56-60: Fix type parameter order in panic messages (matches earlier review)

Both validate and process panic messages print BoxedSchema[%T, %T] but pass new(T), new(B), even though BoxedSchema is defined as BoxedSchema[B, T]. This is just a diagnostics mismatch, but it can be confusing when debugging.

You can align the messages with the type parameter order like this:

func (s *BoxedSchema[B, T]) validate(ctx *p.SchemaCtx) {
 	boxPtr, ok := ctx.ValPtr.(*B)
 	if !ok {
-		p.Panicf("BoxedSchema[%T, %T]: Expected valPtr type to correspond with type defined in schema. But it does not. Expected type: %T, got: %T", new(T), new(B), new(*B), ctx.ValPtr)
+		p.Panicf("BoxedSchema[%T, %T]: Expected valPtr type to correspond with type defined in schema. But it does not. Expected type: %T, got: %T", new(B), new(T), new(*B), ctx.ValPtr)
 	}
}

func (s *BoxedSchema[B, T]) process(ctx *p.SchemaCtx) {
 	boxPtr, ok := ctx.ValPtr.(*B)
 	if !ok {
-		p.Panicf("BoxedSchema[%T, %T]: Expected valPtr type to correspond with type defined in schema. But it does not. Expected type: %T, got: %T", new(T), new(B), new(*B), ctx.ValPtr)
+		p.Panicf("BoxedSchema[%T, %T]: Expected valPtr type to correspond with type defined in schema. But it does not. Expected type: %T, got: %T", new(B), new(T), new(*B), ctx.ValPtr)
 	}
}

Also applies to: 81-85

🧹 Nitpick comments (3)
boxedSchema.go (3)

20-22: Guard against nil unboxFunc to avoid silent panics on misuse

Right now Boxed accepts a potentially nil unboxFunc, and both validate and process unconditionally call s.unbox, which would panic if a caller accidentally passed nil.

If you want to keep misuse from turning into a hard-to-diagnose panic, consider a constructor-time guard:

func Boxed[B any, T any](schema ZogSchema, unboxFunc UnboxFunc[B, T], boxFunc CreateBoxFunc[T, B]) *BoxedSchema[B, T] {
-	return &BoxedSchema[B, T]{schema: schema, unbox: unboxFunc, box: boxFunc}
+	if unboxFunc == nil {
+		p.Panicf("BoxedSchema[%T, %T]: unboxFunc must not be nil", new(B), new(T))
+	}
+	return &BoxedSchema[B, T]{schema: schema, unbox: unboxFunc, box: boxFunc}
}

This keeps the API contract explicit and fails fast if violated.


40-54: Validate will panic on nil dest; consider making this precondition explicit or guarding it

Validate dereferences dest unconditionally:

sctx := ctx.NewSchemaCtx(*dest, dest, path, s.getType())

If a caller passes nil (e.g. var b *B; schema.Validate(b)), this will panic before any validation happens. If the library contract is “dest must be non-nil”, it’d help to either (a) document that clearly, or (b) add a runtime guard that panics with a more descriptive message.

For example:

func (s *BoxedSchema[B, T]) Validate(dest *B, options ...ExecOption) ZogIssueMap {
+	if dest == nil {
+		p.Panicf("BoxedSchema[%T, %T]: Validate dest must not be nil", new(B), new(T))
+	}
 	errs := p.NewErrsMap()
 	defer errs.Free()
 	ctx := p.NewExecCtx(errs, conf.IssueFormatter)
 	defer ctx.Free()
 	// ...
}

This keeps the failure mode intentional and easier to debug.


87-129: process flow (unbox → inner parse → re-box) is solid; consider clarifying behavior when boxFunc is nil

The process flow looks good:

  • Assert ctx.ValPtr is *B and keep that pointer to write back.
  • Normalize ctx.Data by:
    • Unboxing when data is B or *B.
    • Passing raw data through otherwise.
  • Allocate a temporary inner T, set ctx.ValPtr = &inner, and delegate to s.schema.process(ctx).
  • Re-box via s.box(inner, ctx) and assign back to *boxPtr when s.box != nil.

This matches the documented Parse semantics for boxed types. The TODO:

// TODO maybe some kind of flag that you executed process with boxFunc is nil

suggests you might want observability when boxFunc is nil and nothing is written back to *boxPtr. If that matters, a lightweight option would be to add debug-only instrumentation or a small helper on BoxedSchema (e.g., hasBox() or a comment near the constructor) rather than a runtime flag in process itself.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b79a073 and 047a860.

📒 Files selected for processing (2)
  • boxedSchema.go (1 hunks)
  • docs/docs/reference.md (2 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
docs/docs/reference.md

253-253: Hard tabs
Column: 1

(MD010, no-hard-tabs)


257-257: Hard tabs
Column: 1

(MD010, no-hard-tabs)


258-258: Hard tabs
Column: 1

(MD010, no-hard-tabs)


259-259: Hard tabs
Column: 1

(MD010, no-hard-tabs)


270-270: Hard tabs
Column: 1

(MD010, no-hard-tabs)


271-271: Hard tabs
Column: 1

(MD010, no-hard-tabs)


275-275: Hard tabs
Column: 1

(MD010, no-hard-tabs)


276-276: Hard tabs
Column: 1

(MD010, no-hard-tabs)


277-277: Hard tabs
Column: 1

(MD010, no-hard-tabs)


278-278: Hard tabs
Column: 1

(MD010, no-hard-tabs)


279-279: Hard tabs
Column: 1

(MD010, no-hard-tabs)


280-280: Hard tabs
Column: 1

(MD010, no-hard-tabs)


281-281: Hard tabs
Column: 1

(MD010, no-hard-tabs)


282-282: Hard tabs
Column: 1

(MD010, no-hard-tabs)


283-283: Hard tabs
Column: 1

(MD010, no-hard-tabs)


284-284: Hard tabs
Column: 1

(MD010, no-hard-tabs)


290-290: Hard tabs
Column: 1

(MD010, no-hard-tabs)


291-291: Hard tabs
Column: 1

(MD010, no-hard-tabs)


295-295: Hard tabs
Column: 1

(MD010, no-hard-tabs)


296-296: Hard tabs
Column: 1

(MD010, no-hard-tabs)


297-297: Hard tabs
Column: 1

(MD010, no-hard-tabs)


298-298: Hard tabs
Column: 1

(MD010, no-hard-tabs)


299-299: Hard tabs
Column: 1

(MD010, no-hard-tabs)


300-300: Hard tabs
Column: 1

(MD010, no-hard-tabs)


301-301: Hard tabs
Column: 1

(MD010, no-hard-tabs)


302-302: Hard tabs
Column: 1

(MD010, no-hard-tabs)


303-303: Hard tabs
Column: 1

(MD010, no-hard-tabs)


304-304: Hard tabs
Column: 1

(MD010, no-hard-tabs)


305-305: Hard tabs
Column: 1

(MD010, no-hard-tabs)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (5)
boxedSchema.go (3)

9-22: Boxed API surface and type aliases look consistent and idiomatic

CreateBoxFunc/UnboxFunc, the BoxedSchema[B, T] definition, and the Boxed constructor all follow the “B = box, T = inner” convention and match the docs. The interface conformance check via var _ ComplexZogSchema = &BoxedSchema[any, any]{} is a nice guardrail.


24-38: Parse wiring into ExecCtx/SchemaCtx matches existing patterns

The Parse implementation correctly mirrors other complex schemas: allocate/free ErrsMap and ExecCtx, apply ExecOptions, build a PathBuilder, then allocate a SchemaCtx and delegate to process. This should integrate smoothly with existing issue formatting and path handling.


131-137: Delegating type and coercer behavior to the inner schema is appropriate

getType and setCoercer simply forward to the wrapped schema, which keeps BoxedSchema transparent to callers and lets it participate correctly in any type-based or coercion-based logic elsewhere.

docs/docs/reference.md (2)

83-84: Schema Types list correctly advertises z.Boxed

Adding z.Boxed[B, T](schema, unboxFunc, boxFunc) // boxed type wrapper here keeps the reference section aligned with the new API and mirrors the implementation signature.


223-318: Boxed Types section is clear and matches the implementation semantics

This section does a good job of:

  • Defining UnboxFunc, CreateBoxFunc, and z.Boxed[B, T] consistently with the Go implementation.
  • Explaining the roles of B (box) and T (inner) and the responsibilities of schema, unboxFunc, and boxFunc.
  • Providing realistic patterns: driver.Valuer-style wrappers, a nullable wrapper, and an Omittable wrapper.
  • Explicitly documenting the behavioral difference between Parse (handling raw/boxed inputs) and Validate (validating and re-boxing an existing box).

The examples should give users enough guidance to implement their own boxing patterns.

Comment on lines +245 to +307
```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
},
)
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Resolve markdownlint MD010 “no-hard-tabs” warnings in new code examples

markdownlint is flagging hard tabs in this new Boxed examples block (e.g., around the StringValuer, NullString, and Omittable definitions). To keep docs lint-clean, it’s worth normalizing indentation to spaces inside these code fences.

For example:

-type StringValuer interface {
-	Value() (string, error)
-}
+type StringValuer interface {
+    Value() (string, error)
+}

and similarly for the other indented lines in the Boxed examples. Using spaces throughout will satisfy MD010 without changing how the snippets render.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```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
},
)
```
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

253-253: Hard tabs
Column: 1

(MD010, no-hard-tabs)


257-257: Hard tabs
Column: 1

(MD010, no-hard-tabs)


258-258: Hard tabs
Column: 1

(MD010, no-hard-tabs)


259-259: Hard tabs
Column: 1

(MD010, no-hard-tabs)


270-270: Hard tabs
Column: 1

(MD010, no-hard-tabs)


271-271: Hard tabs
Column: 1

(MD010, no-hard-tabs)


275-275: Hard tabs
Column: 1

(MD010, no-hard-tabs)


276-276: Hard tabs
Column: 1

(MD010, no-hard-tabs)


277-277: Hard tabs
Column: 1

(MD010, no-hard-tabs)


278-278: Hard tabs
Column: 1

(MD010, no-hard-tabs)


279-279: Hard tabs
Column: 1

(MD010, no-hard-tabs)


280-280: Hard tabs
Column: 1

(MD010, no-hard-tabs)


281-281: Hard tabs
Column: 1

(MD010, no-hard-tabs)


282-282: Hard tabs
Column: 1

(MD010, no-hard-tabs)


283-283: Hard tabs
Column: 1

(MD010, no-hard-tabs)


284-284: Hard tabs
Column: 1

(MD010, no-hard-tabs)


290-290: Hard tabs
Column: 1

(MD010, no-hard-tabs)


291-291: Hard tabs
Column: 1

(MD010, no-hard-tabs)


295-295: Hard tabs
Column: 1

(MD010, no-hard-tabs)


296-296: Hard tabs
Column: 1

(MD010, no-hard-tabs)


297-297: Hard tabs
Column: 1

(MD010, no-hard-tabs)


298-298: Hard tabs
Column: 1

(MD010, no-hard-tabs)


299-299: Hard tabs
Column: 1

(MD010, no-hard-tabs)


300-300: Hard tabs
Column: 1

(MD010, no-hard-tabs)


301-301: Hard tabs
Column: 1

(MD010, no-hard-tabs)


302-302: Hard tabs
Column: 1

(MD010, no-hard-tabs)


303-303: Hard tabs
Column: 1

(MD010, no-hard-tabs)


304-304: Hard tabs
Column: 1

(MD010, no-hard-tabs)


305-305: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🤖 Prompt for AI Agents
In docs/docs/reference.md around lines 245 to 307, the fenced Go code examples
contain hard tab characters triggering markdownlint MD010; replace all hard tabs
inside the code block with spaces (use consistent indentation, e.g., 2 or 4
spaces) so every indented line (type definitions, schema assignments, function
params and bodies) uses spaces only, and re-run lint to confirm the MD010
warning is resolved.

Oudwins added a commit that referenced this pull request Dec 14, 2025
* wp

* wp

* test: initial validate boxed schema tests

* wp

* wp

* wp

* wp

* wp

* feat: parse boxed schema

* wp

* wp

* wp

* fix: review
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant