aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md51
-rw-r--r--examples/form/src/main.rs51
-rw-r--r--src/hyper_body.rs19
-rw-r--r--src/lib.rs12
-rw-r--r--src/request.rs74
-rw-r--r--src/response.rs34
-rw-r--r--src/security.rs33
-rw-r--r--src/security/signed.rs8
8 files changed, 179 insertions, 103 deletions
diff --git a/README.md b/README.md
index 7d88c25..f19d3b5 100644
--- a/README.md
+++ b/README.md
@@ -35,14 +35,14 @@ header matches your domain name (especially if you have unauthenticated POST end
## Hyper Example
```rust
-use std::convert::Infallible;
-use hyper::service::{service_fn, make_service_fn};
-use hyper::{Method, Server, StatusCode, Body};
use hyper::http::request::Parts;
use hyper::http::response::Builder;
+use hyper::service::{make_service_fn, service_fn};
+use hyper::{Body, Method, Server, StatusCode};
use serde::Deserialize;
+use sputnik::hyper_body::{FormError, SputnikBody};
use sputnik::{html_escape, mime, request::SputnikParts, response::SputnikBuilder};
-use sputnik::hyper_body::{SputnikBody, FormError};
+use std::convert::Infallible;
type Response = hyper::Response<Body>;
@@ -51,7 +51,7 @@ enum Error {
#[error("page not found")]
NotFound(String),
#[error("{0}")]
- FormError(#[from] FormError)
+ FormError(#[from] FormError),
}
fn render_error(err: Error) -> (StatusCode, String) {
@@ -65,33 +65,37 @@ async fn route(req: &mut Parts, body: Body) -> Result<Response, Error> {
match (&req.method, req.uri.path()) {
(&Method::GET, "/form") => Ok(get_form(req)),
(&Method::POST, "/form") => post_form(req, body).await,
- _ => return Err(Error::NotFound("page not found".to_owned()))
+ _ => return Err(Error::NotFound("page not found".to_owned())),
}
}
fn get_form(_req: &mut Parts) -> Response {
Builder::new()
- .content_type(mime::TEXT_HTML)
- .body(
- "<form method=post><input name=text> <button>Submit</button></form>".into()
- ).unwrap()
+ .content_type(mime::TEXT_HTML)
+ .body("<form method=post><input name=text> <button>Submit</button></form>".into())
+ .unwrap()
}
#[derive(Deserialize)]
-struct FormData {text: String}
+struct FormData {
+ text: String,
+}
async fn post_form(_req: &mut Parts, body: Body) -> Result<Response, Error> {
let msg: FormData = body.into_form().await?;
- Ok(Builder::new().content_type(mime::TEXT_HTML).body(
- format!("hello <em>{}</em>", html_escape(msg.text)).into()
- ).unwrap())
+ Ok(Builder::new()
+ .content_type(mime::TEXT_HTML)
+ .body(format!("hello <em>{}</em>", html_escape(msg.text)).into())
+ .unwrap())
}
-async fn service(req: hyper::Request<hyper::Body>) -> Result<hyper::Response<hyper::Body>, Infallible> {
+async fn service(
+ req: hyper::Request<hyper::Body>,
+) -> Result<hyper::Response<hyper::Body>, Infallible> {
let (mut parts, body) = req.into_parts();
match route(&mut parts, body).await {
Ok(mut res) => {
- for (k,v) in parts.response_headers().iter() {
+ for (k, v) in parts.response_headers().iter() {
res.headers_mut().append(k, v.clone());
}
Ok(res)
@@ -99,19 +103,18 @@ async fn service(req: hyper::Request<hyper::Body>) -> Result<hyper::Response<hyp
Err(err) => {
let (code, message) = render_error(err);
// you can easily wrap or log errors here
- Ok(hyper::Response::builder().status(code).body(message.into()).unwrap())
+ Ok(hyper::Response::builder()
+ .status(code)
+ .body(message.into())
+ .unwrap())
}
}
}
#[tokio::main]
async fn main() {
- let service = make_service_fn(move |_| {
- async move {
- Ok::<_, hyper::Error>(service_fn(move |req| {
- service(req)
- }))
- }
+ let service = make_service_fn(move |_| async move {
+ Ok::<_, hyper::Error>(service_fn(move |req| service(req)))
});
let addr = ([127, 0, 0, 1], 8000).into();
@@ -155,4 +158,4 @@ let userid = req.cookies().find(|(name, _value)| *name == "userid")
Tip: If you want to store multiple claims in the cookie, you can
(de)serialize a struct with [serde_json](https://docs.serde.rs/serde_json/).
This approach can pose a lightweight alternative to JWT, if you don't care
-about the standardization aspect. \ No newline at end of file
+about the standardization aspect.
diff --git a/examples/form/src/main.rs b/examples/form/src/main.rs
index a63560c..af6b2af 100644
--- a/examples/form/src/main.rs
+++ b/examples/form/src/main.rs
@@ -1,11 +1,11 @@
-use std::convert::Infallible;
-use hyper::service::{service_fn, make_service_fn};
-use hyper::{Method, Server, StatusCode, Body};
use hyper::http::request::Parts;
use hyper::http::response::Builder;
+use hyper::service::{make_service_fn, service_fn};
+use hyper::{Body, Method, Server, StatusCode};
use serde::Deserialize;
+use sputnik::hyper_body::{FormError, SputnikBody};
use sputnik::{html_escape, mime, request::SputnikParts, response::SputnikBuilder};
-use sputnik::hyper_body::{SputnikBody, FormError};
+use std::convert::Infallible;
type Response = hyper::Response<Body>;
@@ -14,7 +14,7 @@ enum Error {
#[error("page not found")]
NotFound(String),
#[error("{0}")]
- FormError(#[from] FormError)
+ FormError(#[from] FormError),
}
fn render_error(err: Error) -> (StatusCode, String) {
@@ -28,33 +28,37 @@ async fn route(req: &mut Parts, body: Body) -> Result<Response, Error> {
match (&req.method, req.uri.path()) {
(&Method::GET, "/form") => Ok(get_form(req)),
(&Method::POST, "/form") => post_form(req, body).await,
- _ => return Err(Error::NotFound("page not found".to_owned()))
+ _ => return Err(Error::NotFound("page not found".to_owned())),
}
}
fn get_form(_req: &mut Parts) -> Response {
Builder::new()
- .content_type(mime::TEXT_HTML)
- .body(
- "<form method=post><input name=text> <button>Submit</button></form>".into()
- ).unwrap()
+ .content_type(mime::TEXT_HTML)
+ .body("<form method=post><input name=text> <button>Submit</button></form>".into())
+ .unwrap()
}
#[derive(Deserialize)]
-struct FormData {text: String}
+struct FormData {
+ text: String,
+}
async fn post_form(_req: &mut Parts, body: Body) -> Result<Response, Error> {
let msg: FormData = body.into_form().await?;
- Ok(Builder::new().content_type(mime::TEXT_HTML).body(
- format!("hello <em>{}</em>", html_escape(msg.text)).into()
- ).unwrap())
+ Ok(Builder::new()
+ .content_type(mime::TEXT_HTML)
+ .body(format!("hello <em>{}</em>", html_escape(msg.text)).into())
+ .unwrap())
}
-async fn service(req: hyper::Request<hyper::Body>) -> Result<hyper::Response<hyper::Body>, Infallible> {
+async fn service(
+ req: hyper::Request<hyper::Body>,
+) -> Result<hyper::Response<hyper::Body>, Infallible> {
let (mut parts, body) = req.into_parts();
match route(&mut parts, body).await {
Ok(mut res) => {
- for (k,v) in parts.response_headers().iter() {
+ for (k, v) in parts.response_headers().iter() {
res.headers_mut().append(k, v.clone());
}
Ok(res)
@@ -62,23 +66,22 @@ async fn service(req: hyper::Request<hyper::Body>) -> Result<hyper::Response<hyp
Err(err) => {
let (code, message) = render_error(err);
// you can easily wrap or log errors here
- Ok(hyper::Response::builder().status(code).body(message.into()).unwrap())
+ Ok(hyper::Response::builder()
+ .status(code)
+ .body(message.into())
+ .unwrap())
}
}
}
#[tokio::main]
async fn main() {
- let service = make_service_fn(move |_| {
- async move {
- Ok::<_, hyper::Error>(service_fn(move |req| {
- service(req)
- }))
- }
+ let service = make_service_fn(move |_| async move {
+ Ok::<_, hyper::Error>(service_fn(move |req| service(req)))
});
let addr = ([127, 0, 0, 1], 8000).into();
let server = Server::bind(&addr).serve(service);
println!("Listening on http://{}", addr);
server.await;
-} \ No newline at end of file
+}
diff --git a/src/hyper_body.rs b/src/hyper_body.rs
index 50efdc3..7400633 100644
--- a/src/hyper_body.rs
+++ b/src/hyper_body.rs
@@ -15,17 +15,22 @@ impl EmptyBuilder<hyper::Body> for Builder {
/// Adds deserialization methods to `hyper::Body`.
pub trait SputnikBody {
/// Parses a `application/x-www-form-urlencoded` request body into a given struct.
- fn into_form<T: DeserializeOwned>(self) -> Pin<Box<dyn Future<Output=Result<T, FormError>> + Send + Sync>>;
+ fn into_form<T: DeserializeOwned>(
+ self,
+ ) -> Pin<Box<dyn Future<Output = Result<T, FormError>> + Send + Sync>>;
/// Attempts to deserialize the request body as JSON.
#[cfg(feature = "hyper_body_json")]
#[cfg_attr(docsrs, doc(cfg(feature = "hyper_body_json")))]
- fn into_json<T: DeserializeOwned>(self) -> Pin<Box<dyn Future<Output=Result<T, JsonError>> + Send + Sync>>;
+ fn into_json<T: DeserializeOwned>(
+ self,
+ ) -> Pin<Box<dyn Future<Output = Result<T, JsonError>> + Send + Sync>>;
}
impl SputnikBody for hyper::Body {
-
- fn into_form<T: DeserializeOwned>(self) -> Pin<Box<dyn Future<Output=Result<T, FormError>> + Send + Sync>> {
+ fn into_form<T: DeserializeOwned>(
+ self,
+ ) -> Pin<Box<dyn Future<Output = Result<T, FormError>> + Send + Sync>> {
Box::pin(async move {
let full_body = hyper::body::to_bytes(self).await.map_err(BodyError)?;
Ok(serde_urlencoded::from_bytes::<T>(&full_body)?)
@@ -34,7 +39,9 @@ impl SputnikBody for hyper::Body {
#[cfg(feature = "hyper_body_json")]
#[cfg_attr(docsrs, doc(cfg(feature = "hyper_body_json")))]
- fn into_json<T: DeserializeOwned>(self) -> Pin<Box<dyn Future<Output=Result<T, JsonError>> + Send + Sync>> {
+ fn into_json<T: DeserializeOwned>(
+ self,
+ ) -> Pin<Box<dyn Future<Output = Result<T, JsonError>> + Send + Sync>> {
Box::pin(async move {
let full_body = hyper::body::to_bytes(self).await.map_err(BodyError)?;
Ok(serde_json::from_slice::<T>(&full_body)?)
@@ -64,4 +71,4 @@ pub enum JsonError {
#[error("json deserialize error: {0}")]
Deserialize(#[from] serde_json::Error),
-} \ No newline at end of file
+}
diff --git a/src/lib.rs b/src/lib.rs
index b32957d..2bac891 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,23 +5,23 @@
use std::borrow::Cow;
-pub use mime;
pub use httpdate;
+pub use mime;
pub mod request;
pub mod response;
-#[cfg(feature="security")]
+#[cfg(feature = "security")]
#[cfg_attr(docsrs, doc(cfg(feature = "security")))]
pub mod security;
-#[cfg(feature="hyper_body")]
+#[cfg(feature = "hyper_body")]
#[cfg_attr(docsrs, doc(cfg(feature = "hyper_body")))]
pub mod hyper_body;
-#[cfg(not(feature="hyper_body"))]
+#[cfg(not(feature = "hyper_body"))]
use http;
-#[cfg(feature="hyper_body")]
+#[cfg(feature = "hyper_body")]
use hyper::http;
/// HTML escapes the given string.
@@ -52,4 +52,4 @@ pub fn html_escape<'a, S: Into<Cow<'a, str>>>(input: S) -> Cow<'a, str> {
} else {
input
}
-} \ No newline at end of file
+}
diff --git a/src/request.rs b/src/request.rs
index 19292a1..6b8dbe1 100644
--- a/src/request.rs
+++ b/src/request.rs
@@ -5,13 +5,13 @@ use serde::Deserialize;
use std::str::Split;
use std::time::Duration;
-use crate::response::{Cookie, SputnikHeaders, delete_cookie};
-use crate::http::{HeaderMap, header, request::Parts};
+use crate::http::{header, request::Parts, HeaderMap};
+use crate::response::{delete_cookie, Cookie, SputnikHeaders};
/// Adds convenience methods to [`http::request::Parts`](Parts).
pub trait SputnikParts {
/// Parses the query string of the request into a given struct.
- fn query<'a, X: Deserialize<'a>>(&'a self) -> Result<X,QueryError>;
+ fn query<'a, X: Deserialize<'a>>(&'a self) -> Result<X, QueryError>;
/// Parses the cookies of the request.
fn cookies(&self) -> CookieIter;
@@ -42,7 +42,7 @@ impl<'a> Iterator for CookieIter<'a> {
None => self.next(),
Some(mut value) => {
if value.starts_with('"') && value.ends_with('"') && value.len() >= 2 {
- value = &value[1..value.len()-1];
+ value = &value[1..value.len() - 1];
}
Some((name, value))
}
@@ -52,7 +52,7 @@ impl<'a> Iterator for CookieIter<'a> {
}
impl SputnikParts for Parts {
- fn query<'a, T: Deserialize<'a>>(&'a self) -> Result<T,QueryError> {
+ fn query<'a, T: Deserialize<'a>>(&'a self) -> Result<T, QueryError> {
serde_urlencoded::from_str::<T>(self.uri.query().unwrap_or("")).map_err(QueryError)
}
@@ -64,16 +64,29 @@ impl SputnikParts for Parts {
}
fn cookies(&self) -> CookieIter {
- CookieIter(self.headers.get(header::COOKIE).and_then(|h| std::str::from_utf8(h.as_bytes()).ok()).unwrap_or("").split(';'))
+ CookieIter(
+ self.headers
+ .get(header::COOKIE)
+ .and_then(|h| std::str::from_utf8(h.as_bytes()).ok())
+ .unwrap_or("")
+ .split(';'),
+ )
}
fn enforce_content_type(&self, mime: Mime) -> Result<(), WrongContentTypeError> {
if let Some(content_type) = self.headers.get(header::CONTENT_TYPE) {
if *content_type == mime.to_string() {
- return Ok(())
+ return Ok(());
}
}
- Err(WrongContentTypeError{expected: mime, received: self.headers.get(header::CONTENT_TYPE).as_ref().and_then(|h| h.to_str().ok().map(|s| s.to_owned()))})
+ Err(WrongContentTypeError {
+ expected: mime,
+ received: self
+ .headers
+ .get(header::CONTENT_TYPE)
+ .as_ref()
+ .and_then(|h| h.to_str().ok().map(|s| s.to_owned())),
+ })
}
}
@@ -100,33 +113,50 @@ impl Flash {
/// If the request has a flash cookie retrieve it and append a set-cookie
/// header to delete the cookie to [`SputnikParts::response_headers`].
pub fn from_request(req: &mut Parts) -> Option<Self> {
- let value = req.cookies().find(|(name, _value)| *name == FLASH_COOKIE_NAME)?.1.to_owned();
- req.response_headers().set_cookie(delete_cookie(FLASH_COOKIE_NAME));
+ let value = req
+ .cookies()
+ .find(|(name, _value)| *name == FLASH_COOKIE_NAME)?
+ .1
+ .to_owned();
+ req.response_headers()
+ .set_cookie(delete_cookie(FLASH_COOKIE_NAME));
let mut iter = value.splitn(2, ':');
if let (Some(name), Some(message)) = (iter.next(), iter.next()) {
- return Some(Flash{name: name.to_owned(), message: message.to_owned()})
+ return Some(Flash {
+ name: name.to_owned(),
+ message: message.to_owned(),
+ });
}
None
}
/// Constructs a new Flash message. The name must not contain a colon (`:`).
pub fn new(name: String, message: String) -> Self {
- Flash{name, message}
+ Flash { name, message }
}
/// Constructs a new "success" Flash message.
pub fn success(message: String) -> Self {
- Flash{name: "success".to_owned(), message}
+ Flash {
+ name: "success".to_owned(),
+ message,
+ }
}
/// Constructs a new "warning" Flash message.
pub fn warning(message: String) -> Self {
- Flash{name: "warning".to_owned(), message}
+ Flash {
+ name: "warning".to_owned(),
+ message,
+ }
}
/// Constructs a new "error" Flash message.
pub fn error(message: String) -> Self {
- Flash{name: "error".to_owned(), message}
+ Flash {
+ name: "error".to_owned(),
+ message,
+ }
}
/// Returns the name of the Flash message.
@@ -140,7 +170,6 @@ impl Flash {
}
}
-
#[derive(thiserror::Error, Debug)]
#[error("query deserialize error: {0}")]
pub struct QueryError(pub serde_urlencoded::de::Error);
@@ -152,12 +181,11 @@ pub struct WrongContentTypeError {
pub received: Option<String>,
}
-
#[cfg(test)]
mod tests {
use std::convert::TryInto;
- use crate::http::{Request, header};
+ use crate::http::{header, Request};
use super::SputnikParts;
@@ -166,10 +194,14 @@ mod tests {
let (mut parts, _body) = Request::new("").into_parts();
assert!(parts.enforce_content_type(mime::APPLICATION_JSON).is_err());
- parts.headers.append(header::CONTENT_TYPE, "application/json".try_into().unwrap());
+ parts
+ .headers
+ .append(header::CONTENT_TYPE, "application/json".try_into().unwrap());
assert!(parts.enforce_content_type(mime::APPLICATION_JSON).is_ok());
- parts.headers.insert(header::CONTENT_TYPE, "text/html".try_into().unwrap());
+ parts
+ .headers
+ .insert(header::CONTENT_TYPE, "text/html".try_into().unwrap());
assert!(parts.enforce_content_type(mime::APPLICATION_JSON).is_err());
}
-} \ No newline at end of file
+}
diff --git a/src/response.rs b/src/response.rs
index c9d83a7..8035c4f 100644
--- a/src/response.rs
+++ b/src/response.rs
@@ -1,8 +1,12 @@
//! Provides convenience traits and functions to build HTTP responses.
-use std::{convert::TryInto, fmt::Display, time::{Duration, SystemTime}};
+use std::{
+ convert::TryInto,
+ fmt::Display,
+ time::{Duration, SystemTime},
+};
-use crate::http::{self, HeaderMap, StatusCode, header, response::Builder};
+use crate::http::{self, header, response::Builder, HeaderMap, StatusCode};
/// Adds convenience methods to [`Builder`].
pub trait SputnikBuilder {
@@ -54,14 +58,16 @@ impl Display for Cookie {
if let Some(time) = self.expires {
write!(f, "; Expires={}", httpdate::fmt_http_date(time))?;
}
-
+
Ok(())
}
}
#[derive(Debug, PartialEq)]
pub enum SameSite {
- Strict, Lax, None
+ Strict,
+ Lax,
+ None,
}
impl Display for SameSite {
@@ -76,7 +82,9 @@ impl Display for SameSite {
/// Creates a new builder with a given Location header and status code.
pub fn redirect(location: &str, code: StatusCode) -> Builder {
- Builder::new().status(code).header(header::LOCATION, location)
+ Builder::new()
+ .status(code)
+ .header(header::LOCATION, location)
}
impl SputnikBuilder for Builder {
@@ -93,10 +101,10 @@ impl SputnikBuilder for Builder {
/// Constructs an expired cookie to delete a cookie.
pub fn delete_cookie(name: &str) -> Cookie {
- Cookie{
+ Cookie {
name: name.into(),
max_age: Some(Duration::from_secs(0)),
- expires: Some(SystemTime::now() - Duration::from_secs(60*60*24)),
+ expires: Some(SystemTime::now() - Duration::from_secs(60 * 60 * 24)),
..Default::default()
}
}
@@ -133,8 +141,16 @@ mod tests {
#[test]
fn test_set_cookie() {
let mut map = HeaderMap::new();
- map.set_cookie(Cookie{name: "some".into(), value: "cookie".into(), ..Default::default()});
- map.set_cookie(Cookie{name: "some".into(), value: "cookie".into(), ..Default::default()});
+ map.set_cookie(Cookie {
+ name: "some".into(),
+ value: "cookie".into(),
+ ..Default::default()
+ });
+ map.set_cookie(Cookie {
+ name: "some".into(),
+ value: "cookie".into(),
+ ..Default::default()
+ });
assert_eq!(map.len(), 2);
}
diff --git a/src/security.rs b/src/security.rs
index cd9d7bd..bc3b381 100644
--- a/src/security.rs
+++ b/src/security.rs
@@ -7,17 +7,30 @@ mod signed;
/// Join a string and an expiry date together into a string.
pub fn encode_expiring_claim(claim: &str, expiry_date: SystemTime) -> String {
- format!("{}:{}", claim, expiry_date.duration_since(UNIX_EPOCH).unwrap().as_secs())
+ format!(
+ "{}:{}",
+ claim,
+ expiry_date.duration_since(UNIX_EPOCH).unwrap().as_secs()
+ )
}
/// Extract the string, failing if the expiry date is in the past.
pub fn decode_expiring_claim(value: &str) -> Result<&str, &'static str> {
let mut parts = value.rsplitn(2, ':');
- let expiry_date = parts.next().expect("first .rsplitn().next() is expected to return Some");
+ let expiry_date = parts
+ .next()
+ .expect("first .rsplitn().next() is expected to return Some");
let claim = parts.next().ok_or("expected colon")?;
- let expiry_date: u64 = expiry_date.parse().map_err(|_| "failed to parse timestamp")?;
-
- if expiry_date > SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() {
+ let expiry_date: u64 = expiry_date
+ .parse()
+ .map_err(|_| "failed to parse timestamp")?;
+
+ if expiry_date
+ > SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap()
+ .as_secs()
+ {
Ok(claim)
} else {
Err("token is expired")
@@ -26,17 +39,19 @@ pub fn decode_expiring_claim(value: &str) -> Result<&str, &'static str> {
#[cfg(test)]
mod tests {
- use std::time::{SystemTime, Duration};
+ use std::time::{Duration, SystemTime};
#[test]
fn test_expiring_claim() {
for claim in vec!["test", "", "foo:bar"] {
- let encoded_claim = super::encode_expiring_claim(claim, SystemTime::now() + Duration::from_secs(60));
+ let encoded_claim =
+ super::encode_expiring_claim(claim, SystemTime::now() + Duration::from_secs(60));
assert_eq!(super::decode_expiring_claim(&encoded_claim).unwrap(), claim);
- let encoded_claim = super::encode_expiring_claim(claim, SystemTime::now() - Duration::from_secs(60));
+ let encoded_claim =
+ super::encode_expiring_claim(claim, SystemTime::now() - Duration::from_secs(60));
assert!(super::decode_expiring_claim(&encoded_claim).is_err());
}
assert!(super::decode_expiring_claim("test".into()).is_err());
}
-} \ No newline at end of file
+}
diff --git a/src/security/signed.rs b/src/security/signed.rs
index 4da6760..2954954 100644
--- a/src/security/signed.rs
+++ b/src/security/signed.rs
@@ -1,4 +1,4 @@
-use hmac::{Hmac,NewMac,Mac};
+use hmac::{Hmac, Mac, NewMac};
use sha2::Sha256;
const SIGNED_KEY_LEN: usize = 32;
@@ -9,11 +9,11 @@ const BASE64_DIGEST_LEN: usize = 44;
/// This code was adapted from the [`cookie`] crate which does not make the sign and verify functions public
/// forcing the use of [`CookieJar`](cookie::CookieJar)s, which are akward to work with without a high-level framework.
// Thanks to Sergio Benitez for writing the original code and releasing it under MIT!
-pub struct Key (pub [u8; SIGNED_KEY_LEN]);
+pub struct Key(pub [u8; SIGNED_KEY_LEN]);
impl Key {
const fn zero() -> Self {
- Key ( [0; SIGNED_KEY_LEN])
+ Key([0; SIGNED_KEY_LEN])
}
/// Attempts to generate signing/encryption keys from a secure, random
@@ -69,4 +69,4 @@ impl Key {
.map(|_| value.to_string())
.map_err(|_| "value did not verify".to_string())
}
-} \ No newline at end of file
+}