Skip to content

Commit

Permalink
vmux: Split each muxer into its own file
Browse files Browse the repository at this point in the history
  • Loading branch information
sam boyer committed May 17, 2023
1 parent fe255a5 commit b572cba
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 162 deletions.
30 changes: 30 additions & 0 deletions vmux/byte.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package vmux

import "github.com/grafana/thema"

// ByteMux is a version multiplexer that maps a []byte containing data at any
// schematized version to a []byte containing data at a particular schematized version.
type ByteMux func(b []byte) ([]byte, thema.TranslationLacunas, error)

// NewByteMux creates a [ByteMux] func from the provided [thema.Schema].
//
// When the returned mux func is called, it will:
//
// - Decode the input []byte using the provided [Codec], then
// - Pass the result to [thema.Schema.Validate], then
// - Call [thema.Instance.Translate] on the result, to the version of the provided [thema.Schema], then
// - Encode the resulting [thema.Instance] to a []byte, then
// - Return the resulting []byte, [thema.TranslationLacunas], and error
//
// The returned error may be from any of the above steps.
func NewByteMux(sch thema.Schema, codec Codec) ByteMux {
f := NewUntypedMux(sch, codec)
return func(b []byte) ([]byte, thema.TranslationLacunas, error) {
ti, lac, err := f(b)
if err != nil {
return nil, lac, err
}
ob, err := codec.Encode(ti.Underlying())
return ob, lac, err
}
}
162 changes: 0 additions & 162 deletions vmux/mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package vmux

import (
"encoding/json"
"fmt"
"strings"

"cuelang.org/go/cue"
Expand All @@ -12,167 +11,6 @@ import (
"github.com/grafana/thema"
)

// UntypedMux is a version multiplexer that maps a []byte containing data at any
// schematized version to a [thema.Instance] at a particular schematized version.
type UntypedMux func(b []byte) (*thema.Instance, thema.TranslationLacunas, error)

// NewUntypedMux creates an [UntypedMux] from the provided [thema.Schema].
//
// When the returned mux func is called, it will:
//
// - Decode the input []byte using the provided [Decoder], then
// - Pass the result to [thema.Schema.Validate], then
// - Call [thema.Instance.Translate] on the result, to the version of the provided [thema.Schema], then
// - Return the resulting [thema.Instance], [thema.TranslationLacunas], and error
//
// The returned error may be from any of the above steps.
func NewUntypedMux(sch thema.Schema, dec Decoder) UntypedMux {
ctx := sch.Lineage().Underlying().Context()
// Prepare no-match error string once for reuse
vstring := allvstr(sch)

return func(b []byte) (*thema.Instance, thema.TranslationLacunas, error) {
v, err := dec.Decode(ctx, b)
if err != nil {
// TODO wrap error for use with errors.Is
return nil, nil, err
}

// Try the given schema first, on the premise that in general it's the
// most likely one for an application to encounter
tinst, err := sch.Validate(v)
if err == nil {
return tinst, nil, nil
}

// Walk in reverse order on the premise that, in general, newer versions are more
// likely to be provided than older versions
isch := latest(sch.Lineage())
for ; isch != nil; isch = isch.Predecessor() {
if isch.Version() == sch.Version() {
continue
}

if inst, ierr := isch.Validate(v); ierr == nil {
trinst, lac := inst.Translate(sch.Version())
return trinst, lac, nil
}
}

return nil, nil, fmt.Errorf("data invalid against all versions (%s), error against %s: %w", vstring, sch.Version(), err)
}
}

// ByteMux is a version multiplexer that maps a []byte containing data at any
// schematized version to a []byte containing data at a particular schematized version.
type ByteMux func(b []byte) ([]byte, thema.TranslationLacunas, error)

// NewByteMux creates a [ByteMux] func from the provided [thema.Schema].
//
// When the returned mux func is called, it will:
//
// - Decode the input []byte using the provided [Codec], then
// - Pass the result to [thema.Schema.Validate], then
// - Call [thema.Instance.Translate] on the result, to the version of the provided [thema.Schema], then
// - Encode the resulting [thema.Instance] to a []byte, then
// - Return the resulting []byte, [thema.TranslationLacunas], and error
//
// The returned error may be from any of the above steps.
func NewByteMux(sch thema.Schema, codec Codec) ByteMux {
f := NewUntypedMux(sch, codec)
return func(b []byte) ([]byte, thema.TranslationLacunas, error) {
ti, lac, err := f(b)
if err != nil {
return nil, lac, err
}
ob, err := codec.Encode(ti.Underlying())
return ob, lac, err
}
}

// ValueMux is a version multiplexer that maps a []byte containing data at any
// schematized version to a Go var of a type that a particular schematized
// version is [thema.AssignableTo].
type ValueMux[T thema.Assignee] func(b []byte) (T, thema.TranslationLacunas, error)

// NewValueMux creates a [ValueMux] func from the provided [thema.TypedSchema].
//
// When the returned mux func is called, it will:
//
// - Decode the input []byte using the provided [Decoder], then
// - Pass the result to [thema.TypedSchema.ValidateTyped], then
// - Call [thema.Instance.Translate] on the result, to the version of the provided [thema.TypedSchema], then
// - Populate an instance of T by calling [thema.TypedInstance.Value] on the result, then
// - Return the resulting T, [thema.TranslationLacunas], and error
//
// The returned error may be from any of the above steps.
func NewValueMux[T thema.Assignee](sch thema.TypedSchema[T], dec Decoder) ValueMux[T] {
f := NewTypedMux[T](sch, dec)
return func(b []byte) (T, thema.TranslationLacunas, error) {
ti, lac, err := f(b)
if err != nil {
return sch.NewT(), lac, err
}
t, err := ti.Value()
return t, lac, err
}
}

// TypedMux is a version multiplexer that maps a []byte containing data at any
// schematized version to a [thema.TypedInstance] at a particular schematized version.
type TypedMux[T thema.Assignee] func(b []byte) (*thema.TypedInstance[T], thema.TranslationLacunas, error)

// NewTypedMux creates a [TypedMux] func from the provided [thema.TypedSchema].
//
// When the returned mux func is called, it will:
//
// - Decode the input []byte using the provided [Decoder], then
// - Pass the result to [thema.TypedSchema.ValidateTyped], then
// - Call [thema.Instance.Translate] on the result, to the version of the provided [thema.TypedSchema], then
// - Return the resulting [thema.TypedInstance], [thema.TranslationLacunas], and error
//
// The returned error may be from any of the above steps.
func NewTypedMux[T thema.Assignee](sch thema.TypedSchema[T], dec Decoder) TypedMux[T] {
ctx := sch.Lineage().Underlying().Context()
// Prepare no-match error string once for reuse
vstring := allvstr(sch)

return func(b []byte) (*thema.TypedInstance[T], thema.TranslationLacunas, error) {
v, err := dec.Decode(ctx, b)
if err != nil {
// TODO wrap error for use with errors.Is
return nil, nil, err
}

// Try the given schema first, on the premise that in general it's the
// most likely one for an application to encounter
tinst, err := sch.ValidateTyped(v)
if err == nil {
return tinst, nil, nil
}

// Walk in reverse order on the premise that, in general, newer versions are more
// likely to be provided than older versions
isch := latest(sch.Lineage())
for ; isch != nil; isch = isch.Predecessor() {
if isch.Version() == sch.Version() {
continue
}

if inst, ierr := isch.Validate(v); ierr == nil {
trinst, lac := inst.Translate(sch.Version())
tinst, err := thema.BindInstanceType(trinst, sch)
if err != nil {
panic(fmt.Errorf("unreachable, instance type should always be bindable: %w", err))
}
return tinst, lac, nil
}
}

return nil, nil, fmt.Errorf("data invalid against all versions (%s), error against %s: %w", vstring, sch.Version(), err)
}
}

func allvstr(sch thema.Schema) string {
var vl []string
for isch := thema.SchemaP(sch.Lineage(), thema.SV(0, 0)); isch != nil; isch = isch.Successor() {
Expand Down
62 changes: 62 additions & 0 deletions vmux/typed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package vmux

import (
"fmt"

"github.com/grafana/thema"
)

// TypedMux is a version multiplexer that maps a []byte containing data at any
// schematized version to a [thema.TypedInstance] at a particular schematized version.
type TypedMux[T thema.Assignee] func(b []byte) (*thema.TypedInstance[T], thema.TranslationLacunas, error)

// NewTypedMux creates a [TypedMux] func from the provided [thema.TypedSchema].
//
// When the returned mux func is called, it will:
//
// - Decode the input []byte using the provided [Decoder], then
// - Pass the result to [thema.TypedSchema.ValidateTyped], then
// - Call [thema.Instance.Translate] on the result, to the version of the provided [thema.TypedSchema], then
// - Return the resulting [thema.TypedInstance], [thema.TranslationLacunas], and error
//
// The returned error may be from any of the above steps.
func NewTypedMux[T thema.Assignee](sch thema.TypedSchema[T], dec Decoder) TypedMux[T] {
ctx := sch.Lineage().Underlying().Context()
// Prepare no-match error string once for reuse
vstring := allvstr(sch)

return func(b []byte) (*thema.TypedInstance[T], thema.TranslationLacunas, error) {
v, err := dec.Decode(ctx, b)
if err != nil {
// TODO wrap error for use with errors.Is
return nil, nil, err
}

// Try the given schema first, on the premise that in general it's the
// most likely one for an application to encounter
tinst, err := sch.ValidateTyped(v)
if err == nil {
return tinst, nil, nil
}

// Walk in reverse order on the premise that, in general, newer versions are more
// likely to be provided than older versions
isch := latest(sch.Lineage())
for ; isch != nil; isch = isch.Predecessor() {
if isch.Version() == sch.Version() {
continue
}

if inst, ierr := isch.Validate(v); ierr == nil {
trinst, lac := inst.Translate(sch.Version())
tinst, err := thema.BindInstanceType(trinst, sch)
if err != nil {
panic(fmt.Errorf("unreachable, instance type should always be bindable: %w", err))
}
return tinst, lac, nil
}
}

return nil, nil, fmt.Errorf("data invalid against all versions (%s), error against %s: %w", vstring, sch.Version(), err)
}
}
58 changes: 58 additions & 0 deletions vmux/untyped.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package vmux

import (
"fmt"

"github.com/grafana/thema"
)

// UntypedMux is a version multiplexer that maps a []byte containing data at any
// schematized version to a [thema.Instance] at a particular schematized version.
type UntypedMux func(b []byte) (*thema.Instance, thema.TranslationLacunas, error)

// NewUntypedMux creates an [UntypedMux] from the provided [thema.Schema].
//
// When the returned mux func is called, it will:
//
// - Decode the input []byte using the provided [Decoder], then
// - Pass the result to [thema.Schema.Validate], then
// - Call [thema.Instance.Translate] on the result, to the version of the provided [thema.Schema], then
// - Return the resulting [thema.Instance], [thema.TranslationLacunas], and error
//
// The returned error may be from any of the above steps.
func NewUntypedMux(sch thema.Schema, dec Decoder) UntypedMux {
ctx := sch.Lineage().Underlying().Context()
// Prepare no-match error string once for reuse
vstring := allvstr(sch)

return func(b []byte) (*thema.Instance, thema.TranslationLacunas, error) {
v, err := dec.Decode(ctx, b)
if err != nil {
// TODO wrap error for use with errors.Is
return nil, nil, err
}

// Try the given schema first, on the premise that in general it's the
// most likely one for an application to encounter
tinst, err := sch.Validate(v)
if err == nil {
return tinst, nil, nil
}

// Walk in reverse order on the premise that, in general, newer versions are more
// likely to be provided than older versions
isch := latest(sch.Lineage())
for ; isch != nil; isch = isch.Predecessor() {
if isch.Version() == sch.Version() {
continue
}

if inst, ierr := isch.Validate(v); ierr == nil {
trinst, lac := inst.Translate(sch.Version())
return trinst, lac, nil
}
}

return nil, nil, fmt.Errorf("data invalid against all versions (%s), error against %s: %w", vstring, sch.Version(), err)
}
}
31 changes: 31 additions & 0 deletions vmux/value.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package vmux

import "github.com/grafana/thema"

// ValueMux is a version multiplexer that maps a []byte containing data at any
// schematized version to a Go var of a type that a particular schematized
// version is [thema.AssignableTo].
type ValueMux[T thema.Assignee] func(b []byte) (T, thema.TranslationLacunas, error)

// NewValueMux creates a [ValueMux] func from the provided [thema.TypedSchema].
//
// When the returned mux func is called, it will:
//
// - Decode the input []byte using the provided [Decoder], then
// - Pass the result to [thema.TypedSchema.ValidateTyped], then
// - Call [thema.Instance.Translate] on the result, to the version of the provided [thema.TypedSchema], then
// - Populate an instance of T by calling [thema.TypedInstance.Value] on the result, then
// - Return the resulting T, [thema.TranslationLacunas], and error
//
// The returned error may be from any of the above steps.
func NewValueMux[T thema.Assignee](sch thema.TypedSchema[T], dec Decoder) ValueMux[T] {
f := NewTypedMux[T](sch, dec)
return func(b []byte) (T, thema.TranslationLacunas, error) {
ti, lac, err := f(b)
if err != nil {
return sch.NewT(), lac, err
}
t, err := ti.Value()
return t, lac, err
}
}

0 comments on commit b572cba

Please sign in to comment.