Skip to content

Commit

Permalink
Modify generated ToRawInfo methods to always include required fields.
Browse files Browse the repository at this point in the history
Prior to this change, required fields would be omitted if they
were set to their default values.

Reported in GitHub Issue #82.
  • Loading branch information
timburks committed May 20, 2018
1 parent abb23ee commit bcaf8dd
Show file tree
Hide file tree
Showing 12 changed files with 538 additions and 166 deletions.
297 changes: 208 additions & 89 deletions OpenAPIv2/OpenAPIv2.go

Large diffs are not rendered by default.

262 changes: 218 additions & 44 deletions OpenAPIv3/OpenAPIv3.go

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions examples/v2.0/yaml/empty-v2.yaml
@@ -0,0 +1,6 @@
swagger: "2.0"
info:
version: ""
title: ""
description: ""
paths:
6 changes: 6 additions & 0 deletions examples/v3.0/yaml/empty-v3.yaml
@@ -0,0 +1,6 @@
openapi: "3.0"
info:
version:
title:
description:
paths:
27 changes: 17 additions & 10 deletions generate-gnostic/generate-compiler.go
Expand Up @@ -798,14 +798,17 @@ func (domain *Domain) generateToRawInfoMethodForType(code *printer.Code, typeNam
code.Print("return nil")
} else {
code.Print("info := yaml.MapSlice{}")
code.Print("if m == nil {return info}")
for _, propertyModel := range typeModel.Properties {
isRequired := typeModel.IsRequired(propertyModel.Name)
switch propertyModel.Type {
case "string":
propertyName := propertyModel.Name
if !propertyModel.Repeated {
code.Print("if m.%s != \"\" {", propertyModel.FieldName())
code.PrintIf(isRequired, "// always include this required field.")
code.PrintIf(!isRequired, "if m.%s != \"\" {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
code.PrintIf(!isRequired, "}")
} else {
code.Print("if len(m.%s) != 0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
Expand All @@ -814,9 +817,10 @@ func (domain *Domain) generateToRawInfoMethodForType(code *printer.Code, typeNam
case "bool":
propertyName := propertyModel.Name
if !propertyModel.Repeated {
code.Print("if m.%s != false {", propertyModel.FieldName())
code.PrintIf(isRequired, "// always include this required field.")
code.PrintIf(!isRequired, "if m.%s != false {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
code.PrintIf(!isRequired, "}")
} else {
code.Print("if len(m.%s) != 0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
Expand All @@ -825,9 +829,10 @@ func (domain *Domain) generateToRawInfoMethodForType(code *printer.Code, typeNam
case "int":
propertyName := propertyModel.Name
if !propertyModel.Repeated {
code.Print("if m.%s != 0 {", propertyModel.FieldName())
code.PrintIf(isRequired, "// always include this required field.")
code.PrintIf(!isRequired, "if m.%s != 0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
code.PrintIf(!isRequired, "}")
} else {
code.Print("if len(m.%s) != 0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
Expand All @@ -836,9 +841,10 @@ func (domain *Domain) generateToRawInfoMethodForType(code *printer.Code, typeNam
case "float":
propertyName := propertyModel.Name
if !propertyModel.Repeated {
code.Print("if m.%s != 0.0 {", propertyModel.FieldName())
code.PrintIf(isRequired, "// always include this required field.")
code.PrintIf(!isRequired, "if m.%s != 0.0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
code.PrintIf(!isRequired, "}")
} else {
code.Print("if len(m.%s) != 0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
Expand All @@ -849,7 +855,8 @@ func (domain *Domain) generateToRawInfoMethodForType(code *printer.Code, typeNam
if propertyName == "value" {
code.Print("// %+v", propertyModel)
} else if !propertyModel.Repeated {
code.Print("if m.%s != nil {", propertyModel.FieldName())
code.PrintIf(isRequired, "// always include this required field.")
code.PrintIf(!isRequired, "if m.%s != nil {", propertyModel.FieldName())
if propertyModel.Type == "TypeItem" {
code.Print("if len(m.Type.Value) == 1 {")
code.Print("info = append(info, yaml.MapItem{Key:\"type\", Value:m.Type.Value[0]})")
Expand All @@ -870,7 +877,7 @@ func (domain *Domain) generateToRawInfoMethodForType(code *printer.Code, typeNam
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s.ToRawInfo()})",
propertyName, propertyModel.FieldName())
}
code.Print("}")
code.PrintIf(!isRequired, "}")
code.Print("// %+v", propertyModel)
} else if propertyModel.MapType == "string" {
code.Print("// %+v", propertyModel)
Expand Down
9 changes: 9 additions & 0 deletions generate-gnostic/types.go
Expand Up @@ -130,3 +130,12 @@ func NewTypeModel() *TypeModel {
typeModel.Properties = make([]*TypeProperty, 0)
return typeModel
}

func (typeModel *TypeModel) IsRequired(propertyName string) bool {
for _, requiredName := range typeModel.Required {
if requiredName == propertyName {
return true
}
}
return false
}
31 changes: 23 additions & 8 deletions gnostic_test.go
Expand Up @@ -10,17 +10,18 @@ import (
)

func testCompiler(t *testing.T, inputFile string, referenceFile string, expectErrors bool) {
textFile := strings.Replace(filepath.Base(inputFile), filepath.Ext(inputFile), ".text", 1)
outputFormat := filepath.Ext(referenceFile)[1:]
outputFile := strings.Replace(filepath.Base(inputFile), filepath.Ext(inputFile), "."+outputFormat, 1)
errorsFile := strings.Replace(filepath.Base(inputFile), filepath.Ext(inputFile), ".errors", 1)
// remove any preexisting output files
os.Remove(textFile)
os.Remove(outputFile)
os.Remove(errorsFile)
// run the compiler
var err error
var cmd = exec.Command(
"gnostic",
inputFile,
"--text-out=.",
"--"+outputFormat+"-out=.",
"--errors-out=.",
"--resolve-refs")
//t.Log(cmd.Args)
Expand All @@ -30,19 +31,19 @@ func testCompiler(t *testing.T, inputFile string, referenceFile string, expectEr
t.FailNow()
}
// verify the output against a reference
var outputFile string
var testFile string
if expectErrors {
outputFile = errorsFile
testFile = errorsFile
} else {
outputFile = textFile
testFile = outputFile
}
err = exec.Command("diff", outputFile, referenceFile).Run()
err = exec.Command("diff", testFile, referenceFile).Run()
if err != nil {
t.Logf("Diff failed: %+v", err)
t.FailNow()
} else {
// if the test succeeded, clean up
os.Remove(textFile)
os.Remove(outputFile)
os.Remove(errorsFile)
}
}
Expand Down Expand Up @@ -451,3 +452,17 @@ func TestPetstoreJSON_30(t *testing.T) {
"examples/v3.0/json/petstore.json",
"test/v3.0/petstore.text")
}

// Test that empty required fields are exported.

func TestEmptyRequiredFields_v2(t *testing.T) {
testNormal(t,
"examples/v2.0/yaml/empty-v2.yaml",
"test/v2.0/json/empty-v2.json")
}

func TestEmptyRequiredFields_v3(t *testing.T) {
testNormal(t,
"examples/v3.0/yaml/empty-v3.yaml",
"test/v3.0/json/empty-v3.json")
}
14 changes: 14 additions & 0 deletions printer/code.go
Expand Up @@ -38,6 +38,20 @@ func (c *Code) Print(args ...interface{}) {
c.text += "\n"
}

// PrintIf adds a line of code using the current indentation if a condition is true. Accepts printf-style format strings and arguments.
func (c *Code) PrintIf(condition bool, args ...interface{}) {
if !condition {
return
}
if len(args) > 0 {
for i := 0; i < c.indent; i++ {
c.text += indentation
}
c.text += fmt.Sprintf(args[0].(string), args[1:]...)
}
c.text += "\n"
}

// String returns the accumulated code as a string.
func (c *Code) String() string {
return c.text
Expand Down
28 changes: 15 additions & 13 deletions surface/model_openapiv2.go
Expand Up @@ -58,19 +58,21 @@ func (b *OpenAPI2Builder) build(document *openapiv2.Document) (err error) {
}
}
// Collect service method descriptions from Paths section.
for _, pair := range document.Paths.Path {
v := pair.Value
if v.Get != nil {
b.buildMethodFromOperation(v.Get, "GET", pair.Name)
}
if v.Post != nil {
b.buildMethodFromOperation(v.Post, "POST", pair.Name)
}
if v.Put != nil {
b.buildMethodFromOperation(v.Put, "PUT", pair.Name)
}
if v.Delete != nil {
b.buildMethodFromOperation(v.Delete, "DELETE", pair.Name)
if document.Paths != nil {
for _, pair := range document.Paths.Path {
v := pair.Value
if v.Get != nil {
b.buildMethodFromOperation(v.Get, "GET", pair.Name)
}
if v.Post != nil {
b.buildMethodFromOperation(v.Post, "POST", pair.Name)
}
if v.Put != nil {
b.buildMethodFromOperation(v.Put, "PUT", pair.Name)
}
if v.Delete != nil {
b.buildMethodFromOperation(v.Delete, "DELETE", pair.Name)
}
}
}
return err
Expand Down
6 changes: 4 additions & 2 deletions surface/model_openapiv3.go
Expand Up @@ -64,8 +64,10 @@ func (b *OpenAPI3Builder) build(document *openapiv3.Document) (err error) {
}
}
// Collect service method descriptions from each PathItem.
for _, pair := range document.Paths.Path {
b.buildMethodFromPathItem(pair.Name, pair.Value)
if document.Paths != nil {
for _, pair := range document.Paths.Path {
b.buildMethodFromPathItem(pair.Name, pair.Value)
}
}
return err
}
Expand Down
9 changes: 9 additions & 0 deletions test/v2.0/json/empty-v2.json
@@ -0,0 +1,9 @@
{
"swagger": "2.0",
"info": {
"title": "",
"version": ""
},
"paths": {
}
}
9 changes: 9 additions & 0 deletions test/v3.0/json/empty-v3.json
@@ -0,0 +1,9 @@
{
"openapi": "3.0",
"info": {
"title": "",
"version": ""
},
"paths": {
}
}

0 comments on commit bcaf8dd

Please sign in to comment.