smithy-lang / smithy-rs

Code generation for the AWS SDK for Rust, as well as server and generic smithy client generation.
Apache License 2.0
508 stars 190 forks source link

Edge cases involving non-public constraint types #3895

Open drganjoo opened 1 month ago

drganjoo commented 1 month ago

Missing From Implementation

Consider the following model:

service SampleService {
    operations: [SampleOp]
}

@http(uri: "/sample", method: "POST")
operation SampleOp {
    input := {
        items: ItemMap
    }
    errors: [ValidationException]
}

@length(min: 0, max: 65535)
string ItemName
string ItemDescription

@length(min: 1, max: 100)
map ItemMap {
    key: ItemName,
    value: ItemListA
}

list ItemListA { 
    member: ItemListB
}

list ItemListB {
    member: ItemDescription
}

An SDK generated with publicConstrainedTypes set to false does not compile and raises the following error:

error[E0277]: the trait bound `HashMap<std::string::String, Vec<Vec<std::string::String>>>: From<ItemMap>` is not satisfied
   --> src/input.rs:136:55

This error occurs because SampleOpInput is defined as a HashMap with String as the key:

pub struct SampleOpInput {
    #[allow(missing_docs)] // documentation missing in model
    pub items: ::std::option::Option<
        ::std::collections::HashMap<
            ::std::string::String,
            ::std::vec::Vec<::std::vec::Vec<::std::string::String>>,
        >,
    >,
}

The method build_enforcing_all_constraints attempts to construct SampleOpInput by invoking the From implementation for ItemMap. However, the generated implementation uses ItemName (not String) as the key type for the HashMap:

impl ::std::convert::From<ItemMap> 
    for ::std::collections::HashMap<
        crate::model::ItemName,
        ::std::vec::Vec<::std::vec::Vec<::std::string::String>>,
    >

An additional From<ItemMap> needs to be generated:

impl ::std::convert::From<ItemMap>
    for ::std::collections::HashMap<
        ::std::string::String,
        ::std::vec::Vec<::std::vec::Vec<::std::string::String>>,
    >
{
    fn from(value: ItemMap) -> Self {
        value
            .into_inner()
            .into_iter()
            .map(|(k, v)| (k.into(), v))
            .collect()
    }
}

A directly constrained list with indirectly constrained map

Consider the following model:

operation SampleOp {
    input := {
        items: ItemList
    }
}

@length(min: 1 max: 100)
list ItemList {
    member: Item
}
map Item {
    key: ItemName
    value: ItemDescription
}

This results in non-compilable code:

93 |         value.into_inner().into_iter().map(|v| v.into()).collect()
   |                                                  ^^^^ the trait `From<HashMap<ItemName, std::string::String>>` is not implemented for `HashMap<std::string::String, std::string::String>`, which is required by `HashMap<ItemName, std::string::String>: Into<_>`

This happens because ItemList is defined as:

pub(crate) struct ItemList(
    pub(crate) 
        ::std::vec::Vec<::std::collections::HashMap<crate::model::ItemName, ::std::string::String>>,
);

Where as the TryFrom implementation assumes that the HashMap has String as key instead of ItemName:

impl ::std::convert::From<ItemList>
    for ::std::vec::Vec<::std::collections::HashMap<::std::string::String, ::std::string::String>>
{
    fn from(value: ItemList) -> Self {
        value.into_inner().into_iter().map(|v| v.into()).collect()
    }
}

Constrained map with a non-constrained list that has a constrained list as member

@length(min: 1 max: 100)
map ItemMap {
    key: ItemName,
    value: ItemListA
}
list ItemListA {
    member: ItemListB
}
@length(min: 1 max: 100)
list ItemListB {
    member: ItemDescription
}

Results in a compilation error on v.into() in the following From<ItemMap> implementation:

impl ::std::convert::From<ItemMap>
    for ::std::collections::HashMap<
        ::std::string::String,
        ::std::vec::Vec<::std::vec::Vec<::std::string::String>>,
    >
{
    fn from(value: ItemMap) -> Self {
        value
            .into_inner()
            .into_iter()
            .map(|(k, v)| (k.into(), v.into()))
            .collect()
    }
}

Nested lists

@length(min: 1 max: 100)
  list ItemList {
      member: ItemA
  }
  list ItemA {
      member: ItemB
  }
  list ItemB {
      member: ItemName
  }

Results in an error in TryFrom<ItemsList> implementation at v.into().

pub(crate) struct ItemList(
    pub(crate) ::std::vec::Vec<::std::vec::Vec<::std::vec::Vec<crate::model::ItemName>>>,
);

impl ::std::convert::From<ItemList>
    for ::std::vec::Vec<::std::vec::Vec<::std::vec::Vec<::std::string::String>>>
{
    fn from(value: ItemList) -> Self {
        value.into_inner().into_iter().map(|v| v.into()).collect()
    }
}