Skip to content
This repository has been archived by the owner on Nov 18, 2021. It is now read-only.

Commit

Permalink
cmd/cue/cmd: compute schema before values
Browse files Browse the repository at this point in the history
This allows the schema with which data files will
be merged before they are parsed. This is necessary
to support protobuf parsing, including Protobuf /
JSON mappings.

The usage of this property will be done in a followup CL.

Issue #5

Change-Id: I54b8826070217bd3d4e61d7d6f5b692f33553b12
  • Loading branch information
mpvl committed Apr 6, 2021
1 parent 99023e9 commit 478ebe3
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 41 deletions.
124 changes: 84 additions & 40 deletions cmd/cue/cmd/common.go
Expand Up @@ -123,6 +123,10 @@ type buildPlan struct {
cmd *Command
insts []*build.Instance

// instance is a pre-compiled instance, which exists if value files are
// being processed, which may require a schema to decode.
instance *cue.Instance

cfg *config

// If orphanFiles are mixed with CUE files and/or if placement flags are used,
Expand All @@ -149,18 +153,25 @@ type buildPlan struct {
outFile *build.File

encConfig *encoding.Config
merge []*build.Instance
}

// instances iterates either over a list of instances, or a list of
// data files. In the latter case, there must be either 0 or 1 other
// instance, with which the data instance may be merged.
func (b *buildPlan) instances() iterator {
var i iterator
if len(b.orphaned) == 0 {
i = &instanceIterator{a: buildInstances(b.cmd, b.insts), i: -1}
} else {
switch {
case len(b.orphaned) > 0:
i = newStreamingIterator(b)
case len(b.insts) > 0:
i = &instanceIterator{
inst: b.instance,
a: buildInstances(b.cmd, b.insts),
i: -1,
}
default:
i = &instanceIterator{a: []*cue.Instance{b.instance}, i: -1}
b.instance = nil
}
if len(b.expressions) > 0 {
return &expressionIter{
Expand All @@ -183,22 +194,34 @@ type iterator interface {
}

type instanceIterator struct {
a []*cue.Instance
i int
e error
inst *cue.Instance
a []*cue.Instance
i int
e error
}

func (i *instanceIterator) scan() bool {
i.i++
return i.i < len(i.a) && i.e == nil
}

func (i *instanceIterator) close() {}
func (i *instanceIterator) err() error { return i.e }
func (i *instanceIterator) value() cue.Value { return i.a[i.i].Value() }
func (i *instanceIterator) instance() *cue.Instance { return i.a[i.i] }
func (i *instanceIterator) file() *ast.File { return nil }
func (i *instanceIterator) id() string { return i.a[i.i].Dir }
func (i *instanceIterator) close() {}
func (i *instanceIterator) err() error { return i.e }
func (i *instanceIterator) value() cue.Value {
v := i.a[i.i].Value()
if i.inst != nil {
v = v.Unify(i.inst.Value())
}
return v
}
func (i *instanceIterator) instance() *cue.Instance {
if i.inst != nil {
return nil
}
return i.a[i.i]
}
func (i *instanceIterator) file() *ast.File { return nil }
func (i *instanceIterator) id() string { return i.a[i.i].Dir }

type streamingIterator struct {
r *cue.Runtime
Expand All @@ -223,13 +246,16 @@ func newStreamingIterator(b *buildPlan) *streamingIterator {
switch len(b.insts) {
case 0:
i.r = &cue.Runtime{}
if v := b.encConfig.Schema; v.Exists() {
i.r = internal.GetRuntime(v).(*cue.Runtime)
}
case 1:
p := b.insts[0]
inst := buildInstances(b.cmd, []*build.Instance{p})[0]
if inst.Err != nil {
return &streamingIterator{e: inst.Err}
}
i.r = internal.GetRuntime(inst).(*cue.Runtime)
b.instance = inst
if b.schema == nil {
b.encConfig.Schema = inst.Value()
} else {
Expand Down Expand Up @@ -495,10 +521,6 @@ func parseArgs(cmd *Command, args []string, cfg *config) (p *buildPlan, err erro
return nil, err
}

// TODO(v0.4.0): what to do:
// - when there is already a single instance
// - when we are importing and there are schemas and values.

if values == nil {
values, schemas = schemas, values
}
Expand All @@ -515,42 +537,64 @@ func parseArgs(cmd *Command, args []string, cfg *config) (p *buildPlan, err erro
}
}

// TODO(v0.4.0): if schema is specified and data format is JSON, YAML or
// Protobuf, reinterpret with schema.
if len(p.insts) > 1 && p.schema != nil {
return nil, errors.Newf(token.NoPos,
"cannot use --schema/-d with flag more than one schema")
}

switch {
case p.usePlacement() || p.importing:
if err = p.placeOrphans(b, values); err != nil {
return nil, err
var schema *build.Instance
switch n := len(p.insts); n {
default:
return nil, errors.Newf(token.NoPos,
"too many packages defined (%d) in combination with files", n)
case 1:
if len(schemas) > 0 {
return nil, errors.Newf(token.NoPos,
"cannot combine packages with individual schema files")
}
schema = p.insts[0]
p.insts = nil

case 0:
bb := *b
schema = &bb
b.BuildFiles = nil
b.Files = nil
}

case !p.mergeData || p.schema != nil:
p.orphaned = values
if schema != nil && len(schema.Files) > 0 {
inst := buildInstances(p.cmd, []*build.Instance{schema})[0]

default:
for _, di := range values {
d := di.dec
for ; !d.Done(); d.Next() {
if err := b.AddSyntax(d.File()); err != nil {
return nil, err
}
}
if err := d.Err(); err != nil {
if inst.Err != nil {
return nil, err
}
p.instance = inst
p.encConfig.Schema = inst.Value()
if p.schema != nil {
v := inst.Eval(p.schema)
if err := v.Err(); err != nil {
return nil, err
}
p.encConfig.Schema = v
}
}

switch {
case p.mergeData, p.usePlacement(), p.importing:
// case p.usePlacement() || p.importing:
if err = p.placeOrphans(b, values); err != nil {
return nil, err
}

default:
p.orphaned = values
}

if len(b.Files) > 0 {
p.insts = append(p.insts, b)
}
}

if len(p.insts) > 1 && p.schema != nil {
return nil, errors.Newf(token.NoPos,
"cannot use --schema/-d flag more than one schema")
}

if len(p.expressions) > 1 {
p.encConfig.Stream = true
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/cue/cmd/vet.go
Expand Up @@ -143,7 +143,7 @@ func doVet(cmd *Command, args []string) error {
func vetFiles(cmd *Command, b *buildPlan) {
// Use -r type root, instead of -e

if len(b.insts) == 0 {
if !b.encConfig.Schema.Exists() {
exitOnErr(cmd, errors.New("data files specified without a schema"), true)
}

Expand Down

0 comments on commit 478ebe3

Please sign in to comment.