Documentation
¶
Overview ¶
Package patch provides the two wrapper types an update input is made of.
Everything else rig generates is a plain Go type — string, time.Time, *string for a nullable column. Update input is the exception, because a PATCH has to distinguish things a plain value cannot: a field the caller left out from one they sent, and, where the column allows it, a field they explicitly cleared.
There are two types rather than one because a column either can be null or it cannot, and the type should say which:
- Optional is absent or set. It is what a NOT NULL column gets, and there is no way to write null into one.
- Nullable is absent, null, or set. It is what a nullable column gets.
The alternative — one three-state type everywhere — makes clearing a NOT NULL column something you can write down and have rejected at runtime. This way the compiler rejects it, and the only null that has to be caught is one that arrives over the wire.
The zero value of each is absent, which is what makes them work with encoding/json without any cooperation from the surrounding struct: UnmarshalJSON is only called for keys that are present, so a field the client omitted is never touched.
Index ¶
- type ErrNullNotAllowed
- type Nullable
- func (n Nullable[T]) Get() (T, bool)
- func (n Nullable[T]) IsAbsent() bool
- func (n Nullable[T]) IsNull() bool
- func (n Nullable[T]) IsSet() bool
- func (n Nullable[T]) IsZero() bool
- func (n Nullable[T]) MarshalJSON() ([]byte, error)
- func (n Nullable[T]) Or(fallback T) T
- func (n Nullable[T]) Ptr() *T
- func (n Nullable[T]) String() string
- func (n Nullable[T]) Touched() bool
- func (n *Nullable[T]) UnmarshalJSON(data []byte) error
- type Optional
- func (o Optional[T]) Get() (T, bool)
- func (o Optional[T]) IsAbsent() bool
- func (o Optional[T]) IsSet() bool
- func (o Optional[T]) IsZero() bool
- func (o Optional[T]) MarshalJSON() ([]byte, error)
- func (o Optional[T]) Or(fallback T) T
- func (o Optional[T]) String() string
- func (o *Optional[T]) UnmarshalJSON(data []byte) error
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ErrNullNotAllowed ¶
type ErrNullNotAllowed struct{}
ErrNullNotAllowed is returned when a client sends null for a column that cannot hold one.
It is a distinct type so the decoding layer can turn it into a 400 that names the field, rather than the generic complaint encoding/json would produce.
type Nullable ¶
type Nullable[T any] struct { // contains filtered or unexported fields }
Nullable is a value that may be left out, cleared, or set.
Three states, matching a nullable column exactly.
Example ¶
A generated update input is a struct of these two types: Optional for a NOT NULL column, Nullable for one that allows null. Decoding a PATCH body into it is the whole point — a field the caller omitted is never touched, so it stays absent, while one they sent as null arrives as null.
package main
import (
"encoding/json"
"fmt"
"github.com/simonjanss/rig/runtime/patch"
)
func main() {
// What a generated update input for a `todo` table looks like.
var in struct {
Title patch.Optional[string] `json:"title"`
Notes patch.Nullable[string] `json:"notes"`
Priority patch.Nullable[int] `json:"priority"`
}
// The caller renamed the todo, cleared its notes, and said nothing at all
// about its priority.
body := `{"title": "Buy milk", "notes": null}`
if err := json.Unmarshal([]byte(body), &in); err != nil {
panic(err)
}
fmt.Println("title: ", in.Title, in.Title.IsSet())
fmt.Println("notes: ", in.Notes, in.Notes.IsNull())
fmt.Println("priority:", in.Priority, in.Priority.Touched())
}
Output: title: Buy milk true notes: null true priority: absent false
Example (States) ¶
Absent and null are different answers, and only one of them is a change. A repository asks Touched before it writes a column at all, and IsNull to decide whether it writes a value or a NULL.
package main
import (
"fmt"
"github.com/simonjanss/rig/runtime/patch"
)
func main() {
set := patch.NewNullable("weekly")
cleared := patch.Null[string]()
untouched := patch.Unspecified[string]()
for _, n := range []patch.Nullable[string]{set, cleared, untouched} {
fmt.Printf("%-8s touched=%-5t null=%t\n", n, n.Touched(), n.IsNull())
}
}
Output: weekly touched=true null=false null touched=true null=true absent touched=false null=false
func FromPtr ¶
FromPtr builds a Nullable from a pointer: nil clears, anything else sets.
It is how a previous row's value — which the model holds as *T — becomes an input, which is what merging a partial update against the current state needs.
Example ¶
FromPtr is how the current row — which a generated model holds as *T — becomes an input, so a partial update can be merged against the state it is patching.
package main
import (
"fmt"
"github.com/simonjanss/rig/runtime/patch"
)
func main() {
current := "the previous note"
fmt.Println(patch.FromPtr(¤t))
fmt.Println(patch.FromPtr[string](nil))
}
Output: the previous note null
func FromSlice ¶
FromSlice builds a Nullable from a slice: nil clears, anything else sets.
The array counterpart of FromPtr. A nullable array column is held as a plain slice rather than a pointer to one — nil already means "no value" — so the pointer form has nothing to take the address of.
An empty non-nil slice is a value: a column set to {} is not a column set to NULL, and a replay that confused the two would quietly change the row.
func NewNullable ¶
NewNullable builds a Nullable carrying a value.
func Unspecified ¶
Unspecified builds a Nullable that leaves the field alone. It is the zero value, and exists so the intent can be written down.
func (Nullable[T]) Get ¶
Get returns the value, and whether there is one. It reports false for both absent and null; use Nullable.IsNull when the difference matters.
func (Nullable[T]) IsZero ¶
IsZero implements the check encoding/json's `omitzero` tag makes, so that a struct field holding an absent value is left out of the encoded object entirely.
An explicit null is not zero, and that is the whole distinction: a client that cleared a column has to put null on the wire, and one that said nothing about it has to put nothing. See Optional.IsZero.
func (Nullable[T]) MarshalJSON ¶
MarshalJSON implements json.Marshaler.
func (Nullable[T]) Or ¶
func (n Nullable[T]) Or(fallback T) T
Or returns the value, or the fallback when absent or null.
func (Nullable[T]) Ptr ¶
func (n Nullable[T]) Ptr() *T
Ptr returns a pointer to the value, or nil when absent or null.
It is what a repository passes to the driver: a nil pointer is exactly how a NULL is written, so no branch is needed at the call site.
func (Nullable[T]) Touched ¶
Touched reports whether the field was mentioned at all, whether it was set to a value or cleared. It is the question a repository asks when deciding which columns belong in an UPDATE.
func (*Nullable[T]) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler.
type Optional ¶
type Optional[T any] struct { // contains filtered or unexported fields }
Optional is a value that may be left out of an update.
Two states: absent, or set to a value. A column that is NOT NULL cannot be cleared, so there is nothing here that could express clearing it.
Example ¶
Optional has no third state, which is what makes clearing a NOT NULL column something that cannot be written down rather than something rejected at runtime.
package main
import (
"fmt"
"github.com/simonjanss/rig/runtime/patch"
)
func main() {
title := patch.NewOptional("Buy milk")
if v, ok := title.Get(); ok {
fmt.Println("set to:", v)
}
fmt.Println("left alone:", patch.Absent[string]().IsAbsent())
}
Output: set to: Buy milk left alone: true
func Absent ¶
Absent builds an Optional that leaves the field alone. It is the zero value, and exists so the intent can be written down.
func NewOptional ¶
NewOptional builds an Optional carrying a value.
func (Optional[T]) IsZero ¶
IsZero implements the check encoding/json's `omitzero` tag makes, so that a struct field holding an absent value is left out of the encoded object entirely.
That matters for anything that sends an update rather than receiving one — the generated Go client, or a test building a request by hand. MarshalJSON below has to encode absence as null, because a marshaler cannot remove its own field; `omitzero` is the only thing that can, and this is what it asks.
func (Optional[T]) MarshalJSON ¶
MarshalJSON implements json.Marshaler.
An absent value still encodes as null, because a struct field cannot be omitted from the outside. Generated responses use plain values, so this only matters when a request is echoed back.
func (Optional[T]) Or ¶
func (o Optional[T]) Or(fallback T) T
Or returns the value, or the fallback when absent.
func (*Optional[T]) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler.
An explicit null is an error rather than a silent absence. The two mean different things — "leave this alone" and "clear this" — and a column that cannot be cleared should say so rather than quietly ignoring the request.