简体   繁体   中英

Associated types to normalize serializable data via traits and a generic type

I tried to implement a type that would "enforce" some schema to my responses in Tide but keep getting the "Items from traits can only be used..." compiler error.

#![feature(async_await, futures_api, await_macro, arbitrary_self_types)]
#![allow(proc_macro_derive_resolution_fallback)]

use serde_derive::Serialize;
use tide::{body::Json, IntoResponse, Response};

#[derive(Serialize)]
struct Document<Attrs, Rels> {
    data: PrimaryData<Attrs, Rels>,
}

#[derive(Serialize)]
struct PrimaryData<Attrs, Rels> {
    id: i32,
    kind: String,
    attributes: Attrs,
    relationships: Rels,
}

trait IntoPrimaryData: Send {
    type Attrs: serde::Serialize;
    type Rels: serde::Serialize;

    fn into_primary_data(self) -> PrimaryData<Self::Attrs, Self::Rels>;
}

struct ServiceResponse<T: IntoPrimaryData>(T);

impl<T: IntoPrimaryData> IntoResponse for ServiceResponse<T> {
    fn into_response(self) -> Response {
        Json(Document {
            data: self.0.into_primary_data(),
        })
        .with_status(http::status::StatusCode::OK)
        .into_response()
    }
}

#[derive(Serialize)]
struct User {
    id: i32,
    primary_email: String,
}

#[derive(Serialize)]
struct UserAttrs {
    primary_email: String,
}

impl IntoPrimaryData for User {
    type Attrs = UserAttrs;
    type Rels = ();

    fn into_primary_data(self) -> PrimaryData<Self::Attrs, Self::Rels> {
        PrimaryData {
            id: self.id,
            kind: "user".into(),
            attributes: UserAttrs {
                primary_email: self.primary_email,
            },
            relationships: (),
        }
    }
}

fn main() {}
[dependencies]
tide = "0.0.5"
http = "0.1.16"
serde = "1.0.89"
serde_derive = "1.0.89"

The compiler returns the error

error[E0599]: no method named `with_status` found for type `tide::body::Json<Document<<T as IntoPrimaryData>::Attrs, <T as IntoPrimaryData>::Rels>>` in the current scope
  --> src/main.rs:34:10
   |
34 |         .with_status(http::status::StatusCode::OK)
   |          ^^^^^^^^^^^
   |
   = note: the method `with_status` exists but the following trait bounds were not satisfied:
           `tide::body::Json<Document<<T as IntoPrimaryData>::Attrs, <T as IntoPrimaryData>::Rels>> : tide::response::IntoResponse`
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `with_status`, perhaps you need to implement it:
           candidate #1: `tide::response::IntoResponse`

I'm not sure why I'm getting this error but I feel like it has something to do with the line data: self.0.into_primary_data() not being "specific" enough and that it is not known what the types of Self::Attrs and Self::Rels are. However, I know that I also get this same error (minus the help tip about "items from traits can only be...") if one of the nested types don't implement serde::Serialize but from what I can tell, I've added those bounds everywhere they need to be.

I've tried doing this in what feels like a million ways now and can't quite seem to come up with a way to get some normalized structure for my responses.

I'm using rustc 1.34.0-nightly (02c4c2892 2019-02-26)

You haven't correctly specified the complete bounds on your associated types.

Json only implements IntoResponse when the type it contains implements both Send and Serialize :

impl<T: Send + Serialize> IntoResponse for Json<T>

You need to include Send in the bounds for the associated types:

trait IntoPrimaryData: Send {
    type Attrs: serde::Serialize + Send;
    //                           ^^^^^^
    type Rels: serde::Serialize + Send;
    //                          ^^^^^^

    fn into_primary_data(self) -> PrimaryData<Self::Attrs, Self::Rels>;
}

Debugging steps

This line of the error message seemed promising:

the method `with_status` exists but the following trait bounds were not satisfied:
`tide::body::Json<Document<<T as IntoPrimaryData>::Attrs, <T as IntoPrimaryData>::Rels>> : tide::response::IntoResponse`

This states that we could call with_status except that the compiler didn't know that the type implemented the trait. From there, I went to the documentation of Json to see if it implemented IntoRespose and if so, under what conditions:

impl<T: Send + Serialize> IntoResponse for Json<T>

Based on that, we know that this T must be PrimaryData<T::Attrs, T::Rels> and it must implement Send + Serialize .

We see that PrimaryData derives Serialize :

#[derive(Serialize)]
struct PrimaryData<Attrs, Rels> {

By existing knowledge, I know that most derive d traits require that all of the generic types also implement the trait. It's less obvious, but the same is true for Send .

From there, it's a matter of proving that the specific types for Attrs and Rels implement Serialize and Send . The associated type bounds handled one but not the other.

Deciding where to place the bounds is a matter of intent and style — they could go on the function, the impl block, or in the trait. Since the trait already had mention of Serialize , it seemed a natural place to add the additional bound.

I also made one large misstep — I assumed that you had correctly specified the bounds and were running into a compiler limitation ( also ). Only when I tried to apply the suggested duplicate did I realize that the bounds were incorrect.

See also:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM