Optional Pointer Validation
Learn how optional pointer validation works in NLG Form using OptionalPtr, nullable fields, PATCH APIs, nested objects, and reusable conditional validation pipelines.
OptionalPtr is the core primitive behind optional validation in form.
It allows validation rules to execute only when a pointer value exists.
This makes it possible to validate nullable input safely while supporting partial updates, PATCH APIs, optional nested objects, and transport-independent validation workflows.
Why OptionalPtr Exists
Many APIs require fields that:
- may be omitted
- may explicitly contain
null - may appear only during partial updates
- may be conditionally validated
Examples include:
- profile updates
- billing configuration
- nested settings
- optional embedded objects
- optional metadata
- nullable database fields
- PATCH endpoints
- feature toggles
- optional onboarding sections
OptionalPtr solves this by separating:
- existence validation
- value validation
TL;DR
| Helper | Description |
|---|---|
optional.OptionalPtr(field, rules...) | Runs nested rules only when the pointer is non-nil |
optional.OptionalPtrWith(field, fn, rules...) | Runs rules only when a custom condition passes |
optional.OptionalPtrValue(field, value, rules...) | Runs rules only when the pointer matches a specific value |
Pointer-Based Validation
Optional pointer validation is based on nullable pointer fields.
Example:
type UpdateProfileRequest struct {
Age *int `json:"age"`
Price *float64 `json:"price"`
Nickname *string `json:"nickname"`
}This allows the application to distinguish between:
{
"nickname": null
}and:
{}This distinction is extremely important in PATCH APIs and partial updates.
Defining Optional Pointer Fields
Optional pointer fields use typed optional field definitions.
OptionalPtrForm := struct {
Nickname form.OptStringField[OptionalPtrRequest]
Age form.OptIntField[OptionalPtrRequest]
Price form.OptFloat64Field[OptionalPtrRequest]
}{
Nickname: form.OptString[OptionalPtrRequest]("nickname", func(r *OptionalPtrRequest) *string {
return r.Nickname
}),
Age: form.OptInt[OptionalPtrRequest]("age", func(r *OptionalPtrRequest) *int {
return r.Age
}),
Price: form.OptFloat64[OptionalPtrRequest]("price", func(r *OptionalPtrRequest) *float64 {
return r.Price
}),
}Applying OptionalPtr
OptionalPtr executes nested validation rules only when the pointer exists.
return form.Schema[OptionalPtrRequest]{
optional.OptionalPtr(
OptionalPtrForm.Nickname.Field,
rules.MinLen(OptionalPtrForm.Nickname, 3),
),
optional.OptionalPtr(
OptionalPtrForm.Age.Field,
optional.MinOpt(OptionalPtrForm.Age, 18),
),
}Validation behavior:
| Input | Result |
|---|---|
null | skipped |
| omitted field | skipped |
| present value | validated |
OptionalPtrWith
OptionalPtrWith allows custom conditional execution.
Example:
optional.OptionalPtrWith(
OptionalPtrForm.Price.Field,
func(v *float64) bool {
return v != nil && *v > 0
},
optional.MinFloat64Opt(OptionalPtrForm.Price, 1),
)Typical use cases:
- conditional billing validation
- feature-based validation
- advanced business rules
- staged onboarding
OptionalPtrValue
OptionalPtrValue runs validation only when the pointer matches a specific value.
Example:
optional.OptionalPtrValue(
OptionalPtrForm.Role.Field,
"admin",
rules.Required(AdminForm.AccessLevel),
)Typical use cases:
- role-based validation
- workflow branching
- feature toggles
- conditional form sections
Nested Object Validation
OptionalPtr is especially useful for optional nested objects.
Example:
type Billing struct {
VAT string `json:"vat"`
}
type Request struct {
Billing *Billing `json:"billing"`
}Validation:
optional.OptionalPtr(
RequestForm.Billing.Field,
BillingSchema(),
)This allows nested validation only when the object exists.
Complete Example
schema.go
package main
import (
"github.com/netlifeguru/form"
"github.com/netlifeguru/form/optional"
"github.com/netlifeguru/form/rules"
)
const (
CodeNicknameMinLen = form.Code("nickname_min_len")
CodeAgeMin = form.Code("age_min")
CodePriceMin = form.Code("price_min")
)
type OptionalPtrRequest struct {
Nickname *string `json:"nickname"`
Age *int `json:"age"`
Price *float64 `json:"price"`
}
func OptionalPtrSchema() form.Schema[OptionalPtrRequest] {
OptionalPtrForm := struct {
Nickname form.OptStringField[OptionalPtrRequest]
Age form.OptIntField[OptionalPtrRequest]
Price form.OptFloat64Field[OptionalPtrRequest]
}{
Nickname: form.OptString[OptionalPtrRequest]("nickname", func(r *OptionalPtrRequest) *string {
return r.Nickname
}),
Age: form.OptInt[OptionalPtrRequest]("age", func(r *OptionalPtrRequest) *int {
return r.Age
}),
Price: form.OptFloat64[OptionalPtrRequest]("price", func(r *OptionalPtrRequest) *float64 {
return r.Price
}),
}
return form.Schema[OptionalPtrRequest]{
optional.OptionalPtr(
OptionalPtrForm.Nickname.Field,
rules.MinLenWithCode(OptionalPtrForm.Nickname, 3, CodeNicknameMinLen),
),
optional.OptionalPtr(
OptionalPtrForm.Age.Field,
optional.MinOptWithCode(OptionalPtrForm.Age, 18, CodeAgeMin),
),
optional.OptionalPtr(
OptionalPtrForm.Price.Field,
optional.MinFloat64OptWithCode(OptionalPtrForm.Price, 0.01, CodePriceMin),
),
}
}main.go
package main
import (
"encoding/json"
"fmt"
"log/slog"
"net/http"
"os"
"github.com/netlifeguru/form"
"github.com/netlifeguru/form/httpform"
"github.com/netlifeguru/router"
)
func main() {
r := router.New()
r.HandleFunc("/optional-ptr", "POST", func(w http.ResponseWriter, req *http.Request, ctx *router.Context) {
var in OptionalPtrRequest
if !httpform.BindAndValidate(w, req, &in, OptionalPtrSchema(), 1<<20) {
fmt.Println("optional ptr validation failed")
return
}
fmt.Println("optional ptr validation passed:", in)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"message": "optional ptr validation passed",
"data": in,
})
})
validPayload := map[string]any{
"nickname": "john",
"age": 25,
"price": 19.99,
}
invalidPayload := map[string]any{
"nickname": "ab",
"age": 10,
"price": -1,
}
skippedPayload := map[string]any{
"nickname": nil,
"age": nil,
"price": nil,
}
fmt.Println("\n--- Valid request ---")
form.SendTestPost(":8080/optional-ptr", validPayload)
fmt.Println("\n--- Invalid request ---")
form.SendTestPost(":8080/optional-ptr", invalidPayload)
fmt.Println("\n--- Skipped validation request ---")
form.SendTestPost(":8080/optional-ptr", skippedPayload)
if err := r.ListenAndServe(8080); err != nil {
slog.Error("failed to start server", "error", err)
os.Exit(1)
}
}Validation Flow
Optional pointer validation behaves like this:
nil pointer
↓
validation skippednon-nil pointer
↓
nested rules executedNotes
OptionalPtris the foundation of nullable validation inform.- Validation runs only when the pointer exists.
- Nil values are intentionally skipped and are not validation failures.
- Pointer validation is especially useful for PATCH APIs and partial updates.
- Optional pointer validation supports nested schemas and embedded objects.
- Optional validation remains explicit and transport-independent.
- Optional pointer validation helps separate field existence from value correctness.
Optional Time Validation
Learn how optional time validation works in NLG Form using nullable time fields, OptionalTime, date ranges, scheduling validation, and reusable time-based validation pipelines.
Optional Slice Validation
Learn how optional slice validation works in NLG Form using OptionalSlice, optional arrays, collection validation, item limits, and reusable validation pipelines.