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

Proposal: return status with a body from fn #105

Closed
eupn opened this issue Feb 6, 2020 · 1 comment
Closed

Proposal: return status with a body from fn #105

eupn opened this issue Feb 6, 2020 · 1 comment

Comments

@eupn
Copy link

eupn commented Feb 6, 2020

Hello and thank you for your efforts and great HTTP mock library! I use it extensively.

I have some more of an edge use-case when I want to test my retry logic. For that purpose, I've created a mock that will respond with an error for some N amount of requests and after that, it'll respond without the error.

My current implementation is looking like this:

/// Creates a mock of rate-limited request which requires to receive least `num_tries` requests
/// before returning a `body`.
fn create_retrying_mock(num_tries: usize, path: &str, body: &'static str) -> mockito::Mock {
    use std::sync::{Arc, Mutex};

    // A counter needs to be thread-safe since it'll be moved to server side
    // and has to preserve its value between HTTP requests.
    let counter = Arc::new(Mutex::new(0));

    mockito::mock("POST", path)
        .expect_at_least(num_tries)
        .with_body_from_fn(move |w| {
            if let Ok(mut c) = counter.lock() {
                if *c < num_tries {
                    *c += 1;

                    // There's no way to return non-200 status code conditionally,
                    // so just fail with some error here.
                    return Err(std::io::Error::new(
                        std::io::ErrorKind::Other,
                        "<- ignore this error",
                    ));
                } else {
                    write!(w, "{}", body).unwrap();
                }
            }

            Ok(())
        })
        .create()
}

As you can see from the code, I have two possible branches in with_body_from_fn's closure:

  1. If a counter is less than the required number of tries, return an error
  2. If enough number of tries done, return expected body with HTTP code 200

In order to implement (1.) I had to abort the closure with a random I/O error and this error will also show up in log during the testing. But this error is expected and, from my understanding, I have no way to abort with HTTP error status code or otherwise express that with current API.

I would like to propose an addition to the API that will allow:

  1. Either responding with a body if some predicate is met and with an error otherwise:
fn create_retrying_mock(num_tries: usize, path: &str, body: &'static str) -> mockito::Mock {
    use std::sync::{Arc, Mutex};

    let status_code =429; // rate-limiting error if not enough retries

    // A counter needs to be thread-safe since it'll be moved to server side
    // and has to preserve its value between HTTP requests.
    let counter = Arc::new(Mutex::new(0));

    mockito::mock("POST", path)
        .expect_at_least(num_tries)
        .with_body_or_status(move || {
            if let Ok(mut c) = counter.lock() {
                if *c < num_tries {
                    *c += 1;
                    false
                } else {
                    true
                }
            }
        }, body, status_code)
        .create()
}
  1. Or to return both body and status code from closure:
fn create_retrying_mock(num_tries: usize, path: &str, body: &'static str) -> mockito::Mock {
    use std::sync::{Arc, Mutex};

    let status_code =429; // rate-limiting error if not enough retries

    // A counter needs to be thread-safe since it'll be moved to server side
    // and has to preserve its value between HTTP requests.
    let counter = Arc::new(Mutex::new(0));

    mockito::mock("POST", path)
        .expect_at_least(num_tries)
        .with_body_and_status_from_fn(move |w| {
            if let Ok(mut c) = counter.lock() {
                if *c < num_tries {
                    *c += 1;
                    write!(w, "{{ \"error\": \"rate limiting error\"}}").unwrap();
                    return Ok(status_code)
                } else {
                    write!(w, "{}", body).unwrap();
                }
            }

            Ok(200)
        })
        .create()
}

If one of these APIs suits your vision for this library, I would happy to implement it and to file a PR!

@lipanski
Copy link
Owner

lipanski commented Feb 7, 2020

I think an easier solution (both on your side and on the library side) could be the one implemented in #99 - also check the related issue. would that solve your problem?

if we go with something around your proposal, I'd like to include the possibility to update headers as well and turn it into something like with_response_from_fn, which would return a tuple then (status code and headers?).

@lipanski lipanski closed this as completed Mar 2, 2023
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

No branches or pull requests

2 participants