Skip to content

Commit

Permalink
Merge pull request #176 from grafana/sdboyer/docs-fixups
Browse files Browse the repository at this point in the history
thema: Tidy up base package and docs
  • Loading branch information
sam boyer committed Jun 27, 2023
2 parents b6e2199 + 1741492 commit 817e9da
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 59 deletions.
12 changes: 7 additions & 5 deletions assignable.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import (
// If the provided T is a pointer, it will be dereferenced before verification.
// Double pointers (or any n-pointer > 1) are not allowed.
//
// The provided T must necessarily be of struct type, as it is a requirement
// that all Thema schemas are of base type struct.
// The provided T must be struct-kinded, as it is a requirement that all Thema
// schemas are of base type struct.
//
// type MyType struct {
// MyField string `json:"myfield"`
Expand All @@ -32,9 +32,11 @@ func AssignableTo(sch Schema, T any) error {
return assignable(sch.Underlying().LookupPath(pathSchDef), T)
}

// ErrPointerDepth indicates that a Go type having pointer indirection depth > 1
// (e.g. **struct{ V: string }) was provided to a Thema func that checks
// assignability, such as [BindType].
// ErrPointerDepth indicates that a Go type having pointer indirection depth greater than 1, such as
//
// **struct{ V: string })
//
// was provided to a Thema func that checks assignability, such as [BindType].
var ErrPointerDepth = errors.New("assignability does not support more than one level of pointer indirection")

const scalarKinds = cue.NullKind | cue.BoolKind |
Expand Down
2 changes: 1 addition & 1 deletion encoding/cue/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func appendlin(lin thema.Lineage, sch cue.Value) (ast.Node, error) {
linf := astutil.Format(lin.Underlying()).(*ast.File)
schnode := astutil.ToExpr(astutil.Format(sch))

lv := thema.LatestVersion(lin)
lv := lin.Latest().Version()
lsch := thema.SchemaP(lin, lv)
if err := compat.ThemaCompatible(lsch.Underlying(), sch); err == nil {
// Is compatible, bump minor version
Expand Down
4 changes: 3 additions & 1 deletion errors/errors.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package errors

import "github.com/cockroachdb/errors"
import (
"github.com/cockroachdb/errors"
)

// ValidationCode represents different classes of validation errors that may
// occur vs. concrete data inputs.
Expand Down
21 changes: 0 additions & 21 deletions impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,6 @@ import (
"cuelang.org/go/cue/errors"
)

// ErrValueNotExist indicates that an operation failed because a provided
// cue.Value does not exist.
type ErrValueNotExist struct {
path string
}

func (e *ErrValueNotExist) Error() string {
return fmt.Sprintf("value from path %q does not exist, absent values cannot be lineages", e.path)
}

// ErrNoSchemaWithVersion indicates that an operation was requested against a
// schema version that does not exist within a particular lineage.
type ErrNoSchemaWithVersion struct {
lin Lineage
v SyntacticVersion
}

func (e *ErrNoSchemaWithVersion) Error() string {
return fmt.Sprintf("lineage %q does not contain a schema with version %v", e.lin.Name(), e.v)
}

type compatInvariantError struct {
rawlin cue.Value
violation [2]SyntacticVersion
Expand Down
77 changes: 59 additions & 18 deletions instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,26 @@ func BindInstanceType[T Assignee](inst *Instance, tsch TypedSchema[T]) (*TypedIn
}, nil
}

// An Instance represents some data that has been validated against a
// lineage's schema. It includes a reference to the schema.
// Instance represents data that is a valid instance of a Thema [Schema].
//
// It is not possible to create a valid Instance directly. They can only be
// obtained by successful call to [Schema.Validate].
type Instance struct {
// The CUE representation of the input data
raw cue.Value
// A name for the input data, primarily for use in error messages
name string
// The schema the data validated against/of which the input data is a valid instance
sch Schema

// simple flag the prevents external creation
valid bool
}

func (i *Instance) check() {
if !i.valid {
panic("Instance is not valid; Instances must be created by a call to thema.Schema.Validate")
}
}

// Hydrate returns a copy of the Instance with all default values specified by
Expand All @@ -42,6 +53,8 @@ type Instance struct {
// NOTE hydration implementation is a WIP. If errors are encountered, the
// original input is returned unchanged.
func (i *Instance) Hydrate() *Instance {
i.check()

i.sch.Lineage().Runtime()
ni, err := doHydrate(i.sch.Underlying(), i.raw)
// FIXME For now, just no-op it if we error
Expand All @@ -50,9 +63,10 @@ func (i *Instance) Hydrate() *Instance {
}

return &Instance{
raw: ni,
name: i.name,
sch: i.sch,
valid: true,
raw: ni,
name: i.name,
sch: i.sch,
}
}

Expand All @@ -62,66 +76,90 @@ func (i *Instance) Hydrate() *Instance {
// NOTE dehydration implementation is a WIP. If errors are encountered, the
// original input is returned unchanged.
func (i *Instance) Dehydrate() *Instance {
i.check()

ni, _, err := doDehydrate(i.sch.Underlying(), i.raw)
// FIXME For now, just no-op it if we error
if err != nil {
return i
}

return &Instance{
raw: ni,
name: i.name,
sch: i.sch,
valid: true,
raw: ni,
name: i.name,
sch: i.sch,
}
}

// AsSuccessor translates the instance into the form specified by the successor
// schema.
//
// TODO figure out how to represent unary vs. composite lineages here
func (i *Instance) AsSuccessor() (*Instance, TranslationLacunas) {
i.check()
return i.Translate(i.sch.Successor().Version())
}

// AsPredecessor translates the instance into the form specified by the predecessor
// schema.
//
// TODO figure out how to represent unary vs. composite lineages here
func (i *Instance) AsPredecessor() (*Instance, TranslationLacunas) {
panic("TODO translation from newer to older schema is not yet implemented")
i.check()
return i.Translate(i.sch.Predecessor().Version())
}

// Underlying returns the cue.Value representing the instance's underlying data.
// Underlying returns the cue.Value representing the data contained in the Instance.
func (i *Instance) Underlying() cue.Value {
i.check()
return i.raw
}

// Schema returns the schema which subsumes/validated this instance.
// Schema returns the [Schema] corresponding to this instance.
func (i *Instance) Schema() Schema {
i.check()
return i.sch
}

func (i *Instance) rt() *Runtime {
return getLinLib(i.Schema().Lineage())
}

// TypedInstance represents data that is a valid instance of a Thema
// [TypedSchema].
//
// A TypedInstance is to a [TypedSchema] as an [Instance] is to a [Schema].
//
// It is not possible to create a valid TypedInstance directly. They can only be
// obtained by successful call to [TypedSchema.Validate].
type TypedInstance[T Assignee] struct {
*Instance
tsch TypedSchema[T]
}

// TypedSchema returns the [TypedSchema] corresponding to this instance.
//
// This method is identical to [Instance.Schema], except that it returns the already-typed variant.
func (inst *TypedInstance[T]) TypedSchema() TypedSchema[T] {
inst.check()
return inst.tsch
}

// Value returns a Go struct of this TypedInstance's generic [Assignee] type,
// populated with the data contained in this instance, including default values, etc.
//
// This method is similar to [json.Unmarshal] - it decodes serialized data into a standard Go type
// for working with in all the usual ways.
func (inst *TypedInstance[T]) Value() (T, error) {
inst.check()

t := inst.tsch.NewT()
// TODO figure out correct pointer handling here
err := inst.Instance.raw.Decode(&t)
return t, err
}

// ValueP is the same as Value, but panics if an error is encountered.
func (inst *TypedInstance[T]) ValueP() T {
inst.check()

t, err := inst.Value()
if err != nil {
panic(fmt.Errorf("error decoding value: %w", err))
Expand Down Expand Up @@ -150,6 +188,8 @@ func (inst *TypedInstance[T]) ValueP() T {
// achieved in the program depending on Thema, so we avoid introducing
// complexity into Thema that is not essential for all use cases.
func (i *Instance) Translate(to SyntacticVersion) (*Instance, TranslationLacunas) {
i.check()

// TODO define this in terms of AsSuccessor and AsPredecessor, rather than those in terms of this.
newsch, err := i.Schema().Lineage().Schema(to)
if err != nil {
Expand Down Expand Up @@ -179,9 +219,10 @@ func (i *Instance) Translate(to SyntacticVersion) (*Instance, TranslationLacunas
raw, _ := out.LookupPath(cue.MakePath(cue.Str("result"), cue.Str("result"))).Default()

return &Instance{
raw: raw,
name: i.name,
sch: newsch,
valid: true,
raw: raw,
name: i.name,
sch: newsch,
}, lac
}

Expand Down
9 changes: 4 additions & 5 deletions lineage.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (

"cuelang.org/go/cue"
cerrors "cuelang.org/go/cue/errors"
"github.com/cockroachdb/errors"
terrors "github.com/grafana/thema/errors"
"github.com/grafana/thema/internal/cuetil"
)

Expand Down Expand Up @@ -40,7 +42,7 @@ type baseLineage struct {

// BindLineage takes a raw [cue.Value], checks that it correctly follows Thema's
// invariants, such as translatability and backwards compatibility version
// numbering. If checks succeed, a [Lineage] is returned.
// numbering. If these checks succeed, a [Lineage] is returned.
//
// This function is the only way to create non-nil Lineage objects. As a result,
// all non-nil instances of Lineage in any Go program are guaranteed to follow
Expand Down Expand Up @@ -247,10 +249,7 @@ func (lin *baseLineage) Schema(v SyntacticVersion) (Schema, error) {
isValidLineage(lin)

if !synvExists(lin.allv, v) {
return nil, &ErrNoSchemaWithVersion{
lin: lin,
v: v,
}
return nil, errors.Mark(errors.Newf("no schema with version %s in lineage %s", v, lin.name), terrors.ErrVersionNotExist)
}

return lin.schema(v), nil
Expand Down
14 changes: 8 additions & 6 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ func (sch *schemaDef) Examples() map[string]*Instance {
for it.Next() {
label := it.Selector().String()
examples[label] = &Instance{
raw: it.Value(),
name: label,
sch: sch,
valid: true,
raw: it.Value(),
name: label,
sch: sch,
}
}

Expand Down Expand Up @@ -86,9 +87,10 @@ func (sch *schemaDef) Validate(data cue.Value) (*Instance, error) {
}

return &Instance{
raw: data,
sch: sch,
name: "", // FIXME how are we getting this out?
valid: true,
raw: data,
sch: sch,
name: "", // FIXME how are we getting this out?
}, nil
}

Expand Down
8 changes: 6 additions & 2 deletions surface.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func SchemaP(lin Lineage, v SyntacticVersion) Schema {
// LatestVersion returns the version number of the newest (largest) schema
// version in the provided lineage.
//
// DEPRECATED: call Lineage.Latest().Version().
// Deprecated: call Lineage.Latest().Version().
func LatestVersion(lin Lineage) SyntacticVersion {
return lin.Latest().Version()
}
Expand All @@ -109,7 +109,7 @@ func LatestVersion(lin Lineage) SyntacticVersion {
//
// An error indicates the number of the provided sequence does not exist.
//
// DEPRECATED: call Schema.LatestInMajor().Version() after loading a schema in the desired major version.
// Deprecated: call Schema.LatestInMajor().Version() after loading a schema in the desired major version.
func LatestVersionInSequence(lin Lineage, seqv uint) (SyntacticVersion, error) {
sch, err := lin.Schema(SV(seqv, 0))
if err != nil {
Expand All @@ -135,6 +135,8 @@ func LatestVersionInSequence(lin Lineage, seqv uint) (SyntacticVersion, error) {
// the builder func to reduce stutter:
//
// func Lineage ...
//
// Deprecated: having an explicit type for this adds little value.
type LineageFactory func(*Runtime, ...BindOption) (Lineage, error)

// A ConvergentLineageFactory is the same as a LineageFactory, but for a
Expand All @@ -143,6 +145,8 @@ type LineageFactory func(*Runtime, ...BindOption) (Lineage, error)
// There is no reason to provide both a ConvergentLineageFactory and a
// LineageFactory, as the latter is always reachable from the former. As such,
// idiomatic naming conventions are unchanged.
//
// Deprecated: having an explicit type for this adds little value.
type ConvergentLineageFactory[T Assignee] func(*Runtime, ...BindOption) (ConvergentLineage[T], error)

// A BindOption defines options that may be specified only at initial
Expand Down

0 comments on commit 817e9da

Please sign in to comment.