-
Notifications
You must be signed in to change notification settings - Fork 291
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
Add a Request
builder
#55
Conversation
I haven't done
|
I'm a fan of not having to import more types. In my own programs, I'd want to reach for let req = Request::builder()
.method(Method::POST)
.uri(httpbin_org_post)
.header(CONTENT_TYPE, JSON)
.build(body); It does read slightly off to me that the body is supplied to
|
Yeah it's true that avoiding too many imported types is good, although maybe we could just add Yeah I understand that headers can have invalid chars, I just find it sort of unfortnate. You mention that you don't like to import types, but the current construction means that we'll require an import of We could potentially vendor a custom version of the |
Oh certainly, I didn't mean to remove the other constructors, just that I'd want
That makes sense! A In the reqwest review, there was wide agreement that a method in a builder that can fail should return the error immediately, not hold on to them and return it in |
Ok I've rebased this on #57 to achieve these ergonomics instead: let request = Request::builder()
.method(method::GET)
.uri("https://www.rust-lang.org/".parse()?)
.header("X-Custom-Foo", "Bar".parse()?)
.request(()); I tried out adding a local
After trying that out for a bit I settled on the addition of thoughts? |
Ok I've pushed another commit which preemptively incorporates #58 and achieves what I'd view as "optimal/desired ergonomics": let request = Request::builder()
.method("GET")
.uri("https://www.rust-lang.org/")
.header("X-Custom-Foo", "Bar")
.request(())?; I believe the only downside of this approach is that if you only specify infallible values like I wanted to address this point though:
This guideline is intended for builders like |
I admit, determining this feels a little cloudy to me. I could say that in reqwest, none of the operations there can fail either, they're just parsing a URL, or serializing into JSON, before setting the actual property on the internal |
Do you have a reason for favoring error-on-each-operation versus deferred until the end other than it was recommended? |
Nope, I don't think I favor one or the other. Just worried about consistency. |
Can you elaborate how you're worried? Do you have reservations about this strategy? Do you have concerns? |
Well, am I correct then that using the guideline in this PR, and applying it to reqwest, I should shift all the errors (that are eventually just conversions) back to the build method? |
If following this exactly, yes. Do you think that's not right? Would you prefer a different strategy? |
Just to throw in some more opinions... It seems to me that the builder is more about convenience, so why deal w/ returning The builder could panic on conversions that fail. If the user wants to be explicit about handling errors, they can convert from |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some initial thoughts on the PR. I'll try to look in more depth when I get home.
src/request.rs
Outdated
/// This type can be used to construct an instance of `Head` through a | ||
/// builder-like pattern. | ||
#[derive(Debug)] | ||
pub struct HeadBuilder { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO this builder can build either Head
or Request
values, so why not name it Builder
? 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think now especially that we've got Request::builder
it makes more sense to call this Builder
src/request.rs
Outdated
/// .build() | ||
/// .unwrap(); | ||
/// ``` | ||
pub fn headers_clear(&mut self) -> &mut HeadBuilder { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you say more about the motivation for this specific fn? Would the use case be satisfied by a clear
fn that resets the entire builder?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh this was mostly just following in the footsteps of Command::env_clear
, but in retrospect it may not fit here. On Command
you inherit everything by default so it makes sense to sometimes need to clear the environment, but here you start with nothing so I don't think it makes as much sense. Will remove.
src/request.rs
Outdated
/// .request(()) | ||
/// .unwrap(); | ||
/// ``` | ||
pub fn request<T>(&mut self, body: T) -> Result<Request<T>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe name this body
as it takes the body argument? It just happens to also perform the final building :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Not necessary for this PR, but I'd also like to see some more ergonomic builders for common paths. For example: Request::get("/")
.header("foo", "bar")
.body(()); // Yeah, this is a bit weird
// .into(); maybe that could work instead for requests w/o a body.
Request::post("/")
.header("content-type", "application/json")
.body("{}"); |
That's a possibility, yeah, but I personally feel at least that it's still best to go through Do you think though that having any result is too much here?
Interesting ideas! I wonder if this'd be odd though with |
Ok I've pushed up a version which also includes a |
What I'd like to do is not treat these as builders for
|
Seems reasonable to me! |
So, even if the Request::builder()
.method(try!(Method::try_from("123")))
// stuff
Really, only builder ergonomics. So, if avoiding all panics in the builder is more important than avoiding the unwrap in apps, then we don't need to do this. I personally would rather not have to call |
Re returning errors: a big part of the justification for It might also be a good idea to have equivalents that don't return results, either because they panic or because they take |
Should Aka, use the |
src/convert.rs
Outdated
use uri::{self, Uri}; | ||
|
||
/// dox | ||
pub trait HttpTryFrom<T>: Sized + Sealed { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the plan to alias this to std TryFrom
when that becomes stable?
Indeed! I found this to be pretty unergonomic though because it involves more imports and a lot more
Oh to clarify I don't imagine apps almost ever wanting to call
Ah it looks like you came to the same conclusion as I after this which is that this doesn't work great if you have a Oh looking back at @withoutboats's comment above, mind taking another look @withoutboats? Right now we've got both |
src/request.rs
Outdated
#[derive(Debug)] | ||
pub struct Builder { | ||
head: Option<Head>, | ||
err: Option<Error>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, by holding on to the error here, we allow a user to do a bunch of method calls, even though it's possible the first one failed. I know I've commented about this before, but seeing the implementation, I again don't really feel that this is probably how it should be done...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's sort of the intention though? Can you elaborate on your worries here?
I've been assuming that we want to optimize for ergonomics on the Builder
as that's sort of the point of having a builder, rather than optimizing for the implementation or the nitty gritty semantics per se.
Yes, I think the suggestion is that you can't construct a I had two other ideas assuming the above changes are made.
I'm not sure how good either of them are, but it's worth a thought. |
src/error.rs
Outdated
Uri(uri::InvalidUri), | ||
HeaderName(header::InvalidHeaderName), | ||
HeaderValue(header::InvalidHeaderValue), | ||
Io(io::Error), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd lean towards "if people really wish http::Error
has this, we can always add it later".
This commit adds a new builder for `request::Head` which will transitively allow creation of a `Request` through a builder-like interface. cc #49
All parsing errors are named `Invalid{Type}`, where Type is the type being parsed into (e.g. `InvalidMethod`). The same error is returned regardless of what type the source being parsed was.
Also add `Request::new` and privatize all `Parts` constructors
I've now updated the OP as well with a list of closed issues |
👍 looks good to me |
This commit adds a new builder for
request::Head
which will transitively allowcreation of a
Request
through a builder-like interface.cc #49
Closes #58
Closes #56
Closes #52
Closes #51
Closes #49
Closes #43