@@ -26,6 +26,17 @@ import (
2626 "github.com/oapi-codegen/runtime/types"
2727)
2828
29+ // RequiredParameterError is returned when a required query parameter is missing.
30+ // Generated server code can use errors.As to detect this and produce a
31+ // framework-specific typed error for the application's error handler.
32+ type RequiredParameterError struct {
33+ ParamName string
34+ }
35+
36+ func (e * RequiredParameterError ) Error () string {
37+ return fmt .Sprintf ("query parameter '%s' is required" , e .ParamName )
38+ }
39+
2940// BindStyledParameter binds a parameter as described in the Path Parameters
3041// section here to a Go object:
3142// https://swagger.io/docs/specification/serialization/
@@ -417,13 +428,17 @@ func BindQueryParameterWithOptions(style string, explode bool, required bool, pa
417428 k := t .Kind ()
418429
419430 switch style {
420- case "form" :
431+ case "form" , "spaceDelimited" , "pipeDelimited" :
421432 var parts []string
422433 if explode {
423434 // ok, the explode case in query arguments is very, very annoying,
424435 // because an exploded object, such as /users?role=admin&firstName=Alex
425436 // isn't actually present in the parameter array. We have to do
426437 // different things based on destination type.
438+ //
439+ // Note: spaceDelimited and pipeDelimited with explode=true are
440+ // serialized identically to form explode=true (each value is a
441+ // separate key=value pair), so we share this code path.
427442 values , found := queryParams [paramName ]
428443 var err error
429444
@@ -434,7 +449,7 @@ func BindQueryParameterWithOptions(style string, explode bool, required bool, pa
434449
435450 if ! found {
436451 if required {
437- return fmt . Errorf ( "query parameter '%s' is required" , paramName )
452+ return & RequiredParameterError { ParamName : paramName }
438453 } else {
439454 // If an optional parameter is not found, we do nothing,
440455 return nil
@@ -469,7 +484,7 @@ func BindQueryParameterWithOptions(style string, explode bool, required bool, pa
469484 // unmarshal.
470485 if len (values ) == 0 {
471486 if required {
472- return fmt . Errorf ( "query parameter '%s' is required" , paramName )
487+ return & RequiredParameterError { ParamName : paramName }
473488 } else {
474489 return nil
475490 }
@@ -480,7 +495,7 @@ func BindQueryParameterWithOptions(style string, explode bool, required bool, pa
480495
481496 if ! found {
482497 if required {
483- return fmt . Errorf ( "query parameter '%s' is required" , paramName )
498+ return & RequiredParameterError { ParamName : paramName }
484499 } else {
485500 // If an optional parameter is not found, we do nothing,
486501 return nil
@@ -502,15 +517,22 @@ func BindQueryParameterWithOptions(style string, explode bool, required bool, pa
502517 values , found := queryParams [paramName ]
503518 if ! found {
504519 if required {
505- return fmt . Errorf ( "query parameter '%s' is required" , paramName )
520+ return & RequiredParameterError { ParamName : paramName }
506521 } else {
507522 return nil
508523 }
509524 }
510525 if len (values ) != 1 {
511526 return fmt .Errorf ("parameter '%s' is not exploded, but is specified multiple times" , paramName )
512527 }
513- parts = strings .Split (values [0 ], "," )
528+ switch style {
529+ case "spaceDelimited" :
530+ parts = strings .Split (values [0 ], " " )
531+ case "pipeDelimited" :
532+ parts = strings .Split (values [0 ], "|" )
533+ default :
534+ parts = strings .Split (values [0 ], "," )
535+ }
514536 }
515537 var err error
516538 switch k {
@@ -549,7 +571,7 @@ func BindQueryParameterWithOptions(style string, explode bool, required bool, pa
549571 default :
550572 if len (parts ) == 0 {
551573 if required {
552- return fmt . Errorf ( "query parameter '%s' is required" , paramName )
574+ return & RequiredParameterError { ParamName : paramName }
553575 } else {
554576 return nil
555577 }
@@ -571,8 +593,6 @@ func BindQueryParameterWithOptions(style string, explode bool, required bool, pa
571593 return errors .New ("deepObjects must be exploded" )
572594 }
573595 return unmarshalDeepObject (dest , paramName , queryParams , required )
574- case "spaceDelimited" , "pipeDelimited" :
575- return fmt .Errorf ("query arguments of style '%s' aren't yet supported" , style )
576596 default :
577597 return fmt .Errorf ("style '%s' on parameter '%s' is invalid" , style , paramName )
578598
@@ -655,10 +675,14 @@ func BindRawQueryParameter(style string, explode bool, required bool, paramName
655675 k := t .Kind ()
656676
657677 switch style {
658- case "form" :
678+ case "form" , "spaceDelimited" , "pipeDelimited" :
659679 if explode {
660680 // For the explode case, url.ParseQuery is fine — there are no
661681 // delimiter commas to confuse with literal commas.
682+ //
683+ // Note: spaceDelimited and pipeDelimited with explode=true are
684+ // serialized identically to form explode=true (each value is a
685+ // separate key=value pair), so we share this code path.
662686 queryParams , err := url .ParseQuery (rawQuery )
663687 if err != nil {
664688 return fmt .Errorf ("error parsing query string: %w" , err )
@@ -669,7 +693,7 @@ func BindRawQueryParameter(style string, explode bool, required bool, paramName
669693 case reflect .Slice :
670694 if ! found {
671695 if required {
672- return fmt . Errorf ( "query parameter '%s' is required" , paramName )
696+ return & RequiredParameterError { ParamName : paramName }
673697 }
674698 return nil
675699 }
@@ -683,7 +707,7 @@ func BindRawQueryParameter(style string, explode bool, required bool, paramName
683707 default :
684708 if len (values ) == 0 {
685709 if required {
686- return fmt . Errorf ( "query parameter '%s' is required" , paramName )
710+ return & RequiredParameterError { ParamName : paramName }
687711 }
688712 return nil
689713 }
@@ -692,7 +716,7 @@ func BindRawQueryParameter(style string, explode bool, required bool, paramName
692716 }
693717 if ! found {
694718 if required {
695- return fmt . Errorf ( "query parameter '%s' is required" , paramName )
719+ return & RequiredParameterError { ParamName : paramName }
696720 }
697721 return nil
698722 }
@@ -707,22 +731,33 @@ func BindRawQueryParameter(style string, explode bool, required bool, paramName
707731 return nil
708732 }
709733
710- // form, explode=false — the core fix.
711- // Use findRawQueryParam to get the still-encoded value, split on
712- // literal ',' (which is the OpenAPI delimiter), then URL-decode
734+ // explode=false — use findRawQueryParam to get the still-encoded
735+ // value, split on the style-specific delimiter, then URL-decode
713736 // each resulting part individually.
714737 rawValues , found := findRawQueryParam (rawQuery , paramName )
715738 if ! found {
716739 if required {
717- return fmt . Errorf ( "query parameter '%s' is required" , paramName )
740+ return & RequiredParameterError { ParamName : paramName }
718741 }
719742 return nil
720743 }
721744 if len (rawValues ) != 1 {
722745 return fmt .Errorf ("parameter '%s' is not exploded, but is specified multiple times" , paramName )
723746 }
724747
725- rawParts := strings .Split (rawValues [0 ], "," )
748+ var rawParts []string
749+ switch style {
750+ case "spaceDelimited" :
751+ // Normalise all space representations to %20, then split.
752+ normalized := strings .ReplaceAll (rawValues [0 ], "+" , "%20" )
753+ normalized = strings .ReplaceAll (normalized , " " , "%20" )
754+ rawParts = strings .Split (normalized , "%20" )
755+ case "pipeDelimited" :
756+ rawParts = strings .Split (rawValues [0 ], "|" )
757+ default :
758+ rawParts = strings .Split (rawValues [0 ], "," )
759+ }
760+
726761 parts := make ([]string , len (rawParts ))
727762 for i , rp := range rawParts {
728763 decoded , err := url .QueryUnescape (rp )
@@ -741,7 +776,7 @@ func BindRawQueryParameter(style string, explode bool, required bool, paramName
741776 default :
742777 if len (parts ) == 0 {
743778 if required {
744- return fmt . Errorf ( "query parameter '%s' is required" , paramName )
779+ return & RequiredParameterError { ParamName : paramName }
745780 }
746781 return nil
747782 }
@@ -767,8 +802,6 @@ func BindRawQueryParameter(style string, explode bool, required bool, paramName
767802 return fmt .Errorf ("error parsing query string: %w" , err )
768803 }
769804 return UnmarshalDeepObject (dest , paramName , queryParams )
770- case "spaceDelimited" , "pipeDelimited" :
771- return fmt .Errorf ("query arguments of style '%s' aren't yet supported" , style )
772805 default :
773806 return fmt .Errorf ("style '%s' on parameter '%s' is invalid" , style , paramName )
774807 }
0 commit comments