Skip to content

Commit 8639476

Browse files
committed
Support union types
1 parent c8aa8cb commit 8639476

File tree

4 files changed

+180
-20
lines changed

4 files changed

+180
-20
lines changed

schema.go

Lines changed: 102 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"bytes"
55
"encoding/json"
6+
"errors"
67
"fmt"
78
"io"
89
"io/ioutil"
@@ -20,11 +21,11 @@ type schema struct {
2021
Title string `json:"title,omitempty"`
2122
Description string `json:"description,omitempty"`
2223
Required []string `json:"required,omitempty"`
23-
Type string `json:"type,omitempty"`
24+
Type PropertyTypes `json:"type,omitempty"`
2425
Properties map[string]*schema `json:"properties,omitempty"`
2526
Items *schema `json:"items,omitempty"`
2627
Definitions map[string]*schema `json:"definitions,omitempty"`
27-
Enum []string `json:"enum"`
28+
Enum []Any `json:"enum"`
2829
}
2930

3031
func newSchema(r io.Reader, workingDir string) (*schema, error) {
@@ -104,16 +105,16 @@ func findDefinitions(s *schema) []*schema {
104105

105106
for k, p := range s.Properties {
106107
// Use the identifier as the title.
107-
if p.Type == "object" {
108+
if p.Type.HasType(PropertyTypeObject) {
108109
p.Title = k
109110
objs = append(objs, p)
110111
}
111112

112113
// If the property is an array of objects, use the name of the array
113114
// property as the title.
114-
if p.Type == "array" {
115+
if p.Type.HasType(PropertyTypeArray) {
115116
if p.Items != nil {
116-
if p.Items.Type == "object" {
117+
if p.Items.Type.HasType(PropertyTypeObject) {
117118
p.Items.Title = k
118119
objs = append(objs, p.Items)
119120
}
@@ -143,22 +144,35 @@ func printProperties(w io.Writer, s *schema) {
143144

144145
for k, p := range s.Properties {
145146
// Generate relative links for objects and arrays of objects.
146-
var propType string
147-
switch p.Type {
148-
case "object":
149-
propType = fmt.Sprintf("[%s](#%s)", p.Type, strings.ToLower(k))
150-
case "array":
151-
if p.Items != nil {
152-
if p.Items.Type == "object" {
153-
propType = fmt.Sprintf("[%s](#%s)[]", p.Items.Type, strings.ToLower(k))
147+
var propType []string
148+
for _, pt := range p.Type {
149+
switch pt {
150+
case PropertyTypeObject:
151+
propType = append(propType, fmt.Sprintf("[object](#%s)", strings.ToLower(k)))
152+
case PropertyTypeArray:
153+
if p.Items != nil {
154+
for _, pi := range p.Items.Type {
155+
if pi == PropertyTypeObject {
156+
propType = append(propType, fmt.Sprintf("[%s](#%s)[]", pi, strings.ToLower(k)))
157+
} else {
158+
propType = append(propType, fmt.Sprintf("%s[]", pi))
159+
}
160+
}
154161
} else {
155-
propType = fmt.Sprintf("%s[]", p.Items.Type)
162+
propType = append(propType, string(pt))
156163
}
157-
} else {
158-
propType = p.Type
164+
default:
165+
propType = append(propType, string(pt))
159166
}
160-
default:
161-
propType = p.Type
167+
}
168+
169+
var propTypeStr string
170+
if len(propType) == 1 {
171+
propTypeStr = propType[0]
172+
} else if len(propType) == 2 {
173+
propTypeStr = strings.Join(propType, " or ")
174+
} else if len(propType) > 2 {
175+
propTypeStr = fmt.Sprintf("%s, or %s", strings.Join(propType[:len(propType)-1], ", "), propType[len(propType)-1])
162176
}
163177

164178
// Emphasize required properties.
@@ -172,10 +186,14 @@ func printProperties(w io.Writer, s *schema) {
172186
desc := p.Description
173187

174188
if len(p.Enum) > 0 {
175-
desc += " Possible values are: `" + strings.Join(p.Enum, "`, `") + "`."
189+
var vals []string
190+
for _, e := range p.Enum {
191+
vals = append(vals, e.String())
192+
}
193+
desc += " Possible values are: `" + strings.Join(vals, "`, `") + "`."
176194
}
177195

178-
rows = append(rows, []string{fmt.Sprintf("`%s`", k), propType, required, strings.TrimSpace(desc)})
196+
rows = append(rows, []string{fmt.Sprintf("`%s`", k), propTypeStr, required, strings.TrimSpace(desc)})
179197
}
180198

181199
// Sort by the required column, then by the name column.
@@ -202,3 +220,67 @@ func in(strs []string, str string) bool {
202220
}
203221
return false
204222
}
223+
224+
type PropertyTypes []PropertyType
225+
226+
func (pts *PropertyTypes) HasType(pt PropertyType) bool {
227+
for _, t := range *pts {
228+
if t == pt {
229+
return true
230+
}
231+
}
232+
return false
233+
}
234+
235+
func (pt *PropertyTypes) UnmarshalJSON(data []byte) error {
236+
var value interface{}
237+
if err := json.Unmarshal(data, &value); err != nil {
238+
return err
239+
}
240+
241+
switch val := value.(type) {
242+
case string:
243+
*pt = []PropertyType{PropertyType(val)}
244+
return nil
245+
case []interface{}:
246+
var pts []PropertyType
247+
for _, t := range val {
248+
s, ok := t.(string)
249+
if !ok {
250+
return errors.New("unsupported property type")
251+
}
252+
pts = append(pts, PropertyType(s))
253+
}
254+
*pt = pts
255+
default:
256+
return errors.New("unsupported property type")
257+
}
258+
259+
return nil
260+
}
261+
262+
type PropertyType string
263+
264+
const (
265+
PropertyTypeString PropertyType = "string"
266+
PropertyTypeNumber PropertyType = "number"
267+
PropertyTypeBoolean PropertyType = "boolean"
268+
PropertyTypeObject PropertyType = "object"
269+
PropertyTypeArray PropertyType = "array"
270+
PropertyTypeNull PropertyType = "null"
271+
)
272+
273+
type Any struct {
274+
value interface{}
275+
}
276+
277+
func (u *Any) UnmarshalJSON(data []byte) error {
278+
if err := json.Unmarshal(data, &u.value); err != nil {
279+
return err
280+
}
281+
return nil
282+
}
283+
284+
func (u *Any) String() string {
285+
return fmt.Sprintf("%v", u.value)
286+
}

schema_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func TestSchema(t *testing.T) {
2525
{name: "card", schema: "card.schema.json"},
2626
{name: "geographical-location", schema: "geographical-location.schema.json"},
2727
{name: "ref-hell", schema: "ref-hell.schema.json"},
28+
{name: "union", schema: "union.schema.json"},
2829
{name: "deep-headings", schema: "ref-hell.schema.json", level: 5},
2930
}
3031

testdata/TestSchema_union.golden

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## Properties
2+
3+
| Property | Type | Required | Description |
4+
|----------|----------------------------------|----------|----------------------------------------------------|
5+
| `five` | string[], string, or null | No | |
6+
| `four` | [object](#four), string, or null | No | |
7+
| `one` | string | No | Possible values are: `auto`. |
8+
| `six` | string[] or [object](#six) | No | |
9+
| `three` | string, boolean, or number | No | Possible values are: `auto`, `true`, `false`, `1`. |
10+
| `two` | string or boolean | No | Possible values are: `auto`, `true`, `false`. |
11+
12+
## four
13+
14+
### Properties
15+
16+
| Property | Type | Required | Description |
17+
|----------|----------|----------|-------------|
18+
| `fruits` | string[] | No | |
19+
20+
## six
21+
22+
### Properties
23+
24+
| Property | Type | Required | Description |
25+
|----------|----------|----------|-------------|
26+
| `fruits` | string[] | No | |
27+

testdata/union.schema.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"$id": "https://example.com/union.schema.json",
3+
"$schema": "http://json-schema.org/draft-07/schema#",
4+
"type": "object",
5+
"properties": {
6+
"one": {
7+
"type": ["string"],
8+
"enum": ["auto"]
9+
},
10+
"two": {
11+
"type": ["string", "boolean"],
12+
"enum": ["auto", true, false]
13+
},
14+
"three": {
15+
"type": ["string", "boolean", "number"],
16+
"enum": ["auto", true, false, 1.0]
17+
},
18+
"four": {
19+
"type": ["object", "string", "null"],
20+
"properties": {
21+
"fruits": {
22+
"type": "array",
23+
"items": {
24+
"type": "string"
25+
}
26+
}
27+
}
28+
},
29+
"five": {
30+
"type": ["array", "string", "null"],
31+
"items": {
32+
"type": "string"
33+
}
34+
},
35+
"six": {
36+
"type": ["array", "object"],
37+
"items": {
38+
"type": "string"
39+
},
40+
"properties": {
41+
"fruits": {
42+
"type": "array",
43+
"items": {
44+
"type": "string"
45+
}
46+
}
47+
}
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)