44 "encoding/json"
55 "fmt"
66 "sort"
7+ "strconv"
78 "strings"
89
910 "github.com/spf13/cobra"
@@ -87,6 +88,7 @@ func doAction(ctx *Context, serviceName, action string) (err error) {
8788 method := "GET"
8889 contentType := ""
8990 apiInfo := rootSupport .GetApiInfo (serviceName , action )
91+ apiMeta := rootSupport .GetApiMeta (serviceName , action )
9092
9193 if apiInfo != nil && apiInfo .Method != "" {
9294 method = apiInfo .Method
@@ -100,7 +102,14 @@ func doAction(ctx *Context, serviceName, action string) (err error) {
100102 for _ , f := range ctx .dynamicFlags .flags {
101103 // rebuild input
102104 if f .Name != "body" {
103- if a , success := util .ParseToJsonArrayOrObject (strings .TrimSpace (f .value )); success {
105+ // Skip JSON parsing for parameters whose declared type is "string".
106+ // Without this, a value like '{"Statement":[...]}' is deserialized
107+ // into a Go map and then flattened into query params, which breaks
108+ // APIs that expect a raw JSON string (e.g. IAM CreatePolicy's
109+ // PolicyDocument parameter).
110+ if isStringParam (apiMeta , f .Name ) {
111+ input [f .Name ] = f .value
112+ } else if a , success := util .ParseToJsonArrayOrObject (strings .TrimSpace (f .value )); success {
104113 input [f .Name ] = a
105114 } else {
106115 input [f .Name ] = f .value
@@ -173,6 +182,69 @@ func doAction(ctx *Context, serviceName, action string) (err error) {
173182 return
174183}
175184
185+ // isStringParam reports whether the named parameter should be treated as a
186+ // literal string when rebuilding request input.
187+ //
188+ // This includes both parameters declared as type "string" and indexed
189+ // elements of repeated string arrays whose metadata key ends with ".N" and is
190+ // declared as "array[string]" (for example ResourceNames.N -> --ResourceNames.0).
191+ // In those cases the caller must NOT attempt to parse the value as JSON.
192+ func isStringParam (apiMeta * ApiMeta , name string ) bool {
193+ mt , matchedKey , ok := getRequestMetaType (apiMeta , name )
194+ if ! ok {
195+ return false
196+ }
197+
198+ switch mt .TypeName {
199+ case "string" :
200+ return true
201+ case "array[string]" :
202+ return isIndexedStringArrayElement (matchedKey )
203+ default :
204+ return false
205+ }
206+ }
207+
208+ func isIndexedStringArrayElement (matchedKey string ) bool {
209+ return strings .HasSuffix (matchedKey , ".N" )
210+ }
211+
212+ func getRequestMetaType (apiMeta * ApiMeta , name string ) (* MetaType , string , bool ) {
213+ if apiMeta == nil || apiMeta .Request == nil || apiMeta .Request .MetaTypes == nil {
214+ return nil , "" , false
215+ }
216+
217+ if mt , ok := apiMeta .Request .MetaTypes [name ]; ok {
218+ return mt , name , true
219+ }
220+
221+ normalizedName := normalizeMetaTypeKey (name )
222+ if normalizedName == name {
223+ return nil , "" , false
224+ }
225+
226+ mt , ok := apiMeta .Request .MetaTypes [normalizedName ]
227+ return mt , normalizedName , ok
228+ }
229+
230+ func normalizeMetaTypeKey (name string ) string {
231+ parts := strings .Split (name , "." )
232+ changed := false
233+
234+ for i , part := range parts {
235+ if _ , err := strconv .Atoi (part ); err == nil {
236+ parts [i ] = "N"
237+ changed = true
238+ }
239+ }
240+
241+ if ! changed {
242+ return name
243+ }
244+
245+ return strings .Join (parts , "." )
246+ }
247+
176248func actionUsageTemplate (params []string ) string {
177249 sort .Strings (params )
178250
0 commit comments