fastedge/lib.rs
1/*
2* Copyright 2025 G-Core Innovations SARL
3*/
4//! # FastEdge Rust SDK
5//!
6//! A comprehensive toolkit for building high-performance edge computing applications using WebAssembly.
7//!
8//! ## Overview
9//!
10//! The `fastedge` crate provides two runtime models:
11//!
12//! * **WebAssembly Component Model** (default): Modern WebAssembly component model using [WIT] (WebAssembly Interface Types)
13//! for type-safe interfaces. This API resides in the root of the `fastedge` crate's namespace.
14//!
15//! * **ProxyWasm API**: Compatibility layer for [ProxyWasm] environments (Envoy, etc.).
16//! Available via the [`fastedge::proxywasm`](`proxywasm`) module when the `proxywasm` feature is enabled.
17//!
18//! * **WASI-HTTP interface**: An alternative to the FastEdge SDK using the standard [WASI-HTTP]
19//! interface via the [`wstd`] crate.
20//!
21//! [WIT]: https://component-model.bytecodealliance.org/design/wit.html
22//! [WebAssembly components]: https://component-model.bytecodealliance.org
23//! [ProxyWasm]: https://github.com/proxy-wasm/spec
24//! [WASI-HTTP]: https://github.com/WebAssembly/wasi-http
25//! [`wstd`]: https://crates.io/crates/wstd
26//!
27//! ## Features
28//!
29//! - **HTTP Request Handling**: Process incoming HTTP requests at the edge
30//! - **Outbound HTTP Client**: Make HTTP requests to backend services via [`send_request`]
31//! - **Key-Value Storage**: Persistent storage with advanced operations (scan, sorted sets, bloom filters)
32//! - **Secret Management**: Secure access to encrypted secrets with time-based retrieval
33//! - **Dictionary**: Fast read-only key-value lookups for configuration
34//! - **WASI-NN**: Machine learning inference support via [`wasi_nn`]
35//!
36//! ## Quick Start
37//!
38//! Add to your `Cargo.toml`:
39//!
40//! ```toml
41//! [dependencies]
42//! fastedge = "0.3"
43//! anyhow = "1.0"
44//!
45//! [lib]
46//! crate-type = ["cdylib"]
47//! ```
48//!
49//! Create a simple HTTP handler:
50//!
51//! ```no_run
52//! use anyhow::Result;
53//! use fastedge::body::Body;
54//! use fastedge::http::{Request, Response, StatusCode};
55//!
56//! #[fastedge::http]
57//! fn main(_req: Request<Body>) -> Result<Response<Body>> {
58//! Response::builder()
59//! .status(StatusCode::OK)
60//! .body(Body::from("Hello, FastEdge!"))
61//! .map_err(Into::into)
62//! }
63//! ```
64//!
65//! Build for WebAssembly:
66//!
67//! ```bash
68//! rustup target add wasm32-wasip1
69//! cargo build --target wasm32-wasip1 --release
70//! ```
71//!
72//! ### Using the WASI-HTTP Interface (async, `wstd`)
73//!
74//! As an alternative to the synchronous FastEdge SDK, you can use the standard [WASI-HTTP]
75//! interface via the [`wstd`] crate. This enables an async handler and a proper HTTP client:
76//!
77//! ```no_run
78//! use wstd::http::body::Body;
79//! use wstd::http::{Client, Request, Response};
80//!
81//! #[wstd::http_server]
82//! async fn main(_request: Request<Body>) -> anyhow::Result<Response<Body>> {
83//! let upstream_req = Request::get("https://api.example.com/data")
84//! .header("accept", "application/json")
85//! .body(Body::empty())?;
86//!
87//! let response = Client::new().send(upstream_req).await?;
88//! Ok(response)
89//! }
90//! ```
91//!
92//! Build for WebAssembly using the `wasm32-wasip2` target:
93//!
94//! ```bash
95//! rustup target add wasm32-wasip2
96//! cargo build --target wasm32-wasip2 --release
97//! ```
98//!
99//! Tip: add a `.cargo/config.toml` to your project to avoid passing `--target` every time:
100//!
101//! ```toml
102//! [build]
103//! target = "wasm32-wasip2"
104//! ```
105//!
106//! Then `cargo build --release` is all you need.
107//!
108//! ## Feature Flags
109//!
110//! - `proxywasm` (default): Enable ProxyWasm compatibility layer
111//! - `json`: Enable JSON body support via `serde_json`
112//!
113//! ## Examples
114//!
115//! ### Making HTTP Requests
116//!
117//! ```no_run
118//! use anyhow::Result;
119//! use fastedge::body::Body;
120//! use fastedge::http::{Method, Request, Response, StatusCode};
121//!
122//! #[fastedge::http]
123//! fn main(_req: Request<Body>) -> Result<Response<Body>> {
124//! // Create a request to a backend service
125//! let backend_request = Request::builder()
126//! .method(Method::GET)
127//! .uri("https://api.example.com/data")
128//! .header("User-Agent", "FastEdge/1.0")
129//! .body(Body::empty())?;
130//!
131//! // Send the request
132//! let backend_response = fastedge::send_request(backend_request)?;
133//!
134//! // Return the response
135//! Response::builder()
136//! .status(StatusCode::OK)
137//! .body(backend_response.into_body())
138//! .map_err(Into::into)
139//! }
140//! ```
141//!
142//! ### Using Key-Value Storage
143//!
144//! ```no_run
145//! use anyhow::Result;
146//! use fastedge::body::Body;
147//! use fastedge::http::{Request, Response, StatusCode};
148//! use fastedge::key_value::Store;
149//!
150//! #[fastedge::http]
151//! fn main(_req: Request<Body>) -> Result<Response<Body>> {
152//! // Open a key-value store
153//! let store = Store::open("my-store")?;
154//!
155//! // Get a value
156//! if let Some(value) = store.get("user:123")? {
157//! return Response::builder()
158//! .status(StatusCode::OK)
159//! .body(Body::from(value))
160//! .map_err(Into::into);
161//! }
162//!
163//! Response::builder()
164//! .status(StatusCode::NOT_FOUND)
165//! .body(Body::empty())
166//! .map_err(Into::into)
167//! }
168//! ```
169//!
170//! ### Accessing Secrets
171//!
172//! ```no_run
173//! use anyhow::Result;
174//! use fastedge::body::Body;
175//! use fastedge::http::{Request, Response, StatusCode};
176//! use fastedge::secret;
177//!
178//! #[fastedge::http]
179//! fn main(_req: Request<Body>) -> Result<Response<Body>> {
180//! // Get a secret value
181//! match secret::get("API_KEY")? {
182//! Some(api_key) => {
183//! // Use the API key
184//! let key = String::from_utf8_lossy(&api_key);
185//! Response::builder()
186//! .status(StatusCode::OK)
187//! .body(Body::from("Secret retrieved"))
188//! .map_err(Into::into)
189//! }
190//! None => {
191//! Response::builder()
192//! .status(StatusCode::NOT_FOUND)
193//! .body(Body::from("Secret not found"))
194//! .map_err(Into::into)
195//! }
196//! }
197//! }
198//! ```
199//!
200//! ### Using the Cache
201//!
202//! ```no_run
203//! use anyhow::Result;
204//! use fastedge::body::Body;
205//! use fastedge::cache;
206//! use fastedge::http::{Request, Response, StatusCode};
207//!
208//! #[fastedge::http]
209//! fn main(_req: Request<Body>) -> Result<Response<Body>> {
210//! let key = "home:rendered";
211//!
212//! // Return cached response if available
213//! if let Some(cached) = cache::get(key)? {
214//! return Response::builder()
215//! .status(StatusCode::OK)
216//! .header("x-cache", "hit")
217//! .body(Body::from(cached))
218//! .map_err(Into::into);
219//! }
220//!
221//! // Compute the response
222//! let content = b"<h1>Hello</h1>".to_vec();
223//!
224//! // Store it for 60 seconds
225//! cache::set(key, content.clone(), Some(60_000))?;
226//!
227//! Response::builder()
228//! .status(StatusCode::OK)
229//! .header("x-cache", "miss")
230//! .body(Body::from(content))
231//! .map_err(Into::into)
232//! }
233//! ```
234pub extern crate http;
235
236pub use fastedge_derive::http;
237pub use http_client::send_request;
238
239#[doc(hidden)]
240pub use crate::exports::gcore::fastedge::http_handler;
241use crate::gcore::fastedge::http::{Error as HttpError, Method, Request, Response};
242
243mod helper;
244
245/// Implementation of Outbound HTTP component
246mod http_client;
247
248/// FastEdge ProxyWasm module extension
249#[cfg(feature = "proxywasm")]
250pub mod proxywasm;
251
252pub mod wasi_nn {
253 #![allow(missing_docs)]
254 wit_bindgen::generate!({
255 world: "ml",
256 path: "wasi-nn/wit"
257 });
258}
259
260wit_bindgen::generate!({
261 world: "reactor",
262 path: "wit",
263 pub_export_macro: true,
264});
265
266/// Fast read-only key-value dictionary for configuration.
267///
268/// The dictionary provides efficient access to read-only configuration values.
269/// It's optimized for fast lookups and is ideal for static configuration that
270/// doesn't change during request processing.
271///
272/// # Examples
273///
274/// ```no_run
275/// use fastedge::dictionary;
276///
277/// // Get a configuration value
278/// if let Some(config) = dictionary::get("api_endpoint")? {
279/// let endpoint = String::from_utf8_lossy(&config);
280/// println!("API endpoint: {}", endpoint);
281/// }
282/// # Ok::<(), Box<dyn std::error::Error>>(())
283/// ```
284pub mod dictionary {
285 #[doc(inline)]
286 pub use crate::gcore::fastedge::dictionary::get;
287}
288
289/// Secure access to encrypted secrets and credentials.
290///
291/// The secret module provides secure storage and retrieval of sensitive data such as
292/// API keys, passwords, and certificates. Secrets are encrypted at rest and support
293/// versioning with time-based retrieval.
294///
295/// # Security
296///
297/// - Secrets are encrypted and can only be accessed by authorized applications
298/// - Access is controlled via platform permissions
299/// - Never log or expose secret values in responses
300///
301/// # Examples
302///
303/// ```no_run
304/// use fastedge::secret;
305///
306/// // Get current secret value
307/// match secret::get("DATABASE_PASSWORD")? {
308/// Some(password) => {
309/// let pwd = String::from_utf8_lossy(&password);
310/// // Use the password to connect to database
311/// }
312/// None => {
313/// eprintln!("Secret not found");
314/// }
315/// }
316///
317/// // Get secret value at a specific time (for rotation scenarios)
318/// use std::time::{SystemTime, UNIX_EPOCH};
319/// let timestamp = SystemTime::now()
320/// .duration_since(UNIX_EPOCH)?
321/// .as_secs() as u32;
322/// let historical = secret::get_effective_at("API_KEY", timestamp)?;
323/// # Ok::<(), Box<dyn std::error::Error>>(())
324/// ```
325pub mod secret {
326 #[doc(inline)]
327 pub use crate::gcore::fastedge::secret::get;
328 #[doc(inline)]
329 pub use crate::gcore::fastedge::secret::get_effective_at;
330 pub use crate::gcore::fastedge::secret::Error;
331}
332
333/// Persistent key-value storage with advanced data structures.
334///
335/// The key-value module provides a persistent storage interface with support for:
336/// - Simple key-value pairs
337/// - Pattern-based scanning with glob-style patterns
338/// - Sorted sets with score-based range queries
339/// - Bloom filters for probabilistic set membership testing
340///
341/// # Storage Model
342///
343/// Data is organized into named stores. Applications must be granted access to specific
344/// stores via platform configuration.
345///
346/// # Examples
347///
348/// ## Basic Operations
349///
350/// ```no_run
351/// use fastedge::key_value::Store;
352///
353/// // Open a store
354/// let store = Store::open("user-data")?;
355///
356/// // Get a value
357/// if let Some(data) = store.get("user:123:profile")? {
358/// let profile = String::from_utf8_lossy(&data);
359/// println!("Profile: {}", profile);
360/// }
361/// # Ok::<(), Box<dyn std::error::Error>>(())
362/// ```
363///
364/// ## Pattern Scanning
365///
366/// ```no_run
367/// use fastedge::key_value::Store;
368///
369/// let store = Store::open("user-data")?;
370///
371/// // Find all keys matching a pattern
372/// let user_keys = store.scan("user:123:*")?;
373/// for key in user_keys {
374/// println!("Found key: {}", key);
375/// }
376/// # Ok::<(), Box<dyn std::error::Error>>(())
377/// ```
378///
379/// ## Sorted Sets (Leaderboards)
380///
381/// ```no_run
382/// use fastedge::key_value::Store;
383///
384/// let store = Store::open("game-data")?;
385///
386/// // Get top players by score (score between 1000 and infinity)
387/// let top_players = store.zrange_by_score("leaderboard", 1000.0, f64::INFINITY)?;
388/// for (player_id, score) in top_players {
389/// println!("Player: {:?}, Score: {}", player_id, score);
390/// }
391/// # Ok::<(), Box<dyn std::error::Error>>(())
392/// ```
393///
394/// ## Bloom Filters
395///
396/// ```no_run
397/// use fastedge::key_value::Store;
398///
399/// let store = Store::open("cache")?;
400///
401/// // Check if an item is in a bloom filter
402/// if store.bf_exists("seen_urls", "https://example.com")? {
403/// println!("URL was probably seen before");
404/// }
405/// # Ok::<(), Box<dyn std::error::Error>>(())
406/// ```
407pub mod key_value {
408 #[doc(inline)]
409 pub use crate::gcore::fastedge::key_value::Store;
410 #[doc(inline)]
411 pub use crate::gcore::fastedge::key_value::Error;
412}
413
414/// Synchronous cache API for FastEdge applications.
415///
416/// This module exposes the cache interface generated from the Component Model
417/// bindings. Calls are blocking and can be used directly inside a sync handler
418/// (e.g. `#[fastedge::http]`).
419///
420/// # Operations
421///
422/// - [`cache::get`] — fetch a cached value by key
423/// - [`cache::set`] — write or overwrite a value by key (with optional TTL)
424/// - [`cache::delete`] — remove a key from the cache
425/// - [`cache::exists`] — check whether a key is present
426/// - [`cache::incr`] — atomically increment (or decrement) an integer value
427/// - [`cache::expire`] — update the TTL of an existing key
428///
429/// # Examples
430///
431/// ## Fetch a value
432///
433/// ```no_run
434/// use fastedge::cache;
435///
436/// match cache::get("session:abc123")? {
437/// Some(data) => println!("cached: {}", String::from_utf8_lossy(&data)),
438/// None => println!("cache miss"),
439/// }
440/// # Ok::<(), Box<dyn std::error::Error>>(())
441/// ```
442///
443/// ## Write a value
444///
445/// ```no_run
446/// use fastedge::cache;
447///
448/// // Store with a 5-minute TTL
449/// cache::set("session:abc123", b"user-data".to_vec(), Some(300_000))?;
450///
451/// // Store with no expiry
452/// cache::set("config:flags", b"enabled".to_vec(), None)?;
453/// # Ok::<(), Box<dyn std::error::Error>>(())
454/// ```
455///
456/// ## Delete a key
457///
458/// ```no_run
459/// use fastedge::cache;
460///
461/// cache::delete("session:abc123")?;
462/// # Ok::<(), Box<dyn std::error::Error>>(())
463/// ```
464///
465/// ## Check existence
466///
467/// ```no_run
468/// use fastedge::cache;
469///
470/// if cache::exists("session:abc123")? {
471/// println!("key is present");
472/// }
473/// # Ok::<(), Box<dyn std::error::Error>>(())
474/// ```
475///
476/// ## Increment a counter
477///
478/// ```no_run
479/// use fastedge::cache;
480///
481/// // Increment by 1
482/// let hits = cache::incr("page:home:hits", 1)?;
483/// println!("total hits: {hits}");
484///
485/// // Decrement by 5
486/// let remaining = cache::incr("rate-limit:user:42", -5)?;
487/// println!("remaining tokens: {remaining}");
488/// # Ok::<(), Box<dyn std::error::Error>>(())
489/// ```
490///
491/// ## Update a key's expiry
492///
493/// ```no_run
494/// use fastedge::cache;
495///
496/// // Extend session TTL by 10 minutes
497/// let updated = cache::expire("session:abc123", 600_000)?;
498/// if !updated {
499/// println!("key no longer exists");
500/// }
501/// # Ok::<(), Box<dyn std::error::Error>>(())
502/// ```
503pub mod cache {
504 #[doc(inline)]
505 pub use crate::gcore::fastedge::cache_sync::get;
506 #[doc(inline)]
507 pub use crate::gcore::fastedge::cache_sync::set;
508 #[doc(inline)]
509 pub use crate::gcore::fastedge::cache_sync::delete;
510 #[doc(inline)]
511 pub use crate::gcore::fastedge::cache_sync::exists;
512 #[doc(inline)]
513 pub use crate::gcore::fastedge::cache_sync::incr;
514 #[doc(inline)]
515 pub use crate::gcore::fastedge::cache_sync::expire;
516 #[doc(inline)]
517 pub use crate::gcore::fastedge::cache_sync::purge;
518 #[doc(inline)]
519 pub use crate::gcore::fastedge::cache_sync::purge_prefix;
520}
521
522
523/// FastEdge-specific utility functions for diagnostics and statistics.
524///
525/// This module provides utilities for debugging and monitoring your FastEdge applications.
526///
527/// # Examples
528///
529/// ```no_run
530/// use fastedge::utils::set_user_diag;
531///
532/// // Set diagnostic information for debugging
533/// set_user_diag("Processing user request: 12345");
534/// ```
535///
536/// Diagnostic messages can be viewed in the FastEdge platform logs.
537pub mod utils {
538 #[doc(inline)]
539 pub use crate::gcore::fastedge::utils::set_user_diag;
540}
541
542/// Errors that can occur when using the FastEdge SDK.
543///
544/// This error type is returned by [`send_request`] and other SDK functions.
545///
546/// # Examples
547///
548/// ```
549/// use fastedge::Error;
550/// use fastedge::http::Method;
551///
552/// // Handling errors
553/// fn handle_error(err: Error) {
554/// match err {
555/// Error::UnsupportedMethod(method) => {
556/// eprintln!("Method {} is not supported", method);
557/// }
558/// Error::InvalidStatusCode(code) => {
559/// eprintln!("Invalid status code: {}", code);
560/// }
561/// _ => {
562/// eprintln!("An error occurred: {}", err);
563/// }
564/// }
565/// }
566/// ```
567#[derive(thiserror::Error, Debug)]
568pub enum Error {
569 /// The HTTP method is not supported by the FastEdge runtime.
570 ///
571 /// Only the following methods are supported: GET, POST, PUT, DELETE, HEAD, PATCH, OPTIONS.
572 #[error("method `{0}` is not supported")]
573 UnsupportedMethod(::http::Method),
574 /// An error occurred in the underlying HTTP client component.
575 #[error("http error: {0}")]
576 BindgenHttpError(#[from] HttpError),
577 /// An error occurred while building or parsing an HTTP message.
578 #[error("http error: {0}")]
579 HttpError(#[from] ::http::Error),
580 /// The HTTP body is invalid or could not be processed.
581 #[error("invalid http body")]
582 InvalidBody,
583 /// The HTTP status code is invalid (not in range 100-599).
584 #[error("invalid status code {0}")]
585 InvalidStatusCode(u16),
586}
587
588/// HTTP request and response body types.
589///
590/// This module provides the [`Body`](body::Body) type which wraps [`Bytes`](bytes::Bytes) and tracks content-type information.
591/// The body type automatically handles content-type detection based on the input data.
592///
593/// # Examples
594///
595/// ```
596/// use fastedge::body::Body;
597///
598/// // Create from string - automatically sets content-type to text/plain
599/// let text_body = Body::from("Hello, world!");
600/// assert_eq!(text_body.content_type(), "text/plain; charset=utf-8");
601///
602/// // Create from bytes - automatically sets content-type to application/octet-stream
603/// let bytes_body = Body::from(vec![1, 2, 3, 4]);
604/// assert_eq!(bytes_body.content_type(), "application/octet-stream");
605///
606/// // Create empty body
607/// let empty_body = Body::empty();
608/// assert_eq!(empty_body.len(), 0);
609/// ```
610pub mod body {
611 use std::ops::Deref;
612
613 use bytes::Bytes;
614
615 /// HTTP request/response body with content-type tracking.
616 ///
617 /// The `Body` type wraps [`Bytes`] and maintains content-type information.
618 /// It automatically detects and sets appropriate MIME types based on the input data.
619 ///
620 /// # Examples
621 ///
622 /// ```
623 /// use fastedge::body::Body;
624 ///
625 /// // From string
626 /// let body = Body::from("Hello");
627 ///
628 /// // From bytes
629 /// let body = Body::from(vec![1, 2, 3]);
630 ///
631 /// // Empty body
632 /// let body = Body::empty();
633 /// ```
634 #[derive(Debug)]
635 pub struct Body {
636 pub(crate) content_type: String,
637 pub(crate) inner: Bytes,
638 }
639
640 impl Deref for Body {
641 type Target = Bytes;
642
643 fn deref(&self) -> &Self::Target {
644 &self.inner
645 }
646 }
647
648 impl From<String> for Body {
649 fn from(value: String) -> Self {
650 Body {
651 content_type: mime::TEXT_PLAIN_UTF_8.to_string(),
652 inner: Bytes::from(value),
653 }
654 }
655 }
656
657 impl From<&'static str> for Body {
658 fn from(value: &'static str) -> Self {
659 Body {
660 content_type: mime::TEXT_PLAIN_UTF_8.to_string(),
661 inner: Bytes::from(value),
662 }
663 }
664 }
665
666 impl From<Vec<u8>> for Body {
667 fn from(value: Vec<u8>) -> Self {
668 Body {
669 content_type: mime::APPLICATION_OCTET_STREAM.to_string(),
670 inner: Bytes::from(value),
671 }
672 }
673 }
674
675 impl From<&'static [u8]> for Body {
676 fn from(value: &'static [u8]) -> Self {
677 Body {
678 content_type: mime::APPLICATION_OCTET_STREAM.to_string(),
679 inner: Bytes::from(value),
680 }
681 }
682 }
683
684 #[cfg(feature = "json")]
685 impl TryFrom<serde_json::Value> for Body {
686 type Error = serde_json::Error;
687 fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
688 Ok(Body {
689 content_type: mime::APPLICATION_JSON.to_string(),
690 inner: Bytes::from(serde_json::to_vec(&value)?),
691 })
692 }
693 }
694
695 impl Default for Body {
696 fn default() -> Self {
697 Self {
698 content_type: mime::TEXT_PLAIN_UTF_8.to_string(),
699 inner: Bytes::default(),
700 }
701 }
702 }
703
704 impl Body {
705 /// Creates an empty body with default content-type.
706 ///
707 /// # Examples
708 ///
709 /// ```
710 /// use fastedge::body::Body;
711 ///
712 /// let body = Body::empty();
713 /// assert_eq!(body.len(), 0);
714 /// ```
715 pub fn empty() -> Self {
716 Body::default()
717 }
718
719 /// Returns the MIME content-type of this body.
720 ///
721 /// The content-type is automatically determined when the body is created:
722 /// - Text strings: `text/plain; charset=utf-8`
723 /// - Byte arrays: `application/octet-stream`
724 /// - JSON (with `json` feature): `application/json`
725 ///
726 /// # Examples
727 ///
728 /// ```
729 /// use fastedge::body::Body;
730 ///
731 /// let body = Body::from("Hello");
732 /// assert_eq!(body.content_type(), "text/plain; charset=utf-8");
733 /// ```
734 pub fn content_type(&self) -> String {
735 self.content_type.to_owned()
736 }
737 }
738}
739
740impl From<Method> for ::http::Method {
741 fn from(method: Method) -> Self {
742 match method {
743 Method::Get => ::http::Method::GET,
744 Method::Post => ::http::Method::POST,
745 Method::Put => ::http::Method::PUT,
746 Method::Delete => ::http::Method::DELETE,
747 Method::Head => ::http::Method::HEAD,
748 Method::Patch => ::http::Method::PATCH,
749 Method::Options => ::http::Method::OPTIONS,
750 }
751 }
752}
753
754impl TryFrom<Request> for ::http::Request<body::Body> {
755 type Error = Error;
756
757 fn try_from(req: Request) -> Result<Self, Self::Error> {
758 let builder = ::http::Request::builder()
759 .method(::http::Method::from(req.method))
760 .uri(req.uri.to_string());
761 let builder = req
762 .headers
763 .iter()
764 .fold(builder, |builder, (k, v)| builder.header(k, v));
765
766 let body = req.body.map_or_else(body::Body::empty, body::Body::from);
767 builder.body(body).map_err(|_| Error::InvalidBody)
768 }
769}
770
771impl From<::http::Response<body::Body>> for Response {
772 fn from(res: ::http::Response<body::Body>) -> Self {
773 let status = res.status().as_u16();
774 let headers = if !res.headers().is_empty() {
775 Some(
776 res.headers()
777 .iter()
778 .map(|(name, value)| (name.to_string(), value.to_str().unwrap().to_string()))
779 .collect::<Vec<(String, String)>>(),
780 )
781 } else {
782 None
783 };
784
785 let body = Some(res.into_body().to_vec());
786
787 Response {
788 status,
789 headers,
790 body,
791 }
792 }
793}
794
795impl TryFrom<Response> for ::http::Response<body::Body> {
796 type Error = Error;
797
798 fn try_from(res: Response) -> Result<Self, Self::Error> {
799 let builder = ::http::Response::builder().status(
800 ::http::StatusCode::try_from(res.status)
801 .map_err(|_| Error::InvalidStatusCode(res.status))?,
802 );
803 let builder = if let Some(headers) = res.headers {
804 headers
805 .iter()
806 .fold(builder, |builder, (k, v)| builder.header(k, v))
807 } else {
808 builder
809 };
810
811 let body = res.body.map_or_else(body::Body::empty, body::Body::from);
812 builder.body(body).map_err(|_| Error::InvalidBody)
813 }
814}