cuelang.org/go@v0.13.0/internal/core/compile/validator.go (about)

     1  // Copyright 2024 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package compile
    16  
    17  // This file contains validator and other non-monotonic builtins.
    18  
    19  import (
    20  	"cuelang.org/go/internal/core/adt"
    21  	"cuelang.org/go/internal/core/validate"
    22  )
    23  
    24  // matchN is a validator that checks that the number of schemas in the given
    25  // list that unify with "self" matches the number passed as the first argument
    26  // of the validator. Note that this number may itself be a number constraint
    27  // and does not have to be a concrete number.
    28  var matchNBuiltin = &adt.Builtin{
    29  	Name:        "matchN",
    30  	Params:      []adt.Param{topParam, intParam, listParam}, // varargs
    31  	Result:      adt.BoolKind,
    32  	NonConcrete: true,
    33  	Func: func(call *adt.CallContext) adt.Expr {
    34  		c := call.OpContext()
    35  		args := call.Args()
    36  
    37  		if !c.IsValidator {
    38  			return c.NewErrf("matchN is a validator and should not be used as a function")
    39  		}
    40  
    41  		self := finalizeSelf(c, args[0])
    42  		if err := bottom(c, self); err != nil {
    43  			return &adt.Bool{B: false}
    44  		}
    45  
    46  		var errs []*adt.Bottom
    47  
    48  		constraints := c.Elems(args[2])
    49  
    50  		var count, possibleCount int64
    51  		for _, check := range constraints {
    52  			v := adt.Unify(c, self, check)
    53  			if err := validate.Validate(c, v, finalCfg); err == nil {
    54  				// TODO: is it always true that the lack of an error signifies
    55  				// success?
    56  				count++
    57  			} else {
    58  				errs = append(errs, err)
    59  				if err.IsIncomplete() {
    60  					possibleCount++
    61  				}
    62  			}
    63  		}
    64  
    65  		bound := args[1]
    66  		// TODO: consider a mode to require "all" to pass, for instance by
    67  		// supporting the value null or "all".
    68  
    69  		b := checkNum(c, bound, count, count+possibleCount)
    70  		if b != nil {
    71  			// Only show errors related to incomplete schema if there is still
    72  			// a possibility that we can resolve it.
    73  			isIncomplete := b.IsIncomplete()
    74  			for _, err := range errs {
    75  				if !isIncomplete || err.IsIncomplete() {
    76  					c.AddBottom(err)
    77  				}
    78  			}
    79  			return b
    80  		}
    81  		return &adt.Bool{B: true}
    82  	},
    83  }
    84  
    85  // matchIf is a validator that checks that if the first argument unifies with
    86  // self, the second argument also unifies with self, otherwise the third
    87  // argument unifies with self.
    88  // The same finalization heuristics are applied to self as are applied
    89  // in matchN.
    90  var matchIfBuiltin = &adt.Builtin{
    91  	Name:        "matchIf",
    92  	Params:      []adt.Param{topParam, topParam, topParam, topParam},
    93  	Result:      adt.BoolKind,
    94  	NonConcrete: true,
    95  	Func: func(call *adt.CallContext) adt.Expr {
    96  		c := call.OpContext()
    97  		args := call.Args()
    98  
    99  		if !c.IsValidator {
   100  			return c.NewErrf("matchIf is a validator and should not be used as a function")
   101  		}
   102  
   103  		self := finalizeSelf(c, args[0])
   104  		if err := bottom(c, self); err != nil {
   105  			return &adt.Bool{B: false}
   106  		}
   107  		ifSchema, thenSchema, elseSchema := args[1], args[2], args[3]
   108  		v := adt.Unify(c, self, ifSchema)
   109  		var chosenSchema adt.Value
   110  		if err := validate.Validate(c, v, finalCfg); err == nil {
   111  			chosenSchema = thenSchema
   112  		} else {
   113  			chosenSchema = elseSchema
   114  		}
   115  		v = adt.Unify(c, self, chosenSchema)
   116  		err := validate.Validate(c, v, finalCfg)
   117  		if err == nil {
   118  			return &adt.Bool{B: true}
   119  		}
   120  		// TODO should we also include in the error something about the fact that
   121  		// the if condition passed or failed?
   122  		return err
   123  	},
   124  }
   125  
   126  var finalCfg = &validate.Config{Final: true}
   127  
   128  // finalizeSelf ensures a value is fully evaluated and then strips it of any
   129  // of its validators or default values.
   130  func finalizeSelf(c *adt.OpContext, self adt.Value) adt.Value {
   131  	if x, ok := self.(*adt.Vertex); ok {
   132  		self = x.ToDataAll(c)
   133  	}
   134  	return self
   135  }
   136  
   137  // TODO: use adt.Unify instead.
   138  func unifyScalar(c *adt.OpContext, self, check adt.Value) *adt.Vertex {
   139  	v := &adt.Vertex{}
   140  	closeInfo := c.CloseInfo()
   141  	v.AddConjunct(adt.MakeConjunct(nil, self, closeInfo))
   142  	v.AddConjunct(adt.MakeConjunct(nil, check, closeInfo))
   143  	v.Finalize(c)
   144  	return v
   145  }
   146  
   147  func checkNum(ctx *adt.OpContext, bound adt.Value, count, maxCount int64) *adt.Bottom {
   148  	cnt := ctx.NewInt64(count)
   149  	n := unifyScalar(ctx, bound, cnt)
   150  	b, _ := n.BaseValue.(*adt.Bottom)
   151  	if b != nil {
   152  		b := ctx.NewErrf("%d matched, expected %v", count, bound)
   153  
   154  		// By default we should mark the error as incomplete, but check if we
   155  		// know for sure it will fail.
   156  		switch bound := bound.(type) {
   157  		case *adt.Num:
   158  			if i, err := bound.X.Int64(); err == nil && i > count && i <= maxCount {
   159  				b.Code = adt.IncompleteError
   160  			}
   161  
   162  		case *adt.BoundValue:
   163  			v := &adt.Vertex{}
   164  			v.AddConjunct(ctx.MakeConjunct(bound))
   165  			v.AddConjunct(ctx.MakeConjunct(&adt.BoundValue{
   166  				Op:    adt.GreaterEqualOp,
   167  				Value: cnt,
   168  			}))
   169  			v.AddConjunct(ctx.MakeConjunct(&adt.BoundValue{
   170  				Op:    adt.LessEqualOp,
   171  				Value: ctx.NewInt64(maxCount),
   172  			}))
   173  			v.Finalize(ctx)
   174  			if _, ok := v.BaseValue.(*adt.Bottom); !ok {
   175  				b.Code = adt.IncompleteError
   176  			}
   177  
   178  		default:
   179  			b.Code = adt.IncompleteError
   180  		}
   181  
   182  		return b
   183  	}
   184  	return nil
   185  }
   186  
   187  func bottom(c *adt.OpContext, v adt.Value) *adt.Bottom {
   188  	switch x := v.(type) {
   189  	case *adt.Vertex:
   190  		return x.Err(c)
   191  	case *adt.Bottom:
   192  		return x
   193  	}
   194  	return nil
   195  }