Skip to content

Commit

Permalink
support for additionalProperties = [true|false], test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
mwlazlo committed Oct 18, 2018
1 parent 811a4e3 commit 3cfcc0c
Show file tree
Hide file tree
Showing 6 changed files with 427 additions and 66 deletions.
13 changes: 12 additions & 1 deletion generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,13 @@ func (g *Generator) processObject(name string, schema *Schema) (typ string, err
return "", err
}
mapTyp := "map[string]" + subTyp
if len(schema.Properties) == 0 {
// If this object is inline property for another object, and only contains additional properties, we can
// collapse the structure down to a map.
//
// If this object is a definition and only contains additional properties, we can't do that or we end up with
// no struct
isDefinitionObject := strings.HasPrefix(schema.PathElement, "definitions")
if len(schema.Properties) == 0 && !isDefinitionObject {
// since there are no regular properties, we don't need to emit a struct for this object - return the
// additionalProperties map type.
return mapTyp, nil
Expand Down Expand Up @@ -245,8 +251,13 @@ func (g *Generator) processObject(name string, schema *Schema) (typ string, err
Description: "",
}
strct.Fields[f.Name] = f
// setting this will cause marshal code to be emitted in Output()
strct.GenerateCode = true
strct.AdditionalType = "interface{}"
} else {
// nothing
strct.GenerateCode = true
strct.AdditionalType = "false"
}
}
g.Structs[strct.Name] = strct
Expand Down
88 changes: 41 additions & 47 deletions output.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,27 +41,25 @@ func Output(w io.Writer, g *Generator, pkg string) {

// write all the code into a buffer, compiler functions will return list of imports
// write list of imports into main output stream, followed by the code
buf := new(bytes.Buffer)
codeBuf := new(bytes.Buffer)
imports := make(map[string]bool)

for _, k := range getOrderedStructNames(structs) {
s := structs[k]
if s.GenerateCode {
emitMarshalCode(buf, s, imports)
emitUnmarshalCode(buf, s, imports)
emitMarshalCode(codeBuf, s, imports)
emitUnmarshalCode(codeBuf, s, imports)
}
}

if len(imports) > 0 {
fmt.Fprintf(w, "import (\n")
fmt.Fprintf(w, "\nimport (\n")
for k := range imports {
fmt.Fprintf(w, " \"%s\"\n", k)
}
fmt.Fprintf(w, ")\n")
}

w.Write(buf.Bytes())

for _, k := range getOrderedFieldNames(aliases) {
a := aliases[k]

Expand Down Expand Up @@ -95,6 +93,9 @@ func Output(w io.Writer, g *Generator, pkg string) {

fmt.Fprintln(w, "}")
}

// write code after structs for clarity
w.Write(codeBuf.Bytes())
}

func emitMarshalCode(w io.Writer, s Struct, imports map[string]bool) {
Expand Down Expand Up @@ -144,15 +145,16 @@ func (strct *%s) MarshalJSON() ([]byte, error) {
}
}
if s.AdditionalType != "" {
imports["fmt"] = true
if s.AdditionalType != "false" {
imports["fmt"] = true

if len(s.Fields) == 0 {
fmt.Fprintf(w, " comma := false\n")
}
if len(s.Fields) == 0 {
fmt.Fprintf(w, " comma := false\n")
}

fmt.Fprintf(w, " // Marshal any additional Properties\n")
// Marshal any additional Properties
fmt.Fprintf(w, ` for k, v := range strct.AdditionalProperties {
fmt.Fprintf(w, " // Marshal any additional Properties\n")
// Marshal any additional Properties
fmt.Fprintf(w, ` for k, v := range strct.AdditionalProperties {
if comma {
buf.WriteString(",")
}
Expand All @@ -165,6 +167,7 @@ func (strct *%s) MarshalJSON() ([]byte, error) {
comma = true
}
`)
}
}

fmt.Fprintf(w, `
Expand All @@ -188,16 +191,23 @@ func (strct *%s) UnmarshalJSON(b []byte) error {
fmt.Fprintf(w, " %sReceived := false\n", f.JSONName)
}
}
fmt.Fprintf(w, `
var jsonMap map[string]json.RawMessage
// setup initial unmarshal
fmt.Fprintf(w, ` var jsonMap map[string]json.RawMessage
if err := json.Unmarshal(b, &jsonMap); err != nil {
return err
}
}`)

// figure out if we need the "v" output of the range keyword
needVal := "_"
if len(s.Fields) > 0 || s.AdditionalType != "false" {
needVal = "v"
}
// start the loop
fmt.Fprintf(w, `
// parse all the defined properties
for k, v := range jsonMap {
for k, %s := range jsonMap {
switch k {
`)
`, needVal)
// handle defined properties
for _, fieldKey := range getOrderedFieldNames(s.Fields) {
f := s.Fields[fieldKey]
Expand All @@ -214,27 +224,27 @@ func (strct *%s) UnmarshalJSON(b []byte) error {
}
}

// TODO: if additionalProperties == false emit default case to return error

// handle additional property
if s.AdditionalType != "" {
// now handle additional values
initialiser, isPointer := getPrimitiveInitialiser(s.AdditionalType)
addressOfInitialiser := "&"
if !isPointer {
addressOfInitialiser = ""
}
fmt.Fprintf(w, ` default:
if s.AdditionalType == "false" {
// all unknown properties are not allowed
imports["fmt"] = true
fmt.Fprintf(w, ` default:
return fmt.Errorf("additional property not allowed: \"" + k + "\"")
`)
} else {
fmt.Fprintf(w, ` default:
// an additional "%s" value
additionalValue := %s
var additionalValue %s
if err := json.Unmarshal([]byte(v), &additionalValue); err != nil {
return err // invalid additionalProperty
}
if strct.AdditionalProperties == nil {
strct.AdditionalProperties = make(map[string]%s, 0)
}
strct.AdditionalProperties[k]= %sadditionalValue
`, s.AdditionalType, initialiser, s.AdditionalType, addressOfInitialiser)
strct.AdditionalProperties[k]= additionalValue
`, s.AdditionalType, s.AdditionalType, s.AdditionalType)
}
}
fmt.Fprintf(w, " }\n") // switch
fmt.Fprintf(w, " }\n") // for
Expand All @@ -246,7 +256,7 @@ func (strct *%s) UnmarshalJSON(b []byte) error {
imports["errors"] = true
fmt.Fprintf(w, ` // check if %s (a required property) was received
if !%sReceived {
return errors.New("%s is required but was not present")
return errors.New("\"%s\" is required but was not present")
}
`, f.JSONName, f.JSONName, f.JSONName)
}
Expand All @@ -256,22 +266,6 @@ func (strct *%s) UnmarshalJSON(b []byte) error {
fmt.Fprintf(w, "}\n") // UnmarshalJSON
}

func getPrimitiveInitialiser(typ string) (string, bool) {
// strip *pointer dereference symbol so we can use in declaration
deref := strings.Replace(typ, "*", "", 1)
switch {
case strings.HasPrefix(typ, "map"):
return typ + "{}", false
case strings.HasPrefix(deref, "int"):
return "0", false
case strings.HasPrefix(deref, "float"):
return "0.0", false
case deref == "string":
return "\"\"", false
}
return deref + "{}", true
}

func outputNameAndDescriptionComment(name, description string, w io.Writer) {
if strings.Index(description, "\n") == -1 {
fmt.Fprintf(w, "// %s %s\n", name, description)
Expand Down
18 changes: 0 additions & 18 deletions test/additionalProperties3.json

This file was deleted.

89 changes: 89 additions & 0 deletions test/additionalPropertiesMarshal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
{
"definitions": {
"thing": {
"type": "object"
},

"apRefNoProp": {
"additionalProperties": {
"$ref": "#/definitions/thing"
},
"type": "object"
},
"apRefProp": {
"properties": {
"stuff": {
"type": "string"
}
},
"additionalProperties": {
"$ref": "#/definitions/thing"
},
"type": "object"
},
"apRefReqProp": {
"properties": {
"stuff": {
"type": "string"
}
},
"additionalProperties": {
"$ref": "#/definitions/thing"
},
"required": [ "stuff" ],
"type": "object"
},


"apTrueNoProp": {
"additionalProperties": true,
"type": "object"
},
"apFalseNoProp": {
"additionalProperties": false,
"type": "object"
},


"apTrueProp": {
"properties": {
"stuff": {
"type": "string"
}
},
"additionalProperties": true,
"type": "object"
},
"apFalseProp": {
"properties": {
"stuff": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},


"apTrueReqProp": {
"properties": {
"stuff": {
"type": "string"
}
},
"additionalProperties": true,
"required": [ "stuff" ],
"type": "object"
},
"apFalseReqProp": {
"properties": {
"stuff": {
"type": "string"
}
},
"additionalProperties": false,
"required": [ "stuff" ],
"type": "object"
}
}
}
Loading

0 comments on commit 3cfcc0c

Please sign in to comment.