Skip to content

Commit

Permalink
feat: show property key in example yaml (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
twelvelabs authored Jan 1, 2024
1 parent f40e81a commit 892aa8b
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 52 deletions.
4 changes: 2 additions & 2 deletions internal/jsonschema/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ func resolveSubSchemas(schema *Schema) (*Schema, error) {
*subSchema = *tmp
}

for _, subSchema := range schema.Properties {
// subSchema.Key = key
for key, subSchema := range schema.Properties {
subSchema.Key = key
subSchema.Parent = schema
tmp, err := resolveRef(subSchema)
if err != nil {
Expand Down
50 changes: 33 additions & 17 deletions internal/jsonschema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,41 +177,48 @@ func (s *Schema) EnsureDocument() {
}
}

// EnumMarkdown returns the enum and enum descriptions
// formatted as a markdown list.
func (s *Schema) EnumMarkdown() string {
// EnumMarkdownItems returns the .Enum items formatted as Markdown.
// If .Const is present, will treat that as a virtual enum of one.
func (s *Schema) EnumMarkdownItems() []string {
// Const takes priority...
if s.Const.IsSet() {
return []string{"`" + s.Const.JSONString() + "`"}
}
// Otherwise use Enum
items := []string{}

for idx, enum := range s.Enum {
desc := ""
if idx < len(s.EnumDescriptions) {
desc = s.EnumDescriptions[idx]
}
item := "- `" + enum.String() + "`"
item := "`" + enum.JSONString() + "`"
if desc != "" {
item += ": " + desc
}
items = append(items, item)
}

if len(items) == 0 {
return ""
}
return strings.Join(items, "\n")
return items
}

// ExamplesMarkdown returns the examples as a markdown list.
func (s *Schema) ExamplesMarkdown() string {
// YAMLExamples returns the examples formatted as YAML.
func (s *Schema) YAMLExamples() []string {
items := []string{}

for _, example := range s.Examples {
items = append(items, fmt.Sprintf(" * `%s`", example.YAMLString()))
if s.Key != "" {
// Wrap the example in a map[string] so that it
// ends up getting rendered as `key: value`.
// Makes for more copy/paste-able examples.
valueOld := example.value
valueNew := map[string]any{
s.Key: valueOld,
}
example.value = valueNew
}
items = append(items, example.YAMLString())
}

if len(items) == 0 {
return ""
}
return strings.Join(items, "\n")
return items
}

// Merge merges fields from other into the receiver.
Expand Down Expand Up @@ -271,6 +278,8 @@ func (s *Schema) Merge(other *Schema) {
s.If = other.If
case "items":
s.Items = other.Items
case "key":
s.Key = other.Key
case "markdownDescription":
s.MarkdownDescription = other.MarkdownDescription
case "maxContains":
Expand Down Expand Up @@ -332,6 +341,9 @@ func (s *Schema) Merge(other *Schema) {
default:
}
}
if other.Parent != nil {
s.Parent = other.Parent
}
}

// RequiredKey returns true when key is a required property.
Expand Down Expand Up @@ -483,6 +495,10 @@ func (a *Any) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &a.value)
}

func (a Any) IsSet() bool {
return a.value != nil
}

func (a Any) String() string {
if a.value == nil {
return ""
Expand Down
106 changes: 97 additions & 9 deletions internal/jsonschema/schema_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jsonschema

import (
"errors"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -143,19 +144,33 @@ func TestSchema_EntityName(t *testing.T) {
require.Equal("Bar", schema.EntityName())
}

func TestSchema_EnumMarkdown(t *testing.T) {
func TestSchema_EnumMarkdownItems(t *testing.T) {
require := require.New(t)

schema := Schema{}
require.Equal("", schema.EnumMarkdown())
require.Equal([]string{}, schema.EnumMarkdownItems())

schema = Schema{
Const: Any{"constant value"},
Enum: []Any{
{"one"},
{"two"},
},
}
require.Equal("- `one`\n- `two`", schema.EnumMarkdown())
require.Equal([]string{
"`\"constant value\"`",
}, schema.EnumMarkdownItems())

schema = Schema{
Enum: []Any{
{"one"},
{"two"},
},
}
require.Equal([]string{
"`\"one\"`",
"`\"two\"`",
}, schema.EnumMarkdownItems())

schema = Schema{
Enum: []Any{
Expand All @@ -167,22 +182,47 @@ func TestSchema_EnumMarkdown(t *testing.T) {
"the second number",
},
}
require.Equal("- `one`: the first number\n- `two`: the second number", schema.EnumMarkdown())
require.Equal([]string{
"`\"one\"`: the first number",
"`\"two\"`: the second number",
}, schema.EnumMarkdownItems())
}

func TestSchema_ExamplesMarkdown(t *testing.T) {
func TestSchema_YAMLExamples(t *testing.T) {
require := require.New(t)

schema := Schema{}
require.Equal("", schema.ExamplesMarkdown())
require.Equal([]string{}, schema.YAMLExamples())

schema = Schema{
Examples: []Any{
{value: "string one"},
{value: []string{
"slice one",
"slice two",
}},
},
}
require.Equal([]string{
"string one",
"- slice one\n- slice two",
}, schema.YAMLExamples())

// Render w/ key when present.
schema = Schema{
Key: "foo",
Examples: []Any{
{"example one"},
{"example two"},
{value: "string one"},
{value: []string{
"slice one",
"slice two",
}},
},
}
require.Equal(" * `example one`\n * `example two`", schema.ExamplesMarkdown())
require.Equal([]string{
"foo: string one",
"foo:\n - slice one\n - slice two",
}, schema.YAMLExamples())
}

func TestSchema_GenPath(t *testing.T) {
Expand Down Expand Up @@ -240,10 +280,14 @@ func TestSchema_Merge(t *testing.T) {
Document: map[string]any{
"description": "schema2 description",
},
Parent: &Schema{
Title: "schema2 parent",
},
}
schema1.Merge(schema2)
require.Equal("schema1 title", schema1.Title)
require.Equal("schema2 description", schema1.Description)
require.Equal("schema2 parent", schema1.Parent.Title)

// Trigger all attributes to be set so coverage doesn't take a hit.
schema1 = &Schema{}
Expand Down Expand Up @@ -273,6 +317,7 @@ func TestSchema_Merge(t *testing.T) {
"$id": true,
"if": true,
"items": true,
"key": true,
"markdownDescription": true,
"maxContains": true,
"maximum": true,
Expand Down Expand Up @@ -443,3 +488,46 @@ func TestTypeInfo_Markdown(t *testing.T) {
}
require.Equal("[Schema2](#schema2)", ti.Markdown())
}

func TestAny_String(t *testing.T) {
require := require.New(t)

require.Equal("", (Any{}).String())
require.Equal("foo", (Any{"foo"}).String())
}

func TestAny_JSONString(t *testing.T) {
require := require.New(t)

require.Equal("", (Any{}).JSONString())
require.Equal("\"foo\"", (Any{"foo"}).JSONString())

value := NewErrEncoder(errors.New("boom"))
require.Contains((Any{value}).JSONString(), "boom")
}

func TestAny_YAMLString(t *testing.T) {
require := require.New(t)

require.Equal("", (Any{}).YAMLString())
require.Equal("foo", (Any{"foo"}).YAMLString())

value := NewErrEncoder(errors.New("boom"))
require.Contains((Any{value}).YAMLString(), "boom")
}

func NewErrEncoder(err error) *ErrEncoder {
return &ErrEncoder{err}
}

type ErrEncoder struct {
err error
}

func (e *ErrEncoder) MarshalText() ([]byte, error) {
return nil, e.err
}

func (e *ErrEncoder) UnmarshalText(_ []byte) error {
return e.err
}
36 changes: 12 additions & 24 deletions internal/jsonschema/templates/markdown.tpl.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
{{ define "ConstTpl" -}}
{{ if . -}}

Allowed Value:

- `{{ . }}`

{{ end -}}
{{ end -}}

{{ define "DescriptionTpl" -}}
{{ if . -}}

Expand All @@ -21,8 +11,10 @@ Allowed Value:

Allowed Values:

{{ . }}
{{ range $enum := . -}}

- {{ $enum }}
{{ end -}}
{{ end -}}
{{ end -}}

Expand All @@ -34,7 +26,7 @@ Examples:
{{ range $example := . -}}

```yaml
{{ $example.YAMLString }}
{{ $example }}
```

{{ end -}}
Expand All @@ -45,19 +37,17 @@ Examples:
{{ if . -}}
| Property | Type | Required | Enum | Default | Description |
| -------- | ---- | -------- | ---- | ------- | ----------- |
{{ $propParent := . -}}
{{ range $key, $prop := $propParent.Properties -}}
| [`{{ $key }}`](#{{ $key }}) | {{ $prop.TypeInfoMarkdown }} | {{ if $propParent.RequiredKey $key }}✅{{ else }}➖{{ end }} | {{ if $prop.Enum }}✅{{ else }}➖{{ end }} | {{ $prop.Default.JSONString | wrapCode | default "➖" }} | {{ $prop.DescriptionMarkdown | stripMarkdown | firstSentence | toHTML }} |
{{ range $key, $prop := .Properties -}}
| [`{{ $prop.Key }}`](#{{ $prop.Key }}) | {{ $prop.TypeInfoMarkdown }} | {{ if $prop.Parent.RequiredKey $prop.Key }}✅{{ else }}➖{{ end }} | {{ if $prop.EnumMarkdownItems }}✅{{ else }}➖{{ end }} | {{ $prop.Default.JSONString | wrapCode | default "➖" }} | {{ $prop.DescriptionMarkdown | stripMarkdown | firstSentence | toHTML }} |
{{ end -}}
{{ end -}}
{{ end -}}

# {{ .EntityName }}

{{ template "DescriptionTpl" .DescriptionMarkdown }}
{{ template "ConstTpl" .Const.String }}
{{ template "EnumTpl" .EnumMarkdown }}
{{ template "ExamplesTpl" .Examples }}
{{ template "EnumTpl" .EnumMarkdownItems }}
{{ template "ExamplesTpl" .YAMLExamples }}

{{ if .OneOf -}}

Expand All @@ -75,18 +65,16 @@ Examples:
{{ template "PropertiesTpl" . }}

{{ end -}}
{{ $root := . -}}
{{ range $key, $prop := .Properties -}}

### `{{ $key }}`
### `{{ $prop.Key }}`

| Type | Required | Enum | Default |
| ---- | -------- | ---- | ------- |
| {{ $prop.TypeInfoMarkdown }} | {{ if $root.RequiredKey $key }}✅{{ else }}➖{{ end }} | {{ if $prop.Enum }}✅{{ else }}➖{{ end }} | {{ $prop.Default.JSONString | wrapCode | default "➖" }} |
| {{ $prop.TypeInfoMarkdown }} | {{ if $prop.Parent.RequiredKey $prop.Key }}✅{{ else }}➖{{ end }} | {{ if $prop.EnumMarkdownItems }}✅{{ else }}➖{{ end }} | {{ $prop.Default.JSONString | wrapCode | default "➖" }} |

{{ template "DescriptionTpl" $prop.DescriptionMarkdown }}
{{ template "ConstTpl" $prop.Const.String }}
{{ template "EnumTpl" $prop.EnumMarkdown }}
{{ template "ExamplesTpl" $prop.Examples }}
{{ template "EnumTpl" $prop.EnumMarkdownItems }}
{{ template "ExamplesTpl" $prop.YAMLExamples }}

{{ end -}}

0 comments on commit 892aa8b

Please sign in to comment.