Repository: deepmap/oapi-codegen
Pull request #574 (2022-05-15 12:05:29)

Use lower-cased `x-go-name` for parameters var name
Small change to how variable names are generated (a follow up to a [comment](https://github.com/deepmap/oapi-codegen/pull/517#issuecomment-1118934346) on my previous MR and extension to it).
```yaml
components:
  parameters:
    PetID:
      name: pet_id
      x-go-name: PetID
      in: path
      schema:
        type: string
      required: true
```
Before:
```go
func (c *ClientWithResponses) UpdatePetWithResponse(ctx context.Context, petId PetID,  ...)
```
Now:
```go
func (c *ClientWithResponses) UpdatePetWithResponse(ctx context.Context, petID PetID,  ...)
```

emilekm: Breaks compatibility, I'll look into doing this differently.

Patch:
diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go
index 10a0a8c..97eae2f 100644
--- a/pkg/codegen/codegen.go
+++ b/pkg/codegen/codegen.go
@@ -541,12 +541,12 @@ func GenerateTypes(t *template.Template, types []TypeDefinition) (string, error)
 }
 
 func GenerateEnums(t *template.Template, types []TypeDefinition) (string, error) {
-	c := Constants{
-		EnumDefinitions: []EnumDefinition{},
-	}
+	enums := []EnumDefinition{}
 
+	// Keep track of which enums we've generated
 	m := map[string]bool{}
 
+	// These are all types defined globally
 	for _, tp := range types {
 		if found := m[tp.TypeName]; found {
 			continue
@@ -559,7 +559,7 @@ func GenerateEnums(t *template.Template, types []TypeDefinition) (string, error)
 			if tp.Schema.GoType == "string" {
 				wrapper = `"`
 			}
-			c.EnumDefinitions = append(c.EnumDefinitions, EnumDefinition{
+			enums = append(enums, EnumDefinition{
 				Schema:       tp.Schema,
 				TypeName:     tp.TypeName,
 				ValueWrapper: wrapper,
@@ -567,7 +567,40 @@ func GenerateEnums(t *template.Template, types []TypeDefinition) (string, error)
 		}
 	}
 
-	return GenerateTemplates([]string{"constants.tmpl"}, t, c)
+	// Now, go through all the enums, and figure out if we have conflicts with
+	// any others.
+	for i := range enums {
+		// Look through all other enums not compared so far. Make sure we don't
+		// compare against self.
+		e1 := enums[i]
+		for j := i + 1; j < len(enums); j++ {
+			e2 := enums[j]
+
+			for e1key := range e1.Schema.EnumValues {
+				_, found := e2.Schema.EnumValues[e1key]
+				if found {
+					e1.Conflicts = true
+					e2.Conflicts = true
+					enums[i] = e1
+					enums[j] = e2
+					break
+				}
+			}
+		}
+
+		// now see if this enum conflicts with any global type names.
+		for _, tp := range types {
+			_, found := e1.Schema.EnumValues[tp.TypeName]
+			if found {
+				e1.Conflicts = true
+				enums[i] = e1
+			}
+		}
+	}
+
+	// Now see if enums conflict with any non-enum typenames
+
+	return GenerateTemplates([]string{"constants.tmpl"}, t, Constants{EnumDefinitions: enums})
 }
 
 // Generate our import statements and package definition.
diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go
index 59b03ff..7d40f4d 100644
--- a/pkg/codegen/operations.go
+++ b/pkg/codegen/operations.go
@@ -119,7 +119,13 @@ func (pd ParameterDefinition) GoVariableName() string {
 }
 
 func (pd ParameterDefinition) GoName() string {
-	return SchemaNameToTypeName(pd.ParamName)
+	goName := pd.ParamName
+	if _, ok := pd.Spec.ExtensionProps.Extensions[extGoFieldName]; ok {
+		if extGoFieldName, err := extParseGoFieldName(pd.Spec.ExtensionProps.Extensions[extGoFieldName]); err == nil {
+			goName = extGoFieldName
+		}
+	}
+	return SchemaNameToTypeName(goName)
 }
 
 func (pd ParameterDefinition) IndirectOptional() bool {
diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go
index b8b86ca..737303e 100644
--- a/pkg/codegen/schema.go
+++ b/pkg/codegen/schema.go
@@ -93,9 +93,32 @@ func (p Property) GoTypeDef() string {
 
 // EnumDefinition holds type information for enum
 type EnumDefinition struct {
-	Schema       Schema
-	TypeName     string
+	// Schema is the scheme of a type which has a list of enum values, eg, the
+	// "container" of the enum.
+	Schema Schema
+	// TypeName is the name of the enum's type, usually aliased from something.
+	TypeName string
+	// ValueWrapper wraps the value. It's used to conditionally apply quotes
+	// around strings.
 	ValueWrapper string
+	// Conflicts is set to true when this enum conflicts with another in
+	// terms of TypeNames
+	Conflicts bool
+}
+
+// GetValues generates enum names in a way to minimize global conflicts
+func (e *EnumDefinition) GetValues() map[string]string {
+	// in case there are no conflicts, it's safe to use the values as-is
+	if !e.Conflicts {
+		return e.Schema.EnumValues
+	}
+	// If we do have conflicts, we will prefix the enum's typename to the values.
+	newValues := make(map[string]string, len(e.Schema.EnumValues))
+	for k, v := range e.Schema.EnumValues {
+		newName := e.TypeName + UppercaseFirstCharacter(k)
+		newValues[newName] = v
+	}
+	return newValues
 }
 
 type Constants struct {
diff --git a/pkg/codegen/templates/constants.tmpl b/pkg/codegen/templates/constants.tmpl
index 314d817..5fc50b1 100644
--- a/pkg/codegen/templates/constants.tmpl
+++ b/pkg/codegen/templates/constants.tmpl
@@ -5,13 +5,11 @@ const (
 {{end}}
 )
 {{end}}
-{{if gt (len .EnumDefinitions) 0 }}
 {{range $Enum := .EnumDefinitions}}
 // Defines values for {{$Enum.TypeName}}.
 const (
-{{range $index, $value := $Enum.Schema.EnumValues}}
-  {{$index}} {{$Enum.TypeName}} = {{$Enum.ValueWrapper}}{{$value}}{{$Enum.ValueWrapper}}
+{{range $name, $value := $Enum.GetValues}}
+  {{$name}} {{$Enum.TypeName}} = {{$Enum.ValueWrapper}}{{$value}}{{$Enum.ValueWrapper}}
 {{end}}
 )
 {{end}}
-{{end}}
diff --git a/pkg/runtime/bindparam.go b/pkg/runtime/bindparam.go
index 3148c3a..33d51de 100644
--- a/pkg/runtime/bindparam.go
+++ b/pkg/runtime/bindparam.go
@@ -263,7 +263,7 @@ func bindSplitPartsToDestinationStruct(paramName string, parts []string, explode
 	return nil
 }
 
-// This works much like BindStyledParameter, however it takes a query argument
+// BindQueryParameter works much like BindStyledParameter, however it takes a query argument
 // input array from the url package, since query arguments come through a
 // different path than the styled arguments. They're also exceptionally fussy.
 // For example, consider the exploded and unexploded form parameter examples:
@@ -336,10 +336,12 @@ func BindQueryParameter(style string, explode bool, required bool, paramName str
 			case reflect.Slice:
 				// In the slice case, we simply use the arguments provided by
 				// http library.
+
 				if !found {
 					if required {
 						return fmt.Errorf("query parameter '%s' is required", paramName)
 					} else {
+						// If an optional parameter is not found, we do nothing,
 						return nil
 					}
 				}
@@ -349,7 +351,13 @@ func BindQueryParameter(style string, explode bool, required bool, paramName str
 				// form style object binding doesn't tell us which arguments
 				// in the query string correspond to the object's fields. We'll
 				// try to bind field by field.
-				err = bindParamsToExplodedObject(paramName, queryParams, output)
+				var fieldsPresent bool
+				fieldsPresent, err = bindParamsToExplodedObject(paramName, queryParams, output)
+				// If no fields were set, and there is no error, we will not fall
+				// through to assign the destination.
+				if !fieldsPresent {
+					return nil
+				}
 			default:
 				// Primitive object case. We expect to have 1 value to
 				// unmarshal.
@@ -363,6 +371,15 @@ func BindQueryParameter(style string, explode bool, required bool, paramName str
 				if len(values) != 1 {
 					return fmt.Errorf("multiple values for single value parameter '%s'", paramName)
 				}
+
+				if !found {
+					if required {
+						return fmt.Errorf("query parameter '%s' is required", paramName)
+					} else {
+						// If an optional parameter is not found, we do nothing,
+						return nil
+					}
+				}
 				err = BindStringToObject(values[0], output)
 			}
 			if err != nil {
@@ -427,21 +444,28 @@ func BindQueryParameter(style string, explode bool, required bool, paramName str
 	}
 }
 
-// This function reflects the destination structure, and pulls the value for
+// bindParamsToExplodedObject reflects the destination structure, and pulls the value for
 // each settable field from the given parameters map. This is to deal with the
 // exploded form styled object which may occupy any number of parameter names.
 // We don't try to be smart here, if the field exists as a query argument,
-// set its value.
-func bindParamsToExplodedObject(paramName string, values url.Values, dest interface{}) error {
+// set its value. This function returns a boolean, telling us whether there was
+// anything to bind. There will be nothing to bind if a parameter isn't found by name,
+// or none of an exploded object's fields are present.
+func bindParamsToExplodedObject(paramName string, values url.Values, dest interface{}) (bool, error) {
 	// Dereference pointers to their destination values
 	binder, v, t := indirect(dest)
 	if binder != nil {
-		return BindStringToObject(values.Get(paramName), dest)
+		_, found := values[paramName]
+		if !found {
+			return false, nil
+		}
+		return true, BindStringToObject(values.Get(paramName), dest)
 	}
 	if t.Kind() != reflect.Struct {
-		return fmt.Errorf("unmarshaling query arg '%s' into wrong type", paramName)
+		return false, fmt.Errorf("unmarshaling query arg '%s' into wrong type", paramName)
 	}
 
+	fieldsPresent := false
 	for i := 0; i < t.NumField(); i++ {
 		fieldT := t.Field(i)
 
@@ -466,15 +490,16 @@ func bindParamsToExplodedObject(paramName string, values url.Values, dest interf
 		fieldVal, found := values[fieldName]
 		if found {
 			if len(fieldVal) != 1 {
-				return fmt.Errorf("field '%s' specified multiple times for param '%s'", fieldName, paramName)
+				return false, fmt.Errorf("field '%s' specified multiple times for param '%s'", fieldName, paramName)
 			}
 			err := BindStringToObject(fieldVal[0], v.Field(i).Addr().Interface())
 			if err != nil {
-				return fmt.Errorf("could not bind query arg '%s' to request object: %s'", paramName, err)
+				return false, fmt.Errorf("could not bind query arg '%s' to request object: %s'", paramName, err)
 			}
+			fieldsPresent = true
 		}
 	}
-	return nil
+	return fieldsPresent, nil
 }
 
 // indirect


Test patch:
diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go
index fbb91f6..569689c 100644
--- a/internal/test/components/components.gen.go
+++ b/internal/test/components/components.gen.go
@@ -1373,25 +1373,25 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
 // Base64 encoded, gzipped, json marshaled Swagger object
 var swaggerSpec = []string{
 
-	"H4sIAAAAAAAC/9RXTW/cNhD9KwO2R9nrj/SyNxdNURdoEzgGeogNgxZHFl3uUCEpb4RA/70YStqVtdRm",
-	"bedSX6ylyPl4M/P49E3kdlVZQgpeLL8Jh19q9OFXqzTGhavNQsM/c0sBKfCjrCqjcxm0pcWjt8RrPi9x",
-	"JfmpcrZCF3orv2s0ih9+dliIpfhpsXW76A75xaf4/8P9I+ZBtG0Wg9EOlVh+7i3c8nLAr2FRGaknLkNT",
-	"oVgKH5ymB9HyH9vwlSU/JNP96H383/LJhEKfO11xjGIpLsDrVWUQhiTBbp31UbChC6U0H5Hm4yaLLqzT",
-	"mHji9ci/poAP6MSO+z+kh+1Z2CIEtgA+DJqCyCbQaZW2TXKFiawzYavOQQqS55hGExl7uM2GrQMi2R4U",
-	"zuZRKKTxOE38N4seyAaQxth1GoO35v2DUjufTy24eiezC07Ig6QmkVWzk9MLYn9Z2O9eFnbsRLLUrGzt",
-	"oeDRgnWp8xLKuR7drQ8Ruu+5/aHpH3a8iyt7DYq/7Jvuw5nr8Llf61BCZwQK60DpPG5yHeA7oXce/tGh",
-	"/NNb2pDqQShn4kmaGiODFdatZBBLEXk7m9l6dsDW9Nj1nlLoP4NqJ/ZCOx/+nkvAoVQfyDRXvUcuEG98",
-	"Dvd1qf1mBEF7GAIESQoGGxl4C6FEeLCwsgoN+NLWRkEpnxB0AC4aVJaZx2VwQ9Lz6lobE6nsntk6N7VC",
-	"Hh0kfqk9eKQAhbMryI3m52DBo3tCd3zD8A3uhwHZzdGadPJrpwOmsp9Q5Ghn52Rao+ghG0E9A+ycy9t4",
-	"s2oqLHs3OkfyuG088dflNccbdOBMxDX6AJ8iBNwZ6HxXptPjk+OT7r5CkpUWS3F+fHJ8ykQjQxnbYYHk",
-	"a4dH+ISuCaWmhyPtjxwW6JByjM3/gGGmB5BUrB/gV+2D7wouA2wHGHJJXMncoQyoQBOEUvsb8hXmsV36",
-	"UleuJlSxgtxXUfRcKrEU72OA7zfxXfqrbXTZSB42cxzyTEEuxvJxqsbOTk7eIMEK/YTf47F91NhmorC1",
-	"e72Jd2ziccxb++ykqI6bhd6QxGnsy9LhG2ycRxtr+3oLZ5E1J8TYq9VC1ibMd0rfDIuJLu9OLyrp5Mrf",
-	"8aVyJ5W64/r72RG5AB6z7gqKJzGg87HpJdxb1fSKoKeO9A2WmIiPMQou3IWKhBGlw8aBWH5OE/awY16C",
-	"RGcs+sWXGl0z3PFLUZ2KMcV1zLqdg30CZed+8qGJtNV9KYg2OyRaGqmpqD+2908HIiEqz3fBPd5QqB1F",
-	"sgmWr5i4s9P/rAFS0fLJtXX/ziNwtheBFym3xN0zbdaU4rpt29tnhEW1MW0mKusT3Rc1EfTcN243Zjep",
-	"id8q7TAPSUAy7tMb2gs8N3bqbKJnmW4nHete+R1/uBo+tAyjb59XSuLhY2ioUzvtlXa3cG3b/hcAAP//",
-	"Jit1A+wQAAA=",
+	"H4sIAAAAAAAC/9RYT2/cthP9KgP+fkfFazvpZW8umqIu0CZwDPQQGwYtjiy6XFLhUN4Igb57MZS0K2up",
+	"zXodFGgu0Urk/Hkz8/jobyJ3q8pZtIHE8pvw+KVGCj87pTG+uNq8aPhn7mxAG/hRVpXRuQza2cUjOcvv",
+	"KC9xJfmp8q5CH3orv2o0ih/+77EQS/G/xdbtottEi0/x/w/3j5gH0bZZDEZ7VGL5ubdwy68Dfg2Lykg9",
+	"cRmaCsVSUPDaPoiW/7ENqpylIZnuR+/jv5ZPJhRS7nXFMYqluADSq8ogDEmC2zrro2BDF0pp3iLNx00W",
+	"XVhnMfHE55F/bQM+oBc77n+TBNu9sEUIXAG8GbQNIptAp1XatpUrTGSdCVd1DlKQPMc0msjYw202LB0Q",
+	"yfagcD6PQiEN4TTxXxwSWBdAGuPWaQxem/cPSu3tfGrB1zuZXXBCBNI2iayanZxeEPvLwn73srBjJ1pn",
+	"m5WrCQoeLViXOi+hnOvR3fpYi/57bn9o+odt7+LKjkHxp33TfThzHT73ax1K6IxA4TwoncdFvgN8J/TO",
+	"w186lL+TsxtSPQjlTDxJU2NksML5lQxiKSJvZzNLzw9Ymh673lMK/WdQ7cReaE/hz7kEPEr1wZrmqvfI",
+	"BeKFz+G+LjVtRhA0wRAgSKtgsJEBOQglwoODlVNogEpXGwWlfELQAbhoUDlmHp/BjZXEb9famEhl98zW",
+	"uakV8uig5Y+agNAGKLxbQW40PwcHhP4J/ckNwze4HwZkN0dn0smvvQ6Yyn5CkaOVnZNpjaKHbAT1DLBz",
+	"Lm/jyapt4di70Tlawm3jiT8urzneoANnIq6RAnyKEHBnoKeuTGcnpyen3XmFVlZaLMXbk9OTMyYaGcrY",
+	"Dgu0VHt8g0/om1Bq+/BG0xuPBXq0Ocbmf8Aw0wNoVawf4FdNgbqCywDbAYZcWq5k7lEGVKAthFLTjaUK",
+	"89gufakrX1tUsYLcV1H0XCqxFO9jgO838V3S1Ta6bCQPmzkOeaYgF2P5OFVj56enr5BghX7C7/HYPmps",
+	"M1G42h9v4h2beBzz1j47KarjZrGvSOIs9mXp8RU23kYba3e8hfPImhNi3BWrlZE5ls6oQUoWsjZhvov6",
+	"RllMNHun6heV9HJFd3zg3Eml7rg3aHZ8LoBHsDue4k4M6CkOhIR7p5peLfS0kj7dEtPyMUbBRb1QkUyi",
+	"rNg4EMvPaTIfVszLk+iMLwTiS42+Gc7/pajOxJj+Otbdzsg+8bJzdlFoIqV1twjRZodEa0dKK2qT7dnU",
+	"gWgRFfE5cY83NtTeRiIKjo+fuLK7G7A+SEXLO9fO/z2PwPleBF6k6hLn0rSRU2rstm1vJ2Q26ucRoR19",
+	"tXs2Lfy5cpRo7CjFoKfccSdzDFJb/qq0xzwksc54BG7s3pryzKT2JsaBWX4yDP7IPx8cLsIPrfDoynWk",
+	"Eh/uYEMLtNM2bP/Vnmjb9p8AAAD//xtfutO+EQAA",
 }
 
 // GetSwagger returns the content of the embedded swagger specification file
diff --git a/internal/test/components/components.yaml b/internal/test/components/components.yaml
index 479159e..d240530 100644
--- a/internal/test/components/components.yaml
+++ b/internal/test/components/components.yaml
@@ -18,6 +18,7 @@ paths:
         $ref: "#/components/requestBodies/RequestBody"
       responses:
         200:
+          description: placeholder
           content:
             application/json:
               schema:
@@ -65,6 +66,13 @@ paths:
                 additionalProperties:
                   type: string
             required: [inner]
+      responses:
+        default:
+          description: placeholder
+          content:
+            text/plain:
+              schema:
+                type: string
     post:
       operationId: BodyWithAddProps
       description: |
@@ -84,6 +92,13 @@ paths:
                     type: integer
               required: [name, inner]
               additionalProperties: true
+      responses:
+        default:
+          description: placeholder
+          content:
+            text/plain:
+              schema:
+                type: string
 components:
   schemas:
     SchemaObject:
diff --git a/internal/test/components/config.yaml b/internal/test/components/config.yaml
new file mode 100644
index 0000000..e69de29
diff --git a/internal/test/parameters/parameters.gen.go b/internal/test/parameters/parameters.gen.go
index 537d26f..63feb8b 100644
--- a/internal/test/parameters/parameters.gen.go
+++ b/internal/test/parameters/parameters.gen.go
@@ -3250,25 +3250,25 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
 // Base64 encoded, gzipped, json marshaled Swagger object
 var swaggerSpec = []string{
 
-	"H4sIAAAAAAAC/9xa34+bOBD+V9DcPZ1ISNo33qrer5Wu295lpZ5U7YMXJsE9wK7t7O0q4n8/2UAAQwgk",
-	"YTe9ty7MzDfzefzVHrKDgCWcpZgqCf4OBErOUonmjxVNeIx/FY/0k4ClClOl/6nwSXk8JjTVf8kgwoSY",
-	"588cwQepBE03kGWZCyHKQFCuKEvBh3eONHGdEsthD18xUKBN8zgG/T3TVk8f85f+DrhgHIWieXI3YQ2N",
-	"pgo3KCBz4Ua+C5M8qeLlA2MxklS/rIL9KHANPvzgVfV7Bbj3scpH4LctFRiC/6V0djV0hXPfCNvMcU2F",
-	"VLckwQ5iXBAs7nphoRortxbq3nBK0zXTzjENsFic1ADBh5s7HV1RpcPDHUrlrFA8ogAXHlHIfBmW88V8",
-	"oQ0Zx5RwCj68nS/mS3CBExWZ/L1ivfP6vB0ngiSZfrNBU64uluh11asBv6F6X3cwoQRJUKGQ4H9p9A/h",
-	"PKaBcfa+SmZ1Ud/yNBujYAN8kza4JQ0GGepcKrHF7N5t9vibxeIQ3t7OszZCZjC9gLF/KPazYSxaNDQ3",
-	"BBc0oYo+akN84jELEfw1iSUWhQVlmLI0cGtUrZlIiMo3wds34Lb2ROYOQtT0HADEsxELlNAhQpDnobCk",
-	"AUsVJnIQ/v5JjtaRTyuNPr6nS2NPCys3zCBeWCOhYVJmQ7cR+yg4DXGq7d6sJMgNKg47KwgYtEnQ7xyp",
-	"iFA03Tj/UhU56TZ5MFLZGWUpG0TY0t1Ul3Qbx0YpIiQhij6l+D23OFcpojJMke7fs081l0k1owd69kvR",
-	"5i+iIu1E3mnr7iReTFMOZPXKytLOKt9m3WRNITSHMvju9KZdSBGoLOgE9bFjLmerwnr2mapodltaj1ak",
-	"mDxgXCyyaURvNzfS81Pv8e4P262tWF1tNuRkdpmN4IJUz+bcayqES5736pyVJ+KxpB06GF+CtSG7ZHJ+",
-	"bllXVx3np+nXQ1BdPP5HfbWvv9lZI4g72lrnMPfavZUQJeiT1Vo07N94H1pOp2w8Gk7eU3l10xG276lR",
-	"jJ2uVUcoG9dMk5HTkioaDiDnAkL1PXdUW6fGsXaGSl17V3Ei5V0k2HYTDRmVfarMewdlIwatrzIG+7ZF",
-	"8fwzIq+moIdKrlkduemGiLz/6mJgqzrDPPTJHWKd+qtGCaucDx2mTSq/MpH01f7n3uhI6YMuuVb1F5uU",
-	"VXVrVxh5ybWyerGkhl12bc6mn6JZiJcA3Jd6bB5jVzvN0Lin2ssBOoXGHcDpH8m98ljASva0KaQVZNQQ",
-	"8ixtz7/UNY9JA268q5bb9c4J8hJhMtYa385G0HY9k4LJGLIP4MfPTKsOvyueFUzP3PAvs6sux6uYFkzG",
-	"0v4DxHB+6p9LLGZOYmJA80xJQ/F/ymeqonxW7O2WA6houU14QVlOfEPRDJtfP+R5b0UMPkRKcd/zip8+",
-	"KJRqrs/5CeFzQiG7z/4LAAD//734if8YIwAA",
+	"H4sIAAAAAAAC/9xa34+jNhD+V9C0TxUJyd0bb6frr5V6e9dmpat02gcvTIKvgH22s91VxP9e2UAAQwgk",
+	"YTfXtwRm5pv5GH+xh+wgYAlnKaZKgr8DgZKzVKL5sqIJj/Gv4pK+ErBUYar0R4VPyuMxoan+JoMIE2Ku",
+	"P3MEH6QSNN1AlmUuhCgDQbmiLAUf3jnSxHVKLIc9fMVAgTbN4xj090xbPX3Mb/o74IJxFIrmyd2ENTSa",
+	"KtyggMyFG/kuTPKkipsPjMVIUn2zCvajwDX48INX1e8V4N7HKh+B37ZUYAj+l9LZ1dAVzn0jbDPHNRVS",
+	"3ZIEO4hxQbC464aFaqzcWqh7wylN10w7xzTA4uGkBgg+3Nzp6IoqHR7uUCpnheIRBbjwiELmj2E5X8wX",
+	"2pBxTAmn4MPb+WK+BBc4UZHJ3yued16ft+NEkCTTdzZoytXFEv1c9dOA31C9rzuYUIIkqFBI8L80+odw",
+	"HtPAOHtfJbO6qO/xNBujYAN8kza4JQ0GGepcKrHF7N5t9vibxeIQ3t7OsxZCZjC9gLF/KPazYSxaNDQX",
+	"BBc0oYo+akN84jELEfw1iSUWhQVlmLI0cGtUrZlIiMoXwds34LbWROYOQtT0HADEsxELlNAhQpDnobCk",
+	"AUsVJnIQ/v5KjtaRTyuNPr6nS2NPCysXzCBeWCOhYVJmQ7cR+yg4DXGq5d6sJMgNKg47KwgYtEnQ9xyp",
+	"iFA03Tj/UhU56TZ5MFLZGWUpG0TY0m2rS4hrso3VqQoTIQlR9CnM77nFuQoTlWGKMv+efaq5TKo1PdCz",
+	"X4rl8SLq007knbbuTuLFtOhAVq+sSO2s8uXZTdYUAnUog+9Op9qFFIHKgk5QLTvmcrYqrGefqYpmt6X1",
+	"iylZTB4wLprDNLC3mxvJ+ql3O/mH7dZWuq72HLITvMwCckGqZ7PPNhXCJfeXdc7KHfhY0g5txC/B2pDV",
+	"NTk/t6yrq47z0/TrIaguOv+jvtrX3+ysEcQdba1zmHvt3kqIEvTJai0a9i+8Dy2nUxYeDSfvqby66Qjb",
+	"99Qoxk7XqiOUjWumychpSRUNB5BzAaH6njuqrVPjWDtDpa69qziR8i4SbLuJhozmPlXmvYO5EYPdVxm7",
+	"fduieP4ZkVdT10Ml16yOnJBDRN5/5DGwVZ1hHvrkDrFOC1WjhFXOl96EmxJ+ZSLp4+zPvdERygYdqi3W",
+	"LjbRq/jSrjDyUG1l9WJJDTtc25xNP+2zEC8BuC/12PzHrnaa4XZPtZcDdAptPIDTPzp85TGElexp01Ir",
+	"yMhh6Rm/Cfkbxeb2asBJedVyu975Ql4iTMZa4x3fCNquZ8IwGUP2xv34XmvV4XfFM4bpmRv+BnnV5XgV",
+	"U4bJWNq/8BjOT/31jMXMSUwMaJ4paSh+Uz5TFeWzaW+3HEBFy23Cg81y4pONZtj8SyPPeyti8CFSivue",
+	"V/xFQ6FUc30+SAifEwrZffZfAAAA///Yf5GUwCMAAA==",
 }
 
 // GetSwagger returns the content of the embedded swagger specification file
diff --git a/internal/test/parameters/parameters.yaml b/internal/test/parameters/parameters.yaml
index bd90e5f..19a8f9d 100644
--- a/internal/test/parameters/parameters.yaml
+++ b/internal/test/parameters/parameters.yaml
@@ -23,7 +23,6 @@ paths:
           $ref: "#/components/responses/SimpleResponse"
   /simpleNoExplodeArray/{param}:
     get:
-      summary:
       operationId: getSimpleNoExplodeArray
       parameters:
       - name: param
@@ -41,7 +40,6 @@ paths:
           $ref: "#/components/responses/SimpleResponse"
   /simpleExplodeArray/{param*}:
     get:
-      summary:
       operationId: getSimpleExplodeArray
       parameters:
         - name: param
@@ -59,7 +57,6 @@ paths:
           $ref: "#/components/responses/SimpleResponse"
   /simpleNoExplodeObject/{param}:
     get:
-      summary:
       operationId: getSimpleNoExplodeObject
       parameters:
         - name: param
@@ -74,7 +71,6 @@ paths:
           $ref: "#/components/responses/SimpleResponse"
   /simpleExplodeObject/{param*}:
     get:
-      summary:
       operationId: getSimpleExplodeObject
       parameters:
         - name: param
@@ -89,7 +85,6 @@ paths:
           $ref: "#/components/responses/SimpleResponse"
   /labelNoExplodeArray/{.param}:
     get:
-      summary:
       operationId: getLabelNoExplodeArray
       parameters:
         - name: param
@@ -107,7 +102,6 @@ paths:
           $ref: "#/components/responses/SimpleResponse"
   /labelExplodeArray/{.param*}:
     get:
-      summary:
       operationId: getLabelExplodeArray
       parameters:
         - name: param
@@ -125,7 +119,6 @@ paths:
           $ref: "#/components/responses/SimpleResponse"
   /labelNoExplodeObject/{.param}:
     get:
-      summary:
       operationId: getLabelNoExplodeObject
       parameters:
         - name: param
@@ -140,7 +133,6 @@ paths:
           $ref: "#/components/responses/SimpleResponse"
   /labelExplodeObject/{.param*}:
     get:
-      summary:
       operationId: getLabelExplodeObject
       parameters:
         - name: param
@@ -155,7 +147,6 @@ paths:
           $ref: "#/components/responses/SimpleResponse"
   /matrixNoExplodeArray/{.id}:
     get:
-      summary:
       operationId: getMatrixNoExplodeArray
       parameters:
         - name: id
@@ -173,7 +164,6 @@ paths:
           $ref: "#/components/responses/SimpleResponse"
   /matrixExplodeArray/{.id*}:
     get:
-      summary:
       operationId: getMatrixExplodeArray
       parameters:
         - name: id
@@ -191,7 +181,6 @@ paths:
           $ref: "#/components/responses/SimpleResponse"
   /matrixNoExplodeObject/{.id}:
     get:
-      summary:
       operationId: getMatrixNoExplodeObject
       parameters:
         - name: id
@@ -206,7 +195,6 @@ paths:
           $ref: "#/components/responses/SimpleResponse"
   /matrixExplodeObject/{.id*}:
     get:
-      summary:
       operationId: getMatrixExplodeObject
       parameters:
         - name: id
@@ -352,6 +340,9 @@ paths:
           explode: true
           schema:
             $ref: "#/components/schemas/ComplexObject"
+      responses:
+        default:
+         $ref: "#/components/responses/SimpleResponse"
   /header:
     get:
       operationId: getHeader
@@ -420,6 +411,10 @@ paths:
           required: false
           schema:
             type: string
+      responses:
+        default:
+          $ref: "#/components/responses/SimpleResponse"
+
   /cookie:
     get:
       operationId: getCookie
@@ -488,6 +483,10 @@ paths:
           required: false
           schema:
             type: string
+      responses:
+        default:
+          $ref: "#/components/responses/SimpleResponse"
+
 components:
   schemas:
     Object:
diff --git a/pkg/runtime/bindparam_test.go b/pkg/runtime/bindparam_test.go
index 54269d1..c745e18 100644
--- a/pkg/runtime/bindparam_test.go
+++ b/pkg/runtime/bindparam_test.go
@@ -335,6 +335,30 @@ func TestBindQueryParameter(t *testing.T) {
 		assert.NoError(t, err)
 		assert.Equal(t, expected, birthday)
 	})
+
+	t.Run("optional", func(t *testing.T) {
+		queryParams := url.Values{
+			"time":   {"2020-12-09T16:09:53+00:00"},
+			"number": {"100"},
+		}
+		// An optional time will be a pointer to a time in a parameter object
+		var optionalTime *time.Time
+		err := BindQueryParameter("form", true, false, "notfound", queryParams, &optionalTime)
+		require.NoError(t, err)
+		assert.Nil(t, optionalTime)
+
+		var optionalNumber *int
+		err = BindQueryParameter("form", true, false, "notfound", queryParams, &optionalNumber)
+		require.NoError(t, err)
+		assert.Nil(t, optionalNumber)
+
+		// If we require values, we require errors when they're not present.
+		err = BindQueryParameter("form", true, true, "notfound", queryParams, &optionalTime)
+		assert.Error(t, err)
+		err = BindQueryParameter("form", true, true, "notfound", queryParams, &optionalNumber)
+		assert.Error(t, err)
+
+	})
 }
 
 func TestBindParameterViaAlias(t *testing.T) {
@@ -423,28 +447,36 @@ func TestBindParamsToExplodedObject(t *testing.T) {
 	}
 
 	var dstTime time.Time
-	err := bindParamsToExplodedObject("time", values, &dstTime)
+	fieldsPresent, err := bindParamsToExplodedObject("time", values, &dstTime)
 	assert.NoError(t, err)
+	assert.True(t, fieldsPresent)
 	assert.EqualValues(t, now, dstTime)
 
 	type AliasedTime time.Time
 	var aDstTime AliasedTime
-	err = bindParamsToExplodedObject("time", values, &aDstTime)
+	fieldsPresent, err = bindParamsToExplodedObject("time", values, &aDstTime)
 	assert.NoError(t, err)
+	assert.True(t, fieldsPresent)
 	assert.EqualValues(t, now, aDstTime)
 
 	expectedDate := MockBinder{Time: time.Date(2020, 11, 6, 0, 0, 0, 0, time.UTC)}
 
 	var dstDate MockBinder
-	err = bindParamsToExplodedObject("date", values, &dstDate)
+	fieldsPresent, err = bindParamsToExplodedObject("date", values, &dstDate)
+	assert.NoError(t, err)
+	assert.True(t, fieldsPresent)
 	assert.EqualValues(t, expectedDate, dstDate)
 
 	var eDstDate EmbeddedMockBinder
-	err = bindParamsToExplodedObject("date", values, &eDstDate)
+	fieldsPresent, err = bindParamsToExplodedObject("date", values, &eDstDate)
+	assert.NoError(t, err)
+	assert.True(t, fieldsPresent)
 	assert.EqualValues(t, expectedDate, dstDate)
 
 	var nTDstDate AnotherMockBinder
-	err = bindParamsToExplodedObject("date", values, &nTDstDate)
+	fieldsPresent, err = bindParamsToExplodedObject("date", values, &nTDstDate)
+	assert.NoError(t, err)
+	assert.True(t, fieldsPresent)
 	assert.EqualValues(t, expectedDate, nTDstDate)
 
 	type ObjectWithOptional struct {
@@ -452,8 +484,9 @@ func TestBindParamsToExplodedObject(t *testing.T) {
 	}
 
 	var optDstTime ObjectWithOptional
-	err = bindParamsToExplodedObject("explodedObject", values, &optDstTime)
+	fieldsPresent, err = bindParamsToExplodedObject("explodedObject", values, &optDstTime)
 	assert.NoError(t, err)
+	assert.True(t, fieldsPresent)
 	assert.EqualValues(t, &now, optDstTime.Time)
 }