Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix directly constrained List shape with indirectly constrained aggregate type member shape #3894

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

drganjoo
Copy link
Contributor

@drganjoo drganjoo commented Oct 27, 2024

This PR fixes a bug in the code generation for a directly constrained List shape that has an indirectly constrained aggregate type as a member shape.

For example, in the following model:

@http(uri: "/sample", method: "POST")
operation SampleOp {
  input := {
      a: ItemList
  }
  errors: [ValidationException]
}
@length(min: 1 max: 100)
list ItemList {
  member: Item
}
map Item {
  key: ItemName
  value: ItemDescription
}
@length(min: 0 max: 65535)
string ItemName
string ItemDescription

A TryFrom implementation is generated that converts from ItemListUnconstrained to ItemList by converting each member of the inner list of ItemUnconstrained to ItemConstrained and then converting it to Vec<Vec<ItemName>>:

    impl std::convert::TryFrom<ItemListUnconstrained> for crate::model::ItemList {
        type Error = crate::model::item_list::ConstraintViolation;
        fn try_from(value: ItemListUnconstrained) -> Result<Self, Self::Error> {
            let res: Result<
                ::std::vec::Vec<
                    ::std::collections::HashMap<crate::model::ItemName, ::std::string::String>,
                >,
                (usize, crate::model::item::ConstraintViolation),
            > = value
                .0
                .into_iter()
                .enumerate()
                .map(|(idx, inner)| {
                    inner.try_into()
                        .map(|ic: crate::constrained::item_constrained::ItemConstrained| ic.into())
                        .map_err(|inner_violation| (idx, inner_violation))
                })
                .collect();
            let inner =
                res.map_err(|(idx, inner_violation)| Self::Error::Member(idx, inner_violation))?;
            Self::try_from(inner)
        }
    }

Partially fixes issue: #3885 for publicly constrained shapes only.

Copy link

A new generated diff is ready to view.

A new doc preview is ready to view.

Copy link

A new generated diff is ready to view.

A new doc preview is ready to view.

Copy link

A new generated diff is ready to view.

A new doc preview is ready to view.

@drganjoo drganjoo marked this pull request as ready for review October 29, 2024 06:00
@drganjoo drganjoo requested a review from a team as a code owner October 29, 2024 06:00
Copy link

A new generated diff is ready to view.

A new doc preview is ready to view.

""",
"FinalMapping" to
writable {
if (constrainedValueTypeIsNotFinalType) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of putting the if statement within the writable, can we write things in two phases and add a statement that does another pass, converting the value? Similar to how we tackle this case in UnconstrainedMapGenerator, where we have an if (constrainedValueTypeIsNotFinalType) { and explain within it this corner case and do a pass calling .into().

Copy link
Contributor Author

@drganjoo drganjoo Oct 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I benchmarked performing this in separate phases, as done in UnconstrainedMapGenerator, and found it to be about 5ns slower on a vector of 4 string values. Therefore, I decided to keep this version instead of adding a second pass. I'll refactor it slightly and move the if statement out. If that doesn’t improve readability, I’ll opt for the double pass.

dir.deleteRecursively()
}

// Simply compiling the crate is sufficient as a test.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather move all these to constraints.smithy, since, as noted here, simply compiling the crate is sufficient. Each of these is generating and compiling a brand new crate, which adds time. Instead, constraints.smithy constitues a "kitchen-sink"-style test for all constraints-related things.

Copy link
Contributor Author

@drganjoo drganjoo Oct 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completely agreed. I should have added a TODO here to clarify why these tests are included. There’s an earlier commit in this PR that adds them to constraints.smithy, but our pubConstrainedTypes = false implementation is currently incomplete and has several edge cases, causing the PR to fail. I’ve created a new issue (#3895) to document all related edge cases. I initially tried to address these within this PR but encountered cases requiring more refactoring than is feasible here.

Copy link

A new generated diff is ready to view.

A new doc preview is ready to view.

Copy link

A new generated diff is ready to view.

A new doc preview is ready to view.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
server Rust server SDK
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Directly constrained lists containing indirectly constrained aggregate shapes generate uncompilable code
2 participants