Repository: rusoto/rusoto (language: rust)

Issue:
Encoding issues when getting policies back from `iam:GetAccountAuthorizationDetails`
I'm calling `GetAccountAuthorizationDetails` and although it all seems to be working, if I look at policies I get back from the rusoto API call, they appear to be URL encoded. So for example, if I iterate over my roles and ask for `role.assume_role_policy_document.unwrap()`, I get strings like the following:

```
"%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22vmie.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Condition%22%3A%7B%22StringEquals%22%3A%7B%22sts%3AExternalid%22%3A%22vmimport%22%7D%7D%7D%5D%7D"
```

Which is obviously the URL-encoded form of this policy:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "vmie.amazonaws.com"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:Externalid": "vmimport"
        }
      }
    }
  ]
}
```

This seems unexpected (at least compared to the other language AWS SDKs) and I'm wondering if it's deliberate or just an oversight here.


Discussion / hints:
Ah, it looks like botocore has a special case for this in their code: 

https://github.com/boto/botocore/blob/1ae092ed95b39c3336ba48c5cbd71b7c37924d05/botocore/handlers.py#L499-L528
I guess the AWS SDKs are somewhat inconsistent about this, and aws-sdk-java doesn't automatically decode it either. 

I'd be happy to submit a PR for automatic decoding to the codegen if maintainers agree that it's desirable.

Note that this would only apply to the IAM service (I'd probably just check for a type definition that matches `policyDocumentType` like botocore does), since other services don't return percent-encoded policies like this.

Fix patch:
diff --git a/rusoto/core/src/signature.rs b/rusoto/core/src/signature.rs
index 1cc5c42ee4b..6e49bf9dcdc 100644
--- a/rusoto/core/src/signature.rs
+++ b/rusoto/core/src/signature.rs
@@ -693,7 +693,8 @@ fn encode_uri_strict(uri: &str) -> String {
 }
 
 #[inline]
-fn decode_uri(uri: &str) -> String {
+#[doc(hidden)]
+pub fn decode_uri(uri: &str) -> String {
     let decoder = percent_decode(uri.as_bytes());
     if let Ok(decoded) = decoder.decode_utf8() {
         decoded.to_string()
diff --git a/rusoto/services/iam/src/generated.rs b/rusoto/services/iam/src/generated.rs
index c1b3a50f526..167be277a2d 100644
--- a/rusoto/services/iam/src/generated.rs
+++ b/rusoto/services/iam/src/generated.rs
@@ -7136,7 +7136,7 @@ impl PolicyDocumentTypeDeserializer {
         stack: &mut T,
     ) -> Result<String, XmlParseError> {
         start_element(tag_name, stack)?;
-        let obj = characters(stack)?;
+        let obj = rusoto_core::signature::decode_uri(&characters(stack)?);
         end_element(tag_name, stack)?;
 
         Ok(obj)
diff --git a/service_crategen/src/commands/generate/codegen/xml_payload_parser.rs b/service_crategen/src/commands/generate/codegen/xml_payload_parser.rs
index 1df6b159d5d..085a3e001d6 100644
--- a/service_crategen/src/commands/generate/codegen/xml_payload_parser.rs
+++ b/service_crategen/src/commands/generate/codegen/xml_payload_parser.rs
@@ -243,7 +243,12 @@ fn generate_deserializer_body(name: &str, shape: &Shape, service: &Service) -> S
         ShapeType::List => generate_list_deserializer(shape, service),
         ShapeType::Map => generate_map_deserializer(shape),
         ShapeType::Structure => generate_struct_deserializer(name, service, shape),
-        _ => generate_primitive_deserializer(shape),
+
+        // All policies returned by the IAM APIs are URI-encoded, and
+        // they all use this shape name so we're able to match on this safely. botocore does the same
+        // thing to ensure a consistent user experience. Other services that handle IAM policies don't
+        // need the same treatment.
+        _ => generate_primitive_deserializer(shape, name == "PolicyDocumentType"),
     }
 }
 
@@ -351,8 +356,9 @@ fn generate_map_deserializer(shape: &Shape) -> String {
     }
 }
 
-fn generate_primitive_deserializer(shape: &Shape) -> String {
+fn generate_primitive_deserializer(shape: &Shape, percent_decode: bool) -> String {
     let statement = match shape.shape_type {
+        ShapeType::String if percent_decode => "rusoto_core::signature::decode_uri(&characters(stack)?)",
         ShapeType::String | ShapeType::Timestamp => "characters(stack)?",
         ShapeType::Integer | ShapeType::Long => {
             "i64::from_str(characters(stack)?.as_ref()).unwrap()"


Test patch: