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

Rewrite core framework to use request-response abstraction #153

Open
wants to merge 1 commit into
base: rewrite/review
Choose a base branch
from

Conversation

ObsidianMinor
Copy link

@ObsidianMinor ObsidianMinor commented Jun 22, 2021

Part 1 of ??.

This focuses on rewriting the core of the library (the framework module) to use a request/response abstraction. This model is much more open to extension and doesn't constrain users to using a specific client abstraction, allowing them to develop their own as they see fit. As a whole, the API has simplified, hopefully making using and building off the library easier.

However, this means the mocking features have been removed since there's no more clients to mock. Users are expected to make their own mock implementation how they see fit based on the client abstraction they design.

Also part of this change, the implementation is extended to allow for new request and response types, which opens up the library to all existing endpoints in the API (including the Workers APIs).

In the interest of making this PR series easier to review, I'm not deleting the whole thing and starting new on this branch. Instead I'll replace pieces as I go, making it easier to compare changes as we move through this rewrite, with the effect of making all checks fail.

pub code: u16,
pub message: String,
#[serde(flatten)]
pub other: HashMap<String, JValue>,
Copy link
Author

Choose a reason for hiding this comment

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

I've never seen this used. Do APIs exist that have error fields other than code and message? Do APIs exist that actually have more than one error??? Either way it doesn't seem like a good way to design this sort of API. If an API has a specific set of data it returns as part of its errors, the Endpoint should design a JSON response around that fact.

Copy link
Contributor

Choose a reason for hiding this comment

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

Unfortunately yes, some APIs do set nonstandard error fields. Last time I checked, the LB team had some rich domain-specific errors here, which is why I included it :(

Copy link
Author

Choose a reason for hiding this comment

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

Well luckily with this design we won't need it like this, endpoints can specify their own json error structures.

@@ -0,0 +1,87 @@
use crate::{Credentials, Endpoint, CLIENT_API_V4_URI};
Copy link
Author

@ObsidianMinor ObsidianMinor Jun 22, 2021

Choose a reason for hiding this comment

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

I'm actually not sure about having this as a separate file. The original idea was to reduce dependencies by making it optional (and removing the url dependency with it) but it doesn't really seem that worth it now. It's only 2 structs and an extension trait.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree, it doesn't seem worth its own feature. If this let you avoid pulling in another dependency from crates.io, I'd say, sure, go ahead, hide it behind a feature. But if not, let's not worry?

Copy link
Contributor

@adamchalmers adamchalmers left a comment

Choose a reason for hiding this comment

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

Looks good so far. I think the main thing I'd like to see is examples of what the new request API looks like. The thing I care most about here is the UX, so an example showing how users will use this would be very helpful for me :)

@@ -0,0 +1,87 @@
use crate::{Credentials, Endpoint, CLIENT_API_V4_URI};
Copy link
Contributor

Choose a reason for hiding this comment

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

Agree, it doesn't seem worth its own feature. If this let you avoid pulling in another dependency from crates.io, I'd say, sure, go ahead, hide it behind a feature. But if not, let's not worry?

///
/// # Panics
///
/// If the API Url cannot be joined with the endpoint's path.
Copy link
Contributor

Choose a reason for hiding this comment

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

weird to see API capitalized but URL not, we should choose one policy and be consistent

Copy link
Author

Choose a reason for hiding this comment

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

I think I made it not capitalized because it refers to the Url type. Should make that obvious with a intra-doc link.

@ObsidianMinor
Copy link
Author

ObsidianMinor commented Jul 21, 2021

There's some examples I converted from the previous crates to the new design that are included in the reqwest integration crate.

    let builder = RequestBuilder::new(credentials);

    let client = Client::new();

    use cloudflare::api::load_balancer;

    let response = load_balancer::pools::Create {
        account_id: &account_id,
        params: load_balancer::pools::CreateParams {
            name: "test-pool".to_owned(),
            description: Some("test description".to_owned()),
            enabled: Some(true),
            minimum_origins: Some(2),
            monitor: Some("9004c07f1c0f33255410e45590251cf4".to_owned()),
            notification_email: Some("[email protected]".to_owned()),
            origins: vec![
                load_balancer::pools::Origin {
                    name: "test-origin".to_owned(),
                    address: Ipv4Addr::new(152, 122, 3, 1).into(),
                    enabled: true,
                    weight: 1.0,
                },
                load_balancer::pools::Origin {
                    name: "test-origin-2".to_owned(),
                    address: Ipv4Addr::new(152, 122, 3, 2).into(),
                    enabled: true,
                    weight: 1.0,
                },
            ],
        },
    }
    .request(&builder)
    .send(&client)
    .await
    .context("Failed to create pool")?;
    let pool = match response {
        JsonResponse {
            body:
                JsonResult {
                    success: true,
                    result: Some(pool),
                    ..
                },
            ..
        } => pool,
        response => anyhow::bail!("Failed to create pool: {:#?}", response),
    };

Definitely open to making the match (which is used often) into a converter that returns a result.

    .request(&builder)
    .send(&client)
    .await
    .context("Failed to create pool")?
    .success_ok()
    .map_err(|r| anyhow!("API call failed: {:#?}", r))?;

/// # Panics
///
/// If the API Url cannot be joined with the endpoint's path.
pub fn build<'a, E: Endpoint + 'a>(&'a self, endpoint: &'a E) -> Request<'a, E> {
Copy link
Contributor

@adamchalmers adamchalmers Jul 30, 2021

Choose a reason for hiding this comment

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

nit: I'd prefer if the shorter name "build" was the safe one (i.e. doesn't panic), and the panicking method had a longer name than "build". Or maybe we could remove the panic, and just let users call .build().unwrap() if they really want to force the panic. What do you think?

(same idea applies to the request/try_request extension methods below)

Copy link
Author

Choose a reason for hiding this comment

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

I prefer shorter names to be obvious happy paths that most users will take. 99.99% of the time the user will have a URL that supports joining with paths (if they use the default URL this will be true) and endpoints will have paths that can be joined with a standard base URL (if they use included endpoints, this will also be true), anything else is something a user probably doesn't want to handle, but if they do, they can use the try_ methods.

Copy link
Collaborator

@Noah-Kennedy Noah-Kennedy left a comment

Choose a reason for hiding this comment

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

I think this is fine as is.

Copy link
Collaborator

@Noah-Kennedy Noah-Kennedy left a comment

Choose a reason for hiding this comment

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

I'm actually going to remove my approval for now. After talking with @sssilver, I think that we should probably have this specced out first and discuss that, then we should do the change atomically in one PR or in a series of semi-atomic PRs that do not at any point leave the repo in a state where CI is broken.

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.

3 participants