encoding/openapi: add FieldFilter option
This option allows excluding certain fields from the output.
The exclusion happens through a single regexp,
where a field name is qualified by its object type
and name.
Change-Id: I60913d92eec8ad60da73904e016c75fc61dcce98
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2444
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/encoding/openapi/build.go b/encoding/openapi/build.go
index 8224a71..9adfbe6 100644
--- a/encoding/openapi/build.go
+++ b/encoding/openapi/build.go
@@ -15,16 +15,17 @@
package openapi
import (
- "fmt"
"math"
"math/big"
"path"
+ "regexp"
"sort"
"strconv"
"strings"
"cuelang.org/go/cue"
"cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/token"
"github.com/cockroachdb/apd/v2"
)
@@ -33,9 +34,10 @@
refPrefix string
path []string
- expandRefs bool
- nameFunc func(inst *cue.Instance, path []string) string
- descFunc func(v cue.Value) string
+ expandRefs bool
+ nameFunc func(inst *cue.Instance, path []string) string
+ descFunc func(v cue.Value) string
+ fieldFilter *regexp.Regexp
schemas *OrderedMap
@@ -55,6 +57,23 @@
type typeFunc func(b *builder, a cue.Value)
func schemas(g *Generator, inst *cue.Instance) (schemas OrderedMap, err error) {
+ var fieldFilter *regexp.Regexp
+ if g.FieldFilter != "" {
+ fieldFilter, err = regexp.Compile(g.FieldFilter)
+ if err != nil {
+ return nil, errors.Newf(token.NoPos, "invalid field filter: %v", err)
+ }
+
+ // verify that certain elements are still passed.
+ for _, f := range strings.Split(
+ "version,title,allOf,anyOf,not,enum,Schema/properties,Schema/items"+
+ "nullable,type", ",") {
+ if fieldFilter.MatchString(f) {
+ return nil, errors.Newf(token.NoPos, "field filter may not exclude %q", f)
+ }
+ }
+ }
+
c := buildContext{
inst: inst,
refPrefix: "components/schema",
@@ -63,6 +82,7 @@
descFunc: g.DescriptionFunc,
schemas: &OrderedMap{},
externalRefs: map[string]*externalType{},
+ fieldFilter: fieldFilter,
}
defer func() {
@@ -73,10 +93,6 @@
default:
panic(x)
}
- if x := recover(); x != nil {
- path := strings.Join(c.path, ".")
- err = fmt.Errorf("error: %s: %v", path, x)
- }
}()
// Although paths is empty for now, it makes it valid OpenAPI spec.
@@ -247,7 +263,7 @@
}
fallthrough
default:
- b.set("default", v)
+ b.setFilter("Schema", "default", v)
}
}
return isRef
@@ -440,7 +456,7 @@
required = append(required, i.Label())
}
if len(required) > 0 {
- b.set("required", required)
+ b.setFilter("Schema", "required", required)
}
properties := map[string]*oaSchema{}
@@ -452,7 +468,7 @@
}
if t, ok := v.Elem(); ok {
- b.set("additionalProperties", b.schema("*", t))
+ b.setFilter("Schema", "additionalProperties", b.schema("*", t))
}
// TODO: maxProperties, minProperties: can be done once we allow cap to
@@ -519,7 +535,7 @@
if typ, ok := v.Elem(); ok {
t := b.schema("*", typ)
if len(items) > 0 {
- b.set("additionalItems", t)
+ b.setFilter("Schema", "additionalItems", t)
} else {
b.set("items", t)
}
@@ -530,14 +546,14 @@
func (b *builder) listCap(v cue.Value) {
switch op, a := v.Expr(); op {
case cue.LessThanOp:
- b.set("maxItems", b.int(a[0])-1)
+ b.setFilter("Schema", "maxItems", b.int(a[0])-1)
case cue.LessThanEqualOp:
- b.set("maxItems", b.int(a[0]))
+ b.setFilter("Schema", "maxItems", b.int(a[0]))
case cue.GreaterThanOp:
- b.set("minItems", b.int(a[0])+1)
+ b.setFilter("Schema", "minItems", b.int(a[0])+1)
case cue.GreaterThanEqualOp:
if b.int(a[0]) > 0 {
- b.set("minItems", b.int(a[0]))
+ b.setFilter("Schema", "minItems", b.int(a[0]))
}
case cue.NoOp:
// must be type, so okay.
@@ -557,10 +573,6 @@
func (b *builder) number(v cue.Value) {
// Multiple conjuncts mostly means just additive constraints.
// Type may be number of float.
- // TODO: deterimine integer kind.
- // if v.IsInt() {
- // b.typ = "integer"
- // }
switch op, a := v.Expr(); op {
// TODO: support the following JSON schema constraints
@@ -568,16 +580,16 @@
// setIntConstraint(t, "multipleOf", a)
case cue.LessThanOp:
- b.set("exclusiveMaximum", b.big(a[0]))
+ b.setFilter("Schema", "exclusiveMaximum", b.big(a[0]))
case cue.LessThanEqualOp:
- b.set("maximum", b.big(a[0]))
+ b.setFilter("Schema", "maximum", b.big(a[0]))
case cue.GreaterThanOp:
- b.set("exclusiveMinimum", b.big(a[0]))
+ b.setFilter("Schema", "exclusiveMinimum", b.big(a[0]))
case cue.GreaterThanEqualOp:
- b.set("minimum", b.big(a[0]))
+ b.setFilter("Schema", "minimum", b.big(a[0]))
case cue.NotEqualOp:
i := b.big(a[0])
@@ -646,7 +658,7 @@
return
}
if op == cue.RegexMatchOp {
- b.set("pattern", s)
+ b.setFilter("schema", "pattern", s)
} else {
b.setNot("pattern", s)
}
@@ -677,7 +689,7 @@
}
if op == cue.RegexMatchOp {
- b.set("pattern", s)
+ b.setFilter("Schema", "pattern", s)
} else {
b.setNot("pattern", s)
}
@@ -733,6 +745,14 @@
}
}
+// setFilter is like set, but allows the key-value pair to be filtered.
+func (b *builder) setFilter(schema, key string, v interface{}) {
+ if re := b.ctx.fieldFilter; re != nil && re.MatchString(path.Join(schema, key)) {
+ return
+ }
+ b.set(key, v)
+}
+
func (b *builder) set(key string, v interface{}) {
if b.current == nil {
b.current = &OrderedMap{}