forked from rs/rest-layer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
schema.go
149 lines (137 loc) · 3.61 KB
/
schema.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package jsonschema
import (
"encoding/json"
"errors"
"fmt"
"io"
"sort"
"strconv"
"strings"
"github.com/rs/rest-layer/schema"
)
var (
// ErrNotImplemented is returned when the JSON schema encoding logic for a schema.FieldValidator has not (yet)
// been implemented.
ErrNotImplemented = errors.New("not implemented")
)
// encodeSchema writes JSON Schema keys and values based on s, without the outer curly braces, to w.
func encodeSchema(w io.Writer, s *schema.Schema) (err error) {
if s == nil {
return
}
ew := errWriter{w: w}
if s.Description != "" {
ew.writeFormat(`"description": %q, `, s.Description)
}
ew.writeString(`"type": "object", `)
ew.writeString(`"additionalProperties": false, `)
ew.writeString(`"properties": {`)
var required []string
var notFirst bool
for _, key := range sortedFieldNames(s.Fields) {
field := s.Fields[key]
if notFirst {
ew.writeString(", ")
}
notFirst = true
if field.Required {
required = append(required, fmt.Sprintf("%q", key))
}
ew.err = encodeField(ew, key, field)
if ew.err != nil {
return ew.err
}
}
ew.writeString("}")
if s.MinLen > 0 {
ew.writeFormat(`, "minProperties": %s`, strconv.FormatInt(int64(s.MinLen), 10))
}
if s.MaxLen > 0 {
ew.writeFormat(`, "maxProperties": %s`, strconv.FormatInt(int64(s.MaxLen), 10))
}
if len(required) > 0 {
ew.writeFormat(`, "required": [%s]`, strings.Join(required, ", "))
}
return ew.err
}
type fieldWriter struct {
errWriter
propertiesCount int
}
// comma optionally outputs a comma. Invoke this when you're about to write a property. Tracks how many have been
// written and emits if not the first.
func (fw *fieldWriter) comma() {
if fw.propertiesCount > 0 {
fw.writeString(",")
}
fw.propertiesCount++
}
func (fw *fieldWriter) resetPropertiesCount() {
fw.propertiesCount = 0
}
// sortedFieldNames returns a list with all field names alphabetically sorted.
func sortedFieldNames(v schema.Fields) []string {
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
func encodeField(ew errWriter, key string, field schema.Field) error {
fw := fieldWriter{ew, 0}
fw.writeFormat("%q: {", key)
if field.Description != "" {
fw.comma()
fw.writeFormat(`"description": %q`, field.Description)
}
if field.ReadOnly {
fw.comma()
fw.writeFormat(`"readOnly": %t`, field.ReadOnly)
}
if field.Validator != nil {
// FIXME: This breaks if there are any Validators that may write nothing. E.g. a schema.Object with
// Schema set to nil. A better solution should be found before adding support for custom validators.
fw.comma()
fw.err = encodeValidator(ew, field.Validator)
}
if field.Default != nil {
b, err := json.Marshal(field.Default)
if err != nil {
return err
}
fw.comma()
fw.writeString(`"default": `)
fw.writeBytes(b)
}
fw.writeString("}")
fw.resetPropertiesCount()
return fw.err
}
// encodeValidator writes JSON Schema keys and values based on v, without the outer curly braces, to w. Note
// that not all FieldValidator types are supported at the moment.
func encodeValidator(w io.Writer, v schema.FieldValidator) (err error) {
if v == nil {
return nil
}
switch t := v.(type) {
case *schema.String:
err = encodeString(w, t)
case *schema.Integer:
err = encodeInteger(w, t)
case *schema.Float:
err = encodeFloat(w, t)
case *schema.Array:
err = encodeArray(w, t)
case *schema.Object:
// FIXME: May break the JSON encoding atm. if t.Schema is nil.
err = encodeObject(w, t)
case *schema.Time:
err = encodeTime(w, t)
case *schema.Bool:
err = encodeBool(w, t)
default:
return ErrNotImplemented
}
return err
}