Skip to main content

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}