blob: 209dda645eec3d9dcaa936849a7f508cdcb0845d [file] [log] [blame]
// Copyright 2019 CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package openapi
import (
"fmt"
"github.com/cockroachdb/apd/v2"
"cuelang.org/go/cue"
"cuelang.org/go/cue/format"
)
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#format
var cueToOpenAPI = map[string]string{
"int32": "int32",
"int64": "int64",
"float64": "double",
"float32": "float",
"string": "string",
"bytes": "binary",
"time.Time": "dateTime",
"time.Time ()": "dateTime",
`time.Format ("2006-01-02")`: "date",
// TODO: if a format is more strict (e.g. using zeros instead of nines
// for fractional seconds), we could still use this as an approximation.
`time.Format ("2006-01-02T15:04:05.999999999Z07:00")`: "dateTime",
// TODO: password.
}
func extractFormat(v cue.Value) string {
switch k := v.IncompleteKind(); {
case k&cue.NumberKind != 0, k&cue.StringKind != 0, k&cue.BytesKind != 0:
default:
return ""
}
b, err := format.Node(v.Syntax())
if err != nil {
return ""
}
if s, ok := cueToOpenAPI[string(b)]; ok {
return s
}
s := fmt.Sprint(v)
return cueToOpenAPI[s]
}
func getDeprecated(v cue.Value) bool {
// only looking at protobuf attribute for now.
a := v.Attribute("protobuf")
r, _ := a.Flag(1, "deprecated")
return r
}
func simplify(b *builder, t *OrderedMap) {
if b.format == "" {
return
}
switch b.typ {
case "number", "integer":
simplifyNumber(t, b.format)
}
}
func simplifyNumber(t *OrderedMap, format string) string {
pairs := t.kvs
k := 0
for i, kv := range pairs {
switch kv.Key {
case "minimum":
if decimalEqual(minMap[format], kv.Value) {
continue
}
case "maximum":
if decimalEqual(maxMap[format], kv.Value) {
continue
}
}
pairs[k] = pairs[i]
k++
}
t.kvs = pairs[:k]
return format
}
func decimalEqual(d *decimal, v interface{}) bool {
if d == nil {
return false
}
b, ok := v.(*decimal)
if !ok {
return false
}
return d.Cmp(b.Decimal) == 0
}
type decimal struct {
*apd.Decimal
}
func (d *decimal) MarshalJSON() (b []byte, err error) {
return d.MarshalText()
}
func mustDecimal(s string) *decimal {
d, _, err := apd.NewFromString(s)
if err != nil {
panic(err)
}
return &decimal{d}
}
var (
minMap = map[string]*decimal{
"int32": mustDecimal("-2147483648"),
"int64": mustDecimal("-9223372036854775808"),
"float": mustDecimal("-3.40282346638528859811704183484516925440e+38"),
"double": mustDecimal("-1.797693134862315708145274237317043567981e+308"),
}
maxMap = map[string]*decimal{
"int32": mustDecimal("2147483647"),
"int64": mustDecimal("9223372036854775807"),
"float": mustDecimal("+3.40282346638528859811704183484516925440e+38"),
"double": mustDecimal("+1.797693134862315708145274237317043567981e+308"),
}
)