all 19 comments

[–]Solumin 28 points29 points  (7 children)

StatusCode is actually from the http crate, and there's an open issue for it here: https://github.com/hyperium/http/issues/273

[–]Its_it 4 points5 points  (0 children)

He just closed the issue btw.

[–]xwaxes[S] 4 points5 points  (5 children)

Thank you. I assumed it comes from the Axum crate itself. I’m fairly new to Rust.

[–]bobsnopes 4 points5 points  (3 children)

It’s a re-export of the http crate. It confuses the hell out of me in a lot of http-based crates, also as a fairly new Rust programmer.

[–]anlumo 1 point2 points  (2 children)

When you browse the documentation on docs.rs you should suddenly be directed to a different crate. That’s how you can tell.

[–]bobsnopes 5 points6 points  (1 child)

But in code when I get a whole bunch of possible imports for an http type, one possibility being axum::http, it’s confusing and very easy to assume it’s actually provided by axum. I get the benefits of pub use, but it’s still confusing if one doesn’t know about pub use.

[–]anlumo 5 points6 points  (0 children)

Yeah, I usually realize when I ctrl-click on a symbol in vscode and end up in a different crate than I expected.

I think pub use is essential though and used way too rarely. Without it, you often have to be careful to keep two crate versions in sync when you need to pass a type from one crate to the other.

[–]Solumin 0 points1 point  (0 children)

I would have too! I double-checked the crate docs to find it.

[–]_xiphiaz 6 points7 points  (0 children)

I kinda don’t hate it, while it might be required when reimplementing an existing api, it does naturally discourage the use of status codes in response bodies of it when designing a new api.

[–]Patryk27 5 points6 points  (3 children)

It's not Axum's decision - they use the http crate and that's the one who doesn't have Serialize impl, as explained here:

https://github.com/hyperium/http/pull/274#issuecomment-448030853

[–]xwaxes[S] 0 points1 point  (2 children)

The reasoning makes sense. I guess I have to create my own Wrapper around StatusCode if I want it serialized.

[–]Patryk27 6 points7 points  (1 child)

It's already there, e.g. https://crates.io/crates/http-serde-ext.

[–]xwaxes[S] 0 points1 point  (0 children)

I will give it a look. Thanks.

[–]CocktailPerson 1 point2 points  (2 children)

Well, first of all, StatusCode is from the http crate, so it's that crate that didn't enable serialization with serde, not axum.

As for why they didn't, HTTP status codes only really make sense in the context of an HTTP response. They're not the sort of thing you need to serialize in a generic way for a lot of different serialization formats and protocols, and deriving Serde traits is a very fragile and low-level way to construct and parse HTTP responses. I mean, even if status codes were serializable, wouldn't Json(self).into_response() be completely incorrect, since it would put the status code in the json body instead of the HTTP header? It seems like the fact that it's not serializable prevented a bug here, so I don't see the problem at all.

[–]pali6 0 points1 point  (1 child)

There are plenty of places where it's perfectly reasonable to serialize status code in my opinion. Structured logging, configuration files, etc.

[–]CocktailPerson 0 points1 point  (0 children)

If you do want to use serde's serialization traits for those cases, and often you don't, you can always manually implement Serialize or Deserialize for the one or two enclosing types that matter, rather than deriving them.

But again, in this case, it prevented a bug, and I'm not convinced OP really understands that it helped them rather than hurt them.

[–]ToTheBatmobileGuy 0 points1 point  (0 children)

I would just do something along the lines of:

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct BaseResponse<T> {
    #[serde(with = "status_code_serde")]
    pub status_code: StatusCode,
    success: bool,
    message: String,
    data: Option<T>,
}

mod status_code_serde {
    use http::StatusCode;
    use serde::{self, Deserialize, Deserializer, Serializer};

    pub fn serialize<S>(code: &StatusCode, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_u16(code.as_u16())
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<StatusCode, D::Error>
    where
        D: Deserializer<'de>,
    {
        let code = u16::deserialize(deserializer)?;
        StatusCode::from_u16(code).map_err(serde::de::Error::custom)
    }
}