@@ -469,6 +469,7 @@ func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.Serialization
469
469
if err != nil {
470
470
return nil , ok , err
471
471
}
472
+
472
473
val , err := makeObject (props , schema )
473
474
return val , ok , err
474
475
}
@@ -654,9 +655,7 @@ func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.Serialization
654
655
case l == 0 :
655
656
// A query parameter's name does not match the required format, so skip it.
656
657
continue
657
- case l == 1 :
658
- props [matches [0 ][1 ]] = strings .Join (values , urlDecoderDelimiter )
659
- case l > 1 :
658
+ case l >= 1 :
660
659
kk := []string {}
661
660
for _ , m := range matches {
662
661
kk = append (kk , m [1 ])
@@ -680,7 +679,6 @@ func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.Serialization
680
679
if props == nil {
681
680
return nil , false , nil
682
681
}
683
-
684
682
val , err := makeObject (props , schema )
685
683
if err != nil {
686
684
return nil , false , err
@@ -896,127 +894,198 @@ func deepSet(m map[string]interface{}, keys []string, value interface{}) {
896
894
func findNestedSchema (parentSchema * openapi3.SchemaRef , keys []string ) (* openapi3.SchemaRef , error ) {
897
895
currentSchema := parentSchema
898
896
for _ , key := range keys {
899
- propertySchema , ok := currentSchema .Value .Properties [key ]
900
- if ! ok {
901
- if currentSchema .Value .AdditionalProperties .Schema == nil {
902
- return nil , fmt .Errorf ("nested schema for key %q not found" , key )
897
+ if currentSchema .Value .Type .Includes (openapi3 .TypeArray ) {
898
+ currentSchema = currentSchema .Value .Items
899
+ } else {
900
+ propertySchema , ok := currentSchema .Value .Properties [key ]
901
+ if ! ok {
902
+ if currentSchema .Value .AdditionalProperties .Schema == nil {
903
+ return nil , fmt .Errorf ("nested schema for key %q not found" , key )
904
+ }
905
+ currentSchema = currentSchema .Value .AdditionalProperties .Schema
906
+ continue
903
907
}
904
- currentSchema = currentSchema .Value .AdditionalProperties .Schema
905
- continue
908
+ currentSchema = propertySchema
906
909
}
907
- currentSchema = propertySchema
908
910
}
909
911
return currentSchema , nil
910
912
}
911
913
912
914
// makeObject returns an object that contains properties from props.
913
- // A value of every property is parsed as a primitive value.
914
- // The function returns an error when an error happened while parse object's properties.
915
915
func makeObject (props map [string ]string , schema * openapi3.SchemaRef ) (map [string ]interface {}, error ) {
916
- obj := make (map [string ]interface {})
917
-
918
- for propName , propSchema := range schema .Value .Properties {
919
- switch {
920
- case propSchema .Value .Type .Is ("array" ):
921
- vals := strings .Split (props [propName ], urlDecoderDelimiter )
922
- for _ , v := range vals {
923
- _ , err := parsePrimitive (v , propSchema .Value .Items )
924
- if err != nil {
925
- return nil , handlePropParseError ([]string {propName }, err )
926
- }
927
- }
916
+ mobj := make (map [string ]interface {})
928
917
929
- ivals , err := convertArrayParameterToType (vals , propSchema .Value .Items .Value .Type )
930
- if err != nil {
931
- return nil , handlePropParseError ([]string {propName }, err )
932
- }
933
- obj [propName ] = ivals
934
- case propSchema .Value .Type .Is ("object" ):
935
- for prop := range props {
936
- if ! strings .HasPrefix (prop , propName + urlDecoderDelimiter ) {
937
- continue
938
- }
939
- mapKeys := strings .Split (prop , urlDecoderDelimiter )
940
- nestedSchema , err := findNestedSchema (schema , mapKeys )
941
- if err != nil {
942
- return nil , & ParseError {path : pathFromKeys (mapKeys ), Reason : err .Error ()}
943
- }
944
- if nestedSchema .Value .Type .Permits ("array" ) {
945
- vals := strings .Split (props [prop ], urlDecoderDelimiter )
946
- for _ , v := range vals {
947
- _ , err := parsePrimitive (v , nestedSchema .Value .Items )
948
- if err != nil {
949
- return nil , handlePropParseError (mapKeys , err )
950
- }
951
- }
952
- ivals , err := convertArrayParameterToType (vals , nestedSchema .Value .Items .Value .Type )
953
- if err != nil {
954
- return nil , handlePropParseError (mapKeys , err )
955
- }
956
- deepSet (obj , mapKeys , ivals )
957
- continue
958
- }
959
- value , err := parsePrimitive (props [prop ], nestedSchema )
960
- if err != nil {
961
- return nil , handlePropParseError (mapKeys , err )
962
- }
963
- deepSet (obj , mapKeys , value )
964
- }
965
- default :
966
- value , err := parsePrimitive (props [propName ], propSchema )
967
- if err != nil {
968
- return nil , handlePropParseError ([]string {propName }, err )
969
- }
970
- obj [propName ] = value
918
+ for kk , value := range props {
919
+ keys := strings .Split (kk , urlDecoderDelimiter )
920
+ if strings .Contains (value , urlDecoderDelimiter ) {
921
+ // don't support implicit array indexes anymore
922
+ p := pathFromKeys (keys )
923
+ return nil , & ParseError {path : p , Kind : KindInvalidFormat , Reason : "array items must be set with indexes" }
971
924
}
925
+ deepSet (mobj , keys , value )
926
+ }
927
+ r , err := buildResObj (mobj , nil , "" , schema )
928
+ if err != nil {
929
+ return nil , err
930
+ }
931
+ result , ok := r .(map [string ]interface {})
932
+ if ! ok {
933
+ return nil , & ParseError {Kind : KindOther , Reason : "invalid param object" , Value : result }
972
934
}
973
935
974
- return obj , nil
936
+ return result , nil
975
937
}
976
938
977
- func convertArrayParameterToType (strArray []string , typ * openapi3.Types ) (interface {}, error ) {
978
- var iarr []interface {}
939
+ // example: map[0:map[key:true] 1:map[key:false]] -> [map[key:true] map[key:false]]
940
+ func sliceMapToSlice (m map [string ]interface {}) ([]interface {}, error ) {
941
+ var result []interface {}
942
+
943
+ keys := make ([]int , 0 , len (m ))
944
+ for k := range m {
945
+ key , err := strconv .Atoi (k )
946
+ if err != nil {
947
+ return nil , fmt .Errorf ("array indexes must be integers: %w" , err )
948
+ }
949
+ keys = append (keys , key )
950
+ }
951
+ max := - 1
952
+ for _ , k := range keys {
953
+ if k > max {
954
+ max = k
955
+ }
956
+ }
957
+ for i := 0 ; i <= max ; i ++ {
958
+ val , ok := m [strconv .Itoa (i )]
959
+ if ! ok {
960
+ result = append (result , nil )
961
+ continue
962
+ }
963
+ result = append (result , val )
964
+ }
965
+ return result , nil
966
+ }
967
+
968
+ // buildResObj constructs an object based on a given schema and param values
969
+ func buildResObj (params map [string ]interface {}, parentKeys []string , key string , schema * openapi3.SchemaRef ) (interface {}, error ) {
970
+ mapKeys := parentKeys
971
+ if key != "" {
972
+ mapKeys = append (mapKeys , key )
973
+ }
974
+
979
975
switch {
980
- case typ .Permits (openapi3 .TypeBoolean ):
981
- for _ , str := range strArray {
982
- if str == "" {
983
- continue
984
- }
985
- parsedBool , err := strconv .ParseBool (str )
976
+ case schema .Value .Type .Is ("array" ):
977
+ paramArr , ok := deepGet (params , mapKeys ... )
978
+ if ! ok {
979
+ return nil , nil
980
+ }
981
+ t , isMap := paramArr .(map [string ]interface {})
982
+ if ! isMap {
983
+ return nil , & ParseError {path : pathFromKeys (mapKeys ), Kind : KindInvalidFormat , Reason : "array items must be set with indexes" }
984
+ }
985
+ // intermediate arrays have to be instantiated
986
+ arr , err := sliceMapToSlice (t )
987
+ if err != nil {
988
+ return nil , & ParseError {path : pathFromKeys (mapKeys ), Kind : KindInvalidFormat , Reason : fmt .Sprintf ("could not convert value map to array: %v" , err )}
989
+ }
990
+ resultArr := make ([]interface {}, len (arr ))
991
+ for i := range arr {
992
+ r , err := buildResObj (params , mapKeys , strconv .Itoa (i ), schema .Value .Items )
986
993
if err != nil {
987
994
return nil , err
988
995
}
989
- iarr = append (iarr , parsedBool )
990
- }
991
- case typ .Permits (openapi3 .TypeInteger ):
992
- for _ , str := range strArray {
993
- if str == "" {
994
- continue
996
+ if r != nil {
997
+ resultArr [i ] = r
995
998
}
996
- parsedInt , err := strconv .Atoi (str )
999
+ }
1000
+ return resultArr , nil
1001
+ case schema .Value .Type .Is ("object" ):
1002
+ resultMap := make (map [string ]interface {})
1003
+ additPropsSchema := schema .Value .AdditionalProperties .Schema
1004
+ pp , _ := deepGet (params , mapKeys ... )
1005
+ objectParams , ok := pp .(map [string ]interface {})
1006
+ if ! ok {
1007
+ // not the expected type, but return it either way and leave validation up to ValidateParameter
1008
+ return pp , nil
1009
+ }
1010
+ for k , propSchema := range schema .Value .Properties {
1011
+ r , err := buildResObj (params , mapKeys , k , propSchema )
997
1012
if err != nil {
998
1013
return nil , err
999
1014
}
1000
- iarr = append (iarr , parsedInt )
1015
+ if r != nil {
1016
+ resultMap [k ] = r
1017
+ }
1018
+ }
1019
+ if additPropsSchema != nil {
1020
+ // dynamic creation of possibly nested objects
1021
+ for k := range objectParams {
1022
+ r , err := buildResObj (params , mapKeys , k , additPropsSchema )
1023
+ if err != nil {
1024
+ return nil , err
1025
+ }
1026
+ if r != nil {
1027
+ resultMap [k ] = r
1028
+ }
1029
+ }
1030
+ }
1031
+
1032
+ return resultMap , nil
1033
+ case len (schema .Value .AnyOf ) > 0 :
1034
+ return buildFromSchemas (schema .Value .AnyOf , params , parentKeys , key )
1035
+ case len (schema .Value .OneOf ) > 0 :
1036
+ return buildFromSchemas (schema .Value .OneOf , params , parentKeys , key )
1037
+ case len (schema .Value .AllOf ) > 0 :
1038
+ return buildFromSchemas (schema .Value .AllOf , params , parentKeys , key )
1039
+ default :
1040
+ val , ok := deepGet (params , mapKeys ... )
1041
+ if ! ok {
1042
+ // leave validation up to ValidateParameter. here there really is not parameter set
1043
+ return nil , nil
1044
+ }
1045
+ v , ok := val .(string )
1046
+ if ! ok {
1047
+ return nil , & ParseError {path : pathFromKeys (mapKeys ), Kind : KindInvalidFormat , Value : val , Reason : "path is not convertible to primitive" }
1001
1048
}
1002
- case typ .Permits (openapi3 .TypeNumber ):
1003
- for _ , str := range strArray {
1004
- if str == "" {
1049
+ prim , err := parsePrimitive (v , schema )
1050
+ if err != nil {
1051
+ return nil , handlePropParseError (mapKeys , err )
1052
+ }
1053
+
1054
+ return prim , nil
1055
+ }
1056
+ }
1057
+
1058
+ // buildFromSchemas decodes params with anyOf, oneOf, allOf schemas.
1059
+ func buildFromSchemas (schemas openapi3.SchemaRefs , params map [string ]interface {}, mapKeys []string , key string ) (interface {}, error ) {
1060
+ resultMap := make (map [string ]interface {})
1061
+ for _ , s := range schemas {
1062
+ val , err := buildResObj (params , mapKeys , key , s )
1063
+ if err == nil && val != nil {
1064
+
1065
+ if m , ok := val .(map [string ]interface {}); ok {
1066
+ for k , v := range m {
1067
+ resultMap [k ] = v
1068
+ }
1005
1069
continue
1006
1070
}
1007
- parsedFloat , err := strconv .ParseFloat (str , 64 )
1008
- if err != nil {
1009
- return nil , err
1071
+
1072
+ if a , ok := val .([]interface {}); ok {
1073
+ if len (a ) > 0 {
1074
+ return a , nil
1075
+ }
1076
+ continue
1010
1077
}
1011
- iarr = append (iarr , parsedFloat )
1078
+
1079
+ // if its a primitive and not nil just return that and let it be validated
1080
+ return val , nil
1012
1081
}
1013
- case typ .Permits (openapi3 .TypeString ):
1014
- return strArray , nil
1015
- default :
1016
- return nil , fmt .Errorf ("unsupported parameter array type: %s" , typ )
1017
1082
}
1018
1083
1019
- return iarr , nil
1084
+ if len (resultMap ) > 0 {
1085
+ return resultMap , nil
1086
+ }
1087
+
1088
+ return nil , nil
1020
1089
}
1021
1090
1022
1091
func handlePropParseError (path []string , err error ) error {
0 commit comments