You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
⚠️ This PR is still a bit of a draft, sorry about that, I'm about to start a new job and need to relearn C++ 😄 But hopefully there is enough here to start a discussion.
Summary
Make gloo_net more idiomatic by increasing ownership and borrowing constraints. As is the Rust way, this will prevent certain classes of bugs and make our implementations flexible for future improvements.
This will require increasing the complexity of wrapper types.
Motivation
In JavaScript the bodies of requests and responses can only be read once, if you read the same response body more than once you'll just receive an empty buffer, without an error. This behavior is almost always a bug, and rust ownership semantics can prevent this. By requiring certain methods to take ownership of the underlying Request/Response, this bug is prevented.
JsValue backed types allow interior mutability through shared references. If we do the same with our wrapper types (Request, Response, RequestBuilder, etc.) we're adding unnecessary constraints to future implementations, and our APIs don't communicate mutability where it exists. One example is rather than having Header be a wrapper over web_sys::Header we could choose to wrap a http::HeaderMap and implement From<Header> for web_sys::Header. In that future, we'd be happier to have committed to
In #312 we added RequestBuilder and ResponseBuilder those types map to RequestInit and ResponseInit. I don't believe that made it into an RFC, I would like to suggest minor changes to those new APIs.
// mutablepubstructRequestBuilder(...);implRequestBuilder{pubfnfrom_raw(raw: websys::RequestInit) -> Self;pubfninto_raw(self) -> websys::RequestInit;// Creates a new request with the provided methodpubfnget(url:&str) -> RequestBuilder;pubfnpost(url:&str) -> RequestBuilder;// ...// Setterspubfnheader(self,header: http::HeaderName,value: http::HeaderValue) -> Self;// or stringspubfnheaders(self,headers: http::HeadersMap) -> Self;// ...// Getters// I'm still investigating if these builders are infailable. They might not be.pubfnbody(self,body:Body) -> Request;pubfnjson<T: serde::Serialize>(self,value:&T) -> Request;pubfnbinary(self,body:Vec<u8>) -> Request;pubfnbuild() -> Request}
ResponseBuilder has a similar API.
Response and Request map into web_sys::Response and web_sys::Request.
// immutablepubstructRequest(...);implRequest{pubfnfrom_raw(raw: web_sys::Request) -> Self;pubfninto_raw(self) -> web_sys::Request;// To construct a Request should make a builderpubfnget(url:&str) -> RequestBuilder;pubfnpost(url:&str) -> RequestBuilder;// ...// I'm propsing changes to these getters to return _borrowed_ values// In my opinion a Request should be immutable after creation, like other librariespubfnheaders(&self) -> &Headers;pubfnmethod(&self) -> &http::Method;pubfnurl(&self) -> &String;// ...// You can extract the body of a request/response, only oncepubasyncfnbody(self) -> Body;pubasyncfntext(self) -> Result<String,Error>;pubasyncfnbinary(self) -> Result<Vec<u8>,Error>;// `fetch()`pubasyncfnsend(self) -> Result<Response,Error>;}implCloneforRequest{// This is https://developer.mozilla.org/en-US/docs/Web/API/Request/clone// It works by `tee`-ing the body of the request, in JS clone misbehaves if// the body has already been consumed. Since you can only extract the body// once, `clone` should always work}
Headers
Remove Headers and replace it with http::HeadersMap.
TODO: explain why
Drawbacks, Rationale, and Alternatives
This RFC includes API breaking changes to basically every type in gloo_net. Since most changes involve ownership and borrowing, cargo check should communicate most of the fixes
When wrapping a web_sys type, we exclusively get owned values back, for example
// web_sys::Request#[doc = "Getter for the `method` field of this object."]#[doc = ""]#[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Request/method)"]#[doc = ""]#[doc = "*This API requires the following crate features to be activated: `Request`*"]pubfnmethod(this:&Request) -> String;// Returns an owned value// Our wrapperpubfnmethod(&self) -> &http::Method;// Returns a borrowed value
I'm proposing adding a new Rust type RequestOptions that stores rust representations of values we used to keep in a web_sys::RequestInit.
Like so:
#[derive(Debug)]structRequestOptions{method: http::Method,headers: http::HeaderMap,// or our current `Headers` typebody:Option<Body>,cache: web_sys::RequestCache,credentials: web_sys::RequestCredentials,integrity:String,mode: web_sys::RequestMode,redirect: web_sys::RequestRedirect,referrer:String,referrer_policy: web_sys::ReferrerPolicy,// signal: Option<&'a web_sys::AbortSignal>, TODO: our requests could be made cancellable }implFrom<RequestOptions>for web_sys::RequestInit{...}// straightforwardimplFrom<web_sys::RequestInit>forRequestOptions{...}// makes significant use of reflection.// Which makes the builderpubstructRequestBuilder{// urlurl:String,init:RequestOptions,}// And to support the getters in the RequestpubstructRequest{inner: web_sys::Request,// Cached Rust representations of the request inners.url:String,init:RequestOptions,}
Unresolved Questions
We can maybe remove Request::inner and instead lazily create it when well call send or try to process the body.
TODO
The text was updated successfully, but these errors were encountered:
It's been a long time since I've thought about this issue. If improvements have been added since I published this issue then it might be worth closing ❤️
Summary
Make
gloo_net
more idiomatic by increasing ownership and borrowing constraints. As is the Rust way, this will prevent certain classes of bugs and make our implementations flexible for future improvements.This will require increasing the complexity of wrapper types.
Motivation
In JavaScript the bodies of requests and responses can only be read once, if you read the same response body more than once you'll just receive an empty buffer, without an error. This behavior is almost always a bug, and rust ownership semantics can prevent this. By requiring certain methods to take ownership of the underlying Request/Response, this bug is prevented.
JsValue
backed types allow interior mutability through shared references. If we do the same with our wrapper types (Request
,Response
,RequestBuilder
, etc.) we're adding unnecessary constraints to future implementations, and our APIs don't communicate mutability where it exists. One example is rather than havingHeader
be a wrapper overweb_sys::Header
we could choose to wrap ahttp::HeaderMap
and implementFrom<Header> for web_sys::Header
. In that future, we'd be happier to have committed toOver
These changes can also open the door to improve https://rust-lang.github.io/api-guidelines/interoperability.html#interoperability.
Detailed Explanation
Request
andResponse
In #312 we added
RequestBuilder
andResponseBuilder
those types map to RequestInit and ResponseInit. I don't believe that made it into an RFC, I would like to suggest minor changes to those new APIs.ResponseBuilder
has a similar API.Response
andRequest
map intoweb_sys::Response
andweb_sys::Request
.Headers
Remove
Headers
and replace it withhttp::HeadersMap
.TODO: explain why
Drawbacks, Rationale, and Alternatives
This RFC includes API breaking changes to basically every type in
gloo_net
. Since most changes involve ownership and borrowing,cargo check
should communicate most of the fixesWhen wrapping a
web_sys
type, we exclusively get owned values back, for exampleI'm proposing adding a new Rust type
RequestOptions
that stores rust representations of values we used to keep in aweb_sys::RequestInit
.Like so:
Unresolved Questions
We can maybe remove
Request::inner
and instead lazily create it when well callsend
or try to process the body.TODO
The text was updated successfully, but these errors were encountered: