Vendor Update (#14496)

* update code.gitea.io/sdk/gitea v0.13.1 -> v0.13.2

* update github.com/go-swagger/go-swagger v0.25.0 -> v0.26.0

* update github.com/google/uuid v1.1.2 -> v1.2.0

* update github.com/klauspost/compress v1.11.3 -> v1.11.7

* update github.com/lib/pq 083382b7e6fc -> v1.9.0

* update github.com/markbates/goth v1.65.0 -> v1.66.1

* update github.com/mattn/go-sqlite3 v1.14.4 -> v1.14.6

* update github.com/mgechev/revive 246eac737dc7 -> v1.0.3

* update github.com/minio/minio-go/v7 v7.0.6 -> v7.0.7

* update github.com/niklasfasching/go-org v1.3.2 -> v1.4.0

* update github.com/olivere/elastic/v7 v7.0.21 -> v7.0.22

* update github.com/pquerna/otp v1.2.0 -> v1.3.0

* update github.com/xanzy/go-gitlab v0.39.0 -> v0.42.0

* update github.com/yuin/goldmark v1.2.1 -> v1.3.1
This commit is contained in:
6543 2021-01-28 17:56:38 +01:00 committed by GitHub
parent e45bf12a34
commit d1353e1f7c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
403 changed files with 29737 additions and 14357 deletions

View file

@ -43,7 +43,7 @@ func (c *DiffCommand) Execute(_ []string) error {
output io.WriteCloser
err error
)
if c.Destination != "" {
if c.Destination != "stdout" {
output, err = os.OpenFile(c.Destination, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
if err != nil {
return fmt.Errorf("%s: %w", c.Destination, err)

View file

@ -0,0 +1,266 @@
package diff
import (
"fmt"
"strings"
"github.com/go-openapi/spec"
)
// CompareEnums returns added, deleted enum values
func CompareEnums(left, right []interface{}) []TypeDiff {
diffs := []TypeDiff{}
leftStrs := []string{}
rightStrs := []string{}
for _, eachLeft := range left {
leftStrs = append(leftStrs, fmt.Sprintf("%v", eachLeft))
}
for _, eachRight := range right {
rightStrs = append(rightStrs, fmt.Sprintf("%v", eachRight))
}
added, deleted, _ := fromStringArray(leftStrs).DiffsTo(rightStrs)
if len(added) > 0 {
typeChange := strings.Join(added, ",")
diffs = append(diffs, TypeDiff{Change: AddedEnumValue, Description: typeChange})
}
if len(deleted) > 0 {
typeChange := strings.Join(deleted, ",")
diffs = append(diffs, TypeDiff{Change: DeletedEnumValue, Description: typeChange})
}
return diffs
}
// CompareProperties recursive property comparison
func CompareProperties(location DifferenceLocation, schema1 *spec.Schema, schema2 *spec.Schema, getRefFn1 SchemaFromRefFn, getRefFn2 SchemaFromRefFn, cmp CompareSchemaFn) []SpecDifference {
propDiffs := []SpecDifference{}
if schema1.Properties == nil && schema2.Properties == nil {
return propDiffs
}
schema1Props := propertiesFor(schema1, getRefFn1)
schema2Props := propertiesFor(schema2, getRefFn2)
// find deleted and changed properties
for eachProp1Name, eachProp1 := range schema1Props {
eachProp1 := eachProp1
childLoc := addChildDiffNode(location, eachProp1Name, eachProp1.Schema)
if eachProp2, ok := schema2Props[eachProp1Name]; ok {
diffs := CheckToFromRequired(eachProp1.Required, eachProp2.Required)
if len(diffs) > 0 {
for _, diff := range diffs {
propDiffs = append(propDiffs, SpecDifference{DifferenceLocation: childLoc, Code: diff.Change})
}
}
cmp(childLoc, eachProp1.Schema, eachProp2.Schema)
} else {
propDiffs = append(propDiffs, SpecDifference{DifferenceLocation: childLoc, Code: DeletedProperty})
}
}
// find added properties
for eachProp2Name, eachProp2 := range schema2.Properties {
eachProp2 := eachProp2
if _, ok := schema1.Properties[eachProp2Name]; !ok {
childLoc := addChildDiffNode(location, eachProp2Name, &eachProp2)
propDiffs = append(propDiffs, SpecDifference{DifferenceLocation: childLoc, Code: AddedProperty})
}
}
return propDiffs
}
// CompareFloatValues compares a float data item
func CompareFloatValues(fieldName string, val1 *float64, val2 *float64, ifGreaterCode SpecChangeCode, ifLessCode SpecChangeCode) []TypeDiff {
diffs := []TypeDiff{}
if val1 != nil && val2 != nil {
if *val2 > *val1 {
diffs = append(diffs, TypeDiff{Change: ifGreaterCode, Description: fmt.Sprintf("%s %f->%f", fieldName, *val1, *val2)})
} else if *val2 < *val1 {
diffs = append(diffs, TypeDiff{Change: ifLessCode, Description: fmt.Sprintf("%s %f->%f", fieldName, *val1, *val2)})
}
} else {
if val1 != val2 {
if val1 != nil {
diffs = append(diffs, TypeDiff{Change: DeletedConstraint, Description: fmt.Sprintf("%s(%f)", fieldName, *val1)})
} else {
diffs = append(diffs, TypeDiff{Change: AddedConstraint, Description: fmt.Sprintf("%s(%f)", fieldName, *val2)})
}
}
}
return diffs
}
// CompareIntValues compares to int data items
func CompareIntValues(fieldName string, val1 *int64, val2 *int64, ifGreaterCode SpecChangeCode, ifLessCode SpecChangeCode) []TypeDiff {
diffs := []TypeDiff{}
if val1 != nil && val2 != nil {
if *val2 > *val1 {
diffs = append(diffs, TypeDiff{Change: ifGreaterCode, Description: fmt.Sprintf("%s %d->%d", fieldName, *val1, *val2)})
} else if *val2 < *val1 {
diffs = append(diffs, TypeDiff{Change: ifLessCode, Description: fmt.Sprintf("%s %d->%d", fieldName, *val1, *val2)})
}
} else {
if val1 != val2 {
if val1 != nil {
diffs = append(diffs, TypeDiff{Change: DeletedConstraint, Description: fmt.Sprintf("%s(%d)", fieldName, *val1)})
} else {
diffs = append(diffs, TypeDiff{Change: AddedConstraint, Description: fmt.Sprintf("%s(%d)", fieldName, *val2)})
}
}
}
return diffs
}
// CheckToFromPrimitiveType check for diff to or from a primitive
func CheckToFromPrimitiveType(diffs []TypeDiff, type1, type2 interface{}) []TypeDiff {
type1IsPrimitive := isPrimitive(type1)
type2IsPrimitive := isPrimitive(type2)
// Primitive to Obj or Obj to Primitive
if type1IsPrimitive != type2IsPrimitive {
typeStr1, isarray1 := getSchemaType(type1)
typeStr2, isarray2 := getSchemaType(type2)
return addTypeDiff(diffs, TypeDiff{Change: ChangedType, FromType: formatTypeString(typeStr1, isarray1), ToType: formatTypeString(typeStr2, isarray2)})
}
return diffs
}
// CheckRefChange has the property ref changed
func CheckRefChange(diffs []TypeDiff, type1, type2 interface{}) (diffReturn []TypeDiff) {
diffReturn = diffs
if isRefType(type1) && isRefType(type2) {
// both refs but to different objects (TODO detect renamed object)
ref1 := definitionFromRef(getRef(type1))
ref2 := definitionFromRef(getRef(type2))
if ref1 != ref2 {
diffReturn = addTypeDiff(diffReturn, TypeDiff{Change: RefTargetChanged, FromType: getSchemaTypeStr(type1), ToType: getSchemaTypeStr(type2)})
}
} else if isRefType(type1) != isRefType(type2) {
diffReturn = addTypeDiff(diffReturn, TypeDiff{Change: ChangedType, FromType: getSchemaTypeStr(type1), ToType: getSchemaTypeStr(type2)})
}
return
}
// checkNumericTypeChanges checks for changes to or from a numeric type
func checkNumericTypeChanges(diffs []TypeDiff, type1, type2 *spec.SchemaProps) []TypeDiff {
// Number
_, type1IsNumeric := numberWideness[type1.Type[0]]
_, type2IsNumeric := numberWideness[type2.Type[0]]
if type1IsNumeric && type2IsNumeric {
foundDiff := false
if type1.ExclusiveMaximum && !type2.ExclusiveMaximum {
diffs = addTypeDiff(diffs, TypeDiff{Change: WidenedType, Description: fmt.Sprintf("Exclusive Maximum Removed:%v->%v", type1.ExclusiveMaximum, type2.ExclusiveMaximum)})
foundDiff = true
}
if !type1.ExclusiveMaximum && type2.ExclusiveMaximum {
diffs = addTypeDiff(diffs, TypeDiff{Change: NarrowedType, Description: fmt.Sprintf("Exclusive Maximum Added:%v->%v", type1.ExclusiveMaximum, type2.ExclusiveMaximum)})
foundDiff = true
}
if type1.ExclusiveMinimum && !type2.ExclusiveMinimum {
diffs = addTypeDiff(diffs, TypeDiff{Change: WidenedType, Description: fmt.Sprintf("Exclusive Minimum Removed:%v->%v", type1.ExclusiveMaximum, type2.ExclusiveMaximum)})
foundDiff = true
}
if !type1.ExclusiveMinimum && type2.ExclusiveMinimum {
diffs = addTypeDiff(diffs, TypeDiff{Change: NarrowedType, Description: fmt.Sprintf("Exclusive Minimum Added:%v->%v", type1.ExclusiveMinimum, type2.ExclusiveMinimum)})
foundDiff = true
}
if !foundDiff {
maxDiffs := CompareFloatValues("Maximum", type1.Maximum, type2.Maximum, WidenedType, NarrowedType)
diffs = append(diffs, maxDiffs...)
minDiffs := CompareFloatValues("Minimum", type1.Minimum, type2.Minimum, NarrowedType, WidenedType)
diffs = append(diffs, minDiffs...)
}
}
return diffs
}
// CheckStringTypeChanges checks for changes to or from a string type
func CheckStringTypeChanges(diffs []TypeDiff, type1, type2 *spec.SchemaProps) []TypeDiff {
// string changes
if type1.Type[0] == StringType &&
type2.Type[0] == StringType {
minLengthDiffs := CompareIntValues("MinLength", type1.MinLength, type2.MinLength, NarrowedType, WidenedType)
diffs = append(diffs, minLengthDiffs...)
maxLengthDiffs := CompareIntValues("MaxLength", type1.MinLength, type2.MinLength, WidenedType, NarrowedType)
diffs = append(diffs, maxLengthDiffs...)
if type1.Pattern != type2.Pattern {
diffs = addTypeDiff(diffs, TypeDiff{Change: ChangedType, Description: fmt.Sprintf("Pattern Changed:%s->%s", type1.Pattern, type2.Pattern)})
}
if type1.Type[0] == StringType {
if len(type1.Enum) > 0 {
enumDiffs := CompareEnums(type1.Enum, type2.Enum)
diffs = append(diffs, enumDiffs...)
}
}
}
return diffs
}
// CheckToFromRequired checks for changes to or from a required property
func CheckToFromRequired(required1, required2 bool) (diffs []TypeDiff) {
if required1 != required2 {
code := ChangedOptionalToRequired
if required1 {
code = ChangedRequiredToOptional
}
diffs = addTypeDiff(diffs, TypeDiff{Change: code})
}
return diffs
}
const objType = "object"
func getTypeHierarchyChange(type1, type2 string) TypeDiff {
fromType := type1
if fromType == "" {
fromType = objType
}
toType := type2
if toType == "" {
toType = objType
}
diffDescription := fmt.Sprintf("%s -> %s", fromType, toType)
if isStringType(type1) && !isStringType(type2) {
return TypeDiff{Change: NarrowedType, Description: diffDescription}
}
if !isStringType(type1) && isStringType(type2) {
return TypeDiff{Change: WidenedType, Description: diffDescription}
}
type1Wideness, type1IsNumeric := numberWideness[type1]
type2Wideness, type2IsNumeric := numberWideness[type2]
if type1IsNumeric && type2IsNumeric {
if type1Wideness == type2Wideness {
return TypeDiff{Change: ChangedToCompatibleType, Description: diffDescription}
}
if type1Wideness > type2Wideness {
return TypeDiff{Change: NarrowedType, Description: diffDescription}
}
if type1Wideness < type2Wideness {
return TypeDiff{Change: WidenedType, Description: diffDescription}
}
}
return TypeDiff{Change: ChangedType, Description: diffDescription}
}
func isRefType(item interface{}) bool {
switch s := item.(type) {
case spec.Refable:
return s.Ref.String() != ""
case *spec.Schema:
return s.Ref.String() != ""
case *spec.SchemaProps:
return s.Ref.String() != ""
case *spec.SimpleSchema:
return false
default:
return false
}
}

View file

@ -32,29 +32,33 @@ func init() {
ChangedTag: NonBreaking,
AddedTag: NonBreaking,
DeletedTag: NonBreaking,
DeletedConstraint: Breaking,
AddedConstraint: NonBreaking,
},
ForRequest: map[SpecChangeCode]Compatibility{
AddedRequiredProperty: Breaking,
DeletedProperty: Breaking,
AddedProperty: Breaking,
AddedOptionalParam: NonBreaking,
AddedRequiredParam: Breaking,
DeletedOptionalParam: NonBreaking,
DeletedRequiredParam: NonBreaking,
WidenedType: NonBreaking,
NarrowedType: Breaking,
ChangedType: Breaking,
ChangedToCompatibleType: NonBreaking,
ChangedOptionalToRequiredParam: Breaking,
ChangedRequiredToOptionalParam: NonBreaking,
AddedEnumValue: NonBreaking,
DeletedEnumValue: Breaking,
ChangedDescripton: NonBreaking,
AddedDescripton: NonBreaking,
DeletedDescripton: NonBreaking,
ChangedTag: NonBreaking,
AddedTag: NonBreaking,
DeletedTag: NonBreaking,
AddedRequiredProperty: Breaking,
DeletedProperty: Breaking,
AddedProperty: Breaking,
AddedOptionalParam: NonBreaking,
AddedRequiredParam: Breaking,
DeletedOptionalParam: NonBreaking,
DeletedRequiredParam: NonBreaking,
WidenedType: NonBreaking,
NarrowedType: Breaking,
ChangedType: Breaking,
ChangedToCompatibleType: NonBreaking,
ChangedOptionalToRequired: Breaking,
ChangedRequiredToOptional: NonBreaking,
AddedEnumValue: NonBreaking,
DeletedEnumValue: Breaking,
ChangedDescripton: NonBreaking,
AddedDescripton: NonBreaking,
DeletedDescripton: NonBreaking,
ChangedTag: NonBreaking,
AddedTag: NonBreaking,
DeletedTag: NonBreaking,
DeletedConstraint: NonBreaking,
AddedConstraint: Breaking,
},
ForChange: map[SpecChangeCode]Compatibility{
NoChangeDetected: NonBreaking,
@ -63,8 +67,8 @@ func init() {
DeletedDeprecatedEndpoint: NonBreaking,
AddedConsumesFormat: NonBreaking,
DeletedConsumesFormat: Breaking,
AddedProducesFormat: Breaking,
DeletedProducesFormat: NonBreaking,
AddedProducesFormat: NonBreaking,
DeletedProducesFormat: Breaking,
AddedSchemes: NonBreaking,
DeletedSchemes: Breaking,
ChangedHostURL: Breaking,
@ -75,6 +79,10 @@ func init() {
ChangedTag: NonBreaking,
AddedTag: NonBreaking,
DeletedTag: NonBreaking,
RefTargetChanged: Breaking,
RefTargetRenamed: NonBreaking,
AddedDefinition: NonBreaking,
DeletedDefinition: NonBreaking,
},
}
}

View file

@ -43,8 +43,6 @@ const (
AddedRequiredParam
// DeletedRequiredParam - A required parameter has been deleted in the new spec
DeletedRequiredParam
// ChangedRequiredToOptional - A required parameter has been made optional in the new spec
ChangedRequiredToOptional
// AddedEndpoint - An endpoint has been added in the new spec
AddedEndpoint
// WidenedType - An type has been changed to a more permissive type eg int->string
@ -61,10 +59,10 @@ const (
DeletedEnumValue
// AddedOptionalParam - A new optional parameter has been added to the new spec
AddedOptionalParam
// ChangedOptionalToRequiredParam - An optional parameter is now required in the new spec
ChangedOptionalToRequiredParam
// ChangedRequiredToOptionalParam - An required parameter is now optional in the new spec
ChangedRequiredToOptionalParam
// ChangedOptionalToRequired - An optional parameter is now required in the new spec
ChangedOptionalToRequired
// ChangedRequiredToOptional - An required parameter is now optional in the new spec
ChangedRequiredToOptional
// AddedResponse An endpoint has new response code in the new spec
AddedResponse
// AddedConsumesFormat - a new consumes format (json/xml/yaml etc) has been added in the new spec
@ -89,88 +87,112 @@ const (
ChangedResponseHeader
// DeletedResponseHeader Added a header Item
DeletedResponseHeader
// RefTargetChanged Changed a ref to point to a different object
RefTargetChanged
// RefTargetRenamed Renamed a ref to point to the same object
RefTargetRenamed
// DeletedConstraint Deleted a schema constraint
DeletedConstraint
// AddedConstraint Added a schema constraint
AddedConstraint
// DeletedDefinition removed one of the definitions
DeletedDefinition
// AddedDefinition removed one of the definitions
AddedDefinition
)
var toLongStringSpecChangeCode = map[SpecChangeCode]string{
NoChangeDetected: "No Change detected",
AddedEndpoint: "Added endpoint",
DeletedEndpoint: "Deleted endpoint",
DeletedDeprecatedEndpoint: "Deleted a deprecated endpoint",
AddedRequiredProperty: "Added required property",
DeletedProperty: "Deleted property",
ChangedDescripton: "Changed a description",
AddedDescripton: "Added a description",
DeletedDescripton: "Deleted a description",
ChangedTag: "Changed a tag",
AddedTag: "Added a tag",
DeletedTag: "Deleted a tag",
AddedProperty: "Added property",
AddedOptionalParam: "Added optional param",
AddedRequiredParam: "Added required param",
DeletedOptionalParam: "Deleted optional param",
DeletedRequiredParam: "Deleted required param",
DeletedResponse: "Deleted response",
AddedResponse: "Added response",
WidenedType: "Widened type",
NarrowedType: "Narrowed type",
ChangedType: "Changed type",
ChangedToCompatibleType: "Changed type to equivalent type",
ChangedOptionalToRequiredParam: "Changed optional param to required",
ChangedRequiredToOptionalParam: "Changed required param to optional",
AddedEnumValue: "Added possible enumeration(s)",
DeletedEnumValue: "Deleted possible enumeration(s)",
AddedConsumesFormat: "Added a consumes format",
DeletedConsumesFormat: "Deleted a consumes format",
AddedProducesFormat: "Added produces format",
DeletedProducesFormat: "Deleted produces format",
AddedSchemes: "Added schemes",
DeletedSchemes: "Deleted schemes",
ChangedHostURL: "Changed host URL",
ChangedBasePath: "Changed base path",
AddedResponseHeader: "Added response header",
ChangedResponseHeader: "Changed response header",
DeletedResponseHeader: "Deleted response header",
NoChangeDetected: "No Change detected",
AddedEndpoint: "Added endpoint",
DeletedEndpoint: "Deleted endpoint",
DeletedDeprecatedEndpoint: "Deleted a deprecated endpoint",
AddedRequiredProperty: "Added required property",
DeletedProperty: "Deleted property",
ChangedDescripton: "Changed a description",
AddedDescripton: "Added a description",
DeletedDescripton: "Deleted a description",
ChangedTag: "Changed a tag",
AddedTag: "Added a tag",
DeletedTag: "Deleted a tag",
AddedProperty: "Added property",
AddedOptionalParam: "Added optional param",
AddedRequiredParam: "Added required param",
DeletedOptionalParam: "Deleted optional param",
DeletedRequiredParam: "Deleted required param",
DeletedResponse: "Deleted response",
AddedResponse: "Added response",
WidenedType: "Widened type",
NarrowedType: "Narrowed type",
ChangedType: "Changed type",
ChangedToCompatibleType: "Changed type to equivalent type",
ChangedOptionalToRequired: "Changed optional param to required",
ChangedRequiredToOptional: "Changed required param to optional",
AddedEnumValue: "Added possible enumeration(s)",
DeletedEnumValue: "Deleted possible enumeration(s)",
AddedConsumesFormat: "Added a consumes format",
DeletedConsumesFormat: "Deleted a consumes format",
AddedProducesFormat: "Added produces format",
DeletedProducesFormat: "Deleted produces format",
AddedSchemes: "Added schemes",
DeletedSchemes: "Deleted schemes",
ChangedHostURL: "Changed host URL",
ChangedBasePath: "Changed base path",
AddedResponseHeader: "Added response header",
ChangedResponseHeader: "Changed response header",
DeletedResponseHeader: "Deleted response header",
RefTargetChanged: "Changed ref to different object",
RefTargetRenamed: "Changed ref to renamed object",
DeletedConstraint: "Deleted a schema constraint",
AddedConstraint: "Added a schema constraint",
DeletedDefinition: "Deleted a schema definition",
AddedDefinition: "Added a schema definition",
}
var toStringSpecChangeCode = map[SpecChangeCode]string{
AddedEndpoint: "AddedEndpoint",
NoChangeDetected: "NoChangeDetected",
DeletedEndpoint: "DeletedEndpoint",
DeletedDeprecatedEndpoint: "DeletedDeprecatedEndpoint",
AddedRequiredProperty: "AddedRequiredProperty",
DeletedProperty: "DeletedProperty",
AddedProperty: "AddedProperty",
ChangedDescripton: "ChangedDescription",
AddedDescripton: "AddedDescription",
DeletedDescripton: "DeletedDescription",
ChangedTag: "ChangedTag",
AddedTag: "AddedTag",
DeletedTag: "DeletedTag",
AddedOptionalParam: "AddedOptionalParam",
AddedRequiredParam: "AddedRequiredParam",
DeletedOptionalParam: "DeletedRequiredParam",
DeletedRequiredParam: "Deleted required param",
DeletedResponse: "DeletedResponse",
AddedResponse: "AddedResponse",
WidenedType: "WidenedType",
NarrowedType: "NarrowedType",
ChangedType: "ChangedType",
ChangedToCompatibleType: "ChangedToCompatibleType",
ChangedOptionalToRequiredParam: "ChangedOptionalToRequiredParam",
ChangedRequiredToOptionalParam: "ChangedRequiredToOptionalParam",
AddedEnumValue: "AddedEnumValue",
DeletedEnumValue: "DeletedEnumValue",
AddedConsumesFormat: "AddedConsumesFormat",
DeletedConsumesFormat: "DeletedConsumesFormat",
AddedProducesFormat: "AddedProducesFormat",
DeletedProducesFormat: "DeletedProducesFormat",
AddedSchemes: "AddedSchemes",
DeletedSchemes: "DeletedSchemes",
ChangedHostURL: "ChangedHostURL",
ChangedBasePath: "ChangedBasePath",
AddedResponseHeader: "AddedResponseHeader",
ChangedResponseHeader: "ChangedResponseHeader",
DeletedResponseHeader: "DeletedResponseHeader",
AddedEndpoint: "AddedEndpoint",
NoChangeDetected: "NoChangeDetected",
DeletedEndpoint: "DeletedEndpoint",
DeletedDeprecatedEndpoint: "DeletedDeprecatedEndpoint",
AddedRequiredProperty: "AddedRequiredProperty",
DeletedProperty: "DeletedProperty",
AddedProperty: "AddedProperty",
ChangedDescripton: "ChangedDescription",
AddedDescripton: "AddedDescription",
DeletedDescripton: "DeletedDescription",
ChangedTag: "ChangedTag",
AddedTag: "AddedTag",
DeletedTag: "DeletedTag",
AddedOptionalParam: "AddedOptionalParam",
AddedRequiredParam: "AddedRequiredParam",
DeletedOptionalParam: "DeletedRequiredParam",
DeletedRequiredParam: "Deleted required param",
DeletedResponse: "DeletedResponse",
AddedResponse: "AddedResponse",
WidenedType: "WidenedType",
NarrowedType: "NarrowedType",
ChangedType: "ChangedType",
ChangedToCompatibleType: "ChangedToCompatibleType",
ChangedOptionalToRequired: "ChangedOptionalToRequiredParam",
ChangedRequiredToOptional: "ChangedRequiredToOptionalParam",
AddedEnumValue: "AddedEnumValue",
DeletedEnumValue: "DeletedEnumValue",
AddedConsumesFormat: "AddedConsumesFormat",
DeletedConsumesFormat: "DeletedConsumesFormat",
AddedProducesFormat: "AddedProducesFormat",
DeletedProducesFormat: "DeletedProducesFormat",
AddedSchemes: "AddedSchemes",
DeletedSchemes: "DeletedSchemes",
ChangedHostURL: "ChangedHostURL",
ChangedBasePath: "ChangedBasePath",
AddedResponseHeader: "AddedResponseHeader",
ChangedResponseHeader: "ChangedResponseHeader",
DeletedResponseHeader: "DeletedResponseHeader",
RefTargetChanged: "RefTargetChanged",
RefTargetRenamed: "RefTargetRenamed",
DeletedConstraint: "DeletedConstraint",
AddedConstraint: "AddedConstraint",
DeletedDefinition: "DeletedDefinition",
AddedDefinition: "AddedDefinition",
}
var toIDSpecChangeCode = map[string]SpecChangeCode{}
@ -273,5 +295,4 @@ func init() {
for key, val := range toStringCompatibility {
toIDCompatibility[val] = key
}
}

View file

@ -1,5 +1,11 @@
package diff
import (
"fmt"
"github.com/go-openapi/spec"
)
// Node is the position od a diff in a spec
type Node struct {
Field string `json:"name,omitempty"`
@ -12,14 +18,12 @@ type Node struct {
func (n *Node) String() string {
name := n.Field
if n.IsArray {
name = "array[" + n.TypeName + "]"
name = fmt.Sprintf("%s<array[%s]>", name, n.TypeName)
} else if len(n.TypeName) > 0 {
name = fmt.Sprintf("%s<%s>", name, n.TypeName)
}
if n.ChildNode != nil {
return name + "." + n.ChildNode.String()
}
if len(n.TypeName) > 0 {
return name + " : " + n.TypeName
return fmt.Sprintf("%s.%s", name, n.ChildNode.String())
}
return name
}
@ -36,12 +40,43 @@ func (n *Node) AddLeafNode(toAdd *Node) *Node {
return n
}
//Copy deep copy of this node and children
// Copy deep copy of this node and children
func (n Node) Copy() *Node {
newNode := n
if newNode.ChildNode != nil {
n.ChildNode = newNode.ChildNode.Copy()
newChild := n.ChildNode
if newChild != nil {
newChild = newChild.Copy()
}
newNode := Node{
Field: n.Field,
TypeName: n.TypeName,
IsArray: n.IsArray,
ChildNode: newChild,
}
return &newNode
}
func getSchemaDiffNode(name string, schema interface{}) *Node {
node := Node{
Field: name,
}
if schema != nil {
switch s := schema.(type) {
case spec.Refable:
node.TypeName, node.IsArray = getSchemaType(s)
case *spec.Schema:
node.TypeName, node.IsArray = getSchemaType(s.SchemaProps)
case spec.SimpleSchema:
node.TypeName, node.IsArray = getSchemaType(s)
case *spec.SimpleSchema:
node.TypeName, node.IsArray = getSchemaType(s)
case *spec.SchemaProps:
node.TypeName, node.IsArray = getSchemaType(s)
case spec.SchemaProps:
node.TypeName, node.IsArray = getSchemaType(&s)
default:
node.TypeName = fmt.Sprintf("Unknown type %v", schema)
}
}
return &node
}

View file

@ -5,8 +5,6 @@ import (
"encoding/json"
"fmt"
"io"
"net/url"
"strings"
"github.com/go-openapi/spec"
)
@ -14,6 +12,9 @@ import (
// ArrayType const for array
var ArrayType = "array"
// ObjectType const for object
var ObjectType = "object"
// Compare returns the result of analysing breaking and non breaking changes
// between to Swagger specs
func Compare(spec1, spec2 *spec.Swagger) (diffs SpecDifferences, err error) {
@ -72,60 +73,6 @@ func getNameOnlyDiffNode(forLocation string) *Node {
return &node
}
func getSimpleSchemaDiffNode(name string, schema *spec.SimpleSchema) *Node {
node := Node{
Field: name,
}
if schema != nil {
node.TypeName, node.IsArray = getSimpleSchemaType(schema)
}
return &node
}
func getSchemaDiffNode(name string, schema *spec.Schema) *Node {
node := Node{
Field: name,
}
if schema != nil {
node.TypeName, node.IsArray = getSchemaType(&schema.SchemaProps)
}
return &node
}
func definitonFromURL(url *url.URL) string {
if url == nil {
return ""
}
fragmentParts := strings.Split(url.Fragment, "/")
numParts := len(fragmentParts)
if numParts == 0 {
return ""
}
return fragmentParts[numParts-1]
}
func getSimpleSchemaType(schema *spec.SimpleSchema) (typeName string, isArray bool) {
typeName = schema.Type
if typeName == ArrayType {
typeName, _ = getSimpleSchemaType(&schema.Items.SimpleSchema)
return typeName, true
}
return typeName, false
}
func getSchemaType(schema *spec.SchemaProps) (typeName string, isArray bool) {
refStr := definitonFromURL(schema.Ref.GetURL())
if len(refStr) > 0 {
return refStr, false
}
typeName = schema.Type[0]
if typeName == ArrayType {
typeName, _ = getSchemaType(&schema.Items.Schema.SchemaProps)
return typeName, true
}
return typeName, false
}
func primitiveTypeString(typeName, typeFormat string) string {
if typeFormat != "" {
return fmt.Sprintf("%s.%s", typeName, typeFormat)

View file

@ -0,0 +1,126 @@
package diff
import (
"fmt"
"strings"
"github.com/go-openapi/spec"
)
func getTypeFromSchema(schema *spec.Schema) (typeName string, isArray bool) {
refStr := definitionFromRef(schema.Ref)
if len(refStr) > 0 {
return refStr, false
}
typeName = schema.Type[0]
if typeName == ArrayType {
typeName, _ = getSchemaType(&schema.Items.Schema.SchemaProps)
return typeName, true
}
return typeName, false
}
func getTypeFromSimpleSchema(schema *spec.SimpleSchema) (typeName string, isArray bool) {
typeName = schema.Type
format := schema.Format
if len(format) > 0 {
typeName = fmt.Sprintf("%s.%s", typeName, format)
}
if typeName == ArrayType {
typeName, _ = getSchemaType(&schema.Items.SimpleSchema)
return typeName, true
}
return typeName, false
}
func getTypeFromSchemaProps(schema *spec.SchemaProps) (typeName string, isArray bool) {
refStr := definitionFromRef(schema.Ref)
if len(refStr) > 0 {
return refStr, false
}
if len(schema.Type) > 0 {
typeName = schema.Type[0]
format := schema.Format
if len(format) > 0 {
typeName = fmt.Sprintf("%s.%s", typeName, format)
}
if typeName == ArrayType {
typeName, _ = getSchemaType(&schema.Items.Schema.SchemaProps)
return typeName, true
}
}
return typeName, false
}
func getSchemaTypeStr(item interface{}) string {
typeStr, isArray := getSchemaType(item)
return formatTypeString(typeStr, isArray)
}
func getSchemaType(item interface{}) (typeName string, isArray bool) {
switch s := item.(type) {
case *spec.Schema:
typeName, isArray = getTypeFromSchema(s)
case *spec.SchemaProps:
typeName, isArray = getTypeFromSchemaProps(s)
case spec.SchemaProps:
typeName, isArray = getTypeFromSchemaProps(&s)
case spec.SimpleSchema:
typeName, isArray = getTypeFromSimpleSchema(&s)
case *spec.SimpleSchema:
typeName, isArray = getTypeFromSimpleSchema(s)
default:
typeName = "unknown"
}
return
}
func formatTypeString(typ string, isarray bool) string {
if isarray {
return fmt.Sprintf("<array[%s]>", typ)
}
return fmt.Sprintf("<%s>", typ)
}
func definitionFromRef(ref spec.Ref) string {
url := ref.GetURL()
if url == nil {
return ""
}
fragmentParts := strings.Split(url.Fragment, "/")
numParts := len(fragmentParts)
return fragmentParts[numParts-1]
}
func isArray(item interface{}) bool {
switch s := item.(type) {
case *spec.Schema:
return isArrayType(s.Type)
case *spec.SchemaProps:
return isArrayType(s.Type)
case *spec.SimpleSchema:
return isArrayType(spec.StringOrArray{s.Type})
default:
return false
}
}
func isPrimitive(item interface{}) bool {
switch s := item.(type) {
case *spec.Schema:
return isPrimitiveType(s.Type)
case *spec.SchemaProps:
return isPrimitiveType(s.Type)
case spec.StringOrArray:
return isPrimitiveType(s)
default:
return false
}
}

View file

@ -7,6 +7,7 @@ import (
"github.com/go-openapi/spec"
)
// StringType For identifying string types
const StringType = "string"
// URLMethodResponse encapsulates these three elements to act as a map key
@ -26,18 +27,19 @@ type URLMethods map[URLMethod]*PathItemOp
// SpecAnalyser contains all the differences for a Spec
type SpecAnalyser struct {
Diffs SpecDifferences
urlMethods1 URLMethods
urlMethods2 URLMethods
Definitions1 spec.Definitions
Definitions2 spec.Definitions
AlreadyComparedDefinitions map[string]bool
Diffs SpecDifferences
urlMethods1 URLMethods
urlMethods2 URLMethods
Definitions1 spec.Definitions
Definitions2 spec.Definitions
ReferencedDefinitions map[string]bool
}
// NewSpecAnalyser returns an empty SpecDiffs
func NewSpecAnalyser() *SpecAnalyser {
return &SpecAnalyser{
Diffs: SpecDifferences{},
Diffs: SpecDifferences{},
ReferencedDefinitions: map[string]bool{},
}
}
@ -50,9 +52,10 @@ func (sd *SpecAnalyser) Analyse(spec1, spec2 *spec.Swagger) error {
sd.analyseSpecMetadata(spec1, spec2)
sd.analyseEndpoints()
sd.analyseParams()
sd.analyseRequestParams()
sd.analyseEndpointData()
sd.analyseResponseParams()
sd.AnalyseDefinitions()
return nil
}
@ -94,7 +97,7 @@ func (sd *SpecAnalyser) analyseSpecMetadata(spec1, spec2 *spec.Swagger) {
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: schemesLocation, Code: DeletedSchemes, Compatibility: Breaking, DiffInfo: eachDeleted})
}
// // host should be able to change without any issues?
// host should be able to change without any issues?
sd.analyseMetaDataProperty(spec1.Info.Description, spec2.Info.Description, ChangedDescripton, NonBreaking)
// // host should be able to change without any issues?
@ -116,6 +119,32 @@ func (sd *SpecAnalyser) analyseEndpoints() {
sd.findAddedEndpoints()
}
// AnalyseDefinitions check for changes to defintion objects not referenced in any endpoint
func (sd *SpecAnalyser) AnalyseDefinitions() {
alreadyReferenced := map[string]bool{}
for k := range sd.ReferencedDefinitions {
alreadyReferenced[k] = true
}
location := DifferenceLocation{Node: &Node{Field: "Spec Definitions"}}
for name1, sch := range sd.Definitions1 {
schema1 := sch
if _, ok := alreadyReferenced[name1]; !ok {
childLocation := location.AddNode(&Node{Field: name1})
if schema2, ok := sd.Definitions2[name1]; ok {
sd.compareSchema(childLocation, &schema1, &schema2)
} else {
sd.addDiffs(childLocation, []TypeDiff{{Change: DeletedDefinition}})
}
}
}
for name2 := range sd.Definitions2 {
if _, ok := sd.Definitions1[name2]; !ok {
childLocation := location.AddNode(&Node{Field: name2})
sd.addDiffs(childLocation, []TypeDiff{{Change: AddedDefinition}})
}
}
}
func (sd *SpecAnalyser) analyseEndpointData() {
for URLMethod, op2 := range sd.urlMethods2 {
@ -124,20 +153,19 @@ func (sd *SpecAnalyser) analyseEndpointData() {
location := DifferenceLocation{URL: URLMethod.Path, Method: URLMethod.Method}
for _, eachAddedTag := range addedTags {
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: location, Code: AddedTag, DiffInfo: eachAddedTag})
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: location, Code: AddedTag, DiffInfo: fmt.Sprintf(`"%s"`, eachAddedTag)})
}
for _, eachDeletedTag := range deletedTags {
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: location, Code: DeletedTag, DiffInfo: eachDeletedTag})
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: location, Code: DeletedTag, DiffInfo: fmt.Sprintf(`"%s"`, eachDeletedTag)})
}
sd.compareDescripton(location, op1.Operation.Description, op2.Operation.Description)
}
}
}
func (sd *SpecAnalyser) analyseParams() {
func (sd *SpecAnalyser) analyseRequestParams() {
locations := []string{"query", "path", "body", "header"}
for _, paramLocation := range locations {
@ -153,7 +181,7 @@ func (sd *SpecAnalyser) analyseParams() {
// detect deleted params
for paramName1, param1 := range params1 {
if _, ok := params2[paramName1]; !ok {
childLocation := location.AddNode(getSchemaDiffNode(paramName1, param1.Schema))
childLocation := location.AddNode(getSchemaDiffNode(paramName1, &param1.SimpleSchema))
code := DeletedOptionalParam
if param1.Required {
code = DeletedRequiredParam
@ -163,12 +191,12 @@ func (sd *SpecAnalyser) analyseParams() {
}
// detect added changed params
for paramName2, param2 := range params2 {
//changed?
// changed?
if param1, ok := params1[paramName2]; ok {
sd.compareParams(URLMethod, paramLocation, paramName2, param1, param2)
} else {
// Added
childLocation := location.AddNode(getSchemaDiffNode(paramName2, param2.Schema))
childLocation := location.AddNode(getSchemaDiffNode(paramName2, &param2.SimpleSchema))
code := AddedOptionalParam
if param2.Required {
code = AddedRequiredParam
@ -183,8 +211,10 @@ func (sd *SpecAnalyser) analyseParams() {
func (sd *SpecAnalyser) analyseResponseParams() {
// Loop through url+methods in spec 2 - check deleted and changed
for URLMethod2, op2 := range sd.urlMethods2 {
if op1, ok := sd.urlMethods1[URLMethod2]; ok {
for eachURLMethodFrom2, op2 := range sd.urlMethods2 {
// present in both specs? Use key from spec 2 to lookup in spec 1
if op1, ok := sd.urlMethods1[eachURLMethodFrom2]; ok {
// compare responses for url and method
op1Responses := op1.Operation.Responses.StatusCodeResponses
op2Responses := op2.Operation.Responses.StatusCodeResponses
@ -192,7 +222,7 @@ func (sd *SpecAnalyser) analyseResponseParams() {
// deleted responses
for code1 := range op1Responses {
if _, ok := op2Responses[code1]; !ok {
location := DifferenceLocation{URL: URLMethod2.Path, Method: URLMethod2.Method, Response: code1}
location := DifferenceLocation{URL: eachURLMethodFrom2.Path, Method: eachURLMethodFrom2.Method, Response: code1, Node: getSchemaDiffNode("Body", op1Responses[code1].Schema)}
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: location, Code: DeletedResponse})
}
}
@ -202,39 +232,48 @@ func (sd *SpecAnalyser) analyseResponseParams() {
if op1Response, ok := op1Responses[code2]; ok {
op1Headers := op1Response.ResponseProps.Headers
headerRootNode := getNameOnlyDiffNode("Headers")
location := DifferenceLocation{URL: URLMethod2.Path, Method: URLMethod2.Method, Response: code2, Node: headerRootNode}
// Iterate Spec2 Headers looking for added and updated
location := DifferenceLocation{URL: eachURLMethodFrom2.Path, Method: eachURLMethodFrom2.Method, Response: code2, Node: headerRootNode}
for op2HeaderName, op2Header := range op2Response.ResponseProps.Headers {
if op1Header, ok := op1Headers[op2HeaderName]; ok {
sd.compareSimpleSchema(location.AddNode(getNameOnlyDiffNode(op2HeaderName)),
&op1Header.SimpleSchema,
&op2Header.SimpleSchema, false, false)
diffs := sd.CompareProps(forHeader(op1Header), forHeader(op2Header))
sd.addDiffs(location, diffs)
} else {
sd.Diffs = sd.Diffs.addDiff(SpecDifference{
DifferenceLocation: location.AddNode(getNameOnlyDiffNode(op2HeaderName)),
DifferenceLocation: location.AddNode(getSchemaDiffNode(op2HeaderName, &op2Header.SimpleSchema)),
Code: AddedResponseHeader})
}
}
for op1HeaderName := range op1Response.ResponseProps.Headers {
if _, ok := op2Response.ResponseProps.Headers[op1HeaderName]; !ok {
op1Header := op1Response.ResponseProps.Headers[op1HeaderName]
sd.Diffs = sd.Diffs.addDiff(SpecDifference{
DifferenceLocation: location.AddNode(getNameOnlyDiffNode(op1HeaderName)),
DifferenceLocation: location.AddNode(getSchemaDiffNode(op1HeaderName, &op1Header.SimpleSchema)),
Code: DeletedResponseHeader})
}
}
responseLocation := DifferenceLocation{URL: URLMethod2.Path, Method: URLMethod2.Method, Response: code2}
schem := op1Response.Schema
node := getNameOnlyDiffNode("NoContent")
if schem != nil {
node = getSchemaDiffNode("Body", &schem.SchemaProps)
}
responseLocation := DifferenceLocation{URL: eachURLMethodFrom2.Path,
Method: eachURLMethodFrom2.Method,
Response: code2,
Node: node}
sd.compareDescripton(responseLocation, op1Response.Description, op2Response.Description)
if op1Response.Schema != nil {
sd.compareSchema(
DifferenceLocation{URL: URLMethod2.Path, Method: URLMethod2.Method, Response: code2},
DifferenceLocation{URL: eachURLMethodFrom2.Path, Method: eachURLMethodFrom2.Method, Response: code2, Node: getSchemaDiffNode("Body", op1Response.Schema)},
op1Response.Schema,
op2Response.Schema, true, true)
op2Response.Schema)
}
} else {
// op2Response
sd.Diffs = sd.Diffs.addDiff(SpecDifference{
DifferenceLocation: DifferenceLocation{URL: URLMethod2.Path, Method: URLMethod2.Method, Response: code2},
DifferenceLocation: DifferenceLocation{URL: eachURLMethodFrom2.Path, Method: eachURLMethodFrom2.Method, Response: code2, Node: getSchemaDiffNode("Body", op2Response.Schema)},
Code: AddedResponse})
}
}
@ -249,126 +288,51 @@ func addTypeDiff(diffs []TypeDiff, diff TypeDiff) []TypeDiff {
return diffs
}
// CheckToFromPrimitiveType check for diff to or from a primitive
func (sd *SpecAnalyser) CheckToFromPrimitiveType(diffs []TypeDiff, type1, type2 spec.SchemaProps) []TypeDiff {
type1IsPrimitive := len(type1.Type) > 0
type2IsPrimitive := len(type2.Type) > 0
// Primitive to Obj or Obj to Primitive
if type1IsPrimitive && !type2IsPrimitive {
return addTypeDiff(diffs, TypeDiff{Change: ChangedType, FromType: type1.Type[0], ToType: "obj"})
}
if !type1IsPrimitive && type2IsPrimitive {
return addTypeDiff(diffs, TypeDiff{Change: ChangedType, FromType: type2.Type[0], ToType: "obj"})
}
return diffs
}
// CheckToFromArrayType check for changes to or from an Array type
func (sd *SpecAnalyser) CheckToFromArrayType(diffs []TypeDiff, type1, type2 spec.SchemaProps) []TypeDiff {
// Single to Array or Array to Single
type1Array := type1.Type[0] == ArrayType
type2Array := type2.Type[0] == ArrayType
if type1Array && !type2Array {
return addTypeDiff(diffs, TypeDiff{Change: ChangedType, FromType: "obj", ToType: type2.Type[0]})
}
if !type1Array && type2Array {
return addTypeDiff(diffs, TypeDiff{Change: ChangedType, FromType: type1.Type[0], ToType: ArrayType})
}
if type1Array && type2Array {
// array
// TODO: Items??
diffs = addTypeDiff(diffs, compareIntValues("MaxItems", type1.MaxItems, type2.MaxItems, WidenedType, NarrowedType))
diffs = addTypeDiff(diffs, compareIntValues("MinItems", type1.MinItems, type2.MinItems, NarrowedType, WidenedType))
}
return diffs
}
// CheckStringTypeChanges checks for changes to or from a string type
func (sd *SpecAnalyser) CheckStringTypeChanges(diffs []TypeDiff, type1, type2 spec.SchemaProps) []TypeDiff {
// string changes
if type1.Type[0] == StringType &&
type2.Type[0] == StringType {
diffs = addTypeDiff(diffs, compareIntValues("MinLength", type1.MinLength, type2.MinLength, NarrowedType, WidenedType))
diffs = addTypeDiff(diffs, compareIntValues("MaxLength", type1.MinLength, type2.MinLength, WidenedType, NarrowedType))
if type1.Pattern != type2.Pattern {
diffs = addTypeDiff(diffs, TypeDiff{Change: ChangedType, Description: fmt.Sprintf("Pattern Changed:%s->%s", type1.Pattern, type2.Pattern)})
}
if type1.Type[0] == StringType {
if len(type1.Enum) > 0 {
enumDiffs := sd.compareEnums(type1.Enum, type2.Enum)
diffs = append(diffs, enumDiffs...)
}
}
}
return diffs
}
// CheckNumericTypeChanges checks for changes to or from a numeric type
func (sd *SpecAnalyser) CheckNumericTypeChanges(diffs []TypeDiff, type1, type2 spec.SchemaProps) []TypeDiff {
// Number
_, type1IsNumeric := numberWideness[type1.Type[0]]
_, type2IsNumeric := numberWideness[type2.Type[0]]
if type1IsNumeric && type2IsNumeric {
diffs = addTypeDiff(diffs, compareFloatValues("Maximum", type1.Maximum, type2.Maximum, WidenedType, NarrowedType))
diffs = addTypeDiff(diffs, compareFloatValues("Minimum", type1.Minimum, type2.Minimum, NarrowedType, WidenedType))
if type1.ExclusiveMaximum && !type2.ExclusiveMaximum {
diffs = addTypeDiff(diffs, TypeDiff{Change: WidenedType, Description: fmt.Sprintf("Exclusive Maximum Removed:%v->%v", type1.ExclusiveMaximum, type2.ExclusiveMaximum)})
}
if !type1.ExclusiveMaximum && type2.ExclusiveMaximum {
diffs = addTypeDiff(diffs, TypeDiff{Change: NarrowedType, Description: fmt.Sprintf("Exclusive Maximum Added:%v->%v", type1.ExclusiveMaximum, type2.ExclusiveMaximum)})
}
if type1.ExclusiveMinimum && !type2.ExclusiveMinimum {
diffs = addTypeDiff(diffs, TypeDiff{Change: WidenedType, Description: fmt.Sprintf("Exclusive Minimum Removed:%v->%v", type1.ExclusiveMaximum, type2.ExclusiveMaximum)})
}
if !type1.ExclusiveMinimum && type2.ExclusiveMinimum {
diffs = addTypeDiff(diffs, TypeDiff{Change: NarrowedType, Description: fmt.Sprintf("Exclusive Minimum Added:%v->%v", type1.ExclusiveMinimum, type2.ExclusiveMinimum)})
}
}
return diffs
}
// CompareTypes computes type specific property diffs
func (sd *SpecAnalyser) CompareTypes(type1, type2 spec.SchemaProps) []TypeDiff {
// CompareProps computes type specific property diffs
func (sd *SpecAnalyser) CompareProps(type1, type2 *spec.SchemaProps) []TypeDiff {
diffs := []TypeDiff{}
diffs = sd.CheckToFromPrimitiveType(diffs, type1, type2)
diffs = CheckToFromPrimitiveType(diffs, type1, type2)
if len(diffs) > 0 {
return diffs
}
diffs = sd.CheckToFromArrayType(diffs, type1, type2)
if isArray(type1) {
maxItemDiffs := CompareIntValues("MaxItems", type1.MaxItems, type2.MaxItems, WidenedType, NarrowedType)
diffs = append(diffs, maxItemDiffs...)
minItemsDiff := CompareIntValues("MinItems", type1.MinItems, type2.MinItems, NarrowedType, WidenedType)
diffs = append(diffs, minItemsDiff...)
}
if len(diffs) > 0 {
return diffs
}
// check type hierarchy change eg string -> integer = NarrowedChange
//Type
//Format
diffs = CheckRefChange(diffs, type1, type2)
if len(diffs) > 0 {
return diffs
}
if !(isPrimitiveType(type1.Type) && isPrimitiveType(type2.Type)) {
return diffs
}
// check primitive type hierarchy change eg string -> integer = NarrowedChange
if type1.Type[0] != type2.Type[0] ||
type1.Format != type2.Format {
diff := getTypeHierarchyChange(primitiveTypeString(type1.Type[0], type1.Format), primitiveTypeString(type2.Type[0], type2.Format))
diffs = addTypeDiff(diffs, diff)
}
diffs = sd.CheckStringTypeChanges(diffs, type1, type2)
diffs = CheckStringTypeChanges(diffs, type1, type2)
if len(diffs) > 0 {
return diffs
}
diffs = sd.CheckNumericTypeChanges(diffs, type1, type2)
diffs = checkNumericTypeChanges(diffs, type1, type2)
if len(diffs) > 0 {
return diffs
@ -385,49 +349,37 @@ func (sd *SpecAnalyser) compareParams(urlMethod URLMethod, location string, name
sd.compareDescripton(paramLocation, param1.Description, param2.Description)
if param1.Schema != nil && param2.Schema != nil {
childLocation = childLocation.AddNode(getSchemaDiffNode(name, param2.Schema))
sd.compareSchema(childLocation, param1.Schema, param2.Schema, param1.Required, param2.Required)
}
diffs := sd.CompareTypes(forParam(param1), forParam(param2))
childLocation = childLocation.AddNode(getSchemaDiffNode(name, param2.Schema))
for _, eachDiff := range diffs {
sd.Diffs = sd.Diffs.addDiff(SpecDifference{
DifferenceLocation: childLocation,
Code: eachDiff.Change,
DiffInfo: eachDiff.Description})
}
if param1.Required != param2.Required {
code := ChangedRequiredToOptionalParam
if param2.Required {
code = ChangedOptionalToRequiredParam
if len(name) > 0 {
childLocation = childLocation.AddNode(getSchemaDiffNode(name, param2.Schema))
}
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: childLocation, Code: code})
sd.compareSchema(childLocation, param1.Schema, param2.Schema)
}
diffs := sd.CompareProps(forParam(param1), forParam(param2))
childLocation = childLocation.AddNode(getSchemaDiffNode(name, &param2.SimpleSchema))
if len(diffs) > 0 {
sd.addDiffs(childLocation, diffs)
}
diffs = CheckToFromRequired(param1.Required, param2.Required)
if len(diffs) > 0 {
sd.addDiffs(childLocation, diffs)
}
}
func (sd *SpecAnalyser) compareSimpleSchema(location DifferenceLocation, schema1, schema2 *spec.SimpleSchema, required1, required2 bool) {
if schema1 == nil || schema2 == nil {
return
}
if schema1.Type == ArrayType {
refSchema1 := schema1.Items.SimpleSchema
refSchema2 := schema2.Items.SimpleSchema
childLocation := location.AddNode(getSimpleSchemaDiffNode("", schema1))
sd.compareSimpleSchema(childLocation, &refSchema1, &refSchema2, required1, required2)
return
}
if required1 != required2 {
code := AddedRequiredProperty
if required1 {
code = ChangedRequiredToOptional
func (sd *SpecAnalyser) addTypeDiff(location DifferenceLocation, diff *TypeDiff) {
diffCopy := diff
desc := diffCopy.Description
if len(desc) == 0 {
if diffCopy.FromType != diffCopy.ToType {
desc = fmt.Sprintf("%s -> %s", diffCopy.FromType, diffCopy.ToType)
}
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: location, Code: code})
}
sd.Diffs = sd.Diffs.addDiff(SpecDifference{
DifferenceLocation: location,
Code: diffCopy.Change,
DiffInfo: desc})
}
func (sd *SpecAnalyser) compareDescripton(location DifferenceLocation, desc1, desc2 string) {
@ -440,145 +392,96 @@ func (sd *SpecAnalyser) compareDescripton(location DifferenceLocation, desc1, de
}
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: location, Code: code})
}
}
func (sd *SpecAnalyser) compareSchema(location DifferenceLocation, schema1, schema2 *spec.Schema, required1, required2 bool) {
func isPrimitiveType(item spec.StringOrArray) bool {
return len(item) > 0 && item[0] != ArrayType && item[0] != ObjectType
}
if schema1 == nil || schema2 == nil {
func isArrayType(item spec.StringOrArray) bool {
return len(item) > 0 && item[0] == ArrayType
}
func (sd *SpecAnalyser) getRefSchemaFromSpec1(ref spec.Ref) (*spec.Schema, string) {
return sd.schemaFromRef(ref, &sd.Definitions1)
}
func (sd *SpecAnalyser) getRefSchemaFromSpec2(ref spec.Ref) (*spec.Schema, string) {
return sd.schemaFromRef(ref, &sd.Definitions2)
}
// CompareSchemaFn Fn spec for comparing schemas
type CompareSchemaFn func(location DifferenceLocation, schema1, schema2 *spec.Schema)
func (sd *SpecAnalyser) compareSchema(location DifferenceLocation, schema1, schema2 *spec.Schema) {
refDiffs := []TypeDiff{}
refDiffs = CheckRefChange(refDiffs, schema1, schema2)
if len(refDiffs) > 0 {
for _, d := range refDiffs {
diff := d
sd.addTypeDiff(location, &diff)
}
return
}
if isRefType(schema1) {
schema1, _ = sd.schemaFromRef(getRef(schema1), &sd.Definitions1)
}
if isRefType(schema2) {
schema2, _ = sd.schemaFromRef(getRef(schema2), &sd.Definitions2)
}
sd.compareDescripton(location, schema1.Description, schema2.Description)
if len(schema1.Type) == 0 {
refSchema1, definition1 := sd.schemaFromRef(schema1, &sd.Definitions1)
refSchema2, definition2 := sd.schemaFromRef(schema2, &sd.Definitions2)
if len(definition1) > 0 {
info := fmt.Sprintf("[%s -> %s]", definition1, definition2)
if definition1 != definition2 {
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: location,
Code: ChangedType,
DiffInfo: info,
})
}
sd.compareSchema(location, refSchema1, refSchema2, required1, required2)
return
}
} else {
if schema1.Type[0] == ArrayType {
refSchema1, definition1 := sd.schemaFromRef(schema1.Items.Schema, &sd.Definitions1)
refSchema2, _ := sd.schemaFromRef(schema2.Items.Schema, &sd.Definitions2)
if len(definition1) > 0 {
childLocation := location.AddNode(getSchemaDiffNode("", schema1))
sd.compareSchema(childLocation, refSchema1, refSchema2, required1, required2)
return
}
}
diffs := sd.CompareTypes(schema1.SchemaProps, schema2.SchemaProps)
for _, eachTypeDiff := range diffs {
if eachTypeDiff.Change != NoChangeDetected {
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: location, Code: eachTypeDiff.Change, DiffInfo: eachTypeDiff.Description})
}
}
typeDiffs := sd.CompareProps(&schema1.SchemaProps, &schema2.SchemaProps)
if len(typeDiffs) > 0 {
sd.addDiffs(location, typeDiffs)
return
}
if required1 != required2 {
code := AddedRequiredProperty
if required1 {
code = ChangedRequiredToOptional
}
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: location, Code: code})
}
requiredProps2 := sliceToStrMap(schema2.Required)
requiredProps1 := sliceToStrMap(schema1.Required)
schema1Props := sd.propertiesFor(schema1, &sd.Definitions1)
schema2Props := sd.propertiesFor(schema2, &sd.Definitions2)
// find deleted and changed properties
for eachProp1Name, eachProp1 := range schema1Props {
eachProp1 := eachProp1
_, required1 := requiredProps1[eachProp1Name]
_, required2 := requiredProps2[eachProp1Name]
childLoc := sd.addChildDiffNode(location, eachProp1Name, &eachProp1)
if eachProp2, ok := schema2Props[eachProp1Name]; ok {
sd.compareSchema(childLoc, &eachProp1, &eachProp2, required1, required2)
sd.compareDescripton(childLoc, eachProp1.Description, eachProp2.Description)
} else {
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: childLoc, Code: DeletedProperty})
}
if isArray(schema1) {
sd.compareSchema(location, schema1.Items.Schema, schema2.Items.Schema)
}
// find added properties
for eachProp2Name, eachProp2 := range schema2.Properties {
eachProp2 := eachProp2
if _, ok := schema1.Properties[eachProp2Name]; !ok {
childLoc := sd.addChildDiffNode(location, eachProp2Name, &eachProp2)
_, required2 := requiredProps2[eachProp2Name]
code := AddedProperty
if required2 {
code = AddedRequiredProperty
}
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: childLoc, Code: code})
diffs := CompareProperties(location, schema1, schema2, sd.getRefSchemaFromSpec1, sd.getRefSchemaFromSpec2, sd.compareSchema)
for _, diff := range diffs {
sd.Diffs = sd.Diffs.addDiff(diff)
}
}
func (sd *SpecAnalyser) addDiffs(location DifferenceLocation, diffs []TypeDiff) {
for _, e := range diffs {
eachTypeDiff := e
if eachTypeDiff.Change != NoChangeDetected {
sd.addTypeDiff(location, &eachTypeDiff)
}
}
}
func (sd *SpecAnalyser) addChildDiffNode(location DifferenceLocation, propName string, propSchema *spec.Schema) DifferenceLocation {
newLoc := location
if newLoc.Node != nil {
newLoc.Node = newLoc.Node.Copy()
}
childNode := sd.fromSchemaProps(propName, &propSchema.SchemaProps)
if newLoc.Node != nil {
newLoc.Node.AddLeafNode(&childNode)
func addChildDiffNode(location DifferenceLocation, propName string, propSchema *spec.Schema) DifferenceLocation {
newNode := location.Node
childNode := fromSchemaProps(propName, &propSchema.SchemaProps)
if newNode != nil {
newNode = newNode.Copy()
newNode.AddLeafNode(&childNode)
} else {
newLoc.Node = &childNode
newNode = &childNode
}
return DifferenceLocation{
URL: location.URL,
Method: location.Method,
Response: location.Response,
Node: newNode,
}
return newLoc
}
func (sd *SpecAnalyser) fromSchemaProps(fieldName string, props *spec.SchemaProps) Node {
func fromSchemaProps(fieldName string, props *spec.SchemaProps) Node {
node := Node{}
node.IsArray = props.Type[0] == ArrayType
if !node.IsArray {
node.TypeName = props.Type[0]
}
node.TypeName, node.IsArray = getSchemaType(props)
node.Field = fieldName
return node
}
func (sd *SpecAnalyser) compareEnums(left, right []interface{}) []TypeDiff {
diffs := []TypeDiff{}
leftStrs := []string{}
rightStrs := []string{}
for _, eachLeft := range left {
leftStrs = append(leftStrs, fmt.Sprintf("%v", eachLeft))
}
for _, eachRight := range right {
rightStrs = append(rightStrs, fmt.Sprintf("%v", eachRight))
}
added, deleted, _ := fromStringArray(leftStrs).DiffsTo(rightStrs)
if len(added) > 0 {
typeChange := strings.Join(added, ",")
diffs = append(diffs, TypeDiff{Change: AddedEnumValue, Description: typeChange})
}
if len(deleted) > 0 {
typeChange := strings.Join(deleted, ",")
diffs = append(diffs, TypeDiff{Change: DeletedEnumValue, Description: typeChange})
}
return diffs
}
func (sd *SpecAnalyser) findAddedEndpoints() {
for URLMethod := range sd.urlMethods2 {
if _, ok := sd.urlMethods1[URLMethod]; !ok {
@ -607,48 +510,23 @@ func (sd *SpecAnalyser) analyseMetaDataProperty(item1, item2 string, codeIfDiff
}
}
func (sd *SpecAnalyser) schemaFromRef(schema *spec.Schema, defns *spec.Definitions) (actualSchema *spec.Schema, definitionName string) {
ref := schema.Ref
url := ref.GetURL()
if url == nil {
return schema, ""
}
fragmentParts := strings.Split(url.Fragment, "/")
numParts := len(fragmentParts)
if numParts == 0 {
return schema, ""
}
definitionName = fragmentParts[numParts-1]
func (sd *SpecAnalyser) schemaFromRef(ref spec.Ref, defns *spec.Definitions) (actualSchema *spec.Schema, definitionName string) {
definitionName = definitionFromRef(ref)
foundSchema, ok := (*defns)[definitionName]
if !ok {
return nil, definitionName
}
sd.ReferencedDefinitions[definitionName] = true
actualSchema = &foundSchema
return
}
func (sd *SpecAnalyser) propertiesFor(schema *spec.Schema, defns *spec.Definitions) map[string]spec.Schema {
schemaFromRef, _ := sd.schemaFromRef(schema, defns)
schema = schemaFromRef
props := map[string]spec.Schema{}
if schema.Properties != nil {
for name, prop := range schema.Properties {
prop := prop
eachProp, _ := sd.schemaFromRef(&prop, defns)
props[name] = *eachProp
}
}
for _, eachAllOf := range schema.AllOf {
eachAllOf := eachAllOf
eachAllOfActual, _ := sd.schemaFromRef(&eachAllOf, defns)
for name, prop := range eachAllOfActual.Properties {
prop := prop
eachProp, _ := sd.schemaFromRef(&prop, defns)
props[name] = *eachProp
}
}
return props
// PropertyDefn combines a property with its required-ness
type PropertyDefn struct {
Schema *spec.Schema
Required bool
}
// PropertyMap a unified map including all AllOf fields
type PropertyMap map[string]PropertyDefn

View file

@ -6,6 +6,7 @@ import (
"fmt"
"io"
"sort"
"strings"
)
// SpecDifference encapsulates the details of an individual diff in part of a spec
@ -89,37 +90,37 @@ func (sd SpecDifference) String() string {
prefix := ""
direction := ""
if isResponse {
direction = " Response"
if hasMethod {
if hasURL {
if hasMethod {
prefix = fmt.Sprintf("%s:%s -> %d", sd.DifferenceLocation.URL, sd.DifferenceLocation.Method, sd.DifferenceLocation.Response)
} else {
prefix = fmt.Sprintf("%s ", sd.DifferenceLocation.URL)
}
prefix = fmt.Sprintf("%s:%s", sd.DifferenceLocation.URL, sd.DifferenceLocation.Method)
}
if isResponse {
prefix += fmt.Sprintf(" -> %d", sd.DifferenceLocation.Response)
direction = "Response"
} else {
direction = "Request"
}
} else {
if hasURL {
if hasMethod {
direction = " Request"
prefix = fmt.Sprintf("%s:%s", sd.DifferenceLocation.URL, sd.DifferenceLocation.Method)
} else {
prefix = fmt.Sprintf("%s ", sd.DifferenceLocation.URL)
}
} else {
prefix = " Metadata"
}
prefix = sd.DifferenceLocation.URL
}
paramOrPropertyLocation := ""
if sd.DifferenceLocation.Node != nil {
paramOrPropertyLocation = " - " + sd.DifferenceLocation.Node.String() + " "
paramOrPropertyLocation = sd.DifferenceLocation.Node.String()
}
optionalInfo := ""
if sd.DiffInfo != "" {
optionalInfo = fmt.Sprintf(" <%s>", sd.DiffInfo)
optionalInfo = sd.DiffInfo
}
return fmt.Sprintf("%s%s%s- %s%s", prefix, direction, paramOrPropertyLocation, sd.Code.Description(), optionalInfo)
items := []string{}
for _, item := range []string{prefix, direction, paramOrPropertyLocation, sd.Code.Description(), optionalInfo} {
if item != "" {
items = append(items, item)
}
}
return strings.Join(items, " - ")
// return fmt.Sprintf("%s%s%s - %s%s", prefix, direction, paramOrPropertyLocation, sd.Code.Description(), optionalInfo)
}
func (sd SpecDifferences) addDiff(diff SpecDifference) SpecDifferences {

View file

@ -1,8 +1,6 @@
package diff
import (
"fmt"
"github.com/go-openapi/spec"
)
@ -32,8 +30,28 @@ func forItems(items *spec.Items) *spec.Schema {
return &schema
}
func forParam(param spec.Parameter) spec.SchemaProps {
return spec.SchemaProps{
func forHeader(header spec.Header) *spec.SchemaProps {
return &spec.SchemaProps{
Type: []string{header.Type},
Format: header.Format,
Items: &spec.SchemaOrArray{Schema: forItems(header.Items)},
Maximum: header.Maximum,
ExclusiveMaximum: header.ExclusiveMaximum,
Minimum: header.Minimum,
ExclusiveMinimum: header.ExclusiveMinimum,
MaxLength: header.MaxLength,
MinLength: header.MinLength,
Pattern: header.Pattern,
MaxItems: header.MaxItems,
MinItems: header.MinItems,
UniqueItems: header.UniqueItems,
MultipleOf: header.MultipleOf,
Enum: header.Enum,
}
}
func forParam(param spec.Parameter) *spec.SchemaProps {
return &spec.SchemaProps{
Type: []string{param.Type},
Format: param.Format,
Items: &spec.SchemaOrArray{Schema: forItems(param.Items)},
@ -95,76 +113,51 @@ func getURLMethodsFor(spec *spec.Swagger) URLMethods {
return returnURLMethods
}
func sliceToStrMap(elements []string) map[string]bool {
elementMap := make(map[string]bool)
for _, s := range elements {
elementMap[s] = true
}
return elementMap
}
func isStringType(typeName string) bool {
return typeName == "string" || typeName == "password"
}
const objType = "obj"
// SchemaFromRefFn define this to get a schema for a ref
type SchemaFromRefFn func(spec.Ref) (*spec.Schema, string)
func getTypeHierarchyChange(type1, type2 string) TypeDiff {
if type1 == type2 {
return TypeDiff{Change: NoChangeDetected, Description: ""}
func propertiesFor(schema *spec.Schema, getRefFn SchemaFromRefFn) PropertyMap {
if isRefType(schema) {
schema, _ = getRefFn(schema.Ref)
}
fromType := type1
if fromType == "" {
fromType = objType
props := PropertyMap{}
requiredProps := schema.Required
requiredMap := map[string]bool{}
for _, prop := range requiredProps {
requiredMap[prop] = true
}
toType := type2
if toType == "" {
toType = objType
}
diffDescription := fmt.Sprintf("%s -> %s", fromType, toType)
if isStringType(type1) && !isStringType(type2) {
return TypeDiff{Change: NarrowedType, Description: diffDescription}
}
if !isStringType(type1) && isStringType(type2) {
return TypeDiff{Change: WidenedType, Description: diffDescription}
}
type1Wideness, type1IsNumeric := numberWideness[type1]
type2Wideness, type2IsNumeric := numberWideness[type2]
if type1IsNumeric && type2IsNumeric {
if type1Wideness == type2Wideness {
return TypeDiff{Change: ChangedToCompatibleType, Description: diffDescription}
}
if type1Wideness > type2Wideness {
return TypeDiff{Change: NarrowedType, Description: diffDescription}
}
if type1Wideness < type2Wideness {
return TypeDiff{Change: WidenedType, Description: diffDescription}
if schema.Properties != nil {
for name, prop := range schema.Properties {
prop := prop
required := requiredMap[name]
props[name] = PropertyDefn{Schema: &prop, Required: required}
}
}
return TypeDiff{Change: ChangedType, Description: diffDescription}
for _, e := range schema.AllOf {
eachAllOf := e
allOfMap := propertiesFor(&eachAllOf, getRefFn)
for name, prop := range allOfMap {
props[name] = prop
}
}
return props
}
func compareFloatValues(fieldName string, val1 *float64, val2 *float64, ifGreaterCode SpecChangeCode, ifLessCode SpecChangeCode) TypeDiff {
if val1 != nil && val2 != nil {
if *val2 > *val1 {
return TypeDiff{Change: ifGreaterCode, Description: fmt.Sprintf("%s %f->%f", fieldName, *val1, *val2)}
}
if *val2 < *val1 {
return TypeDiff{Change: ifLessCode, Description: fmt.Sprintf("%s %f->%f", fieldName, *val1, *val2)}
}
func getRef(item interface{}) spec.Ref {
switch s := item.(type) {
case *spec.Refable:
return s.Ref
case *spec.Schema:
return s.Ref
case *spec.SchemaProps:
return s.Ref
default:
return spec.Ref{}
}
return TypeDiff{Change: NoChangeDetected, Description: ""}
}
func compareIntValues(fieldName string, val1 *int64, val2 *int64, ifGreaterCode SpecChangeCode, ifLessCode SpecChangeCode) TypeDiff {
if val1 != nil && val2 != nil {
if *val2 > *val1 {
return TypeDiff{Change: ifGreaterCode, Description: fmt.Sprintf("%s %d->%d", fieldName, *val1, *val2)}
}
if *val2 < *val1 {
return TypeDiff{Change: ifLessCode, Description: fmt.Sprintf("%s %d->%d", fieldName, *val1, *val2)}
}
}
return TypeDiff{Change: NoChangeDetected, Description: ""}
}

View file

@ -47,11 +47,12 @@ func writeToFile(swspec *spec.Swagger, pretty bool, format string, output string
var err error
asJSON := format == "json"
if pretty && asJSON {
switch {
case pretty && asJSON:
b, err = json.MarshalIndent(swspec, "", " ")
} else if asJSON {
case asJSON:
b, err = json.Marshal(swspec)
} else {
default:
// marshals as YAML
b, err = json.Marshal(swspec)
if err == nil {
@ -62,12 +63,15 @@ func writeToFile(swspec *spec.Swagger, pretty bool, format string, output string
b, err = yaml.Marshal(d)
}
}
if err != nil {
return err
}
if output == "" {
fmt.Println(string(b))
return nil
}
return ioutil.WriteFile(output, b, 0644) // #nosec
}

View file

@ -24,4 +24,5 @@ type Generate struct {
Server *generate.Server `command:"server"`
Spec *generate.SpecFile `command:"spec"`
Client *generate.Client `command:"client"`
Markdown *generate.Markdown `command:"markdown"`
}

View file

@ -0,0 +1,33 @@
package generate
import (
"github.com/go-swagger/go-swagger/generator"
"github.com/jessevdk/go-flags"
)
// Markdown generates a markdown representation of the spec
type Markdown struct {
WithShared
WithModels
WithOperations
Output flags.Filename `long:"output" short:"" description:"the file to write the generated markdown." default:"markdown.md"`
}
func (m Markdown) apply(opts *generator.GenOpts) {
m.Shared.apply(opts)
m.Models.apply(opts)
m.Operations.apply(opts)
}
func (m *Markdown) generate(opts *generator.GenOpts) error {
return generator.GenerateMarkdown(string(m.Output), m.Models.Models, m.Operations.Operations, opts)
}
func (m Markdown) log(rp string) {
}
// Execute runs this command
func (m *Markdown) Execute(args []string) error {
return createSwagger(m)
}

View file

@ -52,7 +52,7 @@ type Server struct {
Name string `long:"name" short:"A" description:"the name of the application, defaults to a mangled value of info.title"`
// TODO(fredbi): CmdName string `long:"cmd-name" short:"A" description:"the name of the server command, when main is generated (defaults to {name}-server)"`
//deprecated flags
// deprecated flags
WithContext bool `long:"with-context" description:"handlers get a context as first arg (deprecated)"`
}

View file

@ -80,10 +80,13 @@ type sharedCommand interface {
type schemeOptions struct {
Principal string `short:"P" long:"principal" description:"the model to use for the security principal"`
DefaultScheme string `long:"default-scheme" description:"the default scheme for this API" default:"http"`
PrincipalIface bool `long:"principal-is-interface" description:"the security principal provided is an interface, not a struct"`
}
func (so schemeOptions) apply(opts *generator.GenOpts) {
opts.Principal = so.Principal
opts.PrincipalCustomIface = so.PrincipalIface
opts.DefaultScheme = so.DefaultScheme
}

View file

@ -16,7 +16,8 @@ import (
const (
// Output messages
nothingToDo = "nothing to do. Need some swagger files to merge.\nUSAGE: swagger mixin [-c <expected#Collisions>] <primary-swagger-file> <mixin-swagger-file...>"
nothingToDo = "nothing to do. Need some swagger files to merge.\nUSAGE: swagger mixin [-c <expected#Collisions>] <primary-swagger-file> <mixin-swagger-file...>"
ignoreConflictsAndCollisionsSpecified = "both the flags ignore conflicts and collisions were specified. These have conflicting meaning so please only specify one"
)
// MixinSpec holds command line flag definitions specific to the mixin
@ -28,6 +29,7 @@ type MixinSpec struct {
Output flags.Filename `long:"output" short:"o" description:"the file to write to"`
KeepSpecOrder bool `long:"keep-spec-order" description:"Keep schema properties order identical to spec file"`
Format string `long:"format" description:"the format for the spec document" default:"json" choice:"yaml" choice:"json"`
IgnoreConflicts bool `long:"ignore-conflicts" description:"Ignore conflict"`
}
// Execute runs the mixin command which merges Swagger 2.0 specs into
@ -52,6 +54,9 @@ func (c *MixinSpec) Execute(args []string) error {
if len(args) < 2 {
return errors.New(nothingToDo)
}
if c.IgnoreConflicts && c.ExpectedCollisionCount != 0 {
return errors.New(ignoreConflictsAndCollisionsSpecified)
}
log.Printf("args[0] = %v\n", args[0])
log.Printf("args[1:] = %v\n", args[1:])
@ -65,6 +70,9 @@ func (c *MixinSpec) Execute(args []string) error {
return err
}
if c.IgnoreConflicts {
return nil
}
if len(collisions) != int(c.ExpectedCollisionCount) {
if len(collisions) != 0 {
// use bash $? to get actual # collisions

View file

@ -27,7 +27,7 @@ type ServeCmd struct {
NoUI bool `long:"no-ui" description:"when present, only the swagger spec will be served"`
Flatten bool `long:"flatten" description:"when present, flatten the swagger spec before serving it"`
Port int `long:"port" short:"p" description:"the port to serve this site" env:"PORT"`
Host string `long:"host" description:"the interface to serve this site, defaults to 0.0.0.0" env:"HOST"`
Host string `long:"host" description:"the interface to serve this site, defaults to 0.0.0.0" default:"0.0.0.0" env:"HOST"`
}
// Execute the serve command

View file

@ -1,6 +1,9 @@
package commands
import "fmt"
import (
"fmt"
"runtime/debug"
)
var (
// Version for the swagger command
@ -16,9 +19,17 @@ type PrintVersion struct {
// Execute this command
func (p *PrintVersion) Execute(args []string) error {
if Version == "" {
if info, available := debug.ReadBuildInfo(); available && info.Main.Version != "(devel)" {
// built from source, with module (e.g. go get)
fmt.Println("version:", info.Main.Version)
fmt.Println("commit:", fmt.Sprintf("(unknown, mod sum: %q)", info.Main.Sum))
return nil
}
// built from source, local repo
fmt.Println("dev")
return nil
}
// released version
fmt.Println("version:", Version)
fmt.Println("commit:", Commit)

View file

@ -41,7 +41,7 @@ func main() {
// Recovering from internal panics
// Stack may be printed in Debug mode
// Need import "runtime/debug".
//defer func() {
// defer func() {
// r := recover()
// if r != nil {
// log.Printf("Fatal error:", r)
@ -50,7 +50,7 @@ func main() {
// }
// os.Exit(1)
// }
//}()
// }()
parser := flags.NewParser(&opts, flags.Default)
parser.ShortDescription = "helps you keep your API well described"
@ -123,6 +123,9 @@ It aims to represent the contract of your API with a language agnostic descripti
case "operation":
cmd.ShortDescription = "generate one or more server operations from the swagger spec"
cmd.LongDescription = cmd.ShortDescription
case "markdown":
cmd.ShortDescription = "generate a markdown representation from the swagger spec"
cmd.LongDescription = cmd.ShortDescription
}
}

View file

@ -0,0 +1,3 @@
# codescan
Version of the go source parser with support for go modules, from go1.11 onwards.

View file

@ -595,7 +595,7 @@ func (a *typeIndex) detectNodes(file *ast.File) (node, error) {
if seenStruct == "" || seenStruct == matches[1] {
seenStruct = matches[1]
} else {
return 0, fmt.Errorf("classifier: already annotated as %s, can't also be %q", seenStruct, matches[1])
return 0, fmt.Errorf("classifier: already annotated as %s, can't also be %q - %s", seenStruct, matches[1], cline.Text)
}
case "meta":
n |= metaNode
@ -604,14 +604,14 @@ func (a *typeIndex) detectNodes(file *ast.File) (node, error) {
if seenStruct == "" || seenStruct == matches[1] {
seenStruct = matches[1]
} else {
return 0, fmt.Errorf("classifier: already annotated as %s, can't also be %q", seenStruct, matches[1])
return 0, fmt.Errorf("classifier: already annotated as %s, can't also be %q - %s", seenStruct, matches[1], cline.Text)
}
case "response":
n |= responseNode
if seenStruct == "" || seenStruct == matches[1] {
seenStruct = matches[1]
} else {
return 0, fmt.Errorf("classifier: already annotated as %s, can't also be %q", seenStruct, matches[1])
return 0, fmt.Errorf("classifier: already annotated as %s, can't also be %q - %s", seenStruct, matches[1], cline.Text)
}
case "strfmt", "name", "discriminated", "file", "enum", "default", "alias", "type":
// TODO: perhaps collect these and pass along to avoid lookups later on

View file

@ -0,0 +1,5 @@
/*Package codescan provides a scanner for go files that produces a swagger spec document.
This package is intendnd for go1.11 onwards, and does support go modules.
*/
package codescan

View file

@ -204,7 +204,8 @@ func parseContactInfo(line string) (*spec.ContactInfo, error) {
ContactInfoProps: spec.ContactInfoProps{
URL: url,
Name: name,
Email: email,},
Email: email,
},
}, nil
}

View file

@ -260,9 +260,6 @@ func (p *parameterBuilder) buildFromField(fld *types.Var, tpe types.Type, typabl
typable.Typed("string", sfnm)
return nil
}
//if err := r.makeRef(decl, typable); err != nil {
// return err
//}
sb := &schemaBuilder{ctx: p.ctx, decl: decl}
sb.inferNames()
if err := sb.buildFromType(decl.Type, typable); err != nil {

View file

@ -626,7 +626,8 @@ COMMENTS:
}
var matched bool
for _, tagger := range st.taggers {
for _, tg := range st.taggers {
tagger := tg
if tagger.Matches(line) {
st.seenTag = true
st.currentTagger = &tagger
@ -1311,13 +1312,13 @@ func (ss *setOpResponses) Matches(line string) bool {
return ss.rx.MatchString(line)
}
//ResponseTag used when specifying a response to point to a defined swagger:response
// ResponseTag used when specifying a response to point to a defined swagger:response
const ResponseTag = "response"
//BodyTag used when specifying a response to point to a model/schema
// BodyTag used when specifying a response to point to a model/schema
const BodyTag = "body"
//DescriptionTag used when specifying a response that gives a description of the response
// DescriptionTag used when specifying a response that gives a description of the response
const DescriptionTag = "description"
func parseTags(line string) (modelOrResponse string, arrays int, isDefinitionRef bool, description string, err error) {
@ -1331,8 +1332,8 @@ func parseTags(line string) (modelOrResponse string, arrays int, isDefinitionRef
tag = tagValList[0]
value = tagValList[1]
} else {
//TODO: Print a warning, and in the long term, do not support not tagged values
//Add a default tag if none is supplied
// TODO: Print a warning, and in the long term, do not support not tagged values
// Add a default tag if none is supplied
if i == 0 {
tag = ResponseTag
} else {
@ -1353,15 +1354,15 @@ func parseTags(line string) (modelOrResponse string, arrays int, isDefinitionRef
}
}
if foundModelOrResponse {
//Read the model or response tag
// Read the model or response tag
parsedModelOrResponse = true
//Check for nested arrays
// Check for nested arrays
arrays = 0
for strings.HasPrefix(value, "[]") {
arrays++
value = value[2:]
}
//What's left over is the model name
// What's left over is the model name
modelOrResponse = value
} else {
foundDescription := false
@ -1369,7 +1370,7 @@ func parseTags(line string) (modelOrResponse string, arrays int, isDefinitionRef
foundDescription = true
}
if foundDescription {
//Descriptions are special, they make they read the rest of the line
// Descriptions are special, they make they read the rest of the line
descriptionWords := []string{value}
if i < len(tags)-1 {
descriptionWords = append(descriptionWords, tags[i+1:]...)
@ -1382,13 +1383,13 @@ func parseTags(line string) (modelOrResponse string, arrays int, isDefinitionRef
} else {
err = fmt.Errorf("invalid tag: %s", tag)
}
//return error
// return error
return
}
}
}
//TODO: Maybe do, if !parsedModelOrResponse {return some error}
// TODO: Maybe do, if !parsedModelOrResponse {return some error}
return
}
@ -1431,7 +1432,7 @@ func (ss *setOpResponses) Parse(lines []string) error {
if err != nil {
return err
}
//A possible exception for having a definition
// A possible exception for having a definition
if _, ok := ss.responses[refTarget]; !ok {
if _, ok := ss.definitions[refTarget]; ok {
isDefinitionRef = true

View file

@ -402,6 +402,12 @@ func (r *responseBuilder) buildFromStruct(decl *entityDecl, tpe *types.Struct, r
return nil, err
}
return append(taggers, otherTaggers...), nil
case *ast.SelectorExpr:
otherTaggers, err := parseArrayTypes(iftpe.Sel, items.Items, level+1)
if err != nil {
return nil, err
}
return otherTaggers, nil
case *ast.StarExpr:
otherTaggers, err := parseArrayTypes(iftpe.X, items, level)
if err != nil {

View file

@ -238,7 +238,7 @@ func (s *schemaBuilder) buildFromType(tpe types.Type, tgt swaggerTypable) error
case *types.Array:
return s.buildFromType(titpe.Elem(), tgt.Items())
case *types.Map:
//debugLog("map: %v -> [%v]%v", fld.Name(), ftpe.Key().String(), ftpe.Elem().String())
// debugLog("map: %v -> [%v]%v", fld.Name(), ftpe.Key().String(), ftpe.Elem().String())
// check if key is a string type, if not print a message
// and skip the map property. Only maps with string keys can go into additional properties
sch := tgt.Schema()
@ -387,7 +387,6 @@ func (s *schemaBuilder) buildFromType(tpe types.Type, tgt swaggerTypable) error
return nil
}
default:
//log.Printf("WARNING: can't determine refined type %s (%T)", titpe.String(), titpe)
panic(fmt.Sprintf("WARNING: can't determine refined type %s (%T)", titpe.String(), titpe))
}
@ -429,7 +428,7 @@ func (s *schemaBuilder) buildFromInterface(decl *entityDecl, it *types.Interface
continue
}
//decl.
// decl.
debugLog("maybe interface field %s: %s(%T)", o.Name(), o.Type().String(), o.Type())
afld = an
break
@ -514,7 +513,7 @@ func (s *schemaBuilder) buildFromInterface(decl *entityDecl, it *types.Interface
var afld *ast.Field
ans, _ := astutil.PathEnclosingInterval(decl.File, fld.Pos(), fld.Pos())
//debugLog("got %d nodes (exact: %t)", len(ans), isExact)
// debugLog("got %d nodes (exact: %t)", len(ans), isExact)
for _, an := range ans {
at, valid := an.(*ast.Field)
if !valid {
@ -600,7 +599,7 @@ func (s *schemaBuilder) buildFromStruct(decl *entityDecl, st *types.Struct, sche
debugLog("maybe allof field(%t) %s: %s (%T) [%q](anon: %t, embedded: %t)", fld.IsField(), fld.Name(), fld.Type().String(), fld.Type(), tg, fld.Anonymous(), fld.Embedded())
var afld *ast.Field
ans, _ := astutil.PathEnclosingInterval(decl.File, fld.Pos(), fld.Pos())
//debugLog("got %d nodes (exact: %t)", len(ans), isExact)
// debugLog("got %d nodes (exact: %t)", len(ans), isExact)
for _, an := range ans {
at, valid := an.(*ast.Field)
if !valid {
@ -696,7 +695,7 @@ func (s *schemaBuilder) buildFromStruct(decl *entityDecl, st *types.Struct, sche
var afld *ast.Field
ans, _ := astutil.PathEnclosingInterval(decl.File, fld.Pos(), fld.Pos())
//debugLog("got %d nodes (exact: %t)", len(ans), isExact)
// debugLog("got %d nodes (exact: %t)", len(ans), isExact)
for _, an := range ans {
at, valid := an.(*ast.Field)
if !valid {

View file

@ -0,0 +1 @@
generated/

File diff suppressed because one or more lines are too long

View file

@ -84,10 +84,11 @@ func (c *clientGenerator) Generate() error {
}
if c.GenOpts.IncludeModel {
for _, mod := range app.Models {
if mod.IsStream {
for _, m := range app.Models {
if m.IsStream {
continue
}
mod := m
if err := c.GenOpts.renderDefinition(&mod); err != nil {
return err
}
@ -95,8 +96,10 @@ func (c *clientGenerator) Generate() error {
}
if c.GenOpts.IncludeHandler {
for _, opg := range app.OperationGroups {
for _, op := range opg.Operations {
for _, g := range app.OperationGroups {
opg := g
for _, o := range opg.Operations {
op := o
if err := c.GenOpts.renderOperation(&op); err != nil {
return err
}

View file

@ -57,7 +57,7 @@ var zeroes = map[string]string{
"strfmt.UUID3": "strfmt.UUID3(\"\")",
"strfmt.UUID4": "strfmt.UUID4(\"\")",
"strfmt.UUID5": "strfmt.UUID5(\"\")",
//"file": "runtime.File",
// "file": "runtime.File",
}
// conversion functions from string representation to a numerical or boolean

View file

@ -262,7 +262,7 @@ func GoLangOpts() *LanguageOpts {
if err != nil {
return "", err
}
return strings.Replace(strings.Replace(strings.Replace(string(b), "}", ",}", -1), "[", "{", -1), "]", ",}", -1), nil
return strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(string(b), "}", ",}"), "[", "{"), "]", ",}"), nil
}
opts.BaseImportFunc = func(tgt string) string {

View file

@ -186,7 +186,7 @@ func shallowValidationLookup(sch GenSchema) bool {
if sch.Required || hasFormatValidation(sch.resolvedType) {
return true
}
if sch.MaxLength != nil || sch.MinLength != nil || sch.Pattern != "" || sch.MultipleOf != nil || sch.Minimum != nil || sch.Maximum != nil || len(sch.Enum) > 0 || len(sch.ItemsEnum) > 0 {
if sch.HasStringValidations() || sch.HasNumberValidations() || sch.HasEnum() || len(sch.ItemsEnum) > 0 || sch.HasObjectValidations() {
return true
}
for _, a := range sch.AllOf {
@ -230,7 +230,7 @@ func makeGenDefinitionHierarchy(name, pkg, container string, schema spec.Schema,
// Check if model is imported from external package using x-go-type
receiver := "m"
// models are resolved in the current package
resolver := newTypeResolver("", specDoc)
resolver := newTypeResolver("", "", specDoc)
resolver.ModelName = name
analyzed := analysis.New(specDoc.Spec())
@ -375,7 +375,7 @@ func makeGenDefinitionHierarchy(name, pkg, container string, schema spec.Schema,
}
func findImports(sch *GenSchema) map[string]string {
imp := map[string]string{}
imp := make(map[string]string, 20)
t := sch.resolvedType
if t.Pkg != "" && t.PkgAlias != "" {
imp[t.PkgAlias] = t.Pkg
@ -404,7 +404,8 @@ func findImports(sch *GenSchema) map[string]string {
}
}
if sch.Properties != nil {
for _, p := range sch.Properties {
for _, props := range sch.Properties {
p := props
sub := findImports(&p)
for k, v := range sub {
imp[k] = v
@ -418,7 +419,8 @@ func findImports(sch *GenSchema) map[string]string {
}
}
if sch.AllOf != nil {
for _, p := range sch.AllOf {
for _, props := range sch.AllOf {
p := props
sub := findImports(&p)
for k, v := range sub {
imp[k] = v
@ -426,8 +428,11 @@ func findImports(sch *GenSchema) map[string]string {
}
}
for k, v := range sch.ExtraImports {
imp[k] = v
if k != "" && v != "" {
imp[k] = v
}
}
return imp
}
@ -621,33 +626,39 @@ func (sg *schemaGenContext) NewAdditionalProperty(schema spec.Schema) *schemaGen
return pg
}
func hasSliceValidations(model *spec.Schema) (hasSliceValidations bool) {
hasSliceValidations = model.MaxItems != nil || model.MinItems != nil || model.UniqueItems || len(model.Enum) > 0
return
func hasContextValidations(model *spec.Schema) bool {
// always assume ref needs context validate
// TODO: find away to determine ref needs context validate or not
if model.ReadOnly || model.Ref.String() != "" {
return true
}
return false
}
func hasValidations(model *spec.Schema, isRequired bool) (hasValidation bool) {
// NOTE: needsValidation has gone deprecated and is replaced by top-level's shallowValidationLookup()
hasNumberValidation := model.Maximum != nil || model.Minimum != nil || model.MultipleOf != nil
hasStringValidation := model.MaxLength != nil || model.MinLength != nil || model.Pattern != ""
hasEnum := len(model.Enum) > 0
func hasValidations(model *spec.Schema, isRequired bool) bool {
if isRequired {
return true
}
v := model.Validations()
if v.HasNumberValidations() || v.HasStringValidations() || v.HasArrayValidations() || v.HasEnum() || v.HasObjectValidations() {
return true
}
// since this was added to deal with discriminator, we'll fix this when testing discriminated types
simpleObject := len(model.Properties) > 0 && model.Discriminator == ""
if len(model.Properties) > 0 && model.Discriminator == "" {
return true
}
// lift validations from allOf branches
hasAllOfValidation := false
for _, s := range model.AllOf {
hasAllOfValidation = hasValidations(&s, false)
hasAllOfValidation = s.Ref.String() != "" || hasAllOfValidation
if hasAllOfValidation {
break
schema := s
if s.Ref.String() != "" || hasValidations(&schema, false) {
return true
}
}
hasValidation = hasNumberValidation || hasStringValidation || hasSliceValidations(model) || hasEnum || simpleObject || hasAllOfValidation || isRequired
return
return false
}
func hasFormatValidation(tpe resolvedType) bool {
@ -660,34 +671,25 @@ func hasFormatValidation(tpe resolvedType) bool {
return false
}
// handleFormatConflicts handles all conflicting model properties when a format is set
func handleFormatConflicts(model *spec.Schema) {
switch model.Format {
case "date", "datetime", "uuid", "bsonobjectid", "base64", "duration":
model.MinLength = nil
model.MaxLength = nil
model.Pattern = ""
// more cases should be inserted here if they arise
}
}
func (sg *schemaGenContext) schemaValidations() sharedValidations {
model := sg.Schema
// resolve any conflicting properties if the model has a format
handleFormatConflicts(&model)
isRequired := sg.Required
if model.Default != nil || model.ReadOnly {
// when readOnly or default is specified, this disables Required validation (Swagger-specific)
isRequired = false
if sg.Required {
log.Printf("warn: properties with a default value or readOnly should not be required [%s]", sg.Name)
}
}
hasSliceValidations := model.MaxItems != nil || model.MinItems != nil || model.UniqueItems || len(model.Enum) > 0
hasValidations := hasValidations(&model, isRequired)
s := sharedValidationsFromSchema(model, sg.Required)
s.HasValidations = hasValidations
s.HasSliceValidations = hasSliceValidations
return s
v := model.Validations()
return sharedValidations{
Required: sg.Required, /* TODO(fred): guard for cases with discriminator field, default and readOnly*/
SchemaValidations: v,
HasSliceValidations: v.HasArrayValidations() || v.HasEnum(),
HasValidations: hasValidations(&model, isRequired),
}
}
func mergeValidation(other *schemaGenContext) bool {
@ -708,6 +710,7 @@ func mergeValidation(other *schemaGenContext) bool {
func (sg *schemaGenContext) MergeResult(other *schemaGenContext, liftsRequired bool) {
sg.GenSchema.HasValidations = sg.GenSchema.HasValidations || mergeValidation(other)
sg.GenSchema.HasContextValidations = sg.GenSchema.HasContextValidations || other.GenSchema.HasContextValidations
if liftsRequired && other.GenSchema.AdditionalProperties != nil && other.GenSchema.AdditionalProperties.Required {
sg.GenSchema.Required = true
@ -743,21 +746,20 @@ func (sg *schemaGenContext) buildProperties() error {
debugLog("building properties %s (parent: %s)", sg.Name, sg.Container)
for k, v := range sg.Schema.Properties {
debugLogAsJSON("building property %s[%q] (tup: %t) (BaseType: %t)",
sg.Name, k, sg.IsTuple, sg.GenSchema.IsBaseType, sg.Schema)
debugLog("property %s[%q] (tup: %t) HasValidations: %t)",
sg.Name, k, sg.IsTuple, sg.GenSchema.HasValidations)
debugLogAsJSON("building property %s[%q] (IsTuple: %t) (IsBaseType: %t) (HasValidations: %t)",
sg.Name, k, sg.IsTuple, sg.GenSchema.IsBaseType, sg.GenSchema.HasValidations, v)
vv := v
// check if this requires de-anonymizing, if so lift this as a new struct and extra schema
tpe, err := sg.TypeResolver.ResolveSchema(&v, true, sg.IsTuple || swag.ContainsStrings(sg.Schema.Required, k))
if sg.Schema.Discriminator == k {
tpe.IsNullable = false
}
tpe, err := sg.TypeResolver.ResolveSchema(&vv, true, sg.IsTuple || swag.ContainsStrings(sg.Schema.Required, k))
if err != nil {
return err
}
if sg.Schema.Discriminator == k {
tpe.IsNullable = false
}
vv := v
var hasValidation bool
if tpe.IsComplexObject && tpe.IsAnonymous && len(v.Properties) > 0 {
// this is an anonymous complex construct: build a new new type for it
@ -904,8 +906,13 @@ func (sg *schemaGenContext) buildProperties() error {
emprop.GenSchema.Extensions = emprop.Schema.Extensions
// set custom serializer tag
if customTag, found := emprop.Schema.Extensions[xGoCustomTag]; found {
emprop.GenSchema.CustomTag = customTag.(string)
if customTag, found := tpe.Extensions[xGoCustomTag]; found {
tagAsStr, ok := customTag.(string)
if ok {
emprop.GenSchema.CustomTag = tagAsStr
} else {
log.Printf("warning: expect %s extension to be a string, got: %v. Skipped", xGoCustomTag, customTag)
}
}
sg.GenSchema.Properties = append(sg.GenSchema.Properties, emprop.GenSchema)
}
@ -926,7 +933,8 @@ func (sg *schemaGenContext) buildAllOf() error {
sg.Container = sg.Name
}
debugLogAsJSON("building all of for %d entries", len(sg.Schema.AllOf), sg.Schema)
for i, sch := range sg.Schema.AllOf {
for i, schema := range sg.Schema.AllOf {
sch := schema
tpe, ert := sg.TypeResolver.ResolveSchema(&sch, sch.Ref.String() == "", false)
if ert != nil {
return ert
@ -1006,7 +1014,12 @@ func (sg *schemaGenContext) buildAllOf() error {
log.Printf("warning: cannot generate serializable allOf with conflicting array definitions in %s", sg.Container)
}
sg.GenSchema.IsNullable = true
// AllOf types are always considered nullable, except when an extension says otherwise
if override, ok := sg.TypeResolver.isNullableOverride(&sg.Schema); ok {
sg.GenSchema.IsNullable = override
} else {
sg.GenSchema.IsNullable = true
}
// prevent IsAliased to bubble up (e.g. when a single branch is itself aliased)
sg.GenSchema.IsAliased = sg.GenSchema.IsAliased && len(sg.GenSchema.AllOf) < 2
@ -1037,7 +1050,7 @@ func newMapStack(context *schemaGenContext) (first, last *mapStack, err error) {
}
if !tpe.IsMap {
//reached the end of the rabbit hole
// reached the end of the rabbit hole
if tpe.IsComplexObject && tpe.IsAnonymous {
// found an anonymous object: create the struct from a newly created definition
nw := l.Context.makeNewStruct(l.Context.makeRefName()+" Anon", *l.Type.AdditionalProperties.Schema)
@ -1047,6 +1060,7 @@ func newMapStack(context *schemaGenContext) (first, last *mapStack, err error) {
l.Type.AdditionalProperties.Schema = sch
l.ValueRef = l.Context.NewAdditionalProperty(*sch)
}
// other cases where to stop are: a $ref or a simple object
break
}
@ -1060,7 +1074,7 @@ func newMapStack(context *schemaGenContext) (first, last *mapStack, err error) {
l = l.Next
}
//return top and bottom entries of this stack of AdditionalProperties
// return top and bottom entries of this stack of AdditionalProperties
return ms, l, nil
}
@ -1442,7 +1456,7 @@ func (sg *schemaGenContext) buildArray() error {
// items from maps of aliased or nullable type remain required
// NOTE(fredbi): since this is reset below, this Required = true serves the obscure purpose
// of indirectly lifting validations from the slice. This is carried on differently now.
// of indirectly lifting validations from the slice. This is carried out differently now.
// elProp.Required = true
if err := elProp.makeGenSchema(); err != nil {
@ -1484,7 +1498,7 @@ func (sg *schemaGenContext) buildArray() error {
// lift validations
sg.GenSchema.HasValidations = sg.GenSchema.HasValidations || schemaCopy.HasValidations
sg.GenSchema.HasSliceValidations = hasSliceValidations(&sg.Schema)
sg.GenSchema.HasSliceValidations = sg.Schema.Validations().HasArrayValidations() || sg.Schema.Validations().HasEnum()
// prevents bubbling custom formatter flag
sg.GenSchema.IsCustomFormatter = false
@ -1516,7 +1530,8 @@ func (sg *schemaGenContext) buildItems() error {
if sg.Named {
sg.GenSchema.Name = sg.Name
sg.GenSchema.GoType = sg.TypeResolver.goTypeName(sg.Name)
for i, s := range sg.Schema.Items.Schemas {
for i, sch := range sg.Schema.Items.Schemas {
s := sch
elProp := sg.NewTupleElement(&s, i)
if s.Ref.String() == "" {
@ -1630,7 +1645,11 @@ func (sg *schemaGenContext) buildAdditionalItems() error {
}
func (sg *schemaGenContext) buildXMLNameWithTags() error {
if sg.WithXML || sg.Schema.XML != nil {
// render some "xml" struct tag under one the following conditions:
// - consumes/produces in spec contains xml
// - struct tags CLI option contains xml
// - XML object present in spec for this schema
if sg.WithXML || swag.ContainsStrings(sg.StructTags, "xml") || sg.Schema.XML != nil {
sg.GenSchema.XMLName = sg.Name
if sg.Schema.XML != nil {
@ -1641,10 +1660,6 @@ func (sg *schemaGenContext) buildXMLNameWithTags() error {
sg.GenSchema.XMLName += ",attr"
}
}
if !sg.GenSchema.Required && sg.GenSchema.IsEmptyOmitted {
sg.GenSchema.XMLName += ",omitempty"
}
}
return nil
}
@ -1730,7 +1745,7 @@ func (sg *schemaGenContext) shortCircuitNamedRef() (bool, error) {
tpe.IsMap = false
tpe.IsArray = false
tpe.IsAnonymous = false
tpe.IsNullable = sg.TypeResolver.IsNullable(&sg.Schema)
tpe.IsNullable = sg.TypeResolver.isNullable(&sg.Schema)
item := sg.NewCompositionBranch(sg.Schema, 0)
if err := item.makeGenSchema(); err != nil {
@ -1759,13 +1774,13 @@ func (sg *schemaGenContext) liftSpecialAllOf() error {
var seenNullable bool
var schemaToLift spec.Schema
for _, sch := range sg.Schema.AllOf {
for _, schema := range sg.Schema.AllOf {
sch := schema
tpe, err := sg.TypeResolver.ResolveSchema(&sch, true, true)
if err != nil {
return err
}
if sg.TypeResolver.IsNullable(&sch) {
if sg.TypeResolver.isNullable(&sch) {
seenNullable = true
}
if len(sch.Type) > 0 || len(sch.Properties) > 0 || sch.Ref.GetURL() != nil || len(sch.AllOf) > 0 {
@ -1839,6 +1854,21 @@ func goName(sch *spec.Schema, orig string) string {
return orig
}
func (sg *schemaGenContext) derefMapElement(outer *GenSchema, sch *GenSchema, elem *GenSchema) {
derefType := strings.TrimPrefix(elem.GoType, "*")
if outer.IsAliased {
nesting := strings.TrimSuffix(strings.TrimSuffix(outer.AliasedType, elem.GoType), "*")
outer.AliasedType = nesting + derefType
outer.GoType = derefType
} else {
nesting := strings.TrimSuffix(strings.TrimSuffix(outer.GoType, elem.GoType), "*")
outer.GoType = nesting + derefType
}
elem.GoType = derefType
}
func (sg *schemaGenContext) checkNeedsPointer(outer *GenSchema, sch *GenSchema, elem *GenSchema) {
derefType := strings.TrimPrefix(elem.GoType, "*")
switch {
@ -1876,10 +1906,21 @@ func (sg *schemaGenContext) buildMapOfNullable(sch *GenSchema) {
// render element of aliased or anonyous map as a pointer
it := elem.Items
for it != nil {
if it.IsPrimitive && it.IsNullable {
switch {
case it.IsPrimitive && it.IsNullable:
sg.checkNeedsPointer(outer, sch, it)
} else if it.IsMap {
case it.IsMap:
sg.buildMapOfNullable(it)
case !it.IsPrimitive && !it.IsArray && it.IsComplexObject && it.IsNullable:
// structs in map are not rendered as pointer by default
// unless some x-nullable overrides says so
_, forced := it.Extensions[xNullable]
if !forced {
_, forced = it.Extensions[xIsNullable]
}
if !forced {
sg.derefMapElement(outer, sch, it)
}
}
it = it.Items
}
@ -1893,12 +1934,18 @@ func (sg *schemaGenContext) makeGenSchema() error {
debugLogAsJSON("making gen schema (anon: %t, req: %t, tuple: %t) %s\n",
!sg.Named, sg.Required, sg.IsTuple, sg.Name, sg.Schema)
ex := ""
sg.GenSchema.Example = ""
if sg.Schema.Example != nil {
ex = fmt.Sprintf("%#v", sg.Schema.Example)
data, err := asJSON(sg.Schema.Example)
if err != nil {
return err
}
// Deleting the unnecessary double quotes for string types
// otherwise the generate spec will generate as "\"foo\""
sg.GenSchema.Example = strings.Trim(data, "\"")
}
sg.GenSchema.ExternalDocs = trimExternalDoc(sg.Schema.ExternalDocs)
sg.GenSchema.IsExported = true
sg.GenSchema.Example = ex
sg.GenSchema.Path = sg.Path
sg.GenSchema.IndexVar = sg.IndexVar
sg.GenSchema.Location = body
@ -1960,12 +2007,37 @@ func (sg *schemaGenContext) makeGenSchema() error {
// include format validations, excluding binary
sg.GenSchema.HasValidations = sg.GenSchema.HasValidations || hasFormatValidation(tpe)
// include context validations
sg.GenSchema.HasContextValidations = sg.GenSchema.HasContextValidations || hasContextValidations(&sg.Schema) && !tpe.IsInterface && !tpe.IsStream && !tpe.SkipExternalValidation
// usage of a polymorphic base type is rendered with getter funcs on private properties.
// In the case of aliased types, the value expression remains unchanged to the receiver.
if tpe.IsArray && tpe.ElemType != nil && tpe.ElemType.IsBaseType && sg.GenSchema.ValueExpression != sg.GenSchema.ReceiverName {
sg.GenSchema.ValueExpression += asMethod
}
if tpe.IsExternal { // anonymous external types
extType, pkg, alias := sg.TypeResolver.knownDefGoType(sg.GenSchema.Name, sg.Schema, sg.TypeResolver.goTypeName)
if pkg != "" && alias != "" {
sg.GenSchema.ExtraImports[alias] = pkg
}
if !tpe.IsEmbedded {
sg.GenSchema.resolvedType = tpe
sg.GenSchema.Required = sg.Required
// assume we validate everything but interface and io.Reader - validation may be disabled by using the noValidation hint
sg.GenSchema.HasValidations = !(tpe.IsInterface || tpe.IsStream || tpe.SkipExternalValidation)
sg.GenSchema.IsAliased = sg.GenSchema.HasValidations
log.Printf("INFO: type %s is external, with inferred spec type %s, referred to as %s", sg.GenSchema.Name, sg.GenSchema.GoType, extType)
sg.GenSchema.GoType = extType
sg.GenSchema.AliasedType = extType
return nil
}
// TODO: case for embedded types as anonymous definitions
return fmt.Errorf("ERROR: inline definitions embedded types are not supported")
}
debugLog("gschema nullable: %t", sg.GenSchema.IsNullable)
if e := sg.buildAdditionalProperties(); e != nil {
return e

View file

@ -193,7 +193,8 @@ func (o *operationGenerator) Generate() error {
operations = append(operations, op)
sort.Sort(operations)
for _, op := range operations {
for _, pp := range operations {
op := pp
if o.GenOpts.DumpData {
_ = dumpData(swag.ToDynamicJSON(op))
continue
@ -312,7 +313,7 @@ func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) {
//
// In all cases, resetting definitions to the _original_ (untransformed) spec is not an option:
// we take from there the spec possibly already transformed by the GenDefinitions stage.
resolver := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(b.ModelsPackage, defaultModelsTarget), b.Doc)
resolver := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(b.ModelsPackage, defaultModelsTarget), b.DefaultImports[b.ModelsPackage], b.Doc)
receiver := "o"
operation := b.Operation
@ -399,6 +400,7 @@ func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) {
defaultResponse = &gr
}
}
// Always render a default response, even when no responses were defined
if operation.Responses == nil || (operation.Responses.Default == nil && len(srs) == 0) {
gr, err := b.MakeResponse(receiver, b.Name+" default", false, resolver, -1, spec.Response{})
@ -408,44 +410,41 @@ func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) {
defaultResponse = &gr
}
if b.Principal == "" {
b.Principal = iface
}
swsp := resolver.Doc.Spec()
var extraSchemes []string
if ess, ok := operation.Extensions.GetStringSlice(xSchemes); ok {
extraSchemes = append(extraSchemes, ess...)
}
if ess1, ok := swsp.Extensions.GetStringSlice(xSchemes); ok {
extraSchemes = concatUnique(ess1, extraSchemes)
}
sort.Strings(extraSchemes)
schemes := concatUnique(swsp.Schemes, operation.Schemes)
sort.Strings(schemes)
schemes, extraSchemes := gatherURISchemes(swsp, operation)
originalSchemes := operation.Schemes
originalExtraSchemes := getExtraSchemes(operation.Extensions)
produces := producesOrDefault(operation.Produces, swsp.Produces, b.DefaultProduces)
sort.Strings(produces)
consumes := producesOrDefault(operation.Consumes, swsp.Consumes, b.DefaultConsumes)
sort.Strings(consumes)
var hasStreamingResponse bool
if defaultResponse != nil && defaultResponse.Schema != nil && defaultResponse.Schema.IsStream {
hasStreamingResponse = true
}
var successResponse *GenResponse
for _, sr := range successResponses {
for _, resp := range successResponses {
sr := resp
if sr.IsSuccess {
successResponse = &sr
break
}
}
for _, sr := range successResponses {
if !hasStreamingResponse && sr.Schema != nil && sr.Schema.IsStream {
hasStreamingResponse = true
break
var hasStreamingResponse bool
if defaultResponse != nil && defaultResponse.Schema != nil && defaultResponse.Schema.IsStream {
hasStreamingResponse = true
}
if !hasStreamingResponse {
for _, sr := range successResponses {
if !hasStreamingResponse && sr.Schema != nil && sr.Schema.IsStream {
hasStreamingResponse = true
break
}
}
}
if !hasStreamingResponse {
for _, r := range responses {
if r.Schema != nil && r.Schema.IsStream {
@ -488,8 +487,9 @@ func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) {
HasBodyParams: hasBodyParams,
HasStreamingResponse: hasStreamingResponse,
Authorized: b.Authed,
Security: b.makeSecurityRequirements(receiver),
Security: b.makeSecurityRequirements(receiver), // resolved security requirements, for codegen
SecurityDefinitions: b.makeSecuritySchemes(receiver),
SecurityRequirements: securityRequirements(operation.Security), // raw security requirements, for doc
Principal: b.Principal,
Responses: responses,
DefaultResponse: defaultResponse,
@ -497,12 +497,19 @@ func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) {
SuccessResponses: successResponses,
ExtraSchemas: gatherExtraSchemas(b.ExtraSchemas),
Schemes: schemeOrDefault(schemes, b.DefaultScheme),
ProducesMediaTypes: produces,
ConsumesMediaTypes: consumes,
ExtraSchemes: extraSchemes,
SchemeOverrides: originalSchemes, // raw operation schemes, for doc
ProducesMediaTypes: produces, // resolved produces, for codegen
ConsumesMediaTypes: consumes, // resolved consumes, for codegen
Produces: operation.Produces, // for doc
Consumes: operation.Consumes, // for doc
ExtraSchemes: extraSchemes, // resolved schemes, for codegen
ExtraSchemeOverrides: originalExtraSchemes, // raw operation extra schemes, for doc
TimeoutName: timeoutName,
Extensions: operation.Extensions,
StrictResponders: b.GenOpts.StrictResponders,
PrincipalIsNullable: b.GenOpts.PrincipalIsNullable(),
ExternalDocs: trimExternalDoc(operation.ExternalDocs),
}, nil
}
@ -523,26 +530,15 @@ func schemeOrDefault(schemes []string, defaultScheme string) []string {
return schemes
}
func concatUnique(collections ...[]string) []string {
resultSet := make(map[string]struct{})
for _, c := range collections {
for _, i := range c {
if _, ok := resultSet[i]; !ok {
resultSet[i] = struct{}{}
}
}
}
var result []string
for k := range resultSet {
result = append(result, k)
}
return result
}
func (b *codeGenOpBuilder) MakeResponse(receiver, name string, isSuccess bool, resolver *typeResolver, code int, resp spec.Response) (GenResponse, error) {
debugLog("[%s %s] making id %q", b.Method, b.Path, b.Operation.ID)
// assume minimal flattening has been carried on, so there is not $ref in response (but some may remain in response schema)
examples := make(GenResponseExamples, 0, len(resp.Examples))
for k, v := range resp.Examples {
examples = append(examples, GenResponseExample{MediaType: k, Example: v})
}
sort.Sort(examples)
res := GenResponse{
Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, defaultOperationsTarget),
@ -559,6 +555,7 @@ func (b *codeGenOpBuilder) MakeResponse(receiver, name string, isSuccess bool, r
Extensions: resp.Extensions,
StrictResponders: b.GenOpts.StrictResponders,
OperationName: b.Name,
Examples: examples,
}
// prepare response headers
@ -583,26 +580,29 @@ func (b *codeGenOpBuilder) MakeResponse(receiver, name string, isSuccess bool, r
}
func (b *codeGenOpBuilder) MakeHeader(receiver, name string, hdr spec.Header) (GenHeader, error) {
tpe := typeForHeader(hdr) //simpleResolvedType(hdr.Type, hdr.Format, hdr.Items)
tpe := simpleResolvedType(hdr.Type, hdr.Format, hdr.Items, &hdr.CommonValidations)
id := swag.ToGoName(name)
res := GenHeader{
sharedValidations: sharedValidationsFromSimple(hdr.CommonValidations, true), // NOTE: Required is not defined by the Swagger schema for header. Set arbitrarily to true for convenience in templates.
resolvedType: tpe,
Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, defaultOperationsTarget),
ReceiverName: receiver,
ID: id,
Name: name,
Path: fmt.Sprintf("%q", name),
ValueExpression: fmt.Sprintf("%s.%s", receiver, id),
Description: trimBOM(hdr.Description),
Default: hdr.Default,
HasDefault: hdr.Default != nil,
Converter: stringConverters[tpe.GoType],
Formatter: stringFormatters[tpe.GoType],
ZeroValue: tpe.Zero(),
CollectionFormat: hdr.CollectionFormat,
IndexVar: "i",
sharedValidations: sharedValidations{
Required: true,
SchemaValidations: hdr.Validations(), // NOTE: Required is not defined by the Swagger schema for header. Set arbitrarily to true for convenience in templates.
},
resolvedType: tpe,
Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, defaultOperationsTarget),
ReceiverName: receiver,
ID: id,
Name: name,
Path: fmt.Sprintf("%q", name),
ValueExpression: fmt.Sprintf("%s.%s", receiver, id),
Description: trimBOM(hdr.Description),
Default: hdr.Default,
HasDefault: hdr.Default != nil,
Converter: stringConverters[tpe.GoType],
Formatter: stringFormatters[tpe.GoType],
ZeroValue: tpe.Zero(),
CollectionFormat: hdr.CollectionFormat,
IndexVar: "i",
}
res.HasValidations, res.HasSliceValidations = b.HasValidations(hdr.CommonValidations, res.resolvedType)
@ -625,8 +625,12 @@ func (b *codeGenOpBuilder) MakeHeader(receiver, name string, hdr spec.Header) (G
func (b *codeGenOpBuilder) MakeHeaderItem(receiver, paramName, indexVar, path, valueExpression string, items, parent *spec.Items) (GenItems, error) {
var res GenItems
res.resolvedType = simpleResolvedType(items.Type, items.Format, items.Items)
res.sharedValidations = sharedValidationsFromSimple(items.CommonValidations, false)
res.resolvedType = simpleResolvedType(items.Type, items.Format, items.Items, &items.CommonValidations)
res.sharedValidations = sharedValidations{
Required: false,
SchemaValidations: items.Validations(),
}
res.Name = paramName
res.Path = path
res.Location = "header"
@ -656,18 +660,20 @@ func (b *codeGenOpBuilder) MakeHeaderItem(receiver, paramName, indexVar, path, v
// HasValidations resolves the validation status for simple schema objects
func (b *codeGenOpBuilder) HasValidations(sh spec.CommonValidations, rt resolvedType) (hasValidations bool, hasSliceValidations bool) {
hasNumberValidation := sh.Maximum != nil || sh.Minimum != nil || sh.MultipleOf != nil
hasStringValidation := sh.MaxLength != nil || sh.MinLength != nil || sh.Pattern != ""
hasSliceValidations = sh.MaxItems != nil || sh.MinItems != nil || sh.UniqueItems || len(sh.Enum) > 0
hasValidations = hasNumberValidation || hasStringValidation || hasSliceValidations || hasFormatValidation(rt)
hasSliceValidations = sh.HasArrayValidations() || sh.HasEnum()
hasValidations = sh.HasNumberValidations() || sh.HasStringValidations() || hasSliceValidations || hasFormatValidation(rt)
return
}
func (b *codeGenOpBuilder) MakeParameterItem(receiver, paramName, indexVar, path, valueExpression, location string, resolver *typeResolver, items, parent *spec.Items) (GenItems, error) {
debugLog("making parameter item recv=%s param=%s index=%s valueExpr=%s path=%s location=%s", receiver, paramName, indexVar, valueExpression, path, location)
var res GenItems
res.resolvedType = simpleResolvedType(items.Type, items.Format, items.Items)
res.sharedValidations = sharedValidationsFromSimple(items.CommonValidations, false)
res.resolvedType = simpleResolvedType(items.Type, items.Format, items.Items, &items.CommonValidations)
res.sharedValidations = sharedValidations{
Required: false,
SchemaValidations: items.Validations(),
}
res.Name = paramName
res.Path = path
res.Location = location
@ -741,8 +747,11 @@ func (b *codeGenOpBuilder) MakeParameter(receiver string, resolver *typeResolver
}
} else {
// Process parameters declared in other inputs: path, query, header (SimpleSchema)
res.resolvedType = simpleResolvedType(param.Type, param.Format, param.Items)
res.sharedValidations = sharedValidationsFromSimple(param.CommonValidations, param.Required)
res.resolvedType = simpleResolvedType(param.Type, param.Format, param.Items, &param.CommonValidations)
res.sharedValidations = sharedValidations{
Required: param.Required,
SchemaValidations: param.Validations(),
}
res.ZeroValue = res.resolvedType.Zero()
@ -960,7 +969,7 @@ func (b *codeGenOpBuilder) setBodyParamValidation(p *GenParameter) {
// makeSecuritySchemes produces a sorted list of security schemes for this operation
func (b *codeGenOpBuilder) makeSecuritySchemes(receiver string) GenSecuritySchemes {
return gatherSecuritySchemes(b.SecurityDefinitions, b.Name, b.Principal, receiver)
return gatherSecuritySchemes(b.SecurityDefinitions, b.Name, b.Principal, receiver, b.GenOpts.PrincipalIsNullable())
}
// makeSecurityRequirements produces a sorted list of security requirements for this operation.
@ -1006,7 +1015,7 @@ func (b *codeGenOpBuilder) saveResolveContext(resolver *typeResolver, schema *sp
if b.PristineDoc == nil {
b.PristineDoc = b.Doc.Pristine()
}
rslv := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, defaultModelsTarget), b.PristineDoc)
rslv := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, defaultModelsTarget), b.DefaultImports[b.ModelsPackage], b.PristineDoc)
return rslv, b.cloneSchema(schema)
}
@ -1026,7 +1035,7 @@ func (b *codeGenOpBuilder) liftExtraSchemas(resolver, rslv *typeResolver, bs *sp
pkg := b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, defaultModelsTarget)
// make a resolver for current package (i.e. operations)
pg.TypeResolver = newTypeResolver("", rslv.Doc).withKeepDefinitionsPackage(pkg)
pg.TypeResolver = newTypeResolver("", b.DefaultImports[b.APIPackage], rslv.Doc).withKeepDefinitionsPackage(pkg)
pg.ExtraSchemas = make(map[string]GenSchema, len(sc.ExtraSchemas))
pg.UseContainerInName = true
@ -1062,7 +1071,10 @@ func (b *codeGenOpBuilder) buildOperationSchema(schemaPath, containerName, schem
if sch == nil {
sch = &spec.Schema{}
}
rslv := resolver
shallowClonedResolver := *resolver
shallowClonedResolver.ModelsFullPkg = b.DefaultImports[b.ModelsPackage]
rslv := &shallowClonedResolver
sc := schemaGenContext{
Path: schemaPath,
Name: containerName,
@ -1203,7 +1215,7 @@ func (b *codeGenOpBuilder) analyzeTags() (string, []string, bool) {
}
}
if tag == b.APIPackage {
// confict with "operations" package is handled separately
// conflict with "operations" package is handled separately
tag = renameOperationPackage(intersected, tag)
}
b.APIPackage = b.GenOpts.LanguageOpts.ManglePackageName(tag, b.APIPackage) // actual package name
@ -1238,11 +1250,14 @@ func deconflictPrincipal(pkg string) string {
// deconflictPkg renames package names which conflict with standard imports
func deconflictPkg(pkg string, renamer func(string) string) string {
switch pkg {
case "api", "httptransport", "formats":
// package conflict with variables
case "api", "httptransport", "formats", "server":
fallthrough
// package conflict with go-openapi imports
case "errors", "runtime", "middleware", "security", "spec", "strfmt", "loads", "swag", "validate":
fallthrough
case "tls", "http", "fmt", "strings", "log":
// package conflict with stdlib/other lib imports
case "tls", "http", "fmt", "strings", "log", "flags", "pflag", "json", "time":
return renamer(pkg)
}
return pkg
@ -1260,5 +1275,16 @@ func renameOperationPackage(seenTags []string, pkg string) string {
}
func renamePrincipalPackage(pkg string) string {
// favors readability over perfect deconfliction
return "auth"
}
func renameServerPackage(pkg string) string {
// favors readability over perfect deconfliction
return "swagger" + pkg + "srv"
}
func renameAPIPackage(pkg string) string {
// favors readability over perfect deconfliction
return "swagger" + pkg
}

View file

@ -197,6 +197,29 @@ func DefaultSectionOpts(gen *GenOpts) {
}
// MarkdownOpts for rendering a spec as markdown
func MarkdownOpts() *LanguageOpts {
opts := &LanguageOpts{}
opts.Init()
return opts
}
// MarkdownSectionOpts for a given opts and output file.
func MarkdownSectionOpts(gen *GenOpts, output string) {
gen.Sections.Models = nil
gen.Sections.OperationGroups = nil
gen.Sections.Operations = nil
gen.LanguageOpts = MarkdownOpts()
gen.Sections.Application = []TemplateOpts{
{
Name: "markdowndocs",
Source: "asset:markdownDocs",
Target: filepath.Dir(output),
FileName: filepath.Base(output),
},
}
}
// TemplateOpts allows for codegen customization
type TemplateOpts struct {
Name string `mapstructure:"name"`
@ -241,6 +264,7 @@ type GenOpts struct {
ServerPackage string
ClientPackage string
Principal string
PrincipalCustomIface bool // user-provided interface for Principal (non-nullable)
Target string
Sections SectionOpts
LanguageOpts *LanguageOpts
@ -268,6 +292,8 @@ type GenOpts struct {
AllowEnumCI bool
StrictResponders bool
AcceptDefinitionsOnly bool
templates *Repository // a shallow clone of the global template repository
}
// CheckOpts carries out some global consistency checks on options.
@ -362,12 +388,23 @@ func (g *GenOpts) SpecPath() string {
return specRel
}
// PrincipalIsNullable indicates whether the principal type used for authentication
// may be used as a pointer
func (g *GenOpts) PrincipalIsNullable() bool {
debugLog("Principal: %s, %t, isnullable: %t", g.Principal, g.PrincipalCustomIface, g.Principal != iface && !g.PrincipalCustomIface)
return g.Principal != iface && !g.PrincipalCustomIface
}
// EnsureDefaults for these gen opts
func (g *GenOpts) EnsureDefaults() error {
if g.defaultsEnsured {
return nil
}
g.templates = templates.ShallowClone()
g.templates.LoadDefaults()
if g.LanguageOpts == nil {
g.LanguageOpts = DefaultLanguageFunc()
}
@ -401,6 +438,7 @@ func (g *GenOpts) EnsureDefaults() error {
if g.Principal == "" {
g.Principal = iface
g.PrincipalCustomIface = false
}
g.defaultsEnsured = true
@ -426,7 +464,9 @@ func (g *GenOpts) location(t *TemplateOpts, data interface{}) (string, string, e
var tags []string
tagsF := v.FieldByName("Tags")
if tagsF.IsValid() {
tags = tagsF.Interface().([]string)
if tt, ok := tagsF.Interface().([]string); ok {
tags = tt
}
}
var useTags bool
@ -482,7 +522,7 @@ func (g *GenOpts) render(t *TemplateOpts, data interface{}) ([]byte, error) {
var templ *template.Template
if strings.HasPrefix(strings.ToLower(t.Source), "asset:") {
tt, err := templates.Get(strings.TrimPrefix(t.Source, "asset:"))
tt, err := g.templates.Get(strings.TrimPrefix(t.Source, "asset:"))
if err != nil {
return nil, err
}
@ -492,7 +532,7 @@ func (g *GenOpts) render(t *TemplateOpts, data interface{}) ([]byte, error) {
if templ == nil {
// try to load from repository (and enable dependencies)
name := swag.ToJSONName(strings.TrimSuffix(t.Source, ".gotmpl"))
tt, err := templates.Get(name)
tt, err := g.templates.Get(name)
if err == nil {
templ = tt
}
@ -611,7 +651,8 @@ func (g *GenOpts) shouldRenderOperations() bool {
func (g *GenOpts) renderApplication(app *GenApp) error {
log.Printf("rendering %d templates for application %s", len(g.Sections.Application), app.Name)
for _, templ := range g.Sections.Application {
for _, tp := range g.Sections.Application {
templ := tp
if !g.shouldRenderApp(&templ, app) {
continue
}
@ -624,7 +665,8 @@ func (g *GenOpts) renderApplication(app *GenApp) error {
func (g *GenOpts) renderOperationGroup(gg *GenOperationGroup) error {
log.Printf("rendering %d templates for operation group %s", len(g.Sections.OperationGroups), g.Name)
for _, templ := range g.Sections.OperationGroups {
for _, tp := range g.Sections.OperationGroups {
templ := tp
if !g.shouldRenderOperations() {
continue
}
@ -638,7 +680,8 @@ func (g *GenOpts) renderOperationGroup(gg *GenOperationGroup) error {
func (g *GenOpts) renderOperation(gg *GenOperation) error {
log.Printf("rendering %d templates for operation %s", len(g.Sections.Operations), g.Name)
for _, templ := range g.Sections.Operations {
for _, tp := range g.Sections.Operations {
templ := tp
if !g.shouldRenderOperations() {
continue
}
@ -652,7 +695,8 @@ func (g *GenOpts) renderOperation(gg *GenOperation) error {
func (g *GenOpts) renderDefinition(gg *GenDefinition) error {
log.Printf("rendering %d templates for model %s", len(g.Sections.Models), gg.Name)
for _, templ := range g.Sections.Models {
for _, tp := range g.Sections.Models {
templ := tp
if !g.IncludeModel {
continue
}
@ -665,20 +709,18 @@ func (g *GenOpts) renderDefinition(gg *GenDefinition) error {
}
func (g *GenOpts) setTemplates() error {
templates.LoadDefaults()
if g.Template != "" {
// set contrib templates
if err := templates.LoadContrib(g.Template); err != nil {
if err := g.templates.LoadContrib(g.Template); err != nil {
return err
}
}
templates.SetAllowOverride(g.AllowTemplateOverride)
g.templates.SetAllowOverride(g.AllowTemplateOverride)
if g.TemplateDir != "" {
// set custom templates
if err := templates.LoadDir(g.TemplateDir); err != nil {
if err := g.templates.LoadDir(g.TemplateDir); err != nil {
return err
}
}
@ -690,27 +732,33 @@ func (g *GenOpts) defaultImports() map[string]string {
baseImport := g.LanguageOpts.baseImport(g.Target)
defaultImports := make(map[string]string, 50)
var modelsAlias, importPath string
if g.ExistingModels == "" {
// generated models
importPath := path.Join(
importPath = path.Join(
baseImport,
g.LanguageOpts.ManglePackagePath(g.ModelPackage, defaultModelsTarget))
defaultImports[g.LanguageOpts.ManglePackageName(g.ModelPackage, defaultModelsTarget)] = importPath
modelsAlias = g.LanguageOpts.ManglePackageName(g.ModelPackage, defaultModelsTarget)
} else {
// external models
importPath := g.LanguageOpts.ManglePackagePath(g.ExistingModels, "")
defaultImports["models"] = importPath
importPath = g.LanguageOpts.ManglePackagePath(g.ExistingModels, "")
modelsAlias = path.Base(defaultModelsTarget)
}
defaultImports[modelsAlias] = importPath
// resolve model representing an authenticated principal
alias, _, target := g.resolvePrincipal()
if alias == "" || target == g.ModelPackage || path.Base(target) == modelsAlias {
// if principal is specified with the models generation package, do not import any extra package
return defaultImports
}
alias, _, target := g.resolvePrincipal()
if alias != "" {
if pth, _ := path.Split(target); pth != "" {
// if principal is specified with an path, generate this import
defaultImports[alias] = target
} else {
// if principal is specified with a relative path, assume it is located in generated target
defaultImports[alias] = path.Join(baseImport, target)
}
if pth, _ := path.Split(target); pth != "" {
// if principal is specified with a path, assume this is a fully qualified package and generate this import
defaultImports[alias] = target
} else {
// if principal is specified with a relative path (no "/", e.g. internal.Principal), assume it is located in generated target
defaultImports[alias] = path.Join(baseImport, target)
}
return defaultImports
}
@ -873,16 +921,18 @@ func trimBOM(in string) string {
}
// gatherSecuritySchemes produces a sorted representation from a map of spec security schemes
func gatherSecuritySchemes(securitySchemes map[string]spec.SecurityScheme, appName, principal, receiver string) (security GenSecuritySchemes) {
func gatherSecuritySchemes(securitySchemes map[string]spec.SecurityScheme, appName, principal, receiver string, nullable bool) (security GenSecuritySchemes) {
for scheme, req := range securitySchemes {
isOAuth2 := strings.ToLower(req.Type) == "oauth2"
var scopes []string
scopes := make([]string, 0, len(req.Scopes))
genScopes := make([]GenSecurityScope, 0, len(req.Scopes))
if isOAuth2 {
for k := range req.Scopes {
for k, v := range req.Scopes {
scopes = append(scopes, k)
genScopes = append(genScopes, GenSecurityScope{Name: k, Description: v})
}
sort.Strings(scopes)
}
sort.Strings(scopes)
security = append(security, GenSecurityScheme{
AppName: appName,
@ -893,6 +943,7 @@ func gatherSecuritySchemes(securitySchemes map[string]spec.SecurityScheme, appNa
IsAPIKeyAuth: strings.ToLower(req.Type) == "apikey",
IsOAuth2: isOAuth2,
Scopes: scopes,
ScopesDesc: genScopes,
Principal: principal,
Source: req.In,
// from original spec
@ -903,12 +954,26 @@ func gatherSecuritySchemes(securitySchemes map[string]spec.SecurityScheme, appNa
AuthorizationURL: req.AuthorizationURL,
TokenURL: req.TokenURL,
Extensions: req.Extensions,
PrincipalIsNullable: nullable,
})
}
sort.Sort(security)
return
}
// securityRequirements just clones the original SecurityRequirements from either the spec
// or an operation, without any modification. This is used to generate documentation.
func securityRequirements(orig []map[string][]string) (result []analysis.SecurityRequirement) {
for _, r := range orig {
for k, v := range r {
result = append(result, analysis.SecurityRequirement{Name: k, Scopes: v})
}
}
// TODO(fred): sort this for stable generation
return
}
// gatherExtraSchemas produces a sorted list of extra schemas.
//
// ExtraSchemas are inlined types rendered in the same model file.
@ -927,42 +992,23 @@ func gatherExtraSchemas(extraMap map[string]GenSchema) (extras GenSchemaList) {
return
}
func sharedValidationsFromSimple(v spec.CommonValidations, isRequired bool) (sh sharedValidations) {
sh = sharedValidations{
Required: isRequired,
Maximum: v.Maximum,
ExclusiveMaximum: v.ExclusiveMaximum,
Minimum: v.Minimum,
ExclusiveMinimum: v.ExclusiveMinimum,
MaxLength: v.MaxLength,
MinLength: v.MinLength,
Pattern: v.Pattern,
MaxItems: v.MaxItems,
MinItems: v.MinItems,
UniqueItems: v.UniqueItems,
MultipleOf: v.MultipleOf,
Enum: v.Enum,
func getExtraSchemes(ext spec.Extensions) []string {
if ess, ok := ext.GetStringSlice(xSchemes); ok {
return ess
}
return
return nil
}
func sharedValidationsFromSchema(v spec.Schema, isRequired bool) (sh sharedValidations) {
sh = sharedValidations{
Required: isRequired,
Maximum: v.Maximum,
ExclusiveMaximum: v.ExclusiveMaximum,
Minimum: v.Minimum,
ExclusiveMinimum: v.ExclusiveMinimum,
MaxLength: v.MaxLength,
MinLength: v.MinLength,
Pattern: v.Pattern,
MaxItems: v.MaxItems,
MinItems: v.MinItems,
UniqueItems: v.UniqueItems,
MultipleOf: v.MultipleOf,
Enum: v.Enum,
}
return
func gatherURISchemes(swsp *spec.Swagger, operation spec.Operation) ([]string, []string) {
var extraSchemes []string
extraSchemes = append(extraSchemes, getExtraSchemes(operation.Extensions)...)
extraSchemes = concatUnique(getExtraSchemes(swsp.Extensions), extraSchemes)
sort.Strings(extraSchemes)
schemes := concatUnique(swsp.Schemes, operation.Schemes)
sort.Strings(schemes)
return schemes, extraSchemes
}
func dumpData(data interface{}) error {
@ -978,3 +1024,20 @@ func importAlias(pkg string) string {
_, k := path.Split(pkg)
return k
}
// concatUnique concatenate collections of strings with deduplication
func concatUnique(collections ...[]string) []string {
resultSet := make(map[string]struct{})
for _, c := range collections {
for _, i := range c {
if _, ok := resultSet[i]; !ok {
resultSet[i] = struct{}{}
}
}
}
result := make([]string, 0, len(resultSet))
for k := range resultSet {
result = append(result, k)
}
return result
}

View file

@ -119,7 +119,7 @@ func (g *GenOpts) printFlattenOpts() {
log.Printf("preprocessing spec with option: %s", preprocessingOption)
}
//findSwaggerSpec fetches a default swagger spec if none is provided
// findSwaggerSpec fetches a default swagger spec if none is provided
func findSwaggerSpec(nm string) (string, error) {
specs := []string{"swagger.json", "swagger.yml", "swagger.yaml"}
if nm != "" {
@ -168,7 +168,7 @@ func WithAutoXOrder(specPath string) string {
for i, prop := range props {
if pSlice, ok := prop.Value.(yaml.MapSlice); ok {
isObject := false
xOrderIndex := -1 //Find if x-order already exists
xOrderIndex := -1 // find if x-order already exists
for i, v := range pSlice {
if v.Key == "type" && v.Value == object {
@ -180,7 +180,7 @@ func WithAutoXOrder(specPath string) string {
}
}
if xOrderIndex > -1 { //Override existing x-order
if xOrderIndex > -1 { // override existing x-order
pSlice[xOrderIndex] = yaml.MapItem{Key: xOrder, Value: i}
} else { // append new x-order
pSlice = append(pSlice, yaml.MapItem{Key: xOrder, Value: i})

View file

@ -8,6 +8,7 @@ import (
"strconv"
"strings"
"github.com/go-openapi/analysis"
"github.com/go-openapi/spec"
)
@ -94,6 +95,106 @@ type GenSchema struct {
WantsMarshalBinary bool // do we generate MarshalBinary interface?
StructTags []string
ExtraImports map[string]string // non-standard imports detected when using external types
ExternalDocs *spec.ExternalDocumentation
}
func (g GenSchema) renderMarshalTag() string {
if g.HasBaseType {
return "-"
}
var result strings.Builder
result.WriteString(g.OriginalName)
if !g.Required && g.IsEmptyOmitted {
result.WriteString(",omitempty")
}
if g.IsJSONString {
result.WriteString(",string")
}
return result.String()
}
// PrintTags takes care of rendering tags for a struct field
func (g GenSchema) PrintTags() string {
tags := make(map[string]string, 3)
orderedTags := make([]string, 0, 3)
tags["json"] = g.renderMarshalTag()
orderedTags = append(orderedTags, "json")
if len(g.XMLName) > 0 {
if !g.Required && g.IsEmptyOmitted {
tags["xml"] = g.XMLName + ",omitempty"
} else {
tags["xml"] = g.XMLName
}
orderedTags = append(orderedTags, "xml")
}
// Add extra struct tags, only if the tag hasn't already been set, i.e. example.
// Extra struct tags have the same value has the `json` tag.
for _, tag := range g.StructTags {
if _, exists := tags[tag]; exists {
// dedupe
continue
}
if tag == "example" && len(g.Example) > 0 {
// only add example tag if it's contained in the struct tags
tags["example"] = g.Example // json representation of the example object
} else {
tags[tag] = tags["json"]
}
orderedTags = append(orderedTags, tag)
}
// Assemble the tags in key value pairs with the value properly quoted.
kvPairs := make([]string, 0, len(orderedTags)+1)
for _, key := range orderedTags {
kvPairs = append(kvPairs, fmt.Sprintf("%s:%s", key, strconv.Quote(tags[key])))
}
if len(g.CustomTag) > 0 {
kvPairs = append(kvPairs, g.CustomTag)
}
// Join the key value pairs by a space.
completeTag := strings.Join(kvPairs, " ")
// If the values contain a backtick, we cannot render the tag using backticks because Go does not support
// escaping backticks in raw string literals.
valuesHaveBacktick := false
for _, value := range tags {
if !strconv.CanBackquote(value) {
valuesHaveBacktick = true
break
}
}
if !valuesHaveBacktick {
return fmt.Sprintf("`%s`", completeTag)
}
// We have to escape the tag again to put it in a literal with double quotes as the tag format uses double quotes.
return strconv.Quote(completeTag)
}
// UnderlyingType tells the go type or the aliased go type
func (g GenSchema) UnderlyingType() string {
if g.IsAliased {
return g.AliasedType
}
return g.GoType
}
// ToString returns a string conversion expression for the schema
func (g GenSchema) ToString() string {
return g.resolvedType.ToString(g.ValueExpression)
}
func (g GenSchemaList) Len() int { return len(g) }
@ -122,32 +223,13 @@ func (g GenSchemaList) Less(i, j int) bool {
}
type sharedValidations struct {
HasValidations bool
Required bool
spec.SchemaValidations
// String validations
MaxLength *int64
MinLength *int64
Pattern string
// Number validations
MultipleOf *float64
Minimum *float64
Maximum *float64
ExclusiveMinimum bool
ExclusiveMaximum bool
Enum []interface{}
ItemsEnum []interface{}
// Slice validations
MinItems *int64
MaxItems *int64
UniqueItems bool
HasSliceValidations bool
// Not used yet (perhaps intended for maxProperties, minProperties validations?)
NeedsSize bool
HasValidations bool
HasContextValidations bool
Required bool
HasSliceValidations bool
ItemsEnum []interface{}
// NOTE: "patternProperties" and "dependencies" not supported by Swagger 2.0
}
@ -176,6 +258,20 @@ type GenResponse struct {
StrictResponders bool
OperationName string
Examples GenResponseExamples
}
// GenResponseExamples is a sortable collection []GenResponseExample
type GenResponseExamples []GenResponseExample
func (g GenResponseExamples) Len() int { return len(g) }
func (g GenResponseExamples) Swap(i, j int) { g[i], g[j] = g[j], g[i] }
func (g GenResponseExamples) Less(i, j int) bool { return g[i].MediaType < g[j].MediaType }
// GenResponseExample captures an example provided for a response for some mime type
type GenResponseExample struct {
MediaType string
Example interface{}
}
// GenHeader represents a header on a response for code generation
@ -210,11 +306,16 @@ type GenHeader struct {
// ItemsDepth returns a string "items.items..." with as many items as the level of nesting of the array.
// For a header objects it always returns "".
func (g *GenHeader) ItemsDepth() string {
func (h *GenHeader) ItemsDepth() string {
// NOTE: this is currently used by templates to generate explicit comments in nested structures
return ""
}
// ToString returns a string conversion expression for the header
func (h GenHeader) ToString() string {
return h.resolvedType.ToString(h.ValueExpression)
}
// GenHeaders is a sorted collection of headers for codegen
type GenHeaders []GenHeader
@ -260,8 +361,8 @@ type GenParameter struct {
Child *GenItems
Parent *GenItems
/// Unused
//BodyParam *GenParameter
// Unused
// BodyParam *GenParameter
Default interface{}
HasDefault bool
@ -323,6 +424,16 @@ func (g *GenParameter) ItemsDepth() string {
return ""
}
// UnderlyingType tells the go type or the aliased go type
func (g GenParameter) UnderlyingType() string {
return g.GoType
}
// ToString returns a string conversion expression for the parameter
func (g GenParameter) ToString() string {
return g.resolvedType.ToString(g.ValueExpression)
}
// GenParameters represents a sorted parameter collection
type GenParameters []GenParameter
@ -377,6 +488,16 @@ func (g *GenItems) ItemsDepth() string {
return strings.Repeat("items.", i)
}
// UnderlyingType tells the go type or the aliased go type
func (g GenItems) UnderlyingType() string {
return g.GoType
}
// ToString returns a string conversion expression for the item
func (g GenItems) ToString() string {
return g.resolvedType.ToString(g.ValueExpression)
}
// GenOperationGroup represents a named (tagged) group of operations
type GenOperationGroup struct {
GenCommon
@ -470,10 +591,12 @@ type GenOperation struct {
ExtraSchemas GenSchemaList
PackageAlias string
Authorized bool
Security []GenSecurityRequirements
SecurityDefinitions GenSecuritySchemes
Principal string
Authorized bool
Security []GenSecurityRequirements // resolved security requirements for the operation
SecurityDefinitions GenSecuritySchemes
SecurityRequirements []analysis.SecurityRequirement // original security requirements as per the spec (for doc)
Principal string
PrincipalIsNullable bool
SuccessResponse *GenResponse
SuccessResponses []GenResponse
@ -494,15 +617,20 @@ type GenOperation struct {
HasBodyParams bool
HasStreamingResponse bool
Schemes []string
ExtraSchemes []string
ProducesMediaTypes []string
ConsumesMediaTypes []string
TimeoutName string
Schemes []string
ExtraSchemes []string
SchemeOverrides []string // original scheme overrides for operation, as per spec (for doc)
ExtraSchemeOverrides []string // original extra scheme overrides for operation, as per spec (for doc)
ProducesMediaTypes []string
ConsumesMediaTypes []string
TimeoutName string
Extensions map[string]interface{}
StrictResponders bool
ExternalDocs *spec.ExternalDocumentation
Produces []string // original produces for operation (for doc)
Consumes []string // original consumes for operation (for doc)
}
// GenOperations represents a list of operations to generate
@ -517,28 +645,33 @@ func (g GenOperations) Swap(i, j int) { g[i], g[j] = g[j], g[i] }
// from a swagger spec
type GenApp struct {
GenCommon
APIPackage string
Package string
ReceiverName string
Name string
Principal string
DefaultConsumes string
DefaultProduces string
Host string
BasePath string
Info *spec.Info
ExternalDocs *spec.ExternalDocumentation
Imports map[string]string
DefaultImports map[string]string
Schemes []string
ExtraSchemes []string
Consumes GenSerGroups
Produces GenSerGroups
SecurityDefinitions GenSecuritySchemes
Models []GenDefinition
Operations GenOperations
OperationGroups GenOperationGroups
SwaggerJSON string
APIPackage string
ServerPackageAlias string
APIPackageAlias string
Package string
ReceiverName string
Name string
Principal string
PrincipalIsNullable bool
DefaultConsumes string
DefaultProduces string
Host string
BasePath string
Info *spec.Info
ExternalDocs *spec.ExternalDocumentation
Tags []spec.Tag
Imports map[string]string
DefaultImports map[string]string
Schemes []string
ExtraSchemes []string
Consumes GenSerGroups
Produces GenSerGroups
SecurityDefinitions GenSecuritySchemes
SecurityRequirements []analysis.SecurityRequirement // original security requirements as per the spec (for doc)
Models []GenDefinition
Operations GenOperations
OperationGroups GenOperationGroups
SwaggerJSON string
// Embedded specs: this is important for when the generated server adds routes.
// NOTE: there is a distinct advantage to having this in runtime rather than generated code.
// We are not ever going to generate the router.
@ -613,16 +746,18 @@ type GenSerializer struct {
// GenSecurityScheme represents a security scheme for code generation
type GenSecurityScheme struct {
AppName string
ID string
Name string
ReceiverName string
IsBasicAuth bool
IsAPIKeyAuth bool
IsOAuth2 bool
Scopes []string
Source string
Principal string
AppName string
ID string
Name string
ReceiverName string
IsBasicAuth bool
IsAPIKeyAuth bool
IsOAuth2 bool
Scopes []string
Source string
Principal string
PrincipalIsNullable bool
// from spec.SecurityScheme
Description string
Type string
@ -631,6 +766,7 @@ type GenSecurityScheme struct {
AuthorizationURL string
TokenURL string
Extensions map[string]interface{}
ScopesDesc []GenSecurityScope
}
// GenSecuritySchemes sorted representation of serializers
@ -646,6 +782,12 @@ type GenSecurityRequirement struct {
Scopes []string
}
// GenSecurityScope represents a scope descriptor for an OAuth2 security scheme
type GenSecurityScope struct {
Name string
Description string
}
// GenSecurityRequirements represents a compounded security requirement specification.
// In a []GenSecurityRequirements complete requirements specification,
// outer elements are interpreted as optional requirements (OR), and

View file

@ -49,6 +49,25 @@ func GenerateSupport(name string, modelNames, operationIDs []string, opts *GenOp
return generator.GenerateSupport(nil)
}
// GenerateMarkdown documentation for a swagger specification
func GenerateMarkdown(output string, modelNames, operationIDs []string, opts *GenOpts) error {
if output == "." || output == "" {
output = "markdown.md"
}
if err := opts.EnsureDefaults(); err != nil {
return err
}
MarkdownSectionOpts(opts, output)
generator, err := newAppGenerator("", modelNames, operationIDs, opts)
if err != nil {
return err
}
return generator.GenerateMarkdown()
}
func newAppGenerator(name string, modelNames, operationIDs []string, opts *GenOpts) (*appGenerator, error) {
if err := opts.CheckOpts(); err != nil {
return nil, err
@ -142,7 +161,8 @@ func (a *appGenerator) Generate() error {
// templates are now lazy loaded so there is concurrent map access I can't guard
if a.GenOpts.IncludeModel {
log.Printf("rendering %d models", len(app.Models))
for _, mod := range app.Models {
for _, md := range app.Models {
mod := md
mod.IncludeModel = true
mod.IncludeValidator = a.GenOpts.IncludeValidator
if err := a.GenOpts.renderDefinition(&mod); err != nil {
@ -153,9 +173,11 @@ func (a *appGenerator) Generate() error {
if a.GenOpts.IncludeHandler {
log.Printf("rendering %d operation groups (tags)", app.OperationGroups.Len())
for _, opg := range app.OperationGroups {
for _, g := range app.OperationGroups {
opg := g
log.Printf("rendering %d operations for %s", opg.Operations.Len(), opg.Name)
for _, op := range opg.Operations {
for _, p := range opg.Operations {
op := p
if err := a.GenOpts.renderOperation(&op); err != nil {
return err
}
@ -190,11 +212,23 @@ func (a *appGenerator) GenerateSupport(ap *GenApp) error {
baseImport := a.GenOpts.LanguageOpts.baseImport(a.Target)
serverPath := path.Join(baseImport,
a.GenOpts.LanguageOpts.ManglePackagePath(a.ServerPackage, defaultServerTarget))
app.DefaultImports[importAlias(serverPath)] = serverPath
pkgAlias := deconflictPkg(importAlias(serverPath), renameServerPackage)
app.DefaultImports[pkgAlias] = serverPath
app.ServerPackageAlias = pkgAlias
return a.GenOpts.renderApplication(app)
}
func (a *appGenerator) GenerateMarkdown() error {
app, err := a.makeCodegenApp()
if err != nil {
return err
}
return a.GenOpts.renderApplication(&app)
}
func (a *appGenerator) makeSecuritySchemes() GenSecuritySchemes {
requiredSecuritySchemes := make(map[string]spec.SecurityScheme, len(a.Analyzed.RequiredSecuritySchemes()))
for _, scheme := range a.Analyzed.RequiredSecuritySchemes() {
@ -202,7 +236,7 @@ func (a *appGenerator) makeSecuritySchemes() GenSecuritySchemes {
requiredSecuritySchemes[scheme] = *req
}
}
return gatherSecuritySchemes(requiredSecuritySchemes, a.Name, a.Principal, a.Receiver)
return gatherSecuritySchemes(requiredSecuritySchemes, a.Name, a.Principal, a.Receiver, a.GenOpts.PrincipalIsNullable())
}
func (a *appGenerator) makeCodegenApp() (GenApp, error) {
@ -219,9 +253,14 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
baseImport := a.GenOpts.LanguageOpts.baseImport(a.Target)
defaultImports := a.GenOpts.defaultImports()
imports := a.GenOpts.initImports(a.OperationsPackage)
log.Println("planning definitions")
imports := make(map[string]string, 50)
alias := deconflictPkg(a.GenOpts.LanguageOpts.ManglePackageName(a.OperationsPackage, defaultOperationsTarget), renameAPIPackage)
imports[alias] = path.Join(
baseImport,
a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, defaultOperationsTarget))
log.Printf("planning definitions (found: %d)", len(a.Models))
genModels := make(GenDefinitions, 0, len(a.Models))
for mn, m := range a.Models {
@ -250,7 +289,7 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
}
sort.Sort(genModels)
log.Println("planning operations")
log.Printf("planning operations (found: %d)", len(a.Operations))
genOps := make(GenOperations, 0, len(a.Operations))
for operationName, opp := range a.Operations {
@ -318,15 +357,16 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
}
sort.Sort(genOps)
log.Println("grouping operations into packages")
opsGroupedByPackage := make(map[string]GenOperations, len(genOps))
for _, operation := range genOps {
opsGroupedByPackage[operation.PackageAlias] = append(opsGroupedByPackage[operation.PackageAlias], operation)
}
log.Printf("grouping operations into packages (packages: %d)", len(opsGroupedByPackage))
opGroups := make(GenOperationGroups, 0, len(opsGroupedByPackage))
for k, v := range opsGroupedByPackage {
log.Printf("operations for package packages %q (found: %d)", k, len(v))
sort.Sort(v)
// trim duplicate extra schemas within the same package
vv := make(GenOperations, 0, len(v))
@ -368,8 +408,7 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
log.Println("planning meta data and facades")
var collectedSchemes []string
var extraSchemes []string
var collectedSchemes, extraSchemes []string
for _, op := range genOps {
collectedSchemes = concatUnique(collectedSchemes, op.Schemes)
extraSchemes = concatUnique(extraSchemes, op.ExtraSchemes)
@ -395,31 +434,36 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
Copyright: a.GenOpts.Copyright,
TargetImportPath: baseImport,
},
APIPackage: a.GenOpts.LanguageOpts.ManglePackageName(a.ServerPackage, defaultServerTarget),
Package: a.Package,
ReceiverName: receiver,
Name: a.Name,
Host: host,
BasePath: basePath,
Schemes: schemeOrDefault(collectedSchemes, a.DefaultScheme),
ExtraSchemes: extraSchemes,
ExternalDocs: sw.ExternalDocs,
Info: sw.Info,
Consumes: consumes,
Produces: produces,
DefaultConsumes: a.DefaultConsumes,
DefaultProduces: a.DefaultProduces,
DefaultImports: defaultImports,
Imports: imports,
SecurityDefinitions: security,
Models: genModels,
Operations: genOps,
OperationGroups: opGroups,
Principal: a.GenOpts.PrincipalAlias(),
SwaggerJSON: generateReadableSpec(jsonb),
FlatSwaggerJSON: generateReadableSpec(flatjsonb),
ExcludeSpec: a.GenOpts.ExcludeSpec,
GenOpts: a.GenOpts,
APIPackage: a.GenOpts.LanguageOpts.ManglePackageName(a.ServerPackage, defaultServerTarget),
APIPackageAlias: alias,
Package: a.Package,
ReceiverName: receiver,
Name: a.Name,
Host: host,
BasePath: basePath,
Schemes: schemeOrDefault(collectedSchemes, a.DefaultScheme),
ExtraSchemes: extraSchemes,
ExternalDocs: trimExternalDoc(sw.ExternalDocs),
Tags: trimTags(sw.Tags),
Info: trimInfo(sw.Info),
Consumes: consumes,
Produces: produces,
DefaultConsumes: a.DefaultConsumes,
DefaultProduces: a.DefaultProduces,
DefaultImports: defaultImports,
Imports: imports,
SecurityDefinitions: security,
SecurityRequirements: securityRequirements(a.SpecDoc.Spec().Security), // top level securityRequirements
Models: genModels,
Operations: genOps,
OperationGroups: opGroups,
Principal: a.GenOpts.PrincipalAlias(),
SwaggerJSON: generateReadableSpec(jsonb),
FlatSwaggerJSON: generateReadableSpec(flatjsonb),
ExcludeSpec: a.GenOpts.ExcludeSpec,
GenOpts: a.GenOpts,
PrincipalIsNullable: a.GenOpts.PrincipalIsNullable(),
}, nil
}
@ -438,3 +482,52 @@ func generateReadableSpec(spec []byte) string {
}
return buf.String()
}
func trimExternalDoc(in *spec.ExternalDocumentation) *spec.ExternalDocumentation {
if in == nil {
return nil
}
return &spec.ExternalDocumentation{
URL: in.URL,
Description: trimBOM(in.Description),
}
}
func trimInfo(in *spec.Info) *spec.Info {
if in == nil {
return nil
}
return &spec.Info{
InfoProps: spec.InfoProps{
Contact: in.Contact,
Title: trimBOM(in.Title),
Description: trimBOM(in.Description),
TermsOfService: trimBOM(in.TermsOfService),
License: in.License,
Version: in.Version,
},
VendorExtensible: in.VendorExtensible,
}
}
func trimTags(in []spec.Tag) []spec.Tag {
if in == nil {
return nil
}
tags := make([]spec.Tag, 0, len(in))
for _, tag := range in {
tags = append(tags, spec.Tag{
TagProps: spec.TagProps{
Name: tag.Name,
Description: trimBOM(tag.Description),
ExternalDocs: trimExternalDoc(tag.ExternalDocs),
},
})
}
return tags
}

View file

@ -5,10 +5,14 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"math"
"os"
"path"
"path/filepath"
"reflect"
"strconv"
"strings"
"sync"
"text/template"
"text/template/parse"
"unicode"
@ -16,6 +20,7 @@ import (
"log"
"github.com/go-openapi/inflect"
"github.com/go-openapi/runtime"
"github.com/go-openapi/swag"
"github.com/kr/pretty"
)
@ -28,6 +33,8 @@ var (
FuncMapFunc func(*LanguageOpts) template.FuncMap
templates *Repository
docFormat map[string]string
)
func initTemplateRepo() {
@ -40,6 +47,11 @@ func initTemplateRepo() {
assets = defaultAssets()
protectedTemplates = defaultProtectedTemplates()
templates = NewRepository(FuncMapFunc(DefaultLanguageFunc()))
docFormat = map[string]string{
"binary": "binary (byte stream)",
"byte": "byte (base64 string)",
}
}
// DefaultFuncMap yields a map with default functions for use n the templates.
@ -72,9 +84,11 @@ func DefaultFuncMap(lang *LanguageOpts) template.FuncMap {
},
"dropPackage": dropPackage,
"upper": strings.ToUpper,
"lower": strings.ToLower,
"contains": swag.ContainsStrings,
"padSurround": padSurround,
"joinFilePath": filepath.Join,
"joinPath": path.Join,
"comment": padComment,
"blockcomment": blockComment,
"inspect": pretty.Sprint,
@ -85,6 +99,44 @@ func DefaultFuncMap(lang *LanguageOpts) template.FuncMap {
"stringContains": strings.Contains,
"imports": lang.imports,
"dict": dict,
"isInteger": isInteger,
"escapeBackticks": func(arg string) string {
return strings.ReplaceAll(arg, "`", "`+\"`\"+`")
},
"paramDocType": func(param GenParameter) string {
return resolvedDocType(param.SwaggerType, param.SwaggerFormat, param.Child)
},
"headerDocType": func(header GenHeader) string {
return resolvedDocType(header.SwaggerType, header.SwaggerFormat, header.Child)
},
"schemaDocType": func(in interface{}) string {
switch schema := in.(type) {
case GenSchema:
return resolvedDocSchemaType(schema.SwaggerType, schema.SwaggerFormat, schema.Items)
case *GenSchema:
if schema == nil {
return ""
}
return resolvedDocSchemaType(schema.SwaggerType, schema.SwaggerFormat, schema.Items)
case GenDefinition:
return resolvedDocSchemaType(schema.SwaggerType, schema.SwaggerFormat, schema.Items)
case *GenDefinition:
if schema == nil {
return ""
}
return resolvedDocSchemaType(schema.SwaggerType, schema.SwaggerFormat, schema.Items)
default:
panic("dev error: schemaDocType should be called with GenSchema or GenDefinition")
}
},
"schemaDocMapType": func(schema GenSchema) string {
return resolvedDocElemType("object", schema.SwaggerFormat, &schema.resolvedType)
},
"docCollectionFormat": resolvedDocCollectionFormat,
"trimSpace": strings.TrimSpace,
"httpStatus": httpStatus,
"cleanupEnumVariant": cleanupEnumVariant,
"gt0": gt0,
})
}
@ -98,6 +150,9 @@ func defaultAssets() map[string][]byte {
"schemavalidator.gotmpl": MustAsset("templates/schemavalidator.gotmpl"),
"schemapolymorphic.gotmpl": MustAsset("templates/schemapolymorphic.gotmpl"),
"schemaembedded.gotmpl": MustAsset("templates/schemaembedded.gotmpl"),
"validation/minimum.gotmpl": MustAsset("templates/validation/minimum.gotmpl"),
"validation/maximum.gotmpl": MustAsset("templates/validation/maximum.gotmpl"),
"validation/multipleOf.gotmpl": MustAsset("templates/validation/multipleOf.gotmpl"),
// schema serialization templates
"additionalpropertiesserializer.gotmpl": MustAsset("templates/serializers/additionalpropertiesserializer.gotmpl"),
@ -117,6 +172,10 @@ func defaultAssets() map[string][]byte {
"model.gotmpl": MustAsset("templates/model.gotmpl"),
"header.gotmpl": MustAsset("templates/header.gotmpl"),
// simple schema generation helpers templates
"simpleschema/defaultsvar.gotmpl": MustAsset("templates/simpleschema/defaultsvar.gotmpl"),
"simpleschema/defaultsinit.gotmpl": MustAsset("templates/simpleschema/defaultsinit.gotmpl"),
"swagger_json_embed.gotmpl": MustAsset("templates/swagger_json_embed.gotmpl"),
// server templates
@ -135,6 +194,8 @@ func defaultAssets() map[string][]byte {
"client/response.gotmpl": MustAsset("templates/client/response.gotmpl"),
"client/client.gotmpl": MustAsset("templates/client/client.gotmpl"),
"client/facade.gotmpl": MustAsset("templates/client/facade.gotmpl"),
"markdown/docs.gotmpl": MustAsset("templates/markdown/docs.gotmpl"),
}
}
@ -167,13 +228,20 @@ func defaultProtectedTemplates() map[string]bool {
"tuplefield": true,
"tuplefieldIface": true,
"typeSchemaType": true,
"validationCustomformat": true,
"validationPrimitive": true,
"validationStructfield": true,
"withBaseTypeBody": true,
"withoutBaseTypeBody": true,
"simpleschemaDefaultsvar": true,
"simpleschemaDefaultsinit": true,
// all serializers TODO(fred)
// validation helpers
"validationCustomformat": true,
"validationPrimitive": true,
"validationStructfield": true,
"withBaseTypeBody": true,
"withoutBaseTypeBody": true,
"validationMinimum": true,
"validationMaximum": true,
"validationMultipleOf": true,
// all serializers
"additionalPropertiesSerializer": true,
"tupleSerializer": true,
"schemaSerializer": true,
@ -213,6 +281,31 @@ type Repository struct {
templates map[string]*template.Template
funcs template.FuncMap
allowOverride bool
mux sync.Mutex
}
// ShallowClone a repository.
//
// Clones the maps of files and templates, so as to be able to use
// the cloned repo concurrently.
func (t *Repository) ShallowClone() *Repository {
clone := &Repository{
files: make(map[string]string, len(t.files)),
templates: make(map[string]*template.Template, len(t.templates)),
funcs: t.funcs,
allowOverride: t.allowOverride,
}
t.mux.Lock()
defer t.mux.Unlock()
for k, file := range t.files {
clone.files[k] = file
}
for k, tpl := range t.templates {
clone.templates[k] = tpl
}
return clone
}
// LoadDefaults will load the embedded templates
@ -532,7 +625,7 @@ func padComment(str string, pads ...string) string {
}
func blockComment(str string) string {
return strings.Replace(str, "*/", "[*]/", -1)
return strings.ReplaceAll(str, "*/", "[*]/")
}
func pascalize(arg string) string {
@ -566,6 +659,28 @@ func prefixForName(arg string) string {
return "Nr"
}
func replaceSpecialChar(in rune) string {
switch in {
case '.':
return "-Dot-"
case '+':
return "-Plus-"
case '-':
return "-Dash-"
case '#':
return "-Hashtag-"
}
return string(in)
}
func cleanupEnumVariant(in string) string {
replaced := ""
for _, char := range in {
replaced += replaceSpecialChar(char)
}
return replaced
}
func dict(values ...interface{}) (map[string]interface{}, error) {
if len(values)%2 != 0 {
return nil, fmt.Errorf("expected even number of arguments, got %d", len(values))
@ -580,3 +695,132 @@ func dict(values ...interface{}) (map[string]interface{}, error) {
}
return dict, nil
}
func isInteger(arg interface{}) bool {
// is integer determines if a value may be represented by an integer
switch val := arg.(type) {
case int8, int16, int32, int, int64, uint8, uint16, uint32, uint, uint64:
return true
case *int8, *int16, *int32, *int, *int64, *uint8, *uint16, *uint32, *uint, *uint64:
v := reflect.ValueOf(arg)
return !v.IsNil()
case float64:
return math.Round(val) == val
case *float64:
return val != nil && math.Round(*val) == *val
case float32:
return math.Round(float64(val)) == float64(val)
case *float32:
return val != nil && math.Round(float64(*val)) == float64(*val)
case string:
_, err := strconv.ParseInt(val, 10, 64)
return err == nil
case *string:
if val == nil {
return false
}
_, err := strconv.ParseInt(*val, 10, 64)
return err == nil
default:
return false
}
}
func resolvedDocCollectionFormat(cf string, child *GenItems) string {
if child == nil {
return cf
}
ccf := cf
if ccf == "" {
ccf = "csv"
}
rcf := resolvedDocCollectionFormat(child.CollectionFormat, child.Child)
if rcf == "" {
return ccf
}
return ccf + "|" + rcf
}
func resolvedDocType(tn, ft string, child *GenItems) string {
if tn == "array" {
if child == nil {
return "[]any"
}
return "[]" + resolvedDocType(child.SwaggerType, child.SwaggerFormat, child.Child)
}
if ft != "" {
if doc, ok := docFormat[ft]; ok {
return doc
}
return fmt.Sprintf("%s (formatted %s)", ft, tn)
}
return tn
}
func resolvedDocSchemaType(tn, ft string, child *GenSchema) string {
if tn == "array" {
if child == nil {
return "[]any"
}
return "[]" + resolvedDocSchemaType(child.SwaggerType, child.SwaggerFormat, child.Items)
}
if tn == "object" {
if child == nil || child.ElemType == nil {
return "map of any"
}
if child.IsMap {
return "map of " + resolvedDocElemType(child.SwaggerType, child.SwaggerFormat, &child.resolvedType)
}
return child.GoType
}
if ft != "" {
if doc, ok := docFormat[ft]; ok {
return doc
}
return fmt.Sprintf("%s (formatted %s)", ft, tn)
}
return tn
}
func resolvedDocElemType(tn, ft string, schema *resolvedType) string {
if schema == nil {
return ""
}
if schema.IsMap {
return "map of " + resolvedDocElemType(schema.ElemType.SwaggerType, schema.ElemType.SwaggerFormat, schema.ElemType)
}
if schema.IsArray {
return "[]" + resolvedDocElemType(schema.ElemType.SwaggerType, schema.ElemType.SwaggerFormat, schema.ElemType)
}
if ft != "" {
if doc, ok := docFormat[ft]; ok {
return doc
}
return fmt.Sprintf("%s (formatted %s)", ft, tn)
}
return tn
}
func httpStatus(code int) string {
if name, ok := runtime.Statuses[code]; ok {
return name
}
// non-standard codes deserve some name
return fmt.Sprintf("Status %d", code)
}
func gt0(in *int64) bool {
// gt0 returns true if the *int64 points to a value > 0
// NOTE: plain {{ gt .MinProperties 0 }} just refuses to work normally
// with a pointer
return in != nil && *in > 0
}

View file

@ -17,8 +17,8 @@ package generator
import (
"fmt"
"log"
"path"
"path/filepath"
"reflect"
"strings"
"github.com/go-openapi/loads"
@ -69,10 +69,14 @@ func initTypes() {
}
}
func simpleResolvedType(tn, fmt string, items *spec.Items) (result resolvedType) {
func simpleResolvedType(tn, fmt string, items *spec.Items, v *spec.CommonValidations) (result resolvedType) {
result.SwaggerType = tn
result.SwaggerFormat = fmt
defer func() {
guardValidations(result.SwaggerType, v)
}()
if tn == file {
// special case of swagger type "file", rendered as io.ReadCloser interface
result.IsPrimitive = true
@ -82,7 +86,11 @@ func simpleResolvedType(tn, fmt string, items *spec.Items) (result resolvedType)
}
if fmt != "" {
fmtn := strings.Replace(fmt, "-", "", -1)
defer func() {
guardFormatConflicts(result.SwaggerFormat, v)
}()
fmtn := strings.ReplaceAll(fmt, "-", "")
if fmm, ok := formatMapping[tn]; ok {
if tpe, ok := fmm[fmtn]; ok {
result.GoType = tpe
@ -114,7 +122,7 @@ func simpleResolvedType(tn, fmt string, items *spec.Items) (result resolvedType)
result.GoType = "[]" + iface
return
}
res := simpleResolvedType(items.Type, items.Format, items.Items)
res := simpleResolvedType(items.Type, items.Format, items.Items, &items.CommonValidations)
result.GoType = "[]" + res.GoType
return
}
@ -123,22 +131,18 @@ func simpleResolvedType(tn, fmt string, items *spec.Items) (result resolvedType)
return
}
func typeForHeader(header spec.Header) resolvedType {
return simpleResolvedType(header.Type, header.Format, header.Items)
}
func newTypeResolver(pkg string, doc *loads.Document) *typeResolver {
func newTypeResolver(pkg, fullPkg string, doc *loads.Document) *typeResolver {
resolver := typeResolver{ModelsPackage: pkg, Doc: doc}
resolver.KnownDefs = make(map[string]struct{}, len(doc.Spec().Definitions))
for k, sch := range doc.Spec().Definitions {
tpe, _, _ := knownDefGoType(k, sch, nil)
tpe, _, _ := resolver.knownDefGoType(k, sch, nil)
resolver.KnownDefs[tpe] = struct{}{}
}
return &resolver
}
// knownDefGoType returns go type, package and package alias for definition
func knownDefGoType(def string, schema spec.Schema, clear func(string) string) (string, string, string) {
func (t typeResolver) knownDefGoType(def string, schema spec.Schema, clear func(string) string) (string, string, string) {
debugLog("known def type: %q", def)
ext := schema.Extensions
nm, hasGoName := ext.GetString(xGoName)
@ -147,8 +151,7 @@ func knownDefGoType(def string, schema spec.Schema, clear func(string) string) (
debugLog("known def type %s named from %s as %q", def, xGoName, nm)
def = nm
}
extType, isExternalType := hasExternalType(ext)
extType, isExternalType := t.resolveExternalType(ext)
if !isExternalType || extType.Embedded {
if clear == nil {
debugLog("known def type no clear: %q", def)
@ -159,7 +162,11 @@ func knownDefGoType(def string, schema spec.Schema, clear func(string) string) (
}
// external type definition trumps regular type resolution
log.Printf("type %s imported as external type %s.%s", def, extType.Import.Package, extType.Type)
if extType.Import.Alias == "" {
debugLog("type %s imported as external type %s, assumed in current package", def, extType.Type)
return extType.Type, extType.Import.Package, extType.Import.Alias
}
debugLog("type %s imported as external type from %s as %s.%s", def, extType.Import.Package, extType.Import.Alias, extType.Type)
return extType.Import.Alias + "." + extType.Type, extType.Import.Package, extType.Import.Alias
}
@ -179,8 +186,9 @@ type externalTypeDefinition struct {
Alias string
}
Hints struct {
Kind string
Nullable bool
Kind string
Nullable *bool
NoValidation *bool
}
Embedded bool
}
@ -190,23 +198,57 @@ func hasExternalType(ext spec.Extensions) (*externalTypeDefinition, bool) {
if !ok {
return nil, false
}
var extType externalTypeDefinition
err := mapstructure.Decode(v, &extType)
if err != nil {
log.Printf("warning: x-go-type extension could not be decoded (%v). Skipped", v)
return nil, false
}
if extType.Import.Package != "" && extType.Import.Alias == "" {
// NOTE(fred): possible name conflict here (TODO(fred): deconflict this default alias)
extType.Import.Alias = path.Base(extType.Import.Package)
}
debugLogAsJSON("known def external %s type", xGoType, extType)
return &extType, true
}
func (t typeResolver) resolveExternalType(ext spec.Extensions) (*externalTypeDefinition, bool) {
extType, hasExt := hasExternalType(ext)
if !hasExt {
return nil, false
}
// NOTE:
// * basic deconfliction of the default alias
// * if no package is specified, defaults to models (as provided from CLI or defaut generation location for models)
toAlias := func(pkg string) string {
mangled := GoLangOpts().ManglePackageName(pkg, "")
return deconflictPkg(mangled, func(in string) string {
return in + "ext"
})
}
switch {
case extType.Import.Package != "" && extType.Import.Alias == "":
extType.Import.Alias = toAlias(extType.Import.Package)
case extType.Import.Package == "" && extType.Import.Alias != "":
extType.Import.Package = t.ModelsFullPkg
case extType.Import.Package == "" && extType.Import.Alias == "":
// in this case, the external type is assumed to be present in the current package.
// For completion, whenever this type is used in anonymous types declared by operations,
// we assume this is the package where models are expected to be found.
extType.Import.Package = t.ModelsFullPkg
if extType.Import.Package != "" {
extType.Import.Alias = toAlias(extType.Import.Package)
}
}
debugLogAsJSON("known def external %s type", xGoType, extType)
return extType, true
}
type typeResolver struct {
Doc *loads.Document
ModelsPackage string
ModelsPackage string // package alias (e.g. "models")
ModelsFullPkg string // fully qualified package (e.g. "github.com/example/models")
ModelName string
KnownDefs map[string]struct{}
// unexported fields
@ -216,7 +258,7 @@ type typeResolver struct {
// NewWithModelName clones a type resolver and specifies a new model name
func (t *typeResolver) NewWithModelName(name string) *typeResolver {
tt := newTypeResolver(t.ModelsPackage, t.Doc)
tt := newTypeResolver(t.ModelsPackage, t.ModelsFullPkg, t.Doc)
tt.ModelName = name
// propagates kept definitions
@ -236,24 +278,12 @@ func (t *typeResolver) withKeepDefinitionsPackage(definitionsPackage string) *ty
return t
}
// IsNullable hints the generator as to render the type with a pointer or not.
//
// A schema is deemed nullable (i.e. rendered by a pointer) when:
// - a custom extension says it has to be so
// - it is an object with properties
// - it is a composed object (allOf)
//
// The interpretation of Required as a mean to make a type nullable is carried on elsewhere.
func (t *typeResolver) IsNullable(schema *spec.Schema) bool {
nullable := t.isNullable(schema)
return nullable || len(schema.AllOf) > 0
}
func (t *typeResolver) resolveSchemaRef(schema *spec.Schema, isRequired bool) (returns bool, result resolvedType, err error) {
if schema.Ref.String() == "" {
return
}
debugLog("resolving ref (anon: %t, req: %t) %s", false, isRequired, schema.Ref.String())
returns = true
var ref *spec.Schema
var er error
@ -264,6 +294,13 @@ func (t *typeResolver) resolveSchemaRef(schema *spec.Schema, isRequired bool) (r
err = er
return
}
extType, isExternalType := t.resolveExternalType(schema.Extensions)
if isExternalType {
// deal with validations for an aliased external type
result.SkipExternalValidation = swag.BoolValue(extType.Hints.NoValidation)
}
res, er := t.ResolveSchema(ref, false, isRequired)
if er != nil {
err = er
@ -272,7 +309,7 @@ func (t *typeResolver) resolveSchemaRef(schema *spec.Schema, isRequired bool) (r
result = res
tn := filepath.Base(schema.Ref.GetURL().Fragment)
tpe, pkg, alias := knownDefGoType(tn, *ref, t.goTypeName)
tpe, pkg, alias := t.knownDefGoType(tn, *ref, t.goTypeName)
debugLog("type name %s, package %s, alias %s", tpe, pkg, alias)
if tpe != "" {
result.GoType = tpe
@ -281,7 +318,7 @@ func (t *typeResolver) resolveSchemaRef(schema *spec.Schema, isRequired bool) (r
}
result.HasDiscriminator = res.HasDiscriminator
result.IsBaseType = result.HasDiscriminator
result.IsNullable = t.IsNullable(ref)
result.IsNullable = result.IsNullable || t.isNullable(ref) // this has to be overriden for slices and maps
result.IsEnumCI = false
return
}
@ -304,7 +341,7 @@ func (t *typeResolver) resolveFormat(schema *spec.Schema, isAnonymous bool, isRe
}
debugLog("resolving format (anon: %t, req: %t)", isAnonymous, isRequired)
schFmt := strings.Replace(schema.Format, "-", "", -1)
schFmt := strings.ReplaceAll(schema.Format, "-", "")
if fmm, ok := formatMapping[result.SwaggerType]; ok {
if tpe, ok := fmm[schFmt]; ok {
returns = true
@ -334,13 +371,33 @@ func (t *typeResolver) resolveFormat(schema *spec.Schema, isAnonymous bool, isRe
case number, integer:
result.IsNullable = nullableNumber(schema, isRequired)
default:
result.IsNullable = t.IsNullable(schema)
result.IsNullable = t.isNullable(schema)
}
}
guardFormatConflicts(schema.Format, schema)
return
}
// isNullable hints the generator as to render the type with a pointer or not.
//
// A schema is deemed nullable (i.e. rendered by a pointer) when:
// - a custom extension says it has to be so
// - it is an object with properties
// - it is a composed object (allOf)
//
// The interpretation of Required as a mean to make a type nullable is carried out elsewhere.
func (t *typeResolver) isNullable(schema *spec.Schema) bool {
if nullable, ok := t.isNullableOverride(schema); ok {
return nullable
}
return len(schema.Properties) > 0 || len(schema.AllOf) > 0
}
// isNullableOverride determines a nullable flag forced by an extension
func (t *typeResolver) isNullableOverride(schema *spec.Schema) (bool, bool) {
check := func(extension string) (bool, bool) {
v, found := schema.Extensions[extension]
nullable, cast := v.(bool)
@ -348,12 +405,14 @@ func (t *typeResolver) isNullable(schema *spec.Schema) bool {
}
if nullable, ok := check(xIsNullable); ok {
return nullable
return nullable, ok
}
if nullable, ok := check(xNullable); ok {
return nullable
return nullable, ok
}
return len(schema.Properties) > 0
return false, false
}
func (t *typeResolver) firstType(schema *spec.Schema) string {
@ -402,9 +461,30 @@ func (t *typeResolver) resolveArray(schema *spec.Schema, isAnonymous, isRequired
err = er
return
}
// override the general nullability rule from ResolveSchema():
// only complex items are nullable (when not discriminated, not forced by x-nullable)
rt.IsNullable = t.IsNullable(schema.Items.Schema) && !rt.HasDiscriminator
// Override the general nullability rule from ResolveSchema() in array elements:
// - only complex items are nullable (when not discriminated, not forced by x-nullable)
// - arrays of allOf have non nullable elements when not forced by x-nullable
elem := schema.Items.Schema
if elem.Ref.String() != "" {
// drill into $ref to figure out whether we want the element type to nullable or not
resolved, erf := spec.ResolveRef(t.Doc.Spec(), &elem.Ref)
if erf != nil {
debugLog("error resolving ref %s: %v", schema.Ref.String(), erf)
}
elem = resolved
}
debugLogAsJSON("resolved item for %s", rt.GoType, elem)
if nullable, ok := t.isNullableOverride(elem); ok {
debugLog("found nullable override in element %s: %t", rt.GoType, nullable)
rt.IsNullable = nullable
} else {
// this differs from isNullable for elements with AllOf
debugLog("no nullable override in element %s: Properties: %t, HasDiscriminator: %t", rt.GoType, len(elem.Properties) > 0, rt.HasDiscriminator)
rt.IsNullable = len(elem.Properties) > 0 && !rt.HasDiscriminator
}
result.GoType = "[]" + rt.GoType
if rt.IsNullable && !strings.HasPrefix(rt.GoType, "*") {
result.GoType = "[]*" + rt.GoType
@ -448,7 +528,7 @@ func (t *typeResolver) resolveObject(schema *spec.Schema, isAnonymous bool) (res
result.IsBaseType = schema.Discriminator != ""
if !isAnonymous {
result.SwaggerType = object
tpe, pkg, alias := knownDefGoType(t.ModelName, *schema, t.goTypeName)
tpe, pkg, alias := t.knownDefGoType(t.ModelName, *schema, t.goTypeName)
result.GoType = tpe
result.Pkg = pkg
result.PkgAlias = alias
@ -457,12 +537,18 @@ func (t *typeResolver) resolveObject(schema *spec.Schema, isAnonymous bool) (res
result.GoType = t.goTypeName(t.ModelName)
result.IsComplexObject = true
var isNullable bool
for _, p := range schema.AllOf {
if t.IsNullable(&p) {
for _, sch := range schema.AllOf {
p := sch
if t.isNullable(&p) {
isNullable = true
}
}
result.IsNullable = isNullable
if override, ok := t.isNullableOverride(schema); ok {
// prioritize x-nullable extensions
result.IsNullable = override
} else {
result.IsNullable = isNullable
}
result.SwaggerType = object
return
}
@ -471,7 +557,7 @@ func (t *typeResolver) resolveObject(schema *spec.Schema, isAnonymous bool) (res
// resolved type, this should also flag the object as anonymous,
// when a ref is found, the anonymous flag will be reset
if len(schema.Properties) > 0 {
result.IsNullable = t.IsNullable(schema)
result.IsNullable = t.isNullable(schema)
result.IsComplexObject = true
// no return here, still need to check for additional properties
}
@ -489,6 +575,12 @@ func (t *typeResolver) resolveObject(schema *spec.Schema, isAnonymous bool) (res
result.SwaggerType = object
if et.IsExternal {
// external AdditionalProperties are a special case because we look ahead into schemas
extType, _, _ := t.knownDefGoType(t.ModelName, *sch, t.goTypeName)
et.GoType = extType
}
// only complex map elements are nullable (when not forced by x-nullable)
// TODO: figure out if required to check when not discriminated like arrays?
et.IsNullable = t.isNullable(schema.AdditionalProperties.Schema)
@ -544,11 +636,16 @@ func (t *typeResolver) resolveObject(schema *spec.Schema, isAnonymous bool) (res
}
// an object without property and without AdditionalProperties schema is rendered as interface{}
result.GoType = iface
result.IsMap = true
result.SwaggerType = object
result.IsNullable = false
result.IsInterface = len(schema.Properties) == 0
// an object without properties but with MinProperties or MaxProperties is rendered as map[string]interface{}
result.IsInterface = len(schema.Properties) == 0 && !schema.Validations().HasObjectValidations()
if result.IsInterface {
result.GoType = iface
} else {
result.GoType = "map[string]interface{}"
}
return
}
@ -656,21 +753,38 @@ func hasEnumCI(ve spec.Extensions) bool {
return ok && isEnumCI
}
func (t *typeResolver) shortCircuitResolveExternal(tpe, pkg, alias string, extType *externalTypeDefinition, schema *spec.Schema) resolvedType {
func (t *typeResolver) shortCircuitResolveExternal(tpe, pkg, alias string, extType *externalTypeDefinition, schema *spec.Schema, isRequired bool) resolvedType {
// short circuit type resolution for external types
debugLogAsJSON("shortCircuitResolveExternal", extType)
var result resolvedType
result.Extensions = schema.Extensions
result.GoType = tpe
result.Pkg = pkg
result.PkgAlias = alias
result.IsInterface = false
// by default consider that we have a type with validations. Use hint "interface" or "noValidation" to disable validations
result.SkipExternalValidation = swag.BoolValue(extType.Hints.NoValidation)
result.IsNullable = isRequired
result.setKind(extType.Hints.Kind)
result.IsNullable = t.IsNullable(schema)
if result.IsInterface || result.IsStream {
result.IsNullable = false
}
if extType.Hints.Nullable != nil {
result.IsNullable = swag.BoolValue(extType.Hints.Nullable)
}
if nullable, ok := t.isNullableOverride(schema); ok {
result.IsNullable = nullable // x-nullable directive rules them all
}
// other extensions
if result.IsArray {
result.IsEmptyOmitted = false
tpe = "array"
}
result.setExtensions(schema, tpe)
return result
}
@ -687,32 +801,73 @@ func (t *typeResolver) ResolveSchema(schema *spec.Schema, isAnonymous, isRequire
return
}
extType, isExternalType := hasExternalType(schema.Extensions)
extType, isExternalType := t.resolveExternalType(schema.Extensions)
if isExternalType {
tpe, pkg, alias := knownDefGoType(t.ModelName, *schema, t.goTypeName)
debugLog("found type declared as external, imported from %s as %s. Has type hints? %t, rendered has embedded? %t",
pkg, tpe, extType.Hints.Kind != "", extType.Embedded)
tpe, pkg, alias := t.knownDefGoType(t.ModelName, *schema, t.goTypeName)
debugLog("found type %s declared as external, imported from %s as %s. Has type hints? %t, rendered has embedded? %t",
t.ModelName, pkg, tpe, extType.Hints.Kind != "", extType.Embedded)
if extType.Hints.Kind != "" && !extType.Embedded {
// use hint to qualify type
debugLog("short circuits external type resolution with hint for %s", tpe)
result = t.shortCircuitResolveExternal(tpe, pkg, alias, extType, schema)
result = t.shortCircuitResolveExternal(tpe, pkg, alias, extType, schema, isRequired)
result.IsExternal = isAnonymous // mark anonymous external types only, not definitions
return
}
// use spec to qualify type
debugLog("marking type %s as external embedded: %t", tpe, extType.Embedded)
// mark this type as an embedded external definition if requested
defer func() {
defer func() { // enforce bubbling up decisions taken about being an external type
// mark this type as an embedded external definition if requested
result.IsEmbedded = extType.Embedded
result.IsExternal = isAnonymous // for non-embedded, mark anonymous external types only, not definitions
result.IsAnonymous = false
result.IsAliased = true
result.IsNullable = isRequired
if extType.Hints.Nullable != nil {
result.IsNullable = swag.BoolValue(extType.Hints.Nullable)
}
result.IsMap = false
result.AliasedType = result.GoType
result.IsInterface = false
if result.IsEmbedded {
result.ElemType = &resolvedType{
GoType: extType.Import.Alias + "." + extType.Type,
Pkg: extType.Import.Package,
PkgAlias: extType.Import.Alias,
IsNullable: extType.Hints.Nullable,
IsExternal: isAnonymous, // mark anonymous external types only, not definitions
IsInterface: false,
Pkg: extType.Import.Package,
PkgAlias: extType.Import.Alias,
SkipExternalValidation: swag.BoolValue(extType.Hints.NoValidation),
}
result.setKind(extType.Hints.Kind)
if extType.Import.Alias != "" {
result.ElemType.GoType = extType.Import.Alias + "." + extType.Type
} else {
result.ElemType.GoType = extType.Type
}
result.ElemType.setKind(extType.Hints.Kind)
if result.IsInterface || result.IsStream {
result.ElemType.IsNullable = false
}
if extType.Hints.Nullable != nil {
result.ElemType.IsNullable = swag.BoolValue(extType.Hints.Nullable)
}
// embedded external: by default consider validation is skipped for the external type
//
// NOTE: at this moment the template generates a type assertion, so this setting does not really matter
// for embedded types.
if extType.Hints.NoValidation != nil {
result.ElemType.SkipExternalValidation = swag.BoolValue(extType.Hints.NoValidation)
} else {
result.ElemType.SkipExternalValidation = true
}
} else {
// non-embedded external type: by default consider that validation is enabled (SkipExternalValidation: false)
result.SkipExternalValidation = swag.BoolValue(extType.Hints.NoValidation)
}
if nullable, ok := t.isNullableOverride(schema); ok {
result.IsNullable = nullable
}
}()
}
@ -720,15 +875,16 @@ func (t *typeResolver) ResolveSchema(schema *spec.Schema, isAnonymous, isRequire
tpe := t.firstType(schema)
var returns bool
guardValidations(tpe, schema, schema.Type...)
returns, result, err = t.resolveSchemaRef(schema, isRequired)
if returns {
if !isAnonymous {
result.IsMap = false
result.IsComplexObject = true
debugLog("not anonymous ref")
}
debugLog("anonymous after ref")
return
}
@ -793,6 +949,18 @@ func (t *typeResolver) ResolveSchema(schema *spec.Schema, isAnonymous, isRequire
result.HasDiscriminator = schema.Discriminator != ""
case "null":
if schema.Validations().HasObjectValidations() {
// no explicit object type, but inferred from object validations:
// this makes the type a map[string]interface{} instead of interface{}
result, err = t.resolveObject(schema, isAnonymous)
if err != nil {
result = resolvedType{}
break
}
result.HasDiscriminator = schema.Discriminator != ""
break
}
result.GoType = iface
result.SwaggerType = object
result.IsNullable = false
@ -801,9 +969,89 @@ func (t *typeResolver) ResolveSchema(schema *spec.Schema, isAnonymous, isRequire
default:
err = fmt.Errorf("unresolvable: %v (format %q)", schema.Type, schema.Format)
}
return
}
func warnSkipValidation(types interface{}) func(string, interface{}) {
return func(validation string, value interface{}) {
value = reflect.Indirect(reflect.ValueOf(value)).Interface()
log.Printf("warning: validation %s (value: %v) not compatible with type %v. Skipped", validation, value, types)
}
}
// guardValidations removes (with a warning) validations that don't fit with the schema type.
//
// Notice that the "enum" validation is allowed on any type but file.
func guardValidations(tpe string, schema interface {
Validations() spec.SchemaValidations
SetValidations(spec.SchemaValidations)
}, types ...string) {
v := schema.Validations()
if len(types) == 0 {
types = []string{tpe}
}
defer func() {
schema.SetValidations(v)
}()
if tpe != array {
v.ClearArrayValidations(warnSkipValidation(types))
}
if tpe != str && tpe != file {
v.ClearStringValidations(warnSkipValidation(types))
}
if tpe != object {
v.ClearObjectValidations(warnSkipValidation(types))
}
if tpe != number && tpe != integer {
v.ClearNumberValidations(warnSkipValidation(types))
}
if tpe == file {
// keep MinLength/MaxLength on file
if v.Pattern != "" {
warnSkipValidation(types)("pattern", v.Pattern)
v.Pattern = ""
}
if v.HasEnum() {
warnSkipValidation(types)("enum", v.Enum)
v.Enum = nil
}
}
// other cases: mapped as interface{}: no validations allowed but Enum
}
// guardFormatConflicts handles all conflicting properties
// (for schema model or simple schema) when a format is set.
//
// At this moment, validation guards already handle all known conflicts, but for the
// special case of binary (i.e. io.Reader).
func guardFormatConflicts(format string, schema interface {
Validations() spec.SchemaValidations
SetValidations(spec.SchemaValidations)
}) {
v := schema.Validations()
msg := fmt.Sprintf("for format %q", format)
// for this format, no additional validations are supported
if format == "binary" {
// no validations supported on binary fields at this moment (io.Reader)
v.ClearStringValidations(warnSkipValidation(msg))
if v.HasEnum() {
warnSkipValidation(msg)
v.Enum = nil
}
schema.SetValidations(v)
}
// more cases should be inserted here if they arise
}
// resolvedType is a swagger type that has been resolved and analyzed for usage
// in a template
type resolvedType struct {
@ -820,6 +1068,7 @@ type resolvedType struct {
IsJSONString bool
IsEnumCI bool
IsBase64 bool
IsExternal bool
// A tuple gets rendered as an anonymous struct with P{index} as property name
IsTuple bool
@ -855,9 +1104,12 @@ type resolvedType struct {
// is generated in models that embeds the external type, with the Validate
// method.
IsEmbedded bool
SkipExternalValidation bool
}
func (rt *resolvedType) Zero() string {
// Zero returns an initializer for the type
func (rt resolvedType) Zero() string {
// if type is aliased, provide zero from the aliased type
if rt.IsAliased {
if zr, ok := zeroes[rt.AliasedType]; ok {
@ -889,10 +1141,39 @@ func (rt *resolvedType) Zero() string {
return ""
}
// ToString returns a string conversion for a type akin to a string
func (rt resolvedType) ToString(value string) string {
if !rt.IsPrimitive || rt.SwaggerType != "string" || rt.IsStream {
return ""
}
if rt.IsCustomFormatter {
if rt.IsAliased {
return fmt.Sprintf("%s(%s).String()", rt.AliasedType, value)
}
return fmt.Sprintf("%s.String()", value)
}
var deref string
if rt.IsNullable {
deref = "*"
}
if rt.GoType == "string" || rt.GoType == "*string" {
return fmt.Sprintf("%s%s", deref, value)
}
return fmt.Sprintf("string(%s%s)", deref, value)
}
func (rt *resolvedType) setExtensions(schema *spec.Schema, origType string) {
rt.IsEnumCI = hasEnumCI(schema.Extensions)
rt.setIsEmptyOmitted(schema, origType)
rt.setIsJSONString(schema, origType)
if customTag, found := schema.Extensions[xGoCustomTag]; found {
if rt.Extensions == nil {
rt.Extensions = make(spec.Extensions)
}
rt.Extensions[xGoCustomTag] = customTag
}
}
func (rt *resolvedType) setIsEmptyOmitted(schema *spec.Schema, tpe string) {

View file

@ -0,0 +1,3 @@
# scan
Pre go1.11 version of the go source parser, without support for go modules.

View file

@ -119,7 +119,7 @@ func (pc *programClassifier) Classify(prog *loader.Program) (*classifiedProgram,
if seenStruct == "" || seenStruct == matches[1] {
seenStruct = matches[1]
} else {
return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q", seenStruct, matches[1])
return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q - %s", seenStruct, matches[1], cline.Text)
}
case "meta":
if !mt {
@ -134,7 +134,7 @@ func (pc *programClassifier) Classify(prog *loader.Program) (*classifiedProgram,
if seenStruct == "" || seenStruct == matches[1] {
seenStruct = matches[1]
} else {
return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q", seenStruct, matches[1])
return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q - %s", seenStruct, matches[1], cline.Text)
}
case "response":
if !rs {
@ -144,7 +144,7 @@ func (pc *programClassifier) Classify(prog *loader.Program) (*classifiedProgram,
if seenStruct == "" || seenStruct == matches[1] {
seenStruct = matches[1]
} else {
return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q", seenStruct, matches[1])
return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q - %s", seenStruct, matches[1], cline.Text)
}
case "strfmt", "name", "discriminated", "file", "enum", "default", "alias", "type":
// TODO: perhaps collect these and pass along to avoid lookups later on

View file

@ -16,6 +16,8 @@
/*Package scan provides a scanner for go files that produces a swagger spec document.
This package is intended for pre-go1.11 versions, and does not support go modules.
You give it a main file and it will parse all the files that are required by that main
package to produce a swagger specification.