Skip to content

Commit ef2221c

Browse files
author
sam boyer
committed
thema: Tidy up base package and docs
1 parent b6e2199 commit ef2221c

File tree

8 files changed

+88
-59
lines changed

8 files changed

+88
-59
lines changed

assignable.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import (
1515
// If the provided T is a pointer, it will be dereferenced before verification.
1616
// Double pointers (or any n-pointer > 1) are not allowed.
1717
//
18-
// The provided T must necessarily be of struct type, as it is a requirement
19-
// that all Thema schemas are of base type struct.
18+
// The provided T must struct-kinded, as it is a requirement that all Thema
19+
// schemas are of base type struct.
2020
//
2121
// type MyType struct {
2222
// MyField string `json:"myfield"`
@@ -32,9 +32,11 @@ func AssignableTo(sch Schema, T any) error {
3232
return assignable(sch.Underlying().LookupPath(pathSchDef), T)
3333
}
3434

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

4042
const scalarKinds = cue.NullKind | cue.BoolKind |

encoding/cue/encode.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func appendlin(lin thema.Lineage, sch cue.Value) (ast.Node, error) {
124124
linf := astutil.Format(lin.Underlying()).(*ast.File)
125125
schnode := astutil.ToExpr(astutil.Format(sch))
126126

127-
lv := thema.LatestVersion(lin)
127+
lv := lin.Latest().Version()
128128
lsch := thema.SchemaP(lin, lv)
129129
if err := compat.ThemaCompatible(lsch.Underlying(), sch); err == nil {
130130
// Is compatible, bump minor version

errors/errors.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package errors
22

3-
import "github.com/cockroachdb/errors"
3+
import (
4+
"github.com/cockroachdb/errors"
5+
)
46

57
// ValidationCode represents different classes of validation errors that may
68
// occur vs. concrete data inputs.

impl.go

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,6 @@ import (
77
"cuelang.org/go/cue/errors"
88
)
99

10-
// ErrValueNotExist indicates that an operation failed because a provided
11-
// cue.Value does not exist.
12-
type ErrValueNotExist struct {
13-
path string
14-
}
15-
16-
func (e *ErrValueNotExist) Error() string {
17-
return fmt.Sprintf("value from path %q does not exist, absent values cannot be lineages", e.path)
18-
}
19-
20-
// ErrNoSchemaWithVersion indicates that an operation was requested against a
21-
// schema version that does not exist within a particular lineage.
22-
type ErrNoSchemaWithVersion struct {
23-
lin Lineage
24-
v SyntacticVersion
25-
}
26-
27-
func (e *ErrNoSchemaWithVersion) Error() string {
28-
return fmt.Sprintf("lineage %q does not contain a schema with version %v", e.lin.Name(), e.v)
29-
}
30-
3110
type compatInvariantError struct {
3211
rawlin cue.Value
3312
violation [2]SyntacticVersion

instance.go

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,26 @@ func BindInstanceType[T Assignee](inst *Instance, tsch TypedSchema[T]) (*TypedIn
2525
}, nil
2626
}
2727

28-
// An Instance represents some data that has been validated against a
29-
// lineage's schema. It includes a reference to the schema.
28+
// Instance represents data that is a valid instance of a Thema [Schema].
29+
//
30+
// It is not possible to create a valid Instance directly. They can only be
31+
// obtained by successful call to [Schema.Validate].
3032
type Instance struct {
3133
// The CUE representation of the input data
3234
raw cue.Value
3335
// A name for the input data, primarily for use in error messages
3436
name string
3537
// The schema the data validated against/of which the input data is a valid instance
3638
sch Schema
39+
40+
// simple flag the prevents external creation
41+
valid bool
42+
}
43+
44+
func (i *Instance) check() {
45+
if !i.valid {
46+
panic("Instance is not valid; Instances must be created by a call to thema.Schema.Validate")
47+
}
3748
}
3849

3950
// Hydrate returns a copy of the Instance with all default values specified by
@@ -42,6 +53,8 @@ type Instance struct {
4253
// NOTE hydration implementation is a WIP. If errors are encountered, the
4354
// original input is returned unchanged.
4455
func (i *Instance) Hydrate() *Instance {
56+
i.check()
57+
4558
i.sch.Lineage().Runtime()
4659
ni, err := doHydrate(i.sch.Underlying(), i.raw)
4760
// FIXME For now, just no-op it if we error
@@ -50,9 +63,10 @@ func (i *Instance) Hydrate() *Instance {
5063
}
5164

5265
return &Instance{
53-
raw: ni,
54-
name: i.name,
55-
sch: i.sch,
66+
valid: true,
67+
raw: ni,
68+
name: i.name,
69+
sch: i.sch,
5670
}
5771
}
5872

@@ -62,66 +76,90 @@ func (i *Instance) Hydrate() *Instance {
6276
// NOTE dehydration implementation is a WIP. If errors are encountered, the
6377
// original input is returned unchanged.
6478
func (i *Instance) Dehydrate() *Instance {
79+
i.check()
80+
6581
ni, _, err := doDehydrate(i.sch.Underlying(), i.raw)
6682
// FIXME For now, just no-op it if we error
6783
if err != nil {
6884
return i
6985
}
7086

7187
return &Instance{
72-
raw: ni,
73-
name: i.name,
74-
sch: i.sch,
88+
valid: true,
89+
raw: ni,
90+
name: i.name,
91+
sch: i.sch,
7592
}
7693
}
7794

7895
// AsSuccessor translates the instance into the form specified by the successor
7996
// schema.
80-
//
81-
// TODO figure out how to represent unary vs. composite lineages here
8297
func (i *Instance) AsSuccessor() (*Instance, TranslationLacunas) {
98+
i.check()
8399
return i.Translate(i.sch.Successor().Version())
84100
}
85101

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

94-
// Underlying returns the cue.Value representing the instance's underlying data.
109+
// Underlying returns the cue.Value representing the data contained in the Instance.
95110
func (i *Instance) Underlying() cue.Value {
111+
i.check()
96112
return i.raw
97113
}
98114

99-
// Schema returns the schema which subsumes/validated this instance.
115+
// Schema returns the [Schema] corresponding to this instance.
100116
func (i *Instance) Schema() Schema {
117+
i.check()
101118
return i.sch
102119
}
103120

104121
func (i *Instance) rt() *Runtime {
105122
return getLinLib(i.Schema().Lineage())
106123
}
107124

125+
// TypedInstance represents data that is a valid instance of a Thema
126+
// [TypedSchema].
127+
//
128+
// A TypedInstance is to a [TypedSchema] as an [Instance] is to a [Schema].
129+
//
130+
// It is not possible to create a valid TypedInstance directly. They can only be
131+
// obtained by successful call to [TypedSchema.Validate].
108132
type TypedInstance[T Assignee] struct {
109133
*Instance
110134
tsch TypedSchema[T]
111135
}
112136

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

145+
// Value returns a Go struct of this TypedInstance's generic [Assignee] type,
146+
// populated with the data contained in this instance, including default values, etc.
147+
//
148+
// This method is similar to [json.Unmarshal] - it decodes serialized data into a standard Go type
149+
// for working with in all the usual ways.
117150
func (inst *TypedInstance[T]) Value() (T, error) {
151+
inst.check()
152+
118153
t := inst.tsch.NewT()
119154
// TODO figure out correct pointer handling here
120155
err := inst.Instance.raw.Decode(&t)
121156
return t, err
122157
}
123158

159+
// ValueP is the same as Value, but panics if an error is encountered.
124160
func (inst *TypedInstance[T]) ValueP() T {
161+
inst.check()
162+
125163
t, err := inst.Value()
126164
if err != nil {
127165
panic(fmt.Errorf("error decoding value: %w", err))
@@ -150,6 +188,8 @@ func (inst *TypedInstance[T]) ValueP() T {
150188
// achieved in the program depending on Thema, so we avoid introducing
151189
// complexity into Thema that is not essential for all use cases.
152190
func (i *Instance) Translate(to SyntacticVersion) (*Instance, TranslationLacunas) {
191+
i.check()
192+
153193
// TODO define this in terms of AsSuccessor and AsPredecessor, rather than those in terms of this.
154194
newsch, err := i.Schema().Lineage().Schema(to)
155195
if err != nil {
@@ -179,9 +219,10 @@ func (i *Instance) Translate(to SyntacticVersion) (*Instance, TranslationLacunas
179219
raw, _ := out.LookupPath(cue.MakePath(cue.Str("result"), cue.Str("result"))).Default()
180220

181221
return &Instance{
182-
raw: raw,
183-
name: i.name,
184-
sch: newsch,
222+
valid: true,
223+
raw: raw,
224+
name: i.name,
225+
sch: newsch,
185226
}, lac
186227
}
187228

lineage.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66

77
"cuelang.org/go/cue"
88
cerrors "cuelang.org/go/cue/errors"
9+
"github.com/cockroachdb/errors"
10+
terrors "github.com/grafana/thema/errors"
911
"github.com/grafana/thema/internal/cuetil"
1012
)
1113

@@ -40,7 +42,7 @@ type baseLineage struct {
4042

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

249251
if !synvExists(lin.allv, v) {
250-
return nil, &ErrNoSchemaWithVersion{
251-
lin: lin,
252-
v: v,
253-
}
252+
return nil, errors.Mark(errors.Newf("no schema with version %s in lineage %s", v, lin.name), terrors.ErrVersionNotExist)
254253
}
255254

256255
return lin.schema(v), nil

schema.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ func (sch *schemaDef) Examples() map[string]*Instance {
4848
for it.Next() {
4949
label := it.Selector().String()
5050
examples[label] = &Instance{
51-
raw: it.Value(),
52-
name: label,
53-
sch: sch,
51+
valid: true,
52+
raw: it.Value(),
53+
name: label,
54+
sch: sch,
5455
}
5556
}
5657

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

8889
return &Instance{
89-
raw: data,
90-
sch: sch,
91-
name: "", // FIXME how are we getting this out?
90+
valid: true,
91+
raw: data,
92+
sch: sch,
93+
name: "", // FIXME how are we getting this out?
9294
}, nil
9395
}
9496

surface.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func SchemaP(lin Lineage, v SyntacticVersion) Schema {
9999
// LatestVersion returns the version number of the newest (largest) schema
100100
// version in the provided lineage.
101101
//
102-
// DEPRECATED: call Lineage.Latest().Version().
102+
// Deprecated: call Lineage.Latest().Version().
103103
func LatestVersion(lin Lineage) SyntacticVersion {
104104
return lin.Latest().Version()
105105
}
@@ -109,7 +109,7 @@ func LatestVersion(lin Lineage) SyntacticVersion {
109109
//
110110
// An error indicates the number of the provided sequence does not exist.
111111
//
112-
// DEPRECATED: call Schema.LatestInMajor().Version() after loading a schema in the desired major version.
112+
// Deprecated: call Schema.LatestInMajor().Version() after loading a schema in the desired major version.
113113
func LatestVersionInSequence(lin Lineage, seqv uint) (SyntacticVersion, error) {
114114
sch, err := lin.Schema(SV(seqv, 0))
115115
if err != nil {
@@ -135,6 +135,8 @@ func LatestVersionInSequence(lin Lineage, seqv uint) (SyntacticVersion, error) {
135135
// the builder func to reduce stutter:
136136
//
137137
// func Lineage ...
138+
//
139+
// Deprecated: having an explicit type for this adds little value.
138140
type LineageFactory func(*Runtime, ...BindOption) (Lineage, error)
139141

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

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

0 commit comments

Comments
 (0)