Skip to content

Commit eb49f3b

Browse files
yanfalirs
authored andcommitted
Issue #50 fix up JSONSchema to not emit invalid JSON (#56)
When a validator hasn't been set the JSONSchema emits invalid JSON. - Add a test case replicating the issue - Create `fieldWriter` type which embeds `errWriter` as field tracking behavior is orthogaonal to `errWriters` purpose - Extract function from encoding loop `serializeField` - Change validator check to test validator for nil instead of just errors - ErrWriter takes care of errors
1 parent c8873dd commit eb49f3b

File tree

2 files changed

+83
-23
lines changed

2 files changed

+83
-23
lines changed

schema/encoding/jsonschema/all_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,25 @@ func TestEncoder(t *testing.T) {
370370
}
371371
}`,
372372
},
373+
{
374+
name: `Incorrectly configured field`,
375+
schema: schema.Schema{
376+
Fields: schema.Fields{
377+
"location": schema.Field{
378+
Description: "location of your stuff",
379+
},
380+
},
381+
},
382+
expect: `{
383+
"type": "object",
384+
"additionalProperties": false,
385+
"properties": {
386+
"location": {
387+
"description": "location of your stuff"
388+
}
389+
}
390+
}`,
391+
},
373392
}
374393
for i := range testCases {
375394
testCases[i].Run(t)

schema/encoding/jsonschema/jsonschema.go

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,39 @@ var (
1818
ErrNotImplemented = errors.New("not implemented")
1919
)
2020

21+
type fieldWriter struct {
22+
errWriter
23+
propertiesCount int
24+
}
25+
26+
// Wrap IO writer so we can consolidate error handling
27+
// in a single place. Also track properties written
28+
// so we know when to emit a separator.
2129
type errWriter struct {
22-
w io.Writer
23-
err error
30+
w io.Writer // writer instance
31+
err error // track errors
32+
}
33+
34+
// comma optionally outputs a comma.
35+
// Invoke this when you're about to write a property.
36+
// Tracks how many have been written and emits if not the first.
37+
func (fw *fieldWriter) comma() {
38+
if fw.propertiesCount > 0 {
39+
fw.writeString(",")
40+
}
41+
fw.propertiesCount++
42+
}
43+
44+
func (fw *fieldWriter) resetPropertiesCount() {
45+
fw.propertiesCount = 0
46+
}
47+
48+
// Compatibility with io.Writer interface
49+
func (ew errWriter) Write(p []byte) (int, error) {
50+
if ew.err != nil {
51+
return 0, ew.err
52+
}
53+
return ew.w.Write(p)
2454
}
2555

2656
func (ew errWriter) writeFormat(format string, a ...interface{}) {
@@ -37,7 +67,7 @@ func (ew errWriter) writeString(s string) {
3767
_, ew.err = ew.w.Write([]byte(s))
3868
}
3969

40-
func (ew errWriter) write(b []byte) {
70+
func (ew errWriter) writeBytes(b []byte) {
4171
if ew.err != nil {
4272
return
4373
}
@@ -135,6 +165,35 @@ func validatorToJSONSchema(w io.Writer, v schema.FieldValidator) (err error) {
135165
return ew.err
136166
}
137167

168+
func serializeField(ew errWriter, key string, field schema.Field) error {
169+
fw := fieldWriter{ew, 0}
170+
fw.writeFormat("%q: {", key)
171+
if field.Description != "" {
172+
fw.comma()
173+
fw.writeFormat(`"description": %q`, field.Description)
174+
}
175+
if field.ReadOnly {
176+
fw.comma()
177+
fw.writeFormat(`"readOnly": %t`, field.ReadOnly)
178+
}
179+
if field.Validator != nil {
180+
fw.comma()
181+
fw.err = validatorToJSONSchema(ew, field.Validator)
182+
}
183+
if field.Default != nil {
184+
b, err := json.Marshal(field.Default)
185+
if err != nil {
186+
return err
187+
}
188+
fw.comma()
189+
fw.writeString(`"default": `)
190+
fw.writeBytes(b)
191+
}
192+
fw.writeString("}")
193+
fw.resetPropertiesCount()
194+
return nil
195+
}
196+
138197
// SchemaToJSONSchema writes JSON Schema keys and values based on s, without the outer curly braces, to w.
139198
func schemaToJSONSchema(w io.Writer, s *schema.Schema) (err error) {
140199
if s == nil {
@@ -155,29 +214,11 @@ func schemaToJSONSchema(w io.Writer, s *schema.Schema) (err error) {
155214
ew.writeString(", ")
156215
}
157216
notFirst = true
158-
ew.writeFormat("%q: {", key)
159-
if field.Description != "" {
160-
ew.writeFormat(`"description": %q, `, field.Description)
161-
}
162217
if field.Required {
163218
required = append(required, fmt.Sprintf("%q", key))
164219
}
165-
if field.ReadOnly {
166-
ew.writeFormat(`"readOnly": %t, `, field.ReadOnly)
167-
}
168-
if ew.err == nil {
169-
ew.err = validatorToJSONSchema(w, field.Validator)
170-
}
171-
if field.Default != nil {
172-
b, err := json.Marshal(field.Default)
173-
if err != nil {
174-
return err
175-
}
176-
ew.writeString(`, "default": `)
177-
ew.write(b)
178-
}
179-
ew.writeString("}")
180-
if ew.err != nil {
220+
err := serializeField(ew, key, field)
221+
if err != nil || ew.err != nil {
181222
break
182223
}
183224
}

0 commit comments

Comments
 (0)