oapi-codegen icon indicating copy to clipboard operation
oapi-codegen copied to clipboard

Server cannot decode binary parameter

Open winder opened this issue 5 years ago • 2 comments

I'm having trouble using binary parameters, I'm not sure what I'm doing wrong.

I have a parameter defined with:

    "note-prefix": {
      "type": "string",
      "format": "byte",
      "description": "Specifies a prefix which must be contained in the note field.",
      "name": "note-prefix",
      "in": "query"
    },

This generates a parameter object as expected with:

    NotePrefix *[]byte `json:"note-prefix,omitempty"`

Now I try to use the parameter by getting a base64 string echo "test" |base64, and url encoding it to get dGVzdAo%3D.

But the parameter isn't properly parsed:

$ curl -s "localhost:8980/transactions?note-prefix=dGVzdAo%3D" | json_pp
{
   "message" : "Invalid format for parameter note-prefix: error setting array element: error binding string parameter: can not bind to destination of type: uint8"
}

I'm hoping that this is user error, but it looks like an entry is missing from this: https://github.com/deepmap/oapi-codegen/blob/88bfbcf8cc7a4c8bfa82c3f0a6cfd0eb1db34d63/pkg/runtime/bindstring.go#L50

Hoping that someone can confirm.

winder avatar Apr 17 '20 18:04 winder

This seems to be a problem with BindQueryParameter for optional parameters, and I was able to detect the byte array type inside the IsNil check. But I wasn't sure how to turn this knowledge into a working version of BindQueryParameter.

	} else {
		// For optional parameters, we have an extra indirect. An optional
		// parameter of type "int" will be *int on the struct. We pass that
		// in by pointer, and have **int.

		// If the destination, is a nil pointer, we need to allocate it.
		if v.IsNil() {
+			switch v.Kind() {
+			case reflect.Slice:
+				if v.Type() == reflect.TypeOf([]byte(nil)) {
+					fmt.Println("byte array parameter detected")
+				}
+			}

			t := v.Type()

			newValue := reflect.New(t.Elem())
			// for now, hang onto the output buffer separately from destination,
			// as we don't want to write anything to destination until we can
			// unmarshal successfully, and check whether a field is required.
			output = newValue.Interface()
		} else {
			// If the destination isn't nil, just use that.
			output = v.Interface()
		}

		// Get rid of that extra indirect as compared to the required case,
		// so the code below doesn't have to care.
		v = reflect.Indirect(reflect.ValueOf(output))
	}

winder avatar Apr 21 '20 19:04 winder

The issue seems to be that BindQueryParameter assumes that if the destination is a slice it's the target for individual query parameters, which is not the case for format byte which is expecting a single parameter stored. Unfortunately even with explode: false it still calls bindSplitPartsToDestinationArray which doesn't handle this case.

We could update BindQueryParameter to treat a slice of uint8 aka byte as a special case, but not sure if that's backwards compatible.

stevenh avatar Mar 09 '24 22:03 stevenh