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

Custom HTTP Response abstraction #923

Merged
merged 7 commits into from
Jul 3, 2021
Merged

Custom HTTP Response abstraction #923

merged 7 commits into from
Jul 3, 2021

Commits on Jul 3, 2021

  1. feat!(http): custom response abstractions

    Our HTTP async request builders return a deserialized model in the
    output of their `Future` implementations, but only if the response has
    an expected body. For example, the `GetUser` request's `Future`
    implementation returns a deserialized `User`. `GetGuildRoles`' `Future`
    implementations returns `Vec<Role>`.
    
    This design has three problems worth noting:
    
    1. It's impossible to know the headers and status code of a response: it
    may be useful to check if the response is a success (2xx status code
    range) prior to deserializing; and certain headers may be useful for
    users to know;
    2. Not all responses are used: in many instances of creating a message
    the created message is not used, so we're spending CPU time on a useless
    operation; and
    3. Response bodies with a list will entirely fail if one entry fails to
    deserialize.
    
    By creating a custom `Response` type we can solve all three of these
    problems. Take the first example: checking the status code prior to
    deserializing a response body. With a `Response` struct this is easy:
    
    ```rust
    use std::env;
    use twilight_http::Client;
    
    let client = Client::new(env::var("DISCORD_TOKEN")?);
    let response = client.user(user_id).await?;
    
    if !response.status().is_success() {
        println!("failed to get user");
    
        return Ok(());
    }
    
    // Twilight already knows to deserialize it into a
    // `twilight_model::user::User`.
    let user = response.model().await?;
    
    println!("user's name: {}:{}", user.name, user.discriminator);
    ```
    
    In this example it's easy to check if the status code of the response is
    in the success range (2xx).
    
    Looking at the example, we can see how the second issue is solved: you
    can simply opt to not deserialize the model. This may not be useful for
    getting a user, but can be for creating a message:
    
    ```rust
    use std::env;
    use twilight_http::Client;
    
    let client = Client::new(env::var("DISCORD_TOKEN")?);
    client.create_message(channel_id)
        .content("test")?
        .await?;
    ```
    
    Here we create a message and return an error if the request failed. We
    don't care about the message itself, so the response is discarded
    without deserializing it.
    
    The final problem of one failure in a list causing the deserialization
    of the entire list to fail can be solved with a lazily deserializing
    iterator. Instead of deserializing everything up front, we can use an
    iterator which will deserialize entries as the iterator is advanced.
    We can see how that looks like when retrieving the roles of a guild and
    printing their names:
    
    ```rust
    use std::env;
    use twilight_http::Client;
    
    let client = Client::new(env::var("DISCORD_TOKEN")?);
    let response = client.roles(guild_id).await?;
    
    // Create an iterator over the roles. Each role in the response body is only
    // deserialized as the iterator advances.
    let mut roles = response.iter().await?;
    
    while let Some(maybe_role) = roles.next() {
        // The role may not have deserialized properly, so we need to check.
        match maybe_role {
            Ok(role) => println!("role {}'s name: {}", role.id, role.name),
            Err(source) => println!("failed to deserialize role: {:?}", source),
        }
    }
    ```
    
    The `Response` struct also has a few other cool functions: `bytes` which
    returns a `Future` that resolves to a `Vec<u8>` of the bytes of the
    response body, `text` which returns a String of the body if it's UTF-8
    valid, and `headers` which returns an iterator of the names and values
    of the response headers.
    
    In the simplest case, this PR causes users that want the returned
    deserialized model to append `.model().await?` or `.models().await?` to
    their request calls.
    
    Signed-off-by: Vivian Hellyer <vivian@hellyer.dev>
    zeylahellyer committed Jul 3, 2021
    Configuration menu
    Copy the full SHA
    6443f9a View commit details
    Browse the repository at this point in the history
  2. address comments

    Signed-off-by: Vivian Hellyer <vivian@hellyer.dev>
    zeylahellyer committed Jul 3, 2021
    Configuration menu
    Copy the full SHA
    8aae832 View commit details
    Browse the repository at this point in the history
  3. remove option from pending response types

    Signed-off-by: Vivian Hellyer <vivian@hellyer.dev>
    zeylahellyer committed Jul 3, 2021
    Configuration menu
    Copy the full SHA
    90162a5 View commit details
    Browse the repository at this point in the history
  4. remove response body iter for now

    `serde_json::StreamDeserializer` doesn't work by consuming bodies like
    `[{"a": 1}, {"b": 2}]`, it instead works by consuming bodies like:
    
    ```json
    {"a": 1}
    {"b": 2}
    ```
    
    Working around this in a performant way will take some work, so for now
    I am removing `MemberIter` and `ModelIter` support.
    
    Signed-off-by: Vivian Hellyer <vivian@hellyer.dev>
    zeylahellyer committed Jul 3, 2021
    Configuration menu
    Copy the full SHA
    db7ba9b View commit details
    Browse the repository at this point in the history
  5. remove docs mentioning iter

    Signed-off-by: Vivian Hellyer <vivian@hellyer.dev>
    zeylahellyer committed Jul 3, 2021
    Configuration menu
    Copy the full SHA
    a2d431c View commit details
    Browse the repository at this point in the history
  6. cargo fmt

    Signed-off-by: Zeyla Hellyer <zeyla@hellyer.dev>
    zeylahellyer committed Jul 3, 2021
    Configuration menu
    Copy the full SHA
    c61852e View commit details
    Browse the repository at this point in the history
  7. add some non exhaustives to body markers

    Signed-off-by: Zeyla Hellyer <zeyla@hellyer.dev>
    zeylahellyer committed Jul 3, 2021
    Configuration menu
    Copy the full SHA
    03d95a2 View commit details
    Browse the repository at this point in the history