crux_http/command.rs
1//! The Command based API for crux_http
2
3use std::{fmt, future::Future, marker::PhantomData};
4
5use crux_core::{command, Command};
6use http_types::{
7 convert::DeserializeOwned,
8 headers::{HeaderName, ToHeaderValues},
9 Body, Method, Mime, Url,
10};
11use serde::Serialize;
12
13use crate::{
14 expect::{ExpectBytes, ExpectJson, ExpectString, ResponseExpectation},
15 middleware::Middleware,
16 protocol::{HttpRequest, HttpResult, ProtocolRequestBuilder},
17 HttpError, Request, Response,
18};
19
20pub struct Http<Effect, Event> {
21 effect: PhantomData<Effect>,
22 event: PhantomData<Event>,
23}
24
25impl<Effect, Event> Http<Effect, Event>
26where
27 Effect: Send + From<crux_core::Request<HttpRequest>> + 'static,
28 Event: Send + 'static,
29{
30 /// Instruct the Shell to perform a HTTP GET request to the provided `url`.
31 ///
32 /// The request can be configured via associated functions on the returned
33 /// [`RequestBuilder`] and then converted to a [`Command`]
34 /// with [`RequestBuilder::build`].
35 ///
36 /// # Panics
37 ///
38 /// This will panic if a malformed URL is passed.
39 ///
40 /// # Examples
41 ///
42 /// ```
43 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<String>>) }
44 /// # #[derive(crux_core::macros::Effect)]
45 /// # #[allow(unused)]
46 /// # struct Capabilities { http: crux_http::Http<Event> }
47 /// # type Http = crux_http::command::Http<Effect, Event>;
48 /// Http::get("https://httpbin.org/get")
49 /// .expect_string()
50 /// .build()
51 /// .then_send(Event::ReceiveResponse);
52 /// ```
53 pub fn get(url: impl AsRef<str>) -> RequestBuilder<Effect, Event> {
54 RequestBuilder::new(Method::Get, url.as_ref().parse().unwrap())
55 }
56
57 /// Instruct the Shell to perform a HTTP HEAD request to the provided `url`.
58 ///
59 /// The request can be configured via associated functions on the returned
60 /// [`RequestBuilder`] and then converted to a [`Command`]
61 /// with [`RequestBuilder::build`].
62 ///
63 /// # Panics
64 ///
65 /// This will panic if a malformed URL is passed.
66 ///
67 /// # Examples
68 ///
69 /// ```
70 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
71 /// # #[derive(crux_core::macros::Effect)]
72 /// # #[allow(unused)]
73 /// # struct Capabilities { http: crux_http::Http<Event> }
74 /// # type Http = crux_http::command::Http<Effect, Event>;
75 /// Http::head("https://httpbin.org/get")
76 /// .build()
77 /// .then_send(Event::ReceiveResponse);
78 pub fn head(url: impl AsRef<str>) -> RequestBuilder<Effect, Event> {
79 RequestBuilder::new(Method::Head, url.as_ref().parse().unwrap())
80 }
81
82 /// Instruct the Shell to perform a HTTP POST request to the provided `url`.
83 ///
84 /// The request can be configured via associated functions on the returned
85 /// [`RequestBuilder`] and then converted to a [`Command`]
86 /// with [`RequestBuilder::build`].
87 ///
88 /// # Panics
89 ///
90 /// This will panic if a malformed URL is passed.
91 ///
92 /// # Examples
93 ///
94 /// ```
95 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
96 /// # #[derive(crux_core::macros::Effect)]
97 /// # #[allow(unused)]
98 /// # struct Capabilities { http: crux_http::Http<Event> }
99 /// # type Http = crux_http::command::Http<Effect, Event>;
100 /// Http::post("https://httpbin.org/post")
101 /// .body_bytes(b"hello_world".to_owned())
102 /// .build()
103 /// .then_send(Event::ReceiveResponse);
104 pub fn post(url: impl AsRef<str>) -> RequestBuilder<Effect, Event> {
105 RequestBuilder::new(Method::Post, url.as_ref().parse().unwrap())
106 }
107
108 /// Instruct the Shell to perform a HTTP PUT request to the provided `url`.
109 ///
110 /// The request can be configured via associated functions on the returned
111 /// [`RequestBuilder`] and then converted to a [`Command`]
112 /// with [`RequestBuilder::build`].
113 ///
114 /// # Panics
115 ///
116 /// This will panic if a malformed URL is passed.
117 ///
118 /// # Examples
119 ///
120 /// ```
121 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
122 /// # #[derive(crux_core::macros::Effect)]
123 /// # #[allow(unused)]
124 /// # struct Capabilities { http: crux_http::Http<Event> }
125 /// # type Http = crux_http::command::Http<Effect, Event>;
126 /// Http::put("https://httpbin.org/put")
127 /// .body_string("hello_world".to_string())
128 /// .build()
129 /// .then_send(Event::ReceiveResponse);
130 pub fn put(url: impl AsRef<str>) -> RequestBuilder<Effect, Event> {
131 RequestBuilder::new(Method::Put, url.as_ref().parse().unwrap())
132 }
133
134 /// Instruct the Shell to perform a HTTP DELETE request to the provided `url`.
135 ///
136 /// The request can be configured via associated functions on the returned
137 /// [`RequestBuilder`] and then converted to a [`Command`]
138 /// with [`RequestBuilder::build`].
139 ///
140 /// # Panics
141 ///
142 /// This will panic if a malformed URL is passed.
143 ///
144 /// # Examples
145 ///
146 /// ```
147 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
148 /// # #[derive(crux_core::macros::Effect)]
149 /// # #[allow(unused)]
150 /// # struct Capabilities { http: crux_http::Http<Event> }
151 /// # type Http = crux_http::command::Http<Effect, Event>;
152 /// Http::delete("https://httpbin.org/delete")
153 /// .build()
154 /// .then_send(Event::ReceiveResponse);
155 pub fn delete(url: impl AsRef<str>) -> RequestBuilder<Effect, Event> {
156 RequestBuilder::new(Method::Delete, url.as_ref().parse().unwrap())
157 }
158
159 /// Instruct the Shell to perform a HTTP PATCH request to the provided `url`.
160 ///
161 /// The request can be configured via associated functions on the returned
162 /// [`RequestBuilder`] and then converted to a [`Command`]
163 /// with [`RequestBuilder::build`].
164 ///
165 /// # Panics
166 ///
167 /// This will panic if a malformed URL is passed.
168 ///
169 /// # Examples
170 ///
171 /// ```
172 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
173 /// # #[derive(crux_core::macros::Effect)]
174 /// # #[allow(unused)]
175 /// # struct Capabilities { http: crux_http::Http<Event> }
176 /// # type Http = crux_http::command::Http<Effect, Event>;
177 /// Http::patch("https://httpbin.org/patch")
178 /// .body_form(&[("name", "Alice")]).unwrap()
179 /// .build()
180 /// .then_send(Event::ReceiveResponse);
181 pub fn patch(url: impl AsRef<str>) -> RequestBuilder<Effect, Event> {
182 RequestBuilder::new(Method::Patch, url.as_ref().parse().unwrap())
183 }
184
185 /// Instruct the Shell to perform a HTTP OPTIONS request to the provided `url`.
186 ///
187 /// The request can be configured via associated functions on the returned
188 /// [`RequestBuilder`] and then converted to a [`Command`]
189 /// with [`RequestBuilder::build`].
190 ///
191 /// # Panics
192 ///
193 /// This will panic if a malformed URL is passed.
194 ///
195 /// # Examples
196 ///
197 /// ```
198 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
199 /// # #[derive(crux_core::macros::Effect)]
200 /// # #[allow(unused)]
201 /// # struct Capabilities { http: crux_http::Http<Event> }
202 /// # type Http = crux_http::command::Http<Effect, Event>;
203 /// Http::options("https://httpbin.org/get")
204 /// .build()
205 /// .then_send(Event::ReceiveResponse);
206 pub fn options(url: impl AsRef<str>) -> RequestBuilder<Effect, Event> {
207 RequestBuilder::new(Method::Options, url.as_ref().parse().unwrap())
208 }
209
210 /// Instruct the Shell to perform a HTTP TRACE request to the provided `url`.
211 ///
212 /// The request can be configured via associated functions on the returned
213 /// [`RequestBuilder`] and then converted to a [`Command`]
214 /// with [`RequestBuilder::build`].
215 ///
216 /// # Panics
217 ///
218 /// This will panic if a malformed URL is passed.
219 ///
220 /// # Examples
221 ///
222 /// ```
223 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
224 /// # #[derive(crux_core::macros::Effect)]
225 /// # #[allow(unused)]
226 /// # struct Capabilities { http: crux_http::Http<Event> }
227 /// # type Http = crux_http::command::Http<Effect, Event>;
228 /// Http::trace("https://httpbin.org/get")
229 /// .build()
230 /// .then_send(Event::ReceiveResponse);
231 pub fn trace(url: impl AsRef<str>) -> RequestBuilder<Effect, Event> {
232 RequestBuilder::new(Method::Trace, url.as_ref().parse().unwrap())
233 }
234
235 /// Instruct the Shell to perform a HTTP CONNECT request to the provided `url`.
236 ///
237 /// The request can be configured via associated functions on the returned
238 /// [`RequestBuilder`] and then converted to a [`Command`]
239 /// with [`RequestBuilder::build`].
240 ///
241 /// # Panics
242 ///
243 /// This will panic if a malformed URL is passed.
244 ///
245 /// # Examples
246 ///
247 /// ```
248 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
249 /// # #[derive(crux_core::macros::Effect)]
250 /// # #[allow(unused)]
251 /// # struct Capabilities { http: crux_http::Http<Event> }
252 /// # type Http = crux_http::command::Http<Effect, Event>;
253 /// Http::connect("https://httpbin.org/get")
254 /// .build()
255 /// .then_send(Event::ReceiveResponse);
256 pub fn connect(url: impl AsRef<str>) -> RequestBuilder<Effect, Event> {
257 RequestBuilder::new(Method::Connect, url.as_ref().parse().unwrap())
258 }
259
260 /// Instruct the Shell to perform an HTTP request to the provided `url`.
261 ///
262 /// The request can be configured via associated functions on the returned
263 /// [`RequestBuilder`] and then converted to a [`Command`]
264 /// with [`RequestBuilder::build`].
265 ///
266 /// # Panics
267 ///
268 /// This will panic if a malformed URL is passed.
269 ///
270 /// # Examples
271 ///
272 /// ```
273 /// # use http_types::Method;
274 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
275 /// # #[derive(crux_core::macros::Effect)]
276 /// # #[allow(unused)]
277 /// # struct Capabilities { http: crux_http::Http<Event> }
278 /// # type Http = crux_http::command::Http<Effect, Event>;
279 /// Http::request(Method::Post, "https://httpbin.org/post".parse().unwrap())
280 /// .body_form(&[("name", "Alice")]).unwrap()
281 /// .build()
282 /// .then_send(Event::ReceiveResponse);
283 pub fn request(method: Method, url: Url) -> RequestBuilder<Effect, Event> {
284 RequestBuilder::new(method, url)
285 }
286}
287
288/// Request Builder
289///
290/// Provides an ergonomic way to chain the creation of a request.
291/// This is generally accessed as the return value from
292/// `crux_http::command::Http::{method}()`.
293///
294/// # Examples
295///
296/// ```
297/// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
298/// # #[derive(crux_core::macros::Effect)]
299/// # #[allow(unused)]
300/// # struct Capabilities { http: crux_http::Http<Event> }
301/// # type Http = crux_http::command::Http<Effect, Event>;
302/// Http::post("https://httpbin.org/post")
303/// .body("<html>hi</html>")
304/// .header("custom-header", "value")
305/// .content_type(crux_http::http::mime::HTML)
306/// .build()
307/// .then_send(Event::ReceiveResponse);
308/// ```
309#[must_use]
310pub struct RequestBuilder<Effect, Event, ExpectBody = Vec<u8>> {
311 /// Holds the state of the request.
312 req: Option<Request>,
313 effect: PhantomData<Effect>,
314 event: PhantomData<fn() -> Event>,
315 expectation: Box<dyn ResponseExpectation<Body = ExpectBody> + Send>,
316}
317
318impl<Effect, Event> RequestBuilder<Effect, Event, Vec<u8>>
319where
320 Effect: Send + From<crux_core::Request<HttpRequest>> + 'static,
321 Event: 'static,
322{
323 pub(crate) fn new(method: Method, url: Url) -> Self {
324 Self {
325 req: Some(Request::new(method, url)),
326 effect: PhantomData,
327 event: PhantomData,
328 expectation: Box::new(ExpectBytes),
329 }
330 }
331}
332
333impl<Effect, Event, ExpectBody> RequestBuilder<Effect, Event, ExpectBody>
334where
335 Effect: Send + From<crux_core::Request<HttpRequest>> + 'static,
336 Event: Send + 'static,
337 ExpectBody: 'static,
338{
339 /// Sets a header on the request.
340 ///
341 /// # Examples
342 ///
343 /// ```
344 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
345 /// # #[derive(crux_core::macros::Effect)]
346 /// # #[allow(unused)]
347 /// # struct Capabilities { http: crux_http::Http<Event> }
348 /// # type Http = crux_http::command::Http<Effect, Event>;
349 /// Http::get("https://httpbin.org/get")
350 /// .body("<html>hi</html>")
351 /// .header("header-name", "header-value")
352 /// .build()
353 /// .then_send(Event::ReceiveResponse);
354 /// ```
355 pub fn header(mut self, key: impl Into<HeaderName>, value: impl ToHeaderValues) -> Self {
356 self.req.as_mut().unwrap().insert_header(key, value);
357 self
358 }
359
360 /// Sets the Content-Type header on the request.
361 ///
362 /// # Examples
363 ///
364 /// ```
365 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
366 /// # #[derive(crux_core::macros::Effect)]
367 /// # #[allow(unused)]
368 /// # struct Capabilities { http: crux_http::Http<Event> }
369 /// # type Http = crux_http::command::Http<Effect, Event>;
370 /// Http::get("https://httpbin.org/get")
371 /// .content_type(crux_http::http::mime::HTML)
372 /// .build()
373 /// .then_send(Event::ReceiveResponse);
374 /// ```
375 pub fn content_type(mut self, content_type: impl Into<Mime>) -> Self {
376 self.req
377 .as_mut()
378 .unwrap()
379 .set_content_type(content_type.into());
380 self
381 }
382
383 /// Sets the body of the request from any type that implements `Into<Body>`
384 ///
385 /// # Mime
386 ///
387 /// The encoding is set to `application/octet-stream`.
388 ///
389 /// # Examples
390 ///
391 /// ```
392 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
393 /// # #[derive(crux_core::macros::Effect)]
394 /// # #[allow(unused)]
395 /// # struct Capabilities { http: crux_http::Http<Event> }
396 /// # type Http = crux_http::command::Http<Effect, Event>;
397 /// Http::post("https://httpbin.org/post")
398 /// .body(serde_json::json!({"any": "Into<Body>"}))
399 /// .content_type(crux_http::http::mime::HTML)
400 /// .build()
401 /// .then_send(Event::ReceiveResponse);
402 /// ```
403 pub fn body(mut self, body: impl Into<Body>) -> Self {
404 self.req.as_mut().unwrap().set_body(body);
405 self
406 }
407
408 /// Pass JSON as the request body.
409 ///
410 /// # Mime
411 ///
412 /// The encoding is set to `application/json`.
413 ///
414 /// # Errors
415 ///
416 /// This method will return an error if the provided data could not be serialized to JSON.
417 ///
418 /// # Examples
419 ///
420 /// ```
421 /// # use serde::{Deserialize, Serialize};
422 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
423 /// # #[derive(crux_core::macros::Effect)]
424 /// # #[allow(unused)]
425 /// # struct Capabilities { http: crux_http::Http<Event> }
426 /// # type Http = crux_http::command::Http<Effect, Event>;
427 /// #[derive(Deserialize, Serialize)]
428 /// struct Ip {
429 /// ip: String
430 /// }
431 ///
432 /// let data = &Ip { ip: "129.0.0.1".into() };
433 /// Http::post("https://httpbin.org/post")
434 /// .body_json(data)
435 /// .expect("could not serialize body")
436 /// .build()
437 /// .then_send(Event::ReceiveResponse);
438 /// ```
439 pub fn body_json(self, json: &impl Serialize) -> crate::Result<Self> {
440 Ok(self.body(Body::from_json(json)?))
441 }
442
443 /// Pass a string as the request body.
444 ///
445 /// # Mime
446 ///
447 /// The encoding is set to `text/plain; charset=utf-8`.
448 ///
449 /// # Examples
450 ///
451 /// ```
452 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
453 /// # #[derive(crux_core::macros::Effect)]
454 /// # #[allow(unused)]
455 /// # struct Capabilities { http: crux_http::Http<Event> }
456 /// # type Http = crux_http::command::Http<Effect, Event>;
457 /// Http::post("https://httpbin.org/post")
458 /// .body_string("hello_world".to_string())
459 /// .build()
460 /// .then_send(Event::ReceiveResponse);
461 /// ```
462 pub fn body_string(self, string: String) -> Self {
463 self.body(Body::from_string(string))
464 }
465
466 /// Pass bytes as the request body.
467 ///
468 /// # Mime
469 ///
470 /// The encoding is set to `application/octet-stream`.
471 ///
472 /// # Examples
473 ///
474 /// ```
475 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
476 /// # #[derive(crux_core::macros::Effect)]
477 /// # #[allow(unused)]
478 /// # struct Capabilities { http: crux_http::Http<Event> }
479 /// # type Http = crux_http::command::Http<Effect, Event>;
480 /// Http::post("https://httpbin.org/post")
481 /// .body_bytes(b"hello_world".to_owned())
482 /// .build()
483 /// .then_send(Event::ReceiveResponse);
484 /// ```
485 pub fn body_bytes(self, bytes: impl AsRef<[u8]>) -> Self {
486 self.body(Body::from(bytes.as_ref()))
487 }
488
489 /// Pass form data as the request body. The form data needs to be
490 /// serializable to name-value pairs.
491 ///
492 /// # Mime
493 ///
494 /// The `content-type` is set to `application/x-www-form-urlencoded`.
495 ///
496 /// # Errors
497 ///
498 /// An error will be returned if the provided data cannot be serialized to
499 /// form data.
500 ///
501 /// # Examples
502 ///
503 /// ```
504 /// # use std::collections::HashMap;
505 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
506 /// # #[derive(crux_core::macros::Effect)]
507 /// # #[allow(unused)]
508 /// # struct Capabilities { http: crux_http::Http<Event> }
509 /// # type Http = crux_http::command::Http<Effect, Event>;
510 /// let form_data = HashMap::from([
511 /// ("name", "Alice"),
512 /// ("location", "UK"),
513 /// ]);
514 /// Http::post("https://httpbin.org/post")
515 /// .body_form(&form_data)
516 /// .expect("could not serialize body")
517 /// .build()
518 /// .then_send(Event::ReceiveResponse);
519 /// ```
520 pub fn body_form(self, form: &impl Serialize) -> crate::Result<Self> {
521 Ok(self.body(Body::from_form(form)?))
522 }
523
524 /// Set the URL querystring.
525 ///
526 /// # Examples
527 ///
528 /// ```
529 /// # use serde::{Deserialize, Serialize};
530 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
531 /// # #[derive(crux_core::macros::Effect)]
532 /// # #[allow(unused)]
533 /// # struct Capabilities { http: crux_http::Http<Event> }
534 /// # type Http = crux_http::command::Http<Effect, Event>;
535 /// #[derive(Serialize, Deserialize)]
536 /// struct Index {
537 /// page: u32
538 /// }
539 ///
540 /// let query = Index { page: 2 };
541 /// Http::post("https://httpbin.org/post")
542 /// .query(&query)
543 /// .expect("could not serialize query string")
544 /// .build()
545 /// .then_send(Event::ReceiveResponse);
546 /// ```
547 pub fn query(mut self, query: &impl Serialize) -> std::result::Result<Self, HttpError> {
548 self.req.as_mut().unwrap().set_query(query)?;
549
550 Ok(self)
551 }
552
553 /// Push middleware onto a per-request middleware stack.
554 ///
555 /// **Important**: Setting per-request middleware incurs extra allocations.
556 /// Creating a `Client` with middleware is recommended.
557 ///
558 /// Client middleware is run before per-request middleware.
559 ///
560 /// See the [middleware] submodule for more information on middleware.
561 ///
562 /// [middleware]: ../middleware/index.html
563 ///
564 /// # Examples
565 ///
566 /// ```
567 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Vec<u8>>>) }
568 /// # #[derive(crux_core::macros::Effect)]
569 /// # #[allow(unused)]
570 /// # struct Capabilities { http: crux_http::Http<Event> }
571 /// # type Http = crux_http::command::Http<Effect, Event>;
572 /// Http::get("https://httpbin.org/redirect/2")
573 /// .middleware(crux_http::middleware::Redirect::default())
574 /// .build()
575 /// .then_send(Event::ReceiveResponse);
576 /// ```
577 pub fn middleware(mut self, middleware: impl Middleware) -> Self {
578 self.req.as_mut().unwrap().middleware(middleware);
579 self
580 }
581
582 /// Return the constructed `Request` in a [`crux_core::command::RequestBuilder`].
583 pub fn build(
584 self,
585 ) -> command::RequestBuilder<
586 Effect,
587 Event,
588 impl Future<Output = Result<Response<ExpectBody>, HttpError>>,
589 > {
590 let req = self.req.expect("RequestBuilder::build called twice");
591
592 command::RequestBuilder::new(|ctx| async move {
593 let operation = req
594 .into_protocol_request()
595 .await
596 .expect("should be able to convert request to protocol request");
597
598 let result = Command::request_from_shell(operation)
599 .into_future(ctx)
600 .await;
601
602 match result {
603 HttpResult::Ok(response) => Response::<Vec<u8>>::new(response.into())
604 .await
605 .and_then(|r| self.expectation.decode(r)),
606 HttpResult::Err(error) => Err(error),
607 }
608 })
609 }
610
611 /// Decode a String from the response body prior to dispatching it to the apps `update`
612 /// function.
613 ///
614 /// # Examples
615 ///
616 /// ```
617 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<String>>) }
618 /// # #[derive(crux_core::macros::Effect)]
619 /// # #[allow(unused)]
620 /// # struct Capabilities { http: crux_http::Http<Event> }
621 /// # type Http = crux_http::command::Http<Effect, Event>;
622 /// Http::post("https://httpbin.org/json")
623 /// .expect_string()
624 /// .build()
625 /// .then_send(Event::ReceiveResponse);
626 /// ```
627 pub fn expect_string(self) -> RequestBuilder<Effect, Event, String> {
628 let expectation = Box::<ExpectString>::default();
629 RequestBuilder {
630 req: self.req,
631 effect: PhantomData,
632 event: PhantomData,
633 expectation,
634 }
635 }
636
637 /// Decode a `T` from a JSON response body prior to dispatching it to the apps `update`
638 /// function.
639 ///
640 /// # Examples
641 ///
642 /// ```
643 /// # use serde::{Deserialize, Serialize};
644 /// # enum Event { ReceiveResponse(crux_http::Result<crux_http::Response<Slideshow>>) }
645 /// # #[derive(crux_core::macros::Effect)]
646 /// # #[allow(unused)]
647 /// # struct Capabilities { http: crux_http::Http<Event> }
648 /// # type Http = crux_http::command::Http<Effect, Event>;
649 /// #[derive(Deserialize)]
650 /// struct Response {
651 /// slideshow: Slideshow
652 /// }
653 ///
654 /// #[derive(Deserialize)]
655 /// struct Slideshow {
656 /// author: String
657 /// }
658 ///
659 /// Http::post("https://httpbin.org/json")
660 /// .expect_json::<Slideshow>()
661 /// .build()
662 /// .then_send(Event::ReceiveResponse);
663 /// ```
664 pub fn expect_json<T>(self) -> RequestBuilder<Effect, Event, T>
665 where
666 T: DeserializeOwned + 'static,
667 {
668 let expectation = Box::<ExpectJson<T>>::default();
669 RequestBuilder {
670 req: self.req,
671 effect: PhantomData,
672 event: PhantomData,
673 expectation,
674 }
675 }
676}
677
678impl<Effect, Event> fmt::Debug for RequestBuilder<Effect, Event> {
679 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
680 fmt::Debug::fmt(&self.req, f)
681 }
682}