Skip to content

Commit

Permalink
Support union types
Browse files Browse the repository at this point in the history
  • Loading branch information
marcusolsson committed Apr 12, 2021
1 parent c8aa8cb commit 8639476
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 20 deletions.
122 changes: 102 additions & 20 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
Expand All @@ -20,11 +21,11 @@ type schema struct {
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Required []string `json:"required,omitempty"`
Type string `json:"type,omitempty"`
Type PropertyTypes `json:"type,omitempty"`
Properties map[string]*schema `json:"properties,omitempty"`
Items *schema `json:"items,omitempty"`
Definitions map[string]*schema `json:"definitions,omitempty"`
Enum []string `json:"enum"`
Enum []Any `json:"enum"`
}

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

for k, p := range s.Properties {
// Use the identifier as the title.
if p.Type == "object" {
if p.Type.HasType(PropertyTypeObject) {
p.Title = k
objs = append(objs, p)
}

// If the property is an array of objects, use the name of the array
// property as the title.
if p.Type == "array" {
if p.Type.HasType(PropertyTypeArray) {
if p.Items != nil {
if p.Items.Type == "object" {
if p.Items.Type.HasType(PropertyTypeObject) {
p.Items.Title = k
objs = append(objs, p.Items)
}
Expand Down Expand Up @@ -143,22 +144,35 @@ func printProperties(w io.Writer, s *schema) {

for k, p := range s.Properties {
// Generate relative links for objects and arrays of objects.
var propType string
switch p.Type {
case "object":
propType = fmt.Sprintf("[%s](#%s)", p.Type, strings.ToLower(k))
case "array":
if p.Items != nil {
if p.Items.Type == "object" {
propType = fmt.Sprintf("[%s](#%s)[]", p.Items.Type, strings.ToLower(k))
var propType []string
for _, pt := range p.Type {
switch pt {
case PropertyTypeObject:
propType = append(propType, fmt.Sprintf("[object](#%s)", strings.ToLower(k)))
case PropertyTypeArray:
if p.Items != nil {
for _, pi := range p.Items.Type {
if pi == PropertyTypeObject {
propType = append(propType, fmt.Sprintf("[%s](#%s)[]", pi, strings.ToLower(k)))
} else {
propType = append(propType, fmt.Sprintf("%s[]", pi))
}
}
} else {
propType = fmt.Sprintf("%s[]", p.Items.Type)
propType = append(propType, string(pt))
}
} else {
propType = p.Type
default:
propType = append(propType, string(pt))
}
default:
propType = p.Type
}

var propTypeStr string
if len(propType) == 1 {
propTypeStr = propType[0]
} else if len(propType) == 2 {
propTypeStr = strings.Join(propType, " or ")
} else if len(propType) > 2 {
propTypeStr = fmt.Sprintf("%s, or %s", strings.Join(propType[:len(propType)-1], ", "), propType[len(propType)-1])
}

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

if len(p.Enum) > 0 {
desc += " Possible values are: `" + strings.Join(p.Enum, "`, `") + "`."
var vals []string
for _, e := range p.Enum {
vals = append(vals, e.String())
}
desc += " Possible values are: `" + strings.Join(vals, "`, `") + "`."
}

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

// Sort by the required column, then by the name column.
Expand All @@ -202,3 +220,67 @@ func in(strs []string, str string) bool {
}
return false
}

type PropertyTypes []PropertyType

func (pts *PropertyTypes) HasType(pt PropertyType) bool {
for _, t := range *pts {
if t == pt {
return true
}
}
return false
}

func (pt *PropertyTypes) UnmarshalJSON(data []byte) error {
var value interface{}
if err := json.Unmarshal(data, &value); err != nil {
return err
}

switch val := value.(type) {
case string:
*pt = []PropertyType{PropertyType(val)}
return nil
case []interface{}:
var pts []PropertyType
for _, t := range val {
s, ok := t.(string)
if !ok {
return errors.New("unsupported property type")
}
pts = append(pts, PropertyType(s))
}
*pt = pts
default:
return errors.New("unsupported property type")
}

return nil
}

type PropertyType string

const (
PropertyTypeString PropertyType = "string"
PropertyTypeNumber PropertyType = "number"
PropertyTypeBoolean PropertyType = "boolean"
PropertyTypeObject PropertyType = "object"
PropertyTypeArray PropertyType = "array"
PropertyTypeNull PropertyType = "null"
)

type Any struct {
value interface{}
}

func (u *Any) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &u.value); err != nil {
return err
}
return nil
}

func (u *Any) String() string {
return fmt.Sprintf("%v", u.value)
}
1 change: 1 addition & 0 deletions schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestSchema(t *testing.T) {
{name: "card", schema: "card.schema.json"},
{name: "geographical-location", schema: "geographical-location.schema.json"},
{name: "ref-hell", schema: "ref-hell.schema.json"},
{name: "union", schema: "union.schema.json"},
{name: "deep-headings", schema: "ref-hell.schema.json", level: 5},
}

Expand Down
27 changes: 27 additions & 0 deletions testdata/TestSchema_union.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Properties

| Property | Type | Required | Description |
|----------|----------------------------------|----------|----------------------------------------------------|
| `five` | string[], string, or null | No | |
| `four` | [object](#four), string, or null | No | |
| `one` | string | No | Possible values are: `auto`. |
| `six` | string[] or [object](#six) | No | |
| `three` | string, boolean, or number | No | Possible values are: `auto`, `true`, `false`, `1`. |
| `two` | string or boolean | No | Possible values are: `auto`, `true`, `false`. |

## four

### Properties

| Property | Type | Required | Description |
|----------|----------|----------|-------------|
| `fruits` | string[] | No | |

## six

### Properties

| Property | Type | Required | Description |
|----------|----------|----------|-------------|
| `fruits` | string[] | No | |

50 changes: 50 additions & 0 deletions testdata/union.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"$id": "https://example.com/union.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"one": {
"type": ["string"],
"enum": ["auto"]
},
"two": {
"type": ["string", "boolean"],
"enum": ["auto", true, false]
},
"three": {
"type": ["string", "boolean", "number"],
"enum": ["auto", true, false, 1.0]
},
"four": {
"type": ["object", "string", "null"],
"properties": {
"fruits": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"five": {
"type": ["array", "string", "null"],
"items": {
"type": "string"
}
},
"six": {
"type": ["array", "object"],
"items": {
"type": "string"
},
"properties": {
"fruits": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}

0 comments on commit 8639476

Please sign in to comment.