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

feat: Allow bounded numbers to be deserialized from strings #296

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

Conversation

DrChat
Copy link
Contributor

@DrChat DrChat commented Mar 16, 2025

Fixes #295.

This allows libraries like serde-html-form to deserialize bounded integers from strings.

@sugyan
Copy link
Owner

sugyan commented Mar 17, 2025

I didn't know that this kind of conversion is possible.
But I don't understand why it is necessary to deserialize from string to integer. Can you tell me in what specific cases this is necessary?

@sugyan
Copy link
Owner

sugyan commented Mar 22, 2025

Oh... sorry, I didn't read the #295 one. I see, so you are saying that this kind of conversion is necessary when HTTP query parameters are received, etc.

@DrChat
Copy link
Contributor Author

DrChat commented Mar 22, 2025

All good! And yup - axum will treat all query parameters as strings when performing deserialization, so we'd have to handle that here in order to be compatible.

I'm also open to other ways to implement this too - this just seemed to be the best way to do it based on what I saw :)

@sugyan
Copy link
Owner

sugyan commented Mar 26, 2025

I have no objection to your implementation way.
However, I am wondering if I can accept the change of being able to deserialize from strings to bounded numbers. I think this conversion is irreversible and should not be deserializable under normal circumstances.
How about not changing the current behavior and only allowing the conversion you wrote if, for example, the deserialize_numbers_from_string feature is enabled?

@DrChat
Copy link
Contributor Author

DrChat commented Mar 29, 2025

Okay, that is a reasonable concern!

So I found an alternate mechanism to implement the functionality I needed.
I can lean on forward_parsed_value! as it is implemented in serde-html-form in order to implicitly convert the string inputs into integers. That way, we don't have to implement the conversions on our end.

The specific requirements on us are: we must implement FromStr for each bounded integer type, and we must call deserialize_$primitive when deserializing.

I've confirmed that this works via testing, but I have not checked in those tests because they would introduce a dev dependency on serde-html-form :)

@DrChat DrChat force-pushed the api_int_strings branch 2 times, most recently from 134934b to ecaaead Compare March 29, 2025 19:24
@DrChat
Copy link
Contributor Author

DrChat commented Mar 29, 2025

Hrm. So the tests I write pass in isolation, but axum's Query extractor is still exhibiting the same limitation and failing to deserialize structures with these bounded types.
The only variable I can see is that they use serde_path_to_error and my tests do not.

For posterity, here is the test I have written that passes:

    #[test]
    fn query_deserialize() {
        #[derive(Deserialize)]
        struct S {
            #[serde(skip_serializing_if = "core::option::Option::is_none")]
            pub cursor: core::option::Option<String>,
            #[serde(skip_serializing_if = "core::option::Option::is_none")]
            pub limit: core::option::Option<crate::types::LimitedNonZeroU16<1000u16>>,
        }

        let s = serde_urlencoded::from_str::<S>("cursor=abcd&limit=100").unwrap();
        assert_eq!(s.limit.unwrap().0, NonZeroU16::new(100).unwrap());
    }

Edit: Okay, it appears that we should not be wrapping query parameters in atrium_api::types::Object.
I've confirmed that if I manually unwrap the parameters (e.g. Query(input): Query<sync::list_repos::ParametersData>), axum is successfully able to deserialize them.

This is a separate issue that I'm not going to fix in this pull request :)

@sugyan
Copy link
Owner

sugyan commented Mar 30, 2025

Thank you for continuing to work on this issue.
It is great to know that serde-html-form can deserialize from a string just by implementing FromStr.
I have no problem adding serde-html-form to dev-dependencies. I have sent a pull-request to you in DrChat#1.
However, when I try to deserialize by serde_json, the deserialization does not seem to succeed even if I pass a number.
Perhaps only the implementation of FromStr is needed to fix this problem, and the change to implement deserialize by Visitor using paste is no longer needed...?
I would be glad if you could confirm this.

@DrChat
Copy link
Contributor Author

DrChat commented Mar 30, 2025

So it's actually expected that serde-json will fail to perform the conversion from a string to an integer.
This all depends on the serialization format.

For json, everything is strongly typed enough that they do not need to implement implicit conversions.
For serde-urlencoded (which I think is the actual crate used by axum - I was wrong earlier), they use forward_parsed_value! to perform implicit conversions.

TY for the pull request btw!

@DrChat DrChat changed the title Allow bounded numbers to be deserialized from strings feat: Allow bounded numbers to be deserialized from strings Mar 30, 2025

fn from_str(src: &str) -> Result<Self, Self::Err> {
Self::new(
src.parse::<$primitive>().map_err(|_| format!("value is not an integer"))?,
Copy link
Owner

Choose a reason for hiding this comment

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

I'm thinking .map_err(|e| e.to_string()) would be fine, but what do you think?

}
}
// TODO: LimitedNonZeroU8
// TODO: BoundedU8
Copy link
Owner

Choose a reason for hiding this comment

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

I've written tests only for LimitedU8, I would be glad if you could add tests for other types as well. If you decide that the current ones are sufficient, you can just delete the // TODO.

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

Successfully merging this pull request may close these issues.

Bounded integer types cannot be deserialized from a string
2 participants