From 6db4d7b097e977a41dcd4b70ba5f37be12acfdf8 Mon Sep 17 00:00:00 2001 From: msrd0 Date: Sun, 10 Nov 2024 16:19:44 +0000 Subject: [PATCH] GitHub Pages for e9dc2133d212ff713d8e27a8c98683e6376440c6 --- doc/.timestamp | 2 +- tarpaulin-report.html | 4 ++-- tarpaulin-report.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/.timestamp b/doc/.timestamp index 37e41e2b32..441e11bdc2 100644 --- a/doc/.timestamp +++ b/doc/.timestamp @@ -1 +1 @@ -Sun Nov 10 16:13:25 UTC 2024 +Sun Nov 10 16:19:43 UTC 2024 diff --git a/tarpaulin-report.html b/tarpaulin-report.html index b77937f3b6..2a33105a8d 100644 --- a/tarpaulin-report.html +++ b/tarpaulin-report.html @@ -118,8 +118,8 @@
\"#,\n\t\t\t\"\",\n\t\t\t\"\"\n\t\t),\n\t\tencoded_spec, script\n\t)\n\t.unwrap();\n\n\tRedoc { html, script_hash }\n}\n","traces":[{"line":20,"address":[8327968,8329195],"length":1,"stats":{"Line":0}},{"line":21,"address":[6453010,6452942],"length":1,"stats":{"Line":0}},{"line":23,"address":[7871840,7871867],"length":1,"stats":{"Line":0}},{"line":24,"address":[6875301],"length":1,"stats":{"Line":0}},{"line":25,"address":[8360743],"length":1,"stats":{"Line":0}},{"line":26,"address":[8360777],"length":1,"stats":{"Line":0}},{"line":27,"address":[8630908],"length":1,"stats":{"Line":0}},{"line":31,"address":[8629746],"length":1,"stats":{"Line":0}},{"line":32,"address":[6453101],"length":1,"stats":{"Line":0}},{"line":33,"address":[7962713],"length":1,"stats":{"Line":0}},{"line":34,"address":[8359644],"length":1,"stats":{"Line":0}},{"line":36,"address":[8111001],"length":1,"stats":{"Line":0}},{"line":37,"address":[6453709],"length":1,"stats":{"Line":0}}],"covered":0,"coverable":13},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","auth.rs"],"content":"use crate::AuthError;\n\nuse base64::prelude::*;\nuse futures_util::{\n\tfuture,\n\tfuture::{FutureExt, TryFutureExt}\n};\nuse gotham::{\n\tanyhow,\n\tcookie::CookieJar,\n\thandler::HandlerFuture,\n\thyper::header::{HeaderMap, HeaderName, AUTHORIZATION},\n\tmiddleware::{cookie::CookieParser, Middleware, NewMiddleware},\n\tprelude::*,\n\tstate::State\n};\nuse jsonwebtoken::DecodingKey;\nuse serde::de::DeserializeOwned;\nuse std::{marker::PhantomData, panic::RefUnwindSafe, pin::Pin};\n\npub type AuthValidation = jsonwebtoken::Validation;\n\n/// The authentication status returned by the auth middleware for each request.\n#[derive(Debug, StateData)]\npub enum AuthStatus {\n\t/// The auth status is unknown. This is likely because no secret was provided\n\t/// that could be used to verify the token of the client.\n\tUnknown,\n\n\t/// The request has been performed without any kind of authentication.\n\tUnauthenticated,\n\n\t/// The request has been performed with an invalid authentication. This\n\t/// includes expired tokens. Further details can be obtained from the\n\t/// included error.\n\tInvalid(jsonwebtoken::errors::Error),\n\n\t/// The request has been performed with a valid authentication. The claims\n\t/// that were decoded from the token are attached.\n\tAuthenticated(T)\n}\n\nimpl Clone for AuthStatus\nwhere\n\tT: Clone + Send + 'static\n{\n\tfn clone(&self) -> Self {\n\t\t// TODO why is this manually implemented?\n\t\tmatch self {\n\t\t\tSelf::Unknown => Self::Unknown,\n\t\t\tSelf::Unauthenticated => Self::Unauthenticated,\n\t\t\tSelf::Invalid(err) => Self::Invalid(err.clone()),\n\t\t\tSelf::Authenticated(data) => Self::Authenticated(data.clone())\n\t\t}\n\t}\n}\n\nimpl AuthStatus {\n\tpub fn ok(self) -> Result {\n\t\tmatch self {\n\t\t\tSelf::Unknown => Err(AuthError::new(\"The authentication could not be determined\")),\n\t\t\tSelf::Unauthenticated => Err(AuthError::new(\"Missing token\")),\n\t\t\tSelf::Invalid(err) => Err(AuthError::new(format!(\"Invalid token: {err}\"))),\n\t\t\tSelf::Authenticated(data) => Ok(data)\n\t\t}\n\t}\n}\n\n/// The source of the authentication token in the request.\n#[derive(Clone, Debug, StateData)]\npub enum AuthSource {\n\t/// Take the token from a cookie with the given name.\n\tCookie(String),\n\t/// Take the token from a header with the given name.\n\tHeader(HeaderName),\n\t/// Take the token from the HTTP Authorization header. This is different from `Header(\"Authorization\")`\n\t/// as it will follow the `scheme param` format from the HTTP specification. The `scheme` will\n\t/// be discarded, so its value doesn't matter.\n\tAuthorizationHeader\n}\n\n/// This trait will help the auth middleware to determine the validity of an authentication token.\n///\n/// A very basic implementation could look like this:\n///\n/// ```\n/// # use gotham_restful::{AuthHandler, gotham::state::State};\n/// #\n/// const SECRET: &'static [u8; 32] = b\"zlBsA2QXnkmpe0QTh8uCvtAEa4j33YAc\";\n///\n/// struct CustomAuthHandler;\n/// impl AuthHandler for CustomAuthHandler {\n/// \tfn jwt_secret Option>(\n/// \t\t&self,\n/// \t\t_state: &mut State,\n/// \t\t_decode_data: F\n/// \t) -> Option> {\n/// \t\tSome(SECRET.to_vec())\n/// \t}\n/// }\n/// ```\npub trait AuthHandler {\n\t/// Return the SHA256-HMAC secret used to verify the JWT token.\n\tfn jwt_secret Option>(\n\t\t&self,\n\t\tstate: &mut State,\n\t\tdecode_data: F\n\t) -> Option>;\n}\n\n/// An [AuthHandler] returning always the same secret. See [AuthMiddleware] for a usage example.\n#[derive(Clone, Debug)]\npub struct StaticAuthHandler {\n\tsecret: Vec\n}\n\nimpl StaticAuthHandler {\n\tpub fn from_vec(secret: Vec) -> Self {\n\t\tSelf { secret }\n\t}\n\n\tpub fn from_array(secret: &[u8]) -> Self {\n\t\tSelf::from_vec(secret.to_vec())\n\t}\n}\n\nimpl AuthHandler for StaticAuthHandler {\n\tfn jwt_secret Option>(\n\t\t&self,\n\t\t_state: &mut State,\n\t\t_decode_data: F\n\t) -> Option> {\n\t\tSome(self.secret.clone())\n\t}\n}\n\n/// This is the auth middleware. To use it, first make sure you have the `auth` feature enabled. Then\n/// simply add it to your pipeline and request it inside your handler:\n///\n/// ```rust,no_run\n/// # #[macro_use] extern crate gotham_restful_derive;\n/// # use gotham::{router::builder::*, pipeline::*, state::State};\n/// # use gotham_restful::*;\n/// # use serde::{Deserialize, Serialize};\n/// #\n/// #[derive(Resource)]\n/// #[resource(read_all)]\n/// struct AuthResource;\n///\n/// #[derive(Debug, Deserialize, Clone)]\n/// struct AuthData {\n/// \tsub: String,\n/// \texp: u64\n/// }\n///\n/// #[read_all]\n/// fn read_all(auth: &AuthStatus) -> Success {\n/// \tformat!(\"{auth:?}\").into()\n/// }\n///\n/// fn main() {\n/// \tlet auth: AuthMiddleware = AuthMiddleware::new(\n/// \t\tAuthSource::AuthorizationHeader,\n/// \t\tAuthValidation::default(),\n/// \t\tStaticAuthHandler::from_array(b\"zlBsA2QXnkmpe0QTh8uCvtAEa4j33YAc\")\n/// \t);\n/// \tlet (chain, pipelines) = single_pipeline(new_pipeline().add(auth).build());\n/// \tgotham::start(\n/// \t\t\"127.0.0.1:8080\",\n/// \t\tbuild_router(chain, pipelines, |route| {\n/// \t\t\troute.resource::(\"auth\");\n/// \t\t})\n/// \t);\n/// }\n/// ```\n#[derive(Debug)]\npub struct AuthMiddleware {\n\tsource: AuthSource,\n\tvalidation: AuthValidation,\n\thandler: Handler,\n\t_data: PhantomData\n}\n\nimpl Clone for AuthMiddleware\nwhere\n\tHandler: Clone\n{\n\tfn clone(&self) -> Self {\n\t\tSelf {\n\t\t\tsource: self.source.clone(),\n\t\t\tvalidation: self.validation.clone(),\n\t\t\thandler: self.handler.clone(),\n\t\t\t_data: self._data\n\t\t}\n\t}\n}\n\nimpl AuthMiddleware\nwhere\n\tData: DeserializeOwned + Send,\n\tHandler: AuthHandler + Default\n{\n\tpub fn from_source(source: AuthSource) -> Self {\n\t\tSelf {\n\t\t\tsource,\n\t\t\tvalidation: Default::default(),\n\t\t\thandler: Default::default(),\n\t\t\t_data: Default::default()\n\t\t}\n\t}\n}\n\nimpl AuthMiddleware\nwhere\n\tData: DeserializeOwned + Send,\n\tHandler: AuthHandler\n{\n\tpub fn new(source: AuthSource, validation: AuthValidation, handler: Handler) -> Self {\n\t\tSelf {\n\t\t\tsource,\n\t\t\tvalidation,\n\t\t\thandler,\n\t\t\t_data: Default::default()\n\t\t}\n\t}\n\n\tfn auth_status(&self, state: &mut State) -> AuthStatus {\n\t\t// extract the provided token, if any\n\t\tlet token = match &self.source {\n\t\t\tAuthSource::Cookie(name) => CookieJar::try_borrow_from(&state)\n\t\t\t\t.map(|jar| jar.get(&name).map(|cookie| cookie.value().to_owned()))\n\t\t\t\t.unwrap_or_else(|| {\n\t\t\t\t\tCookieParser::from_state(&state)\n\t\t\t\t\t\t.get(&name)\n\t\t\t\t\t\t.map(|cookie| cookie.value().to_owned())\n\t\t\t\t}),\n\t\t\tAuthSource::Header(name) => HeaderMap::try_borrow_from(&state)\n\t\t\t\t.and_then(|map| map.get(name))\n\t\t\t\t.and_then(|header| header.to_str().ok())\n\t\t\t\t.map(|value| value.to_owned()),\n\t\t\tAuthSource::AuthorizationHeader => HeaderMap::try_borrow_from(&state)\n\t\t\t\t.and_then(|map| map.get(AUTHORIZATION))\n\t\t\t\t.and_then(|header| header.to_str().ok())\n\t\t\t\t.and_then(|value| value.split_whitespace().nth(1))\n\t\t\t\t.map(|value| value.to_owned())\n\t\t};\n\n\t\t// unauthed if no token\n\t\tlet token = match token {\n\t\t\tSome(token) => token,\n\t\t\tNone => return AuthStatus::Unauthenticated\n\t\t};\n\n\t\t// get the secret from the handler, possibly decoding claims ourselves\n\t\tlet secret = self.handler.jwt_secret(state, || {\n\t\t\tlet b64 = token.split('.').nth(1)?;\n\t\t\tlet raw = BASE64_URL_SAFE_NO_PAD.decode(b64).ok()?;\n\t\t\tserde_json::from_slice(&raw).ok()?\n\t\t});\n\n\t\t// unknown if no secret\n\t\tlet secret = match secret {\n\t\t\tSome(secret) => secret,\n\t\t\tNone => return AuthStatus::Unknown\n\t\t};\n\n\t\t// validate the token\n\t\tlet data: Data = match jsonwebtoken::decode(\n\t\t\t&token,\n\t\t\t&DecodingKey::from_secret(&secret),\n\t\t\t&self.validation\n\t\t) {\n\t\t\tOk(data) => data.claims,\n\t\t\tErr(e) => return AuthStatus::Invalid(e)\n\t\t};\n\n\t\t// we found a valid token\n\t\tAuthStatus::Authenticated(data)\n\t}\n}\n\nimpl Middleware for AuthMiddleware\nwhere\n\tData: DeserializeOwned + Send + 'static,\n\tHandler: AuthHandler\n{\n\tfn call(self, mut state: State, chain: Chain) -> Pin>\n\twhere\n\t\tChain: FnOnce(State) -> Pin>\n\t{\n\t\t// put the source in our state, required for e.g. openapi\n\t\tstate.put(self.source.clone());\n\n\t\t// put the status in our state\n\t\tlet status = self.auth_status(&mut state);\n\t\tstate.put(status);\n\n\t\t// call the rest of the chain\n\t\tchain(state)\n\t\t\t.and_then(|(state, res)| future::ok((state, res)))\n\t\t\t.boxed()\n\t}\n}\n\nimpl NewMiddleware for AuthMiddleware\nwhere\n\tSelf: Clone + Middleware + Sync + RefUnwindSafe\n{\n\ttype Instance = Self;\n\n\tfn new_middleware(&self) -> anyhow::Result {\n\t\tlet c: Self = self.clone();\n\t\tOk(c)\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse gotham::{cookie::Cookie, hyper::header::COOKIE};\n\tuse jsonwebtoken::errors::ErrorKind;\n\tuse std::fmt::Debug;\n\n\t// 256-bit random string\n\tconst JWT_SECRET: &'static [u8; 32] = b\"Lyzsfnta0cdxyF0T9y6VGxp3jpgoMUuW\";\n\n\t// some known tokens\n\tconst VALID_TOKEN: &'static str = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9.8h8Ax-nnykqEQ62t7CxmM3ja6NzUQ4L0MLOOzddjLKk\";\n\tconst EXPIRED_TOKEN: &'static str = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjE1Nzc4MzcxMDB9.eV1snaGLYrJ7qUoMk74OvBY3WUU9M0Je5HTU2xtX1v0\";\n\tconst INVALID_TOKEN: &'static str = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9\";\n\n\t#[derive(Debug, Deserialize, PartialEq)]\n\tstruct TestData {\n\t\tiss: String,\n\t\tsub: String,\n\t\tiat: u64,\n\t\texp: u64\n\t}\n\n\timpl Default for TestData {\n\t\tfn default() -> Self {\n\t\t\tSelf {\n\t\t\t\tiss: \"msrd0\".to_owned(),\n\t\t\t\tsub: \"gotham-restful\".to_owned(),\n\t\t\t\tiat: 1577836800,\n\t\t\t\texp: 4102444800\n\t\t\t}\n\t\t}\n\t}\n\n\t#[derive(Default)]\n\tstruct NoneAuthHandler;\n\timpl AuthHandler for NoneAuthHandler {\n\t\tfn jwt_secret Option>(\n\t\t\t&self,\n\t\t\t_state: &mut State,\n\t\t\t_decode_data: F\n\t\t) -> Option> {\n\t\t\tNone\n\t\t}\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_none_secret() {\n\t\tlet middleware = >::from_source(\n\t\t\tAuthSource::AuthorizationHeader\n\t\t);\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut headers = HeaderMap::new();\n\t\t\theaders.insert(\n\t\t\t\tAUTHORIZATION,\n\t\t\t\tformat!(\"Bearer {VALID_TOKEN}\").parse().unwrap()\n\t\t\t);\n\t\t\tstate.put(headers);\n\t\t\tmiddleware.auth_status(&mut state);\n\t\t});\n\t}\n\n\t#[derive(Default)]\n\tstruct TestAssertingHandler;\n\timpl AuthHandler for TestAssertingHandler\n\twhere\n\t\tT: Debug + Default + PartialEq\n\t{\n\t\tfn jwt_secret Option>(\n\t\t\t&self,\n\t\t\t_state: &mut State,\n\t\t\tdecode_data: F\n\t\t) -> Option> {\n\t\t\tassert_eq!(decode_data(), Some(T::default()));\n\t\t\tSome(JWT_SECRET.to_vec())\n\t\t}\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_decode_data() {\n\t\tlet middleware = >::from_source(\n\t\t\tAuthSource::AuthorizationHeader\n\t\t);\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut headers = HeaderMap::new();\n\t\t\theaders.insert(\n\t\t\t\tAUTHORIZATION,\n\t\t\t\tformat!(\"Bearer {VALID_TOKEN}\").parse().unwrap()\n\t\t\t);\n\t\t\tstate.put(headers);\n\t\t\tmiddleware.auth_status(&mut state);\n\t\t});\n\t}\n\n\tfn new_middleware(source: AuthSource) -> AuthMiddleware\n\twhere\n\t\tT: DeserializeOwned + Send\n\t{\n\t\tAuthMiddleware::new(\n\t\t\tsource,\n\t\t\tDefault::default(),\n\t\t\tStaticAuthHandler::from_array(JWT_SECRET)\n\t\t)\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_no_token() {\n\t\tlet middleware = new_middleware::(AuthSource::AuthorizationHeader);\n\t\tState::with_new(|mut state| {\n\t\t\tlet status = middleware.auth_status(&mut state);\n\t\t\tmatch status {\n\t\t\t\tAuthStatus::Unauthenticated => {},\n\t\t\t\t_ => panic!(\"Expected AuthStatus::Unauthenticated, got {status:?}\")\n\t\t\t};\n\t\t});\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_expired_token() {\n\t\tlet middleware = new_middleware::(AuthSource::AuthorizationHeader);\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut headers = HeaderMap::new();\n\t\t\theaders.insert(\n\t\t\t\tAUTHORIZATION,\n\t\t\t\tformat!(\"Bearer {EXPIRED_TOKEN}\").parse().unwrap()\n\t\t\t);\n\t\t\tstate.put(headers);\n\t\t\tlet status = middleware.auth_status(&mut state);\n\t\t\tmatch status {\n\t\t\t\tAuthStatus::Invalid(err) if *err.kind() == ErrorKind::ExpiredSignature => {},\n\t\t\t\t_ => panic!(\n\t\t\t\t\t\"Expected AuthStatus::Invalid(..) with ErrorKind::ExpiredSignature, got {status:?}\"\n\t\t\t\t)\n\t\t\t};\n\t\t});\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_invalid_token() {\n\t\tlet middleware = new_middleware::(AuthSource::AuthorizationHeader);\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut headers = HeaderMap::new();\n\t\t\theaders.insert(\n\t\t\t\tAUTHORIZATION,\n\t\t\t\tformat!(\"Bearer {INVALID_TOKEN}\").parse().unwrap()\n\t\t\t);\n\t\t\tstate.put(headers);\n\t\t\tlet status = middleware.auth_status(&mut state);\n\t\t\tmatch status {\n\t\t\t\tAuthStatus::Invalid(err) if *err.kind() == ErrorKind::InvalidToken => {},\n\t\t\t\t_ => panic!(\n\t\t\t\t\t\"Expected AuthStatus::Invalid(..) with ErrorKind::InvalidToken, got {status:?}\"\n\t\t\t\t)\n\t\t\t};\n\t\t});\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_auth_header_token() {\n\t\tlet middleware = new_middleware::(AuthSource::AuthorizationHeader);\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut headers = HeaderMap::new();\n\t\t\theaders.insert(\n\t\t\t\tAUTHORIZATION,\n\t\t\t\tformat!(\"Bearer {VALID_TOKEN}\").parse().unwrap()\n\t\t\t);\n\t\t\tstate.put(headers);\n\t\t\tlet status = middleware.auth_status(&mut state);\n\t\t\tmatch status {\n\t\t\t\tAuthStatus::Authenticated(data) => assert_eq!(data, TestData::default()),\n\t\t\t\t_ => panic!(\"Expected AuthStatus::Authenticated, got {status:?}\")\n\t\t\t};\n\t\t})\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_header_token() {\n\t\tlet header_name = \"x-znoiprwmvfexju\";\n\t\tlet middleware =\n\t\t\tnew_middleware::(AuthSource::Header(HeaderName::from_static(header_name)));\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut headers = HeaderMap::new();\n\t\t\theaders.insert(header_name, VALID_TOKEN.parse().unwrap());\n\t\t\tstate.put(headers);\n\t\t\tlet status = middleware.auth_status(&mut state);\n\t\t\tmatch status {\n\t\t\t\tAuthStatus::Authenticated(data) => assert_eq!(data, TestData::default()),\n\t\t\t\t_ => panic!(\"Expected AuthStatus::Authenticated, got {status:?}\")\n\t\t\t};\n\t\t})\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_cookie_token() {\n\t\tlet cookie_name = \"znoiprwmvfexju\";\n\t\tlet middleware = new_middleware::(AuthSource::Cookie(cookie_name.to_owned()));\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut jar = CookieJar::new();\n\t\t\tjar.add_original(Cookie::new(cookie_name, VALID_TOKEN));\n\t\t\tstate.put(jar);\n\t\t\tlet status = middleware.auth_status(&mut state);\n\t\t\tmatch status {\n\t\t\t\tAuthStatus::Authenticated(data) => assert_eq!(data, TestData::default()),\n\t\t\t\t_ => panic!(\"Expected AuthStatus::Authenticated, got {status:?}\")\n\t\t\t};\n\t\t})\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_cookie_no_jar() {\n\t\tlet cookie_name = \"znoiprwmvfexju\";\n\t\tlet middleware = new_middleware::(AuthSource::Cookie(cookie_name.to_owned()));\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut headers = HeaderMap::new();\n\t\t\theaders.insert(\n\t\t\t\tCOOKIE,\n\t\t\t\tformat!(\"{cookie_name}={VALID_TOKEN}\").parse().unwrap()\n\t\t\t);\n\t\t\tstate.put(headers);\n\t\t\tlet status = middleware.auth_status(&mut state);\n\t\t\tmatch status {\n\t\t\t\tAuthStatus::Authenticated(data) => assert_eq!(data, TestData::default()),\n\t\t\t\t_ => panic!(\"Expected AuthStatus::Authenticated, got {status:?}\")\n\t\t\t};\n\t\t})\n\t}\n}\n","traces":[{"line":47,"address":[],"length":0,"stats":{"Line":0}},{"line":49,"address":[],"length":0,"stats":{"Line":0}},{"line":50,"address":[],"length":0,"stats":{"Line":0}},{"line":51,"address":[],"length":0,"stats":{"Line":0}},{"line":52,"address":[],"length":0,"stats":{"Line":0}},{"line":53,"address":[],"length":0,"stats":{"Line":0}},{"line":59,"address":[],"length":0,"stats":{"Line":0}},{"line":60,"address":[],"length":0,"stats":{"Line":0}},{"line":61,"address":[],"length":0,"stats":{"Line":0}},{"line":62,"address":[],"length":0,"stats":{"Line":0}},{"line":63,"address":[7818085,7817919,7817749],"length":1,"stats":{"Line":0}},{"line":64,"address":[],"length":0,"stats":{"Line":0}},{"line":118,"address":[7832336],"length":1,"stats":{"Line":4}},{"line":122,"address":[7684288],"length":1,"stats":{"Line":4}},{"line":123,"address":[8081207],"length":1,"stats":{"Line":4}},{"line":128,"address":[6188016,6188061],"length":1,"stats":{"Line":1}},{"line":133,"address":[6188097,6188045],"length":1,"stats":{"Line":2}},{"line":188,"address":[7938048,7938318],"length":1,"stats":{"Line":1}},{"line":190,"address":[],"length":0,"stats":{"Line":1}},{"line":191,"address":[],"length":0,"stats":{"Line":1}},{"line":192,"address":[],"length":0,"stats":{"Line":1}},{"line":193,"address":[],"length":0,"stats":{"Line":0}},{"line":203,"address":[6188354,6188384,6188128,6188610],"length":1,"stats":{"Line":2}},{"line":206,"address":[],"length":0,"stats":{"Line":2}},{"line":207,"address":[],"length":0,"stats":{"Line":2}},{"line":208,"address":[],"length":0,"stats":{"Line":2}},{"line":218,"address":[],"length":0,"stats":{"Line":4}},{"line":223,"address":[],"length":0,"stats":{"Line":4}},{"line":227,"address":[6190258,6191539,6192851,6188960,6190227,6191570,6190272,6191584,6192882],"length":1,"stats":{"Line":4}},{"line":229,"address":[6191619,6190307,6188995],"length":1,"stats":{"Line":5}},{"line":230,"address":[6190347,6191659,6189035],"length":1,"stats":{"Line":1}},{"line":231,"address":[],"length":0,"stats":{"Line":4}},{"line":232,"address":[7819872,7820054],"length":1,"stats":{"Line":1}},{"line":233,"address":[6193450,6193866,6193355,6193563,6193771,6193658],"length":1,"stats":{"Line":2}},{"line":234,"address":[6193377,6193585,6193793],"length":1,"stats":{"Line":1}},{"line":235,"address":[6193952,6194016,6193982,6194046,6194080,6194110],"length":1,"stats":{"Line":2}},{"line":237,"address":[7818333],"length":1,"stats":{"Line":1}},{"line":238,"address":[6194144,6194169,6194217,6194265,6194192,6194240],"length":1,"stats":{"Line":2}},{"line":239,"address":[],"length":0,"stats":{"Line":2}},{"line":240,"address":[7819568,7819590],"length":1,"stats":{"Line":2}},{"line":241,"address":[7818394],"length":1,"stats":{"Line":4}},{"line":242,"address":[],"length":0,"stats":{"Line":8}},{"line":243,"address":[],"length":0,"stats":{"Line":6}},{"line":244,"address":[6194990,6195118,6195088,6194960,6195024,6195054],"length":1,"stats":{"Line":6}},{"line":245,"address":[],"length":0,"stats":{"Line":6}},{"line":249,"address":[7818444],"length":1,"stats":{"Line":4}},{"line":250,"address":[],"length":0,"stats":{"Line":3}},{"line":251,"address":[7818481],"length":1,"stats":{"Line":2}},{"line":255,"address":[],"length":0,"stats":{"Line":4}},{"line":256,"address":[],"length":0,"stats":{"Line":1}},{"line":257,"address":[],"length":0,"stats":{"Line":2}},{"line":258,"address":[],"length":0,"stats":{"Line":3}},{"line":262,"address":[7818651],"length":1,"stats":{"Line":3}},{"line":263,"address":[],"length":0,"stats":{"Line":2}},{"line":264,"address":[7818691],"length":1,"stats":{"Line":1}},{"line":268,"address":[7818971,7819022],"length":1,"stats":{"Line":4}},{"line":269,"address":[7818778],"length":1,"stats":{"Line":2}},{"line":270,"address":[7818878],"length":1,"stats":{"Line":2}},{"line":271,"address":[7818951],"length":1,"stats":{"Line":2}},{"line":273,"address":[7819085],"length":1,"stats":{"Line":2}},{"line":274,"address":[7819159],"length":1,"stats":{"Line":1}},{"line":278,"address":[],"length":0,"stats":{"Line":2}},{"line":287,"address":[7793088,7794750,7795600,7796016,7793887,7796815,7793504,7794768,7795567,7794303,7794336,7795184,7793471,7793920,7795983,7794713,7795151,7796432,7796399],"length":1,"stats":{"Line":1}},{"line":292,"address":[],"length":0,"stats":{"Line":2}},{"line":295,"address":[],"length":0,"stats":{"Line":1}},{"line":296,"address":[],"length":0,"stats":{"Line":1}},{"line":299,"address":[],"length":0,"stats":{"Line":2}},{"line":300,"address":[],"length":0,"stats":{"Line":2}},{"line":311,"address":[],"length":0,"stats":{"Line":1}},{"line":312,"address":[],"length":0,"stats":{"Line":1}},{"line":313,"address":[],"length":0,"stats":{"Line":1}}],"covered":58,"coverable":71},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","cors.rs"],"content":"use gotham::{\n\thandler::HandlerFuture,\n\thelpers::http::response::create_empty_response,\n\thyper::{\n\t\theader::{\n\t\t\tHeaderMap, HeaderName, HeaderValue, ACCESS_CONTROL_ALLOW_CREDENTIALS,\n\t\t\tACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS,\n\t\t\tACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_MAX_AGE, ACCESS_CONTROL_REQUEST_HEADERS,\n\t\t\tACCESS_CONTROL_REQUEST_METHOD, ORIGIN, VARY\n\t\t},\n\t\tBody, Method, Response, StatusCode\n\t},\n\tmiddleware::Middleware,\n\tpipeline::PipelineHandleChain,\n\tprelude::*,\n\trouter::{builder::ExtendRouteMatcher, route::matcher::AccessControlRequestMethodMatcher},\n\tstate::State\n};\nuse std::{panic::RefUnwindSafe, pin::Pin};\n\n/// Specify the allowed origins of the request. It is up to the browser to check the validity of the\n/// origin. This, when sent to the browser, will indicate whether or not the request's origin was\n/// allowed to make the request.\n#[derive(Clone, Debug)]\npub enum Origin {\n\t/// Do not send any `Access-Control-Allow-Origin` headers.\n\tNone,\n\t/// Send `Access-Control-Allow-Origin: *`. Note that browser will not send credentials.\n\tStar,\n\t/// Set the `Access-Control-Allow-Origin` header to a single origin.\n\tSingle(String),\n\t/// Copy the `Origin` header into the `Access-Control-Allow-Origin` header.\n\tCopy\n}\n\nimpl Default for Origin {\n\tfn default() -> Self {\n\t\tSelf::None\n\t}\n}\n\nimpl Origin {\n\t/// Get the header value for the `Access-Control-Allow-Origin` header.\n\tfn header_value(&self, state: &State) -> Option {\n\t\tmatch self {\n\t\t\tSelf::None => None,\n\t\t\tSelf::Star => Some(\"*\".parse().unwrap()),\n\t\t\tSelf::Single(origin) => Some(origin.parse().unwrap()),\n\t\t\tSelf::Copy => {\n\t\t\t\tlet headers = HeaderMap::borrow_from(state);\n\t\t\t\theaders.get(ORIGIN).cloned()\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Returns true if the `Vary` header has to include `Origin`.\n\tfn varies(&self) -> bool {\n\t\tmatches!(self, Self::Copy)\n\t}\n}\n\n/// Specify the allowed headers of the request. It is up to the browser to check that only the allowed\n/// headers are sent with the request.\n#[derive(Clone, Debug)]\npub enum Headers {\n\t/// Do not send any `Access-Control-Allow-Headers` headers.\n\tNone,\n\t/// Set the `Access-Control-Allow-Headers` header to the following header list. If empty, this\n\t/// is treated as if it was [None].\n\tList(Vec),\n\t/// Copy the `Access-Control-Request-Headers` header into the `Access-Control-Allow-Header`\n\t/// header.\n\tCopy\n}\n\nimpl Default for Headers {\n\tfn default() -> Self {\n\t\tSelf::None\n\t}\n}\n\nimpl Headers {\n\t/// Get the header value for the `Access-Control-Allow-Headers` header.\n\tfn header_value(&self, state: &State) -> Option {\n\t\tmatch self {\n\t\t\tSelf::None => None,\n\t\t\tSelf::List(list) => Some(list.join(\",\").parse().unwrap()),\n\t\t\tSelf::Copy => {\n\t\t\t\tlet headers = HeaderMap::borrow_from(state);\n\t\t\t\theaders.get(ACCESS_CONTROL_REQUEST_HEADERS).cloned()\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Returns true if the `Vary` header has to include `Origin`.\n\tfn varies(&self) -> bool {\n\t\tmatches!(self, Self::Copy)\n\t}\n}\n\n/// This is the configuration that the CORS handler will follow. Its default configuration is basically\n/// not to touch any responses, resulting in the browser's default behaviour.\n///\n/// To change settings, you need to put this type into gotham's [State]:\n///\n/// ```rust,no_run\n/// # use gotham::{router::builder::*, pipeline::*, state::State};\n/// # use gotham_restful::{*, cors::Origin};\n/// # #[cfg_attr(feature = \"cargo-clippy\", allow(clippy::needless_doctest_main))]\n/// fn main() {\n/// \tlet cors = CorsConfig {\n/// \t\torigin: Origin::Star,\n/// \t\t..Default::default()\n/// \t};\n/// \tlet (chain, pipelines) = single_pipeline(new_pipeline().add(cors).build());\n/// \tgotham::start(\n/// \t\t\"127.0.0.1:8080\",\n/// \t\tbuild_router(chain, pipelines, |route| {\n/// \t\t\t// your routing logic\n/// \t\t})\n/// \t);\n/// }\n/// ```\n///\n/// This easy approach allows you to have one global cors configuration. If you prefer to have separate\n/// configurations for different scopes, you need to register the middleware inside your routing logic:\n///\n/// ```rust,no_run\n/// # use gotham::{router::builder::*, pipeline::*, state::State};\n/// # use gotham_restful::{*, cors::Origin};\n/// let pipelines = new_pipeline_set();\n///\n/// // The first cors configuration\n/// let cors_a = CorsConfig {\n/// \torigin: Origin::Star,\n/// \t..Default::default()\n/// };\n/// let (pipelines, chain_a) = pipelines.add(new_pipeline().add(cors_a).build());\n///\n/// // The second cors configuration\n/// let cors_b = CorsConfig {\n/// \torigin: Origin::Copy,\n/// \t..Default::default()\n/// };\n/// let (pipelines, chain_b) = pipelines.add(new_pipeline().add(cors_b).build());\n///\n/// let pipeline_set = finalize_pipeline_set(pipelines);\n/// gotham::start(\n/// \t\"127.0.0.1:8080\",\n/// \tbuild_router((), pipeline_set, |route| {\n/// \t\t// routing without any cors config\n/// \t\troute.with_pipeline_chain((chain_a, ()), |route| {\n/// \t\t\t// routing with cors config a\n/// \t\t});\n/// \t\troute.with_pipeline_chain((chain_b, ()), |route| {\n/// \t\t\t// routing with cors config b\n/// \t\t});\n/// \t})\n/// );\n/// ```\n#[derive(Clone, Debug, Default, NewMiddleware, StateData)]\npub struct CorsConfig {\n\t/// The allowed origins.\n\tpub origin: Origin,\n\t/// The allowed headers.\n\tpub headers: Headers,\n\t/// The amount of seconds that the preflight request can be cached.\n\tpub max_age: u64,\n\t/// Whether or not the request may be made with supplying credentials.\n\tpub credentials: bool\n}\n\nimpl Middleware for CorsConfig {\n\tfn call(self, mut state: State, chain: Chain) -> Pin>\n\twhere\n\t\tChain: FnOnce(State) -> Pin>\n\t{\n\t\tstate.put(self);\n\t\tchain(state)\n\t}\n}\n\n/// Handle CORS for a non-preflight request. This means manipulating the `res` HTTP headers so that\n/// the response is aligned with the `state`'s [CorsConfig].\n///\n/// If you are using the [Resource](crate::Resource) type (which is the recommended way), you'll never\n/// have to call this method. However, if you are writing your own handler method, you might want to\n/// call this after your request to add the required CORS headers.\n///\n/// For further information on CORS, read .\npub fn handle_cors(state: &State, res: &mut Response) {\n\tlet config = CorsConfig::try_borrow_from(state);\n\tif let Some(cfg) = config {\n\t\tlet headers = res.headers_mut();\n\n\t\t// non-preflight requests require the Access-Control-Allow-Origin header\n\t\tif let Some(header) = cfg.origin.header_value(state) {\n\t\t\theaders.insert(ACCESS_CONTROL_ALLOW_ORIGIN, header);\n\t\t}\n\n\t\t// if the origin is copied over, we should tell the browser by specifying the Vary header\n\t\tif cfg.origin.varies() {\n\t\t\tlet vary = headers\n\t\t\t\t.get(VARY)\n\t\t\t\t.map(|vary| format!(\"{},origin\", vary.to_str().unwrap()));\n\t\t\theaders.insert(VARY, vary.as_deref().unwrap_or(\"origin\").parse().unwrap());\n\t\t}\n\n\t\t// if we allow credentials, tell the browser\n\t\tif cfg.credentials {\n\t\t\theaders.insert(\n\t\t\t\tACCESS_CONTROL_ALLOW_CREDENTIALS,\n\t\t\t\tHeaderValue::from_static(\"true\")\n\t\t\t);\n\t\t}\n\t}\n}\n\n/// Add CORS routing for your path. This is required for handling preflight requests.\n///\n/// Example:\n///\n/// ```rust,no_run\n/// # use gotham::{hyper::{Body, Method, Response}, router::builder::*};\n/// # use gotham_restful::*;\n/// build_simple_router(|router| {\n/// \t// The handler that needs preflight handling\n/// \trouter.post(\"/foo\").to(|state| {\n/// \t\tlet mut res: Response = unimplemented!();\n/// \t\thandle_cors(&state, &mut res);\n/// \t\t(state, res)\n/// \t});\n/// \t// Add preflight handling\n/// \trouter.cors(\"/foo\", Method::POST);\n/// });\n/// ```\npub trait CorsRoute\nwhere\n\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\tP: RefUnwindSafe + Send + Sync + 'static\n{\n\t/// Handle a preflight request on `path` for `method`. To configure the behaviour, use\n\t/// [CorsConfig].\n\tfn cors(&mut self, path: &str, method: Method);\n}\n\npub(crate) fn cors_preflight_handler(state: State) -> (State, Response) {\n\tlet config = CorsConfig::try_borrow_from(&state);\n\n\t// prepare the response\n\tlet mut res = create_empty_response(&state, StatusCode::NO_CONTENT);\n\tlet headers = res.headers_mut();\n\tlet mut vary: Vec = Vec::new();\n\n\t// copy the request method over to the response\n\tlet method = HeaderMap::borrow_from(&state)\n\t\t.get(ACCESS_CONTROL_REQUEST_METHOD)\n\t\t.unwrap()\n\t\t.clone();\n\theaders.insert(ACCESS_CONTROL_ALLOW_METHODS, method);\n\tvary.push(ACCESS_CONTROL_REQUEST_METHOD);\n\n\tif let Some(cfg) = config {\n\t\t// if we allow any headers, copy them over\n\t\tif let Some(header) = cfg.headers.header_value(&state) {\n\t\t\theaders.insert(ACCESS_CONTROL_ALLOW_HEADERS, header);\n\t\t}\n\n\t\t// if the headers are copied over, we should tell the browser by specifying the Vary header\n\t\tif cfg.headers.varies() {\n\t\t\tvary.push(ACCESS_CONTROL_REQUEST_HEADERS);\n\t\t}\n\n\t\t// set the max age for the preflight cache\n\t\tif let Some(age) = config.map(|cfg| cfg.max_age) {\n\t\t\theaders.insert(ACCESS_CONTROL_MAX_AGE, age.into());\n\t\t}\n\t}\n\n\t// make sure the browser knows that this request was based on the method\n\theaders.insert(VARY, vary.join(\",\").parse().unwrap());\n\n\thandle_cors(&state, &mut res);\n\t(state, res)\n}\n\nimpl CorsRoute for D\nwhere\n\tD: DrawRoutes,\n\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\tP: RefUnwindSafe + Send + Sync + 'static\n{\n\tfn cors(&mut self, path: &str, method: Method) {\n\t\tlet matcher = AccessControlRequestMethodMatcher::new(method);\n\t\tself.options(path)\n\t\t\t.extend_route_matcher(matcher)\n\t\t\t.to(cors_preflight_handler);\n\t}\n}\n","traces":[{"line":37,"address":[8406592],"length":1,"stats":{"Line":3}},{"line":38,"address":[8136371],"length":1,"stats":{"Line":1}},{"line":44,"address":[6262848],"length":1,"stats":{"Line":3}},{"line":45,"address":[8406667],"length":1,"stats":{"Line":1}},{"line":46,"address":[7647724],"length":1,"stats":{"Line":3}},{"line":47,"address":[8136517,8136675],"length":1,"stats":{"Line":2}},{"line":48,"address":[8406924,8407095],"length":1,"stats":{"Line":2}},{"line":50,"address":[6263344],"length":1,"stats":{"Line":1}},{"line":51,"address":[8407137],"length":1,"stats":{"Line":1}},{"line":57,"address":[7888208],"length":1,"stats":{"Line":3}},{"line":58,"address":[7888213],"length":1,"stats":{"Line":1}},{"line":77,"address":[7888256],"length":1,"stats":{"Line":3}},{"line":78,"address":[7888259],"length":1,"stats":{"Line":1}},{"line":84,"address":[8137104,8137706],"length":1,"stats":{"Line":1}},{"line":85,"address":[7648363],"length":1,"stats":{"Line":3}},{"line":86,"address":[6263661],"length":1,"stats":{"Line":1}},{"line":87,"address":[7888411,7888614,7888856],"length":1,"stats":{"Line":2}},{"line":89,"address":[6230864],"length":1,"stats":{"Line":1}},{"line":90,"address":[7888513],"length":1,"stats":{"Line":1}},{"line":96,"address":[7888912],"length":1,"stats":{"Line":1}},{"line":97,"address":[6264181],"length":1,"stats":{"Line":1}},{"line":174,"address":[6291660,6291894,6291696,6292102,6291488,6292076,6291868,6291904,6291686],"length":1,"stats":{"Line":3}},{"line":178,"address":[6291946,6291530,6291738],"length":1,"stats":{"Line":3}},{"line":179,"address":[6291718,6291510,6291775,6291983,6291926,6291567],"length":1,"stats":{"Line":10}},{"line":191,"address":[8138951,8138983,8137776],"length":1,"stats":{"Line":5}},{"line":192,"address":[7740913],"length":1,"stats":{"Line":7}},{"line":193,"address":[7649060],"length":1,"stats":{"Line":5}},{"line":194,"address":[7889077],"length":1,"stats":{"Line":3}},{"line":197,"address":[8408146,8408206],"length":1,"stats":{"Line":2}},{"line":198,"address":[8408242],"length":1,"stats":{"Line":1}},{"line":202,"address":[6231722],"length":1,"stats":{"Line":1}},{"line":203,"address":[8138223],"length":1,"stats":{"Line":1}},{"line":205,"address":[8139348,8139445,8139062,8139024,8139225],"length":1,"stats":{"Line":5}},{"line":206,"address":[7741404,7742096,7741748],"length":1,"stats":{"Line":2}},{"line":210,"address":[8138203],"length":1,"stats":{"Line":3}},{"line":211,"address":[8409105],"length":1,"stats":{"Line":1}},{"line":212,"address":[8107385],"length":1,"stats":{"Line":1}},{"line":213,"address":[8409039],"length":1,"stats":{"Line":1}},{"line":247,"address":[8411878,8409680,8411124],"length":1,"stats":{"Line":1}},{"line":248,"address":[8139622,8139498],"length":1,"stats":{"Line":4}},{"line":251,"address":[8139630],"length":1,"stats":{"Line":3}},{"line":252,"address":[8409886,8409962],"length":1,"stats":{"Line":4}},{"line":253,"address":[7890930],"length":1,"stats":{"Line":1}},{"line":256,"address":[8139773,8139977,8139835],"length":1,"stats":{"Line":7}},{"line":260,"address":[7743096],"length":1,"stats":{"Line":1}},{"line":261,"address":[8140076],"length":1,"stats":{"Line":3}},{"line":263,"address":[7891317],"length":1,"stats":{"Line":1}},{"line":265,"address":[7743303,7743380],"length":1,"stats":{"Line":4}},{"line":266,"address":[8109077,8108985],"length":1,"stats":{"Line":2}},{"line":270,"address":[6266939,6266973],"length":1,"stats":{"Line":3}},{"line":271,"address":[6267004],"length":1,"stats":{"Line":1}},{"line":275,"address":[7651865,7652912,7652917,7651767],"length":1,"stats":{"Line":6}},{"line":276,"address":[6267116],"length":1,"stats":{"Line":1}},{"line":281,"address":[8141265,8141610,8140206,8140909],"length":1,"stats":{"Line":4}},{"line":283,"address":[7744510],"length":1,"stats":{"Line":3}},{"line":284,"address":[8110041],"length":1,"stats":{"Line":3}},{"line":293,"address":[],"length":0,"stats":{"Line":0}},{"line":294,"address":[],"length":0,"stats":{"Line":0}},{"line":295,"address":[],"length":0,"stats":{"Line":0}},{"line":296,"address":[],"length":0,"stats":{"Line":0}},{"line":297,"address":[],"length":0,"stats":{"Line":0}}],"covered":56,"coverable":61},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","endpoint.rs"],"content":"#[cfg(feature = \"openapi\")]\nuse crate::openapi::operation::OperationId;\nuse crate::{IntoResponse, RequestBody};\nuse futures_util::future::BoxFuture;\nuse gotham::{\n\textractor::{PathExtractor, QueryStringExtractor},\n\thyper::{Body, Method, Response},\n\trouter::response::StaticResponseExtender,\n\tstate::{State, StateData}\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::{OpenapiType, Visitor};\nuse serde::{Deserialize, Deserializer};\nuse std::borrow::Cow;\n\n/// A no-op extractor that can be used as a default type for [Endpoint::Placeholders] and\n/// [Endpoint::Params].\n#[derive(Debug, Clone, Copy)]\npub struct NoopExtractor;\n\nimpl<'de> Deserialize<'de> for NoopExtractor {\n\tfn deserialize>(_: D) -> Result {\n\t\tOk(Self)\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl OpenapiType for NoopExtractor {\n\tfn visit_type(visitor: &mut V) {\n\t\twarn!(\n\t\t\t\"You're asking for the OpenAPI Schema for gotham_restful::NoopExtractor. This is probably not what you want.\"\n\t\t);\n\t\tvisitor.visit_unit();\n\t}\n}\n\nimpl StateData for NoopExtractor {}\n\nimpl StaticResponseExtender for NoopExtractor {\n\ttype ResBody = Body;\n\tfn extend(_: &mut State, _: &mut Response) {}\n}\n\n// TODO: Specify default types once https://github.com/rust-lang/rust/issues/29661 lands.\n#[_private_openapi_trait(EndpointWithSchema)]\npub trait Endpoint {\n\t/// The HTTP Verb of this endpoint.\n\tfn http_method() -> Method;\n\t/// The URI that this endpoint listens on in gotham's format.\n\tfn uri() -> Cow<'static, str>;\n\n\t/// The verb used for generating an operation id if [Self::operation_id] returns [None].\n\t/// For example `read`, `read_all`, `create`, `update` etc.\n\t#[openapi_only]\n\tfn operation_verb() -> Option<&'static str>;\n\n\t/// The output type that provides the response.\n\t#[openapi_bound(Output: crate::ResponseSchema)]\n\ttype Output: IntoResponse + Send;\n\n\t/// Returns `true` _iff_ the URI contains placeholders. `false` by default.\n\tfn has_placeholders() -> bool {\n\t\tfalse\n\t}\n\t/// The type that parses the URI placeholders. Use [NoopExtractor] if `has_placeholders()`\n\t/// returns `false`.\n\t#[openapi_bound(Placeholders: OpenapiType)]\n\ttype Placeholders: PathExtractor + Clone + Sync;\n\n\t/// Returns `true` _iff_ the request parameters should be parsed. `false` by default.\n\tfn needs_params() -> bool {\n\t\tfalse\n\t}\n\t/// The type that parses the request parameters. Use [NoopExtractor] if `needs_params()`\n\t/// returns `false`.\n\t#[openapi_bound(Params: OpenapiType)]\n\ttype Params: QueryStringExtractor + Clone + Sync;\n\n\t/// Returns `true` _iff_ the request body should be parsed. `false` by default.\n\tfn needs_body() -> bool {\n\t\tfalse\n\t}\n\t/// The type to parse the body into. Use `()` if `needs_body()` returns `false`.\n\ttype Body: RequestBody + Send;\n\n\t/// Returns `true` if the request wants to know the auth status of the client. `false` by default.\n\tfn wants_auth() -> bool {\n\t\tfalse\n\t}\n\n\t/// Replace the automatically generated operation id with a custom one. Only relevant for the\n\t/// OpenAPI Specification.\n\t#[openapi_only]\n\tfn operation_id() -> OperationId {\n\t\tOperationId::FullAuto\n\t}\n\n\t/// Add a description to the openapi specification. Usually taken from the rustdoc comment\n\t/// when using the proc macro.\n\t#[openapi_only]\n\tfn description() -> Option {\n\t\tNone\n\t}\n\n\t/// The handler for this endpoint.\n\tfn handle(\n\t\tstate: &mut State,\n\t\tplaceholders: Self::Placeholders,\n\t\tparams: Self::Params,\n\t\tbody: Option\n\t) -> BoxFuture<'_, Self::Output>;\n}\n\n#[cfg(feature = \"openapi\")]\nimpl Endpoint for E {\n\tfn http_method() -> Method {\n\t\tE::http_method()\n\t}\n\tfn uri() -> Cow<'static, str> {\n\t\tE::uri()\n\t}\n\n\ttype Output = E::Output;\n\n\tfn has_placeholders() -> bool {\n\t\tE::has_placeholders()\n\t}\n\ttype Placeholders = E::Placeholders;\n\n\tfn needs_params() -> bool {\n\t\tE::needs_params()\n\t}\n\ttype Params = E::Params;\n\n\tfn needs_body() -> bool {\n\t\tE::needs_body()\n\t}\n\ttype Body = E::Body;\n\n\tfn wants_auth() -> bool {\n\t\tE::wants_auth()\n\t}\n\n\tfn handle<'a>(\n\t\tstate: &'a mut State,\n\t\tplaceholders: Self::Placeholders,\n\t\tparams: Self::Params,\n\t\tbody: Option\n\t) -> BoxFuture<'a, Self::Output> {\n\t\tE::handle(state, placeholders, params, body)\n\t}\n}\n","traces":[{"line":22,"address":[6943088,6943072],"length":1,"stats":{"Line":10}},{"line":23,"address":[6527699,6527737],"length":1,"stats":{"Line":10}},{"line":29,"address":[8065008],"length":1,"stats":{"Line":0}},{"line":30,"address":[6189969,6190047],"length":1,"stats":{"Line":0}},{"line":31,"address":[],"length":0,"stats":{"Line":0}},{"line":33,"address":[7847637],"length":1,"stats":{"Line":0}},{"line":41,"address":[7699744,7699754],"length":1,"stats":{"Line":0}},{"line":62,"address":[],"length":0,"stats":{"Line":0}},{"line":63,"address":[],"length":0,"stats":{"Line":0}},{"line":71,"address":[],"length":0,"stats":{"Line":0}},{"line":72,"address":[],"length":0,"stats":{"Line":0}},{"line":80,"address":[],"length":0,"stats":{"Line":0}},{"line":81,"address":[],"length":0,"stats":{"Line":0}},{"line":87,"address":[],"length":0,"stats":{"Line":0}},{"line":88,"address":[],"length":0,"stats":{"Line":0}},{"line":94,"address":[],"length":0,"stats":{"Line":0}},{"line":95,"address":[],"length":0,"stats":{"Line":0}},{"line":101,"address":[],"length":0,"stats":{"Line":7}},{"line":102,"address":[7296611,7296643,7296515,7296483,7296707,7296547,7296675,7296579],"length":1,"stats":{"Line":7}},{"line":116,"address":[],"length":0,"stats":{"Line":30}},{"line":117,"address":[],"length":0,"stats":{"Line":30}},{"line":119,"address":[7392144,7392208,7392112,7392240,7392272,7392176,7392080],"length":1,"stats":{"Line":30}},{"line":120,"address":[7003128,7003160],"length":1,"stats":{"Line":30}},{"line":125,"address":[],"length":0,"stats":{"Line":0}},{"line":126,"address":[],"length":0,"stats":{"Line":0}},{"line":130,"address":[],"length":0,"stats":{"Line":0}},{"line":131,"address":[],"length":0,"stats":{"Line":0}},{"line":135,"address":[7003024,7003040],"length":1,"stats":{"Line":21}},{"line":136,"address":[7003025,7003041],"length":1,"stats":{"Line":21}},{"line":140,"address":[],"length":0,"stats":{"Line":0}},{"line":141,"address":[],"length":0,"stats":{"Line":0}},{"line":144,"address":[7003184,7003232],"length":1,"stats":{"Line":21}},{"line":150,"address":[],"length":0,"stats":{"Line":21}}],"covered":12,"coverable":33},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","lib.rs"],"content":"#![warn(missing_debug_implementations, rust_2018_idioms, unreachable_pub)]\n#![forbid(unsafe_code)]\n// deny warnings in CI\n#![cfg_attr(gotham_restful_deny_warnings, deny(warnings))]\n// clippy doesn't like our code style\n#![allow(clippy::tabs_in_doc_comments)]\n// intra-doc links only fully work when OpenAPI is enabled\n#![cfg_attr(feature = \"openapi\", deny(rustdoc::broken_intra_doc_links))]\n#![cfg_attr(not(feature = \"openapi\"), allow(rustdoc::broken_intra_doc_links))]\n\n//! This crate is an extension to the popular [gotham web framework][gotham] for Rust. It allows you to\n//! create resources with assigned endpoints that aim to be a more convenient way of creating handlers\n//! for requests.\n//!\n//! # Features\n//!\n//! - Automatically parse **JSON** request and produce response bodies\n//! - Allow using **raw** request and response bodies\n//! - Convenient **macros** to create responses that can be registered with gotham's router\n//! - Auto-Generate an **OpenAPI** specification for your API\n//! - Manage **CORS** headers so you don't have to\n//! - Manage **Authentication** with JWT\n//! - Integrate diesel connection pools for easy **database** integration\n//!\n//! # Safety\n//!\n//! This crate is just as safe as you'd expect from anything written in safe Rust - and\n//! `#![forbid(unsafe_code)]` ensures that no unsafe was used.\n//!\n//! # Endpoints\n//!\n//! There are a set of pre-defined endpoints that should cover the majority of REST APIs. However,\n//! it is also possible to define your own endpoints.\n//!\n//! ## Pre-defined Endpoints\n//!\n//! Assuming you assign `/foobar` to your resource, the following pre-defined endpoints exist:\n//!\n//! | Endpoint Name | Required Arguments | HTTP Verb | HTTP Path |\n//! | ------------- | ------------------ | --------- | -------------- |\n//! | read_all | | GET | /foobar |\n//! | read | id | GET | /foobar/:id |\n//! | search | query | GET | /foobar/search |\n//! | create | body | POST | /foobar |\n//! | update_all | body | PUT | /foobar |\n//! | update | id, body | PUT | /foobar/:id |\n//! | delete_all | | DELETE | /foobar |\n//! | delete | id | DELETE | /foobar/:id |\n//!\n//! Each of those endpoints has a macro that creates the neccessary boilerplate for the Resource. A\n//! simple example looks like this:\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # use gotham::router::builder::*;\n//! # use gotham_restful::*;\n//! # use serde::{Deserialize, Serialize};\n//! /// Our RESTful resource.\n//! #[derive(Resource)]\n//! #[resource(read)]\n//! struct FooResource;\n//!\n//! /// The return type of the foo read endpoint.\n//! #[derive(Serialize)]\n//! # #[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n//! struct Foo {\n//! \tid: u64\n//! }\n//!\n//! /// The foo read endpoint.\n//! #[read]\n//! fn read(id: u64) -> Success {\n//! \tFoo { id }.into()\n//! }\n//! # fn main() {\n//! # \tgotham::start(\"127.0.0.1:8080\", build_simple_router(|route| {\n//! # \t\troute.resource::(\"foo\");\n//! # \t})).expect(\"Failed to start gotham\");\n//! # }\n//! ```\n//!\n//! ## Custom Endpoints\n//!\n//! Defining custom endpoints is done with the `#[endpoint]` macro. The syntax is similar to that\n//! of the pre-defined endpoints, but you need to give it more context:\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # use gotham::{router::build_simple_router, prelude::*};\n//! # use gotham_restful::*;\n//! # use serde::{Deserialize, Serialize};\n//! use gotham_restful::gotham::hyper::Method;\n//!\n//! #[derive(Resource)]\n//! #[resource(custom_endpoint)]\n//! struct CustomResource;\n//!\n//! /// This type is used to parse path parameters.\n//! #[derive(Clone, Deserialize, StateData, StaticResponseExtender)]\n//! # #[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n//! struct CustomPath {\n//! \tname: String\n//! }\n//!\n//! #[endpoint(\n//! \turi = \"custom/:name/read\",\n//! \tmethod = \"Method::GET\",\n//! \tparams = false,\n//! \tbody = false\n//! )]\n//! fn custom_endpoint(path: CustomPath) -> Success {\n//! \tpath.name.into()\n//! }\n//! # fn main() {\n//! # \tgotham::start(\"127.0.0.1:8080\", build_simple_router(|route| {\n//! # \t\troute.resource::(\"custom\");\n//! # \t})).expect(\"Failed to start gotham\");\n//! # }\n//! ```\n//!\n//! # Arguments\n//!\n//! Some endpoints require arguments. Those should be\n//! * **id** Should be a deserializable json-primitive like [`i64`] or [`String`].\n//! * **body** Should be any deserializable object, or any type implementing [`RequestBody`].\n//! * **query** Should be any deserializable object whose variables are json-primitives. It will\n//! however not be parsed from json, but from HTTP GET parameters like in `search?id=1`. The\n//! type needs to implement [`QueryStringExtractor`](gotham::extractor::QueryStringExtractor).\n//!\n//! Additionally, all handlers may take a reference to gotham's [`State`]. Please note that for async\n//! handlers, it needs to be a mutable reference until rustc's lifetime checks across await bounds\n//! improve.\n//!\n//! # Uploads and Downloads\n//!\n//! By default, every request body is parsed from json, and every respone is converted to json using\n//! [serde_json]. However, you may also use raw bodies. This is an example where the request body\n//! is simply returned as the response again, no json parsing involved:\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # use gotham::{mime::{self, Mime}, router::builder::*};\n//! # use gotham_restful::*;\n//! # use serde::{Deserialize, Serialize};\n//! #[derive(Resource)]\n//! #[resource(create)]\n//! struct ImageResource;\n//!\n//! #[derive(FromBody, RequestBody)]\n//! #[supported_types(mime::IMAGE_GIF, mime::IMAGE_JPEG, mime::IMAGE_PNG)]\n//! struct RawImage {\n//! \tcontent: Vec,\n//! \tcontent_type: Mime\n//! }\n//!\n//! #[create]\n//! fn create(body: RawImage) -> Raw> {\n//! \tRaw::new(body.content, body.content_type)\n//! }\n//! # fn main() {\n//! # \tgotham::start(\"127.0.0.1:8080\", build_simple_router(|route| {\n//! # \t\troute.resource::(\"image\");\n//! # \t})).expect(\"Failed to start gotham\");\n//! # }\n//! ```\n//!\n//! # Custom HTTP Headers\n//!\n//! You can read request headers from the state as you would in any other gotham handler, and specify\n//! custom response headers using [Response::header].\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # use gotham::hyper::header::{ACCEPT, HeaderMap, VARY};\n//! # use gotham::{router::builder::*, state::State};\n//! # use gotham_restful::*;\n//! #[derive(Resource)]\n//! #[resource(read_all)]\n//! struct FooResource;\n//!\n//! #[read_all]\n//! async fn read_all(state: &mut State) -> NoContent {\n//! \tlet headers: &HeaderMap = state.borrow();\n//! \tlet accept = &headers[ACCEPT];\n//! # drop(accept);\n//!\n//! \tlet mut res = NoContent::default();\n//! \tres.header(VARY, \"accept\".parse().unwrap());\n//! \tres\n//! }\n//! # fn main() {\n//! # \tgotham::start(\"127.0.0.1:8080\", build_simple_router(|route| {\n//! # \t\troute.resource::(\"foo\");\n//! # \t})).expect(\"Failed to start gotham\");\n//! # }\n//! ```\n//!\n//! # Features\n//!\n//! To make life easier for common use-cases, this create offers a few features that might be helpful\n//! when you implement your web server. The complete feature list is\n//! - [`auth`](#authentication-feature) Advanced JWT middleware\n//! - [`cors`](#cors-feature) CORS handling for all endpoint handlers\n//! - [`database`](#database-feature) diesel middleware support\n//! - `errorlog` log errors returned from endpoint handlers\n//! - `full` enables all features except `without-openapi`\n//! - [`openapi`](#openapi-feature) router additions to generate an openapi spec\n//! - `without-openapi` (**default**) disables `openapi` support.\n//!\n//! ## Authentication Feature\n//!\n//! In order to enable authentication support, enable the `auth` feature gate. This allows you to\n//! register a middleware that can automatically check for the existence of an JWT authentication\n//! token. Besides being supported by the endpoint macros, it supports to lookup the required JWT secret\n//! with the JWT data, hence you can use several JWT secrets and decide on the fly which secret to use.\n//! None of this is currently supported by gotham's own JWT middleware.\n//!\n//! A simple example that uses only a single secret looks like this:\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # #[cfg(feature = \"auth\")]\n//! # mod auth_feature_enabled {\n//! # use gotham::{router::builder::*, pipeline::*, state::State};\n//! # use gotham_restful::*;\n//! # use serde::{Deserialize, Serialize};\n//! #[derive(Resource)]\n//! #[resource(read)]\n//! struct SecretResource;\n//!\n//! #[derive(Serialize)]\n//! # #[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n//! struct Secret {\n//! \tid: u64,\n//! \tintended_for: String\n//! }\n//!\n//! #[derive(Deserialize, Clone)]\n//! struct AuthData {\n//! \tsub: String,\n//! \texp: u64\n//! }\n//!\n//! #[read]\n//! fn read(auth: AuthStatus, id: u64) -> AuthSuccess {\n//! \tlet intended_for = auth.ok()?.sub;\n//! \tOk(Secret { id, intended_for })\n//! }\n//!\n//! fn main() {\n//! \tlet auth: AuthMiddleware = AuthMiddleware::new(\n//! \t\tAuthSource::AuthorizationHeader,\n//! \t\tAuthValidation::default(),\n//! \t\tStaticAuthHandler::from_array(b\"zlBsA2QXnkmpe0QTh8uCvtAEa4j33YAc\")\n//! \t);\n//! \tlet (chain, pipelines) = single_pipeline(new_pipeline().add(auth).build());\n//! \tgotham::start(\n//! \t\t\"127.0.0.1:8080\",\n//! \t\tbuild_router(chain, pipelines, |route| {\n//! \t\t\troute.resource::(\"secret\");\n//! \t\t})\n//! \t)\n//! \t.expect(\"Failed to start gotham\");\n//! }\n//! # }\n//! ```\n//!\n//! ## CORS Feature\n//!\n//! The cors feature allows an easy usage of this web server from other origins. By default, only\n//! the `Access-Control-Allow-Methods` header is touched. To change the behaviour, add your desired\n//! configuration as a middleware.\n//!\n//! A simple example that allows authentication from every origin (note that `*` always disallows\n//! authentication), and every content type, looks like this:\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # #[cfg(feature = \"cors\")]\n//! # mod cors_feature_enabled {\n//! # use gotham::{hyper::header::*, router::builder::*, pipeline::*, state::State};\n//! # use gotham_restful::{*, cors::*};\n//! # use serde::{Deserialize, Serialize};\n//! #[derive(Resource)]\n//! #[resource(read_all)]\n//! struct FooResource;\n//!\n//! #[read_all]\n//! fn read_all() {\n//! \t// your handler\n//! }\n//!\n//! fn main() {\n//! \tlet cors = CorsConfig {\n//! \t\torigin: Origin::Copy,\n//! \t\theaders: Headers::List(vec![CONTENT_TYPE]),\n//! \t\tmax_age: 0,\n//! \t\tcredentials: true\n//! \t};\n//! \tlet (chain, pipelines) = single_pipeline(new_pipeline().add(cors).build());\n//! \tgotham::start(\n//! \t\t\"127.0.0.1:8080\",\n//! \t\tbuild_router(chain, pipelines, |route| {\n//! \t\t\troute.resource::(\"foo\");\n//! \t\t})\n//! \t)\n//! \t.expect(\"Failed to start gotham\");\n//! }\n//! # }\n//! ```\n//!\n//! The cors feature can also be used for non-resource handlers. Take a look at [`CorsRoute`]\n//! for an example.\n//!\n//! ## Database Feature\n//!\n//! The database feature allows an easy integration of [diesel] into your handler functions. Please\n//! note however that due to the way gotham's diesel middleware implementation, it is not possible\n//! to run async code while holding a database connection. If you need to combine async and database,\n//! you'll need to borrow the connection from the [`State`] yourself and return a boxed future.\n//!\n//! A simple non-async example looks like this:\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate diesel;\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # #[cfg(feature = \"database\")]\n//! # mod database_feature_enabled {\n//! # use diesel::{table, PgConnection, QueryResult, RunQueryDsl};\n//! # use gotham::{router::builder::*, pipeline::*, state::State};\n//! # use gotham_middleware_diesel::DieselMiddleware;\n//! # use gotham_restful::*;\n//! # use serde::{Deserialize, Serialize};\n//! # use std::env;\n//! # table! {\n//! # foo (id) {\n//! # id -> Int8,\n//! # value -> Text,\n//! # }\n//! # }\n//! #[derive(Resource)]\n//! #[resource(read_all)]\n//! struct FooResource;\n//!\n//! #[derive(Queryable, Serialize)]\n//! # #[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n//! struct Foo {\n//! \tid: i64,\n//! \tvalue: String\n//! }\n//!\n//! #[read_all]\n//! fn read_all(conn: &mut PgConnection) -> QueryResult> {\n//! \tfoo::table.load(conn)\n//! }\n//!\n//! type Repo = gotham_middleware_diesel::Repo;\n//!\n//! fn main() {\n//! \tlet repo = Repo::new(&env::var(\"DATABASE_URL\").unwrap());\n//! \tlet diesel = DieselMiddleware::new(repo);\n//!\n//! \tlet (chain, pipelines) = single_pipeline(new_pipeline().add(diesel).build());\n//! \tgotham::start(\n//! \t\t\"127.0.0.1:8080\",\n//! \t\tbuild_router(chain, pipelines, |route| {\n//! \t\t\troute.resource::(\"foo\");\n//! \t\t})\n//! \t)\n//! \t.expect(\"Failed to start gotham\");\n//! }\n//! # }\n//! ```\n//!\n//! ## OpenAPI Feature\n//!\n//! The OpenAPI feature is probably the most powerful one of this crate. Definitely read this section\n//! carefully both as a binary as well as a library author to avoid unwanted suprises.\n//!\n//! In order to automatically create an openapi specification, gotham-restful needs knowledge over\n//! all routes and the types returned. `serde` does a great job at serialization but doesn't give\n//! enough type information, so all types used in the router need to implement\n//! [`OpenapiType`](openapi_type::OpenapiType). This can be derived for almoust any type and there\n//! should be no need to implement it manually. A simple example looks like this:\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # #[cfg(feature = \"openapi\")]\n//! # mod openapi_feature_enabled {\n//! # use gotham::{router::builder::*, state::State};\n//! # use gotham_restful::*;\n//! # use openapi_type::OpenapiType;\n//! # use serde::{Deserialize, Serialize};\n//! #[derive(Resource)]\n//! #[resource(read_all)]\n//! struct FooResource;\n//!\n//! #[derive(OpenapiType, Serialize)]\n//! struct Foo {\n//! \tbar: String\n//! }\n//!\n//! #[read_all]\n//! fn read_all() -> Success {\n//! \tFoo {\n//! \t\tbar: \"Hello World\".to_owned()\n//! \t}\n//! \t.into()\n//! }\n//!\n//! fn main() {\n//! \tgotham::start(\n//! \t\t\"127.0.0.1:8080\",\n//! \t\tbuild_simple_router(|route| {\n//! \t\t\tlet info = OpenapiInfo {\n//! \t\t\t\ttitle: \"My Foo API\".to_owned(),\n//! \t\t\t\tversion: \"0.1.0\".to_owned(),\n//! \t\t\t\turls: vec![\"https://example.org/foo/api/v1\".to_owned()]\n//! \t\t\t};\n//! \t\t\troute.with_openapi(info, |mut route| {\n//! \t\t\t\troute.resource::(\"foo\");\n//! \t\t\t\troute.openapi_spec(\"openapi\");\n//! \t\t\t\troute.openapi_doc(\"/\");\n//! \t\t\t});\n//! \t\t})\n//! \t)\n//! \t.expect(\"Failed to start gotham\");\n//! }\n//! # }\n//! ```\n//!\n//! Above example adds the resource as before, but adds two other endpoints as well: `/openapi` and `/`.\n//! The first one will return the generated openapi specification in JSON format, allowing you to easily\n//! generate clients in different languages without worying to exactly replicate your api in each of those\n//! languages. The second one will return documentation in HTML format, so you can easily view your\n//! api and share it with other people.\n//!\n//! ### Gotchas\n//!\n//! The openapi feature has some gotchas you should be aware of.\n//!\n//! - The name of a struct is used as a \"link\" in the openapi specification. Therefore, if you have two\n//! structs with the same name in your project, the openapi specification will be invalid as only one\n//! of the two will make it into the spec.\n//! - By default, the `without-openapi` feature of this crate is enabled. Disabling it in favour of the\n//! `openapi` feature will add additional type bounds and method requirements to some of the traits and\n//! \ttypes in this crate, for example instead of [`Endpoint`] you now have to implement\n//! \t[`EndpointWithSchema`]. This means that some code might only compile on either feature, but not\n//! on both. If you are writing a library that uses gotham-restful, it is strongly recommended to pass\n//! \tboth features through and conditionally enable the openapi code, like this:\n//!\n//! ```rust\n//! # #[macro_use] extern crate gotham_restful;\n//! # use serde::{Deserialize, Serialize};\n//! #[derive(Deserialize, Serialize)]\n//! #[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n//! struct Foo;\n//! ```\n//!\n//! [diesel]: https://diesel.rs/\n//! [`State`]: gotham::state::State\n\n#[cfg(all(feature = \"openapi\", feature = \"without-openapi\"))]\ncompile_error!(\"The 'openapi' and 'without-openapi' features cannot be combined\");\n\n#[cfg(all(not(feature = \"openapi\"), not(feature = \"without-openapi\")))]\ncompile_error!(\"Either the 'openapi' or 'without-openapi' feature needs to be enabled\");\n\n// weird proc macro issue\nextern crate self as gotham_restful;\n\n#[macro_use]\nextern crate gotham_restful_derive;\n#[macro_use]\nextern crate log;\n#[macro_use]\nextern crate serde;\n\n#[cfg(test)]\n#[macro_use]\nextern crate pretty_assertions;\n\n#[doc(no_inline)]\npub use gotham;\n\npub use gotham_restful_derive::*;\n\n/// Not public API\n#[doc(hidden)]\npub mod private {\n\tpub use crate::routing::PathExtractor as IdPlaceholder;\n\tpub use futures_util::future::{BoxFuture, FutureExt};\n\t#[cfg(feature = \"database\")]\n\tpub use gotham_middleware_diesel::Repo;\n\t#[cfg(feature = \"openapi\")]\n\tpub use openapi_type::{OpenapiSchema, OpenapiType, Visitor};\n\tpub use serde_json;\n\n\t#[cfg(feature = \"openapi\")]\n\tuse gotham::hyper::StatusCode;\n\t#[cfg(feature = \"auth\")]\n\tuse gotham::state::{FromState, State};\n\n\t/// This method is used by the endpoint macro to generate a good error message\n\t/// when the used AuthData type does not implement Clone.\n\t#[cfg(feature = \"auth\")]\n\t#[inline]\n\tpub fn clone_from_state(state: &State) -> T\n\twhere\n\t\tT: FromState + Clone\n\t{\n\t\tT::borrow_from(state).clone()\n\t}\n\n\t/// This trait is used by the endpoint macro to generate a good error message\n\t/// when the schema function has the wrong signature.\n\t#[cfg(feature = \"openapi\")]\n\tpub trait CustomSchema {\n\t\tfn schema(self, code: StatusCode) -> OpenapiSchema;\n\t}\n\n\t#[cfg(feature = \"openapi\")]\n\timpl CustomSchema for F\n\twhere\n\t\tF: FnOnce(StatusCode) -> OpenapiSchema\n\t{\n\t\t#[inline]\n\t\tfn schema(self, code: StatusCode) -> OpenapiSchema {\n\t\t\tself(code)\n\t\t}\n\t}\n\n\t/// This trait is used by the endpoint macro to generate a good error message\n\t/// when the status_codes function has the wrong signature.\n\t#[cfg(feature = \"openapi\")]\n\tpub trait CustomStatusCodes {\n\t\tfn status_codes(self) -> Vec;\n\t}\n\n\t#[cfg(feature = \"openapi\")]\n\timpl CustomStatusCodes for F\n\twhere\n\t\tF: FnOnce() -> Vec\n\t{\n\t\t#[inline]\n\t\tfn status_codes(self) -> Vec {\n\t\t\tself()\n\t\t}\n\t}\n}\n\n#[cfg(feature = \"auth\")]\nmod auth;\n#[cfg(feature = \"auth\")]\npub use auth::{\n\tAuthHandler, AuthMiddleware, AuthSource, AuthStatus, AuthValidation, StaticAuthHandler\n};\n\n#[cfg(feature = \"cors\")]\npub mod cors;\n#[cfg(feature = \"cors\")]\npub use cors::{handle_cors, CorsConfig, CorsRoute};\n\n#[cfg(feature = \"openapi\")]\nmod openapi;\n#[cfg(feature = \"openapi\")]\npub use openapi::{builder::OpenapiInfo, operation::OperationId, router::GetOpenapi};\n\nmod endpoint;\n#[cfg(feature = \"openapi\")]\npub use endpoint::EndpointWithSchema;\npub use endpoint::{Endpoint, NoopExtractor};\n\nmod response;\npub use response::{\n\tAuthError, AuthErrorOrOther, AuthResult, AuthSuccess, IntoResponse, IntoResponseError,\n\tNoContent, Raw, Redirect, Response, Success\n};\n#[cfg(feature = \"openapi\")]\npub use response::{IntoResponseWithSchema, ResponseSchema};\n\nmod routing;\npub use routing::{DrawResourceRoutes, DrawResources};\n#[cfg(feature = \"openapi\")]\npub use routing::{DrawResourceRoutesWithSchema, DrawResourcesWithSchema, WithOpenapi};\n\nmod types;\npub use types::{FromBody, RequestBody, ResponseBody};\n\n/// This trait must be implemented for every resource. It allows you to register the different\n/// endpoints that can be handled by this resource to be registered with the underlying router.\n///\n/// It is not recommended to implement this yourself, just use `#[derive(Resource)]`.\n#[_private_openapi_trait(ResourceWithSchema)]\npub trait Resource {\n\t/// Register all methods handled by this resource with the underlying router.\n\t#[openapi_bound(D: crate::DrawResourceRoutesWithSchema)]\n\t#[non_openapi_bound(D: crate::DrawResourceRoutes)]\n\tfn setup(route: D);\n}\n","traces":[{"line":463,"address":[10990634,10990489,10990621],"length":1,"stats":{"Line":0}},{"line":464,"address":[5131090,5131163,5131137],"length":1,"stats":{"Line":0}},{"line":466,"address":[15678501],"length":1,"stats":{"Line":2}},{"line":467,"address":[14193131],"length":1,"stats":{"Line":2}},{"line":508,"address":[16109882],"length":1,"stats":{"Line":0}},{"line":512,"address":[7919933],"length":1,"stats":{"Line":0}},{"line":518,"address":[5130529,5129656,5130516],"length":1,"stats":{"Line":0}},{"line":523,"address":[7920382,7920272],"length":1,"stats":{"Line":0}},{"line":528,"address":[7452103],"length":1,"stats":{"Line":9}},{"line":529,"address":[7920506],"length":1,"stats":{"Line":1}},{"line":536,"address":[7889339],"length":1,"stats":{"Line":6}},{"line":541,"address":[16635776],"length":1,"stats":{"Line":0}},{"line":546,"address":[10543440,10543408],"length":1,"stats":{"Line":15}},{"line":547,"address":[14732925],"length":1,"stats":{"Line":1}},{"line":595,"address":[5133522],"length":1,"stats":{"Line":0}}],"covered":7,"coverable":15},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","openapi","builder.rs"],"content":"use openapi_type::{\n\tindexmap::IndexMap,\n\topenapiv3::{\n\t\tself, Components, OpenAPI, PathItem, ReferenceOr,\n\t\tReferenceOr::{Item, Reference},\n\t\tSchema, Server\n\t},\n\tOpenapiSchema\n};\nuse parking_lot::RwLock;\nuse std::sync::Arc;\n\n#[derive(Clone, Debug)]\npub struct OpenapiInfo {\n\tpub title: String,\n\tpub version: String,\n\tpub urls: Vec\n}\n\n#[derive(Clone, Debug)]\npub(crate) struct OpenapiBuilder {\n\tpub(crate) openapi: Arc>\n}\n\nimpl OpenapiBuilder {\n\tpub(crate) fn new(info: OpenapiInfo) -> Self {\n\t\tSelf {\n\t\t\topenapi: Arc::new(RwLock::new(OpenAPI {\n\t\t\t\topenapi: \"3.0.2\".to_string(),\n\t\t\t\tinfo: openapiv3::Info {\n\t\t\t\t\ttitle: info.title,\n\t\t\t\t\tversion: info.version,\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tservers: info\n\t\t\t\t\t.urls\n\t\t\t\t\t.into_iter()\n\t\t\t\t\t.map(|url| Server {\n\t\t\t\t\t\turl,\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t})\n\t\t\t\t\t.collect(),\n\t\t\t\t..Default::default()\n\t\t\t}))\n\t\t}\n\t}\n\n\t/// Remove path from the OpenAPI spec, or return an empty one if not included. This is handy if you need to\n\t/// modify the path and add it back after the modification\n\tpub(crate) fn remove_path(&mut self, path: &str) -> PathItem {\n\t\tlet mut openapi = self.openapi.write();\n\t\tmatch openapi.paths.paths.swap_remove(path) {\n\t\t\tSome(Item(item)) => item,\n\t\t\t_ => PathItem::default()\n\t\t}\n\t}\n\n\tpub(crate) fn add_path(&mut self, path: Path, item: PathItem) {\n\t\tlet mut openapi = self.openapi.write();\n\t\topenapi.paths.paths.insert(path.to_string(), Item(item));\n\t}\n\n\tfn add_schema_impl(&mut self, name: String, mut schema: OpenapiSchema) {\n\t\tself.add_schema_dependencies(&mut schema.dependencies);\n\n\t\tlet mut openapi = self.openapi.write();\n\t\tmatch &mut openapi.components {\n\t\t\tSome(comp) => {\n\t\t\t\tcomp.schemas.insert(name, Item(schema.schema));\n\t\t\t},\n\t\t\tNone => {\n\t\t\t\tlet mut comp = Components::default();\n\t\t\t\tcomp.schemas.insert(name, Item(schema.schema));\n\t\t\t\topenapi.components = Some(comp);\n\t\t\t}\n\t\t};\n\t}\n\n\tfn add_schema_dependencies(&mut self, dependencies: &mut IndexMap) {\n\t\tlet keys: Vec = dependencies.keys().map(|k| k.to_string()).collect();\n\t\tfor dep in keys {\n\t\t\tlet dep_schema = dependencies.swap_remove(&dep);\n\t\t\tif let Some(dep_schema) = dep_schema {\n\t\t\t\tself.add_schema_impl(dep, dep_schema);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub(crate) fn add_schema(&mut self, mut schema: OpenapiSchema) -> ReferenceOr {\n\t\tmatch schema.schema.schema_data.title.clone() {\n\t\t\tSome(name) => {\n\t\t\t\tlet reference = Reference {\n\t\t\t\t\treference: format!(\"#/components/schemas/{name}\")\n\t\t\t\t};\n\t\t\t\tself.add_schema_impl(name, schema);\n\t\t\t\treference\n\t\t\t},\n\t\t\tNone => {\n\t\t\t\tself.add_schema_dependencies(&mut schema.dependencies);\n\t\t\t\tItem(schema.schema)\n\t\t\t}\n\t\t}\n\t}\n}\n\n#[cfg(test)]\n#[allow(dead_code)]\nmod test {\n\tuse super::*;\n\tuse openapi_type::OpenapiType;\n\n\t#[derive(OpenapiType)]\n\tstruct Message {\n\t\tmsg: String\n\t}\n\n\t#[derive(OpenapiType)]\n\tstruct Messages {\n\t\tmsgs: Vec\n\t}\n\n\tfn info() -> OpenapiInfo {\n\t\tOpenapiInfo {\n\t\t\ttitle: \"TEST CASE\".to_owned(),\n\t\t\tversion: \"1.2.3\".to_owned(),\n\t\t\turls: vec![\n\t\t\t\t\"http://localhost:1234\".to_owned(),\n\t\t\t\t\"https://example.org\".to_owned(),\n\t\t\t]\n\t\t}\n\t}\n\n\tfn openapi(builder: OpenapiBuilder) -> OpenAPI {\n\t\tArc::try_unwrap(builder.openapi).unwrap().into_inner()\n\t}\n\n\t#[test]\n\tfn new_builder() {\n\t\tlet info = info();\n\t\tlet builder = OpenapiBuilder::new(info.clone());\n\t\tlet openapi = openapi(builder);\n\n\t\tassert_eq!(info.title, openapi.info.title);\n\t\tassert_eq!(info.version, openapi.info.version);\n\t\tassert_eq!(info.urls.len(), openapi.servers.len());\n\t}\n\n\t#[test]\n\tfn add_schema() {\n\t\tlet mut builder = OpenapiBuilder::new(info());\n\t\tbuilder.add_schema(>::schema());\n\t\tlet openapi = openapi(builder);\n\n\t\tassert_eq!(\n\t\t\topenapi.components.clone().unwrap_or_default().schemas[\"Message\"],\n\t\t\tReferenceOr::Item(Message::schema().schema)\n\t\t);\n\t\tassert_eq!(\n\t\t\topenapi.components.clone().unwrap_or_default().schemas[\"Messages\"],\n\t\t\tReferenceOr::Item(Messages::schema().schema)\n\t\t);\n\t}\n}\n","traces":[{"line":26,"address":[6108880,6110701,6110506],"length":1,"stats":{"Line":3}},{"line":28,"address":[6176152,6174827,6175657],"length":1,"stats":{"Line":9}},{"line":50,"address":[7687289,7687246,7686608],"length":1,"stats":{"Line":2}},{"line":51,"address":[8083627],"length":1,"stats":{"Line":2}},{"line":52,"address":[6111258,6111321],"length":1,"stats":{"Line":4}},{"line":53,"address":[8052452],"length":1,"stats":{"Line":1}},{"line":54,"address":[8052560,8052445],"length":1,"stats":{"Line":4}},{"line":58,"address":[],"length":0,"stats":{"Line":2}},{"line":59,"address":[6887731,6887809],"length":1,"stats":{"Line":4}},{"line":60,"address":[],"length":0,"stats":{"Line":4}},{"line":63,"address":[8084256,8085371,8085534],"length":1,"stats":{"Line":2}},{"line":64,"address":[7687433],"length":1,"stats":{"Line":2}},{"line":66,"address":[7595656],"length":1,"stats":{"Line":2}},{"line":67,"address":[7687600,7687673],"length":1,"stats":{"Line":4}},{"line":68,"address":[8354880],"length":1,"stats":{"Line":2}},{"line":69,"address":[7595880,7596618],"length":1,"stats":{"Line":4}},{"line":72,"address":[6178177],"length":1,"stats":{"Line":2}},{"line":73,"address":[7596237,7596042],"length":1,"stats":{"Line":4}},{"line":74,"address":[7596256,7596565],"length":1,"stats":{"Line":2}},{"line":79,"address":[6113104,6113733],"length":1,"stats":{"Line":3}},{"line":80,"address":[6179792,6179166,6179827],"length":1,"stats":{"Line":7}},{"line":81,"address":[6179332,6179720,6179388,6179214],"length":1,"stats":{"Line":10}},{"line":82,"address":[7837057,7837125],"length":1,"stats":{"Line":4}},{"line":83,"address":[7689053],"length":1,"stats":{"Line":2}},{"line":84,"address":[7837293,7837210],"length":1,"stats":{"Line":4}},{"line":89,"address":[6180695,6180751,6179856],"length":1,"stats":{"Line":3}},{"line":90,"address":[6179899,6180003],"length":1,"stats":{"Line":6}},{"line":91,"address":[6114030],"length":1,"stats":{"Line":1}},{"line":93,"address":[7597981],"length":1,"stats":{"Line":1}},{"line":95,"address":[8055534],"length":1,"stats":{"Line":1}},{"line":96,"address":[8357298],"length":1,"stats":{"Line":1}},{"line":99,"address":[8356717],"length":1,"stats":{"Line":3}},{"line":100,"address":[6180208],"length":1,"stats":{"Line":3}}],"covered":33,"coverable":33},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","openapi","handler","mod.rs"],"content":"#![cfg_attr(not(feature = \"auth\"), allow(unused_imports))]\nuse super::SECURITY_NAME;\nuse base64::prelude::*;\nuse futures_util::{future, future::FutureExt};\nuse gotham::{\n\tanyhow,\n\thandler::{Handler, HandlerError, HandlerFuture, NewHandler},\n\thelpers::http::response::{create_empty_response, create_response},\n\thyper::{\n\t\theader::{\n\t\t\tHeaderMap, HeaderValue, CACHE_CONTROL, CONTENT_SECURITY_POLICY, ETAG, IF_NONE_MATCH,\n\t\t\tREFERRER_POLICY, X_CONTENT_TYPE_OPTIONS\n\t\t},\n\t\tBody, Response, StatusCode\n\t},\n\tmime::{APPLICATION_JSON, TEXT_HTML_UTF_8, TEXT_PLAIN_UTF_8},\n\tstate::State\n};\nuse gotham_restful_redoc::Redoc;\nuse openapi_type::{\n\tindexmap::IndexMap,\n\topenapiv3::{APIKeyLocation, OpenAPI, ReferenceOr, SecurityScheme}\n};\nuse parking_lot::RwLock;\nuse sha2::{Digest, Sha256};\nuse std::{panic::RefUnwindSafe, pin::Pin, sync::Arc};\n\n#[cfg(feature = \"auth\")]\nfn get_security(state: &State) -> IndexMap> {\n\tuse crate::AuthSource;\n\tuse gotham::state::FromState;\n\n\tlet source = match AuthSource::try_borrow_from(state) {\n\t\tSome(source) => source,\n\t\tNone => return Default::default()\n\t};\n\n\tlet security_scheme = match source {\n\t\tAuthSource::Cookie(name) => SecurityScheme::APIKey {\n\t\t\tlocation: APIKeyLocation::Cookie,\n\t\t\tname: name.to_string(),\n\t\t\tdescription: None,\n\t\t\textensions: Default::default()\n\t\t},\n\t\tAuthSource::Header(name) => SecurityScheme::APIKey {\n\t\t\tlocation: APIKeyLocation::Header,\n\t\t\tname: name.to_string(),\n\t\t\tdescription: None,\n\t\t\textensions: Default::default()\n\t\t},\n\t\tAuthSource::AuthorizationHeader => SecurityScheme::HTTP {\n\t\t\tscheme: \"bearer\".to_owned(),\n\t\t\tbearer_format: Some(\"JWT\".to_owned()),\n\t\t\tdescription: None,\n\t\t\textensions: Default::default()\n\t\t}\n\t};\n\n\tlet mut security_schemes: IndexMap> = Default::default();\n\tsecurity_schemes.insert(SECURITY_NAME.to_owned(), ReferenceOr::Item(security_scheme));\n\n\tsecurity_schemes\n}\n\n#[cfg(not(feature = \"auth\"))]\nfn get_security(_state: &State) -> IndexMap> {\n\tDefault::default()\n}\n\nfn openapi_string(\n\tstate: &State,\n\topenapi: &Arc>\n) -> Result {\n\tlet openapi = openapi.read();\n\n\tlet mut openapi = openapi.clone();\n\tlet security_schemes = get_security(state);\n\tlet mut components = openapi.components.unwrap_or_default();\n\tcomponents.security_schemes = security_schemes;\n\topenapi.components = Some(components);\n\n\tserde_json::to_string(&openapi)\n}\n\nfn create_openapi_response(state: &State, openapi: &Arc>) -> Response {\n\tmatch openapi_string(state, openapi) {\n\t\tOk(body) => {\n\t\t\tlet mut res = create_response(state, StatusCode::OK, APPLICATION_JSON, body);\n\t\t\tlet headers = res.headers_mut();\n\t\t\theaders.insert(X_CONTENT_TYPE_OPTIONS, HeaderValue::from_static(\"nosniff\"));\n\t\t\tres\n\t\t},\n\t\tErr(e) => {\n\t\t\terror!(\"Unable to handle OpenAPI request due to error: {e}\");\n\t\t\tcreate_response(\n\t\t\t\tstate,\n\t\t\t\tStatusCode::INTERNAL_SERVER_ERROR,\n\t\t\t\tTEXT_PLAIN_UTF_8,\n\t\t\t\t\"\"\n\t\t\t)\n\t\t}\n\t}\n}\n\n#[derive(Clone)]\npub(crate) struct OpenapiSpecHandler {\n\topenapi: Arc>\n}\n\n// safety: the handler only ever aquires a read lock, so this usage of\n// RwLock is, in fact, unwind safe\nimpl RefUnwindSafe for OpenapiSpecHandler {}\n\nimpl OpenapiSpecHandler {\n\tpub(crate) fn new(openapi: Arc>) -> Self {\n\t\tSelf { openapi }\n\t}\n}\n\nimpl NewHandler for OpenapiSpecHandler {\n\ttype Instance = Self;\n\n\tfn new_handler(&self) -> anyhow::Result {\n\t\tOk(self.clone())\n\t}\n}\n\nimpl Handler for OpenapiSpecHandler {\n\tfn handle(self, mut state: State) -> Pin> {\n\t\tlet res = create_openapi_response(&mut state, &self.openapi);\n\t\tfuture::ok((state, res)).boxed()\n\t}\n}\n\n#[derive(Clone)]\npub(crate) struct OpenapiDocHandler {\n\topenapi: Arc>\n}\n\n// safety: the handler only ever aquires a read lock, so this usage of\n// RwLock is, in fact, unwind safe\nimpl RefUnwindSafe for OpenapiDocHandler {}\n\nimpl OpenapiDocHandler {\n\tpub(crate) fn new(openapi: Arc>) -> Self {\n\t\tSelf { openapi }\n\t}\n}\n\nimpl NewHandler for OpenapiDocHandler {\n\ttype Instance = Self;\n\n\tfn new_handler(&self) -> anyhow::Result {\n\t\tOk(self.clone())\n\t}\n}\n\nfn redoc_handler(\n\tstate: &State,\n\topenapi: &Arc>\n) -> Result, HandlerError> {\n\tlet spec = openapi_string(state, openapi)?;\n\tlet Redoc { html, script_hash } = gotham_restful_redoc::html(spec);\n\n\tlet mut etag = Sha256::new();\n\tetag.update(&html);\n\tlet etag = format!(\"\\\"{}\\\"\", BASE64_STANDARD.encode(etag.finalize()));\n\n\tif state\n\t\t.borrow::()\n\t\t.get(IF_NONE_MATCH)\n\t\t.map_or(false, |header| header.as_bytes() == etag.as_bytes())\n\t{\n\t\tlet res = create_empty_response(state, StatusCode::NOT_MODIFIED);\n\t\treturn Ok(res);\n\t}\n\n\tlet mut res = create_response(state, StatusCode::OK, TEXT_HTML_UTF_8, html);\n\tlet headers = res.headers_mut();\n\theaders.insert(\n\t\tCACHE_CONTROL,\n\t\tHeaderValue::from_static(\"public,max-age=2592000\")\n\t);\n\theaders.insert(\n\t\tCONTENT_SECURITY_POLICY,\n\t\tformat!(\n\t\t\t\"default-src 'none';base-uri 'none';script-src 'unsafe-inline' https://cdn.jsdelivr.net 'sha256-{script_hash}' 'strict-dynamic';style-src 'unsafe-inline' https://fonts.googleapis.com;font-src https://fonts.gstatic.com;connect-src 'self';img-src blob: data:\",\n\t\t).parse().unwrap()\n\t);\n\theaders.insert(ETAG, etag.parse().unwrap());\n\theaders.insert(REFERRER_POLICY, HeaderValue::from_static(\"no-referrer\"));\n\theaders.insert(X_CONTENT_TYPE_OPTIONS, HeaderValue::from_static(\"nosniff\"));\n\tOk(res)\n}\n\nimpl Handler for OpenapiDocHandler {\n\tfn handle(self, state: State) -> Pin> {\n\t\tmatch redoc_handler(&state, &self.openapi) {\n\t\t\tOk(res) => future::ok((state, res)).boxed(),\n\t\t\tErr(err) => future::err((state, err)).boxed()\n\t\t}\n\t}\n}\n","traces":[{"line":29,"address":[7598464,7599076,7599104],"length":1,"stats":{"Line":2}},{"line":33,"address":[8087301],"length":1,"stats":{"Line":2}},{"line":34,"address":[8055967],"length":1,"stats":{"Line":1}},{"line":35,"address":[7690455],"length":1,"stats":{"Line":1}},{"line":38,"address":[8055985],"length":1,"stats":{"Line":1}},{"line":41,"address":[7598674],"length":1,"stats":{"Line":0}},{"line":43,"address":[7838665],"length":1,"stats":{"Line":0}},{"line":47,"address":[7838712],"length":1,"stats":{"Line":0}},{"line":49,"address":[6181115],"length":1,"stats":{"Line":0}},{"line":52,"address":[7690689],"length":1,"stats":{"Line":1}},{"line":53,"address":[7690714,7691274],"length":1,"stats":{"Line":2}},{"line":55,"address":[6181772],"length":1,"stats":{"Line":1}},{"line":59,"address":[8056446],"length":1,"stats":{"Line":1}},{"line":60,"address":[8088517,8088585],"length":1,"stats":{"Line":2}},{"line":62,"address":[7839913],"length":1,"stats":{"Line":1}},{"line":70,"address":[7840000,7840947,7841413],"length":1,"stats":{"Line":2}},{"line":74,"address":[8057490],"length":1,"stats":{"Line":2}},{"line":76,"address":[7692044,7692116],"length":1,"stats":{"Line":4}},{"line":77,"address":[8089040,8089088],"length":1,"stats":{"Line":4}},{"line":78,"address":[6336525,6336424],"length":1,"stats":{"Line":4}},{"line":79,"address":[8359444],"length":1,"stats":{"Line":2}},{"line":80,"address":[7692587],"length":1,"stats":{"Line":2}},{"line":82,"address":[7840794],"length":1,"stats":{"Line":2}},{"line":85,"address":[8058864,8059477],"length":1,"stats":{"Line":2}},{"line":86,"address":[7841478],"length":1,"stats":{"Line":2}},{"line":87,"address":[6183905],"length":1,"stats":{"Line":2}},{"line":88,"address":[8090373],"length":1,"stats":{"Line":2}},{"line":89,"address":[8059233,8059113],"length":1,"stats":{"Line":4}},{"line":90,"address":[7601849,7602063],"length":1,"stats":{"Line":2}},{"line":91,"address":[6184371],"length":1,"stats":{"Line":2}},{"line":93,"address":[6184073],"length":1,"stats":{"Line":0}},{"line":94,"address":[6184840,6184086,6184488,6184678],"length":1,"stats":{"Line":0}},{"line":115,"address":[7842672],"length":1,"stats":{"Line":2}},{"line":123,"address":[8091504],"length":1,"stats":{"Line":2}},{"line":124,"address":[8091513],"length":1,"stats":{"Line":2}},{"line":129,"address":[7694960,7694656],"length":1,"stats":{"Line":2}},{"line":130,"address":[6185147],"length":1,"stats":{"Line":2}},{"line":131,"address":[8060256,8060177],"length":1,"stats":{"Line":4}},{"line":145,"address":[8362112],"length":1,"stats":{"Line":0}},{"line":153,"address":[8091904],"length":1,"stats":{"Line":0}},{"line":154,"address":[6339209],"length":1,"stats":{"Line":0}},{"line":158,"address":[8095618,8091952,8095391],"length":1,"stats":{"Line":0}},{"line":162,"address":[7843457,7843180],"length":1,"stats":{"Line":0}},{"line":163,"address":[7843532,7843382],"length":1,"stats":{"Line":0}},{"line":165,"address":[8362644],"length":1,"stats":{"Line":0}},{"line":166,"address":[8362707],"length":1,"stats":{"Line":0}},{"line":167,"address":[8061403,8061118],"length":1,"stats":{"Line":0}},{"line":169,"address":[7844260,7844325],"length":1,"stats":{"Line":0}},{"line":172,"address":[7846848,7846873],"length":1,"stats":{"Line":0}},{"line":174,"address":[8093419],"length":1,"stats":{"Line":0}},{"line":175,"address":[8365740],"length":1,"stats":{"Line":0}},{"line":178,"address":[8363468],"length":1,"stats":{"Line":0}},{"line":179,"address":[6186998,6187071],"length":1,"stats":{"Line":0}},{"line":180,"address":[6340925],"length":1,"stats":{"Line":0}},{"line":181,"address":[8062135],"length":1,"stats":{"Line":0}},{"line":182,"address":[6187117],"length":1,"stats":{"Line":0}},{"line":184,"address":[8364544],"length":1,"stats":{"Line":0}},{"line":185,"address":[6187260],"length":1,"stats":{"Line":0}},{"line":186,"address":[6187613,6187701,6187484],"length":1,"stats":{"Line":0}},{"line":190,"address":[6342709,6341740,6342045],"length":1,"stats":{"Line":0}},{"line":191,"address":[7698504,7697932],"length":1,"stats":{"Line":0}},{"line":192,"address":[8365593,8365233],"length":1,"stats":{"Line":0}},{"line":193,"address":[8365456],"length":1,"stats":{"Line":0}},{"line":197,"address":[6343040,6343635],"length":1,"stats":{"Line":0}},{"line":198,"address":[8366001,8366075,8366456],"length":1,"stats":{"Line":0}},{"line":199,"address":[6343198,6343459],"length":1,"stats":{"Line":0}},{"line":200,"address":[6189836,6189600],"length":1,"stats":{"Line":0}}],"covered":32,"coverable":67},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","openapi","mod.rs"],"content":"const SECURITY_NAME: &str = \"authToken\";\n\npub(crate) mod builder;\npub(crate) mod handler;\npub(crate) mod operation;\npub(crate) mod router;\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","openapi","operation.rs"],"content":"use super::SECURITY_NAME;\nuse crate::{response::OrAllTypes, EndpointWithSchema, IntoResponse, RequestBody};\nuse gotham::{hyper::StatusCode, mime::Mime};\nuse openapi_type::{\n\tindexmap::IndexMap,\n\topenapiv3::{\n\t\tMediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr,\n\t\tReferenceOr::Item, RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind,\n\t\tStatusCode as OAStatusCode, Type\n\t},\n\tOpenapiSchema\n};\nuse std::{borrow::Cow, collections::HashMap};\n\nfn new_parameter_data(\n\tname: String,\n\trequired: bool,\n\tschema: ReferenceOr>\n) -> ParameterData {\n\tParameterData {\n\t\tname,\n\t\tdescription: None,\n\t\trequired,\n\t\tdeprecated: None,\n\t\tformat: ParameterSchemaOrContent::Schema(schema.unbox()),\n\t\texample: None,\n\t\texamples: Default::default(),\n\t\texplode: None,\n\t\textensions: Default::default()\n\t}\n}\n\n#[derive(Default)]\nstruct OperationParams {\n\tpath_params: Option,\n\tquery_params: Option\n}\n\nimpl OperationParams {\n\t// TODO shouldn't this be a custom openapi_type::Visitor\n\t// rather than this hacky code?\n\tfn add_path_params(\n\t\tpath_params: Option,\n\t\tparams: &mut Vec>\n\t) {\n\t\tlet path_params = match path_params {\n\t\t\tSome(pp) => pp.schema,\n\t\t\tNone => return\n\t\t};\n\t\tlet path_params = match path_params.schema_kind {\n\t\t\tSchemaKind::Type(Type::Object(ty)) => ty,\n\t\t\t_ => panic!(\"Path Parameters needs to be a plain struct\")\n\t\t};\n\t\tfor (name, schema) in path_params.properties {\n\t\t\tlet required = path_params.required.contains(&name);\n\t\t\tparams.push(Item(Parameter::Path {\n\t\t\t\tparameter_data: new_parameter_data(name, required, schema),\n\t\t\t\tstyle: Default::default()\n\t\t\t}))\n\t\t}\n\t}\n\n\t// TODO shouldn't this be a custom openapi_type::Visitor\n\t// rather than this hacky code?\n\tfn add_query_params(\n\t\tquery_params: Option,\n\t\tparams: &mut Vec>\n\t) {\n\t\tlet query_params = match query_params {\n\t\t\tSome(qp) => qp.schema,\n\t\t\tNone => return\n\t\t};\n\t\tlet query_params = match query_params.schema_kind {\n\t\t\tSchemaKind::Type(Type::Object(ty)) => ty,\n\t\t\t_ => panic!(\"Query Parameters needs to be a plain struct\")\n\t\t};\n\t\tfor (name, schema) in query_params.properties {\n\t\t\tlet required = query_params.required.contains(&name);\n\t\t\tparams.push(Item(Parameter::Query {\n\t\t\t\tparameter_data: new_parameter_data(name, required, schema),\n\t\t\t\tallow_reserved: false,\n\t\t\t\tstyle: Default::default(),\n\t\t\t\tallow_empty_value: None\n\t\t\t}))\n\t\t}\n\t}\n\n\tfn into_params(self) -> Vec> {\n\t\tlet mut params: Vec> = Vec::new();\n\t\tSelf::add_path_params(self.path_params, &mut params);\n\t\tSelf::add_query_params(self.query_params, &mut params);\n\t\tparams\n\t}\n}\n\n#[derive(Debug, Default)]\npub enum OperationId {\n\t/// Automatically generate the operation id based on path and operation verb.\n\t#[default]\n\tFullAuto,\n\t/// Automatically generate the operation id based on path and the provided string.\n\tSemiAuto(Cow<'static, str>),\n\t/// Use the provided operation id.\n\tManual(String)\n}\n\npub(crate) struct OperationDescription {\n\toperation_id: Option,\n\tdescription: Option,\n\n\taccepted_types: Option>,\n\tresponses: HashMap>,\n\tparams: OperationParams,\n\tbody_schema: Option>,\n\tsupported_types: Option>,\n\trequires_auth: bool\n}\n\nimpl OperationDescription {\n\t/// Create a new operation description for the given endpoint type and schema. If the endpoint\n\t/// does not specify an operation id, the path is used to generate one.\n\tpub(crate) fn new(\n\t\tresponses: HashMap>,\n\t\tpath: &str\n\t) -> Self {\n\t\tlet (mut operation_id, op_id_verb) = match E::operation_id() {\n\t\t\tOperationId::FullAuto => (None, E::operation_verb().map(Cow::Borrowed)),\n\t\t\tOperationId::SemiAuto(verb) => (None, Some(verb)),\n\t\t\tOperationId::Manual(id) => (Some(id), None)\n\t\t};\n\t\tif let Some(verb) = op_id_verb {\n\t\t\tlet op_path = path.replace('/', \"_\");\n\t\t\tlet op_path = op_path.trim_start_matches('_');\n\t\t\tif verb.starts_with(op_path) || verb.ends_with(op_path) {\n\t\t\t\toperation_id = Some(verb.into_owned());\n\t\t\t} else {\n\t\t\t\toperation_id = Some(format!(\"{verb}_{op_path}\"));\n\t\t\t}\n\t\t}\n\n\t\tSelf {\n\t\t\toperation_id,\n\t\t\tdescription: E::description(),\n\n\t\t\taccepted_types: E::Output::accepted_types(),\n\t\t\tresponses,\n\t\t\tparams: Default::default(),\n\t\t\tbody_schema: None,\n\t\t\tsupported_types: None,\n\t\t\trequires_auth: E::wants_auth()\n\t\t}\n\t}\n\n\tpub(crate) fn set_path_params(&mut self, params: OpenapiSchema) {\n\t\tself.params.path_params = Some(params);\n\t}\n\n\tpub(crate) fn set_query_params(&mut self, params: OpenapiSchema) {\n\t\tself.params.query_params = Some(params);\n\t}\n\n\tpub(crate) fn set_body(&mut self, schema: ReferenceOr) {\n\t\tself.body_schema = Some(schema);\n\t\tself.supported_types = Body::supported_types();\n\t}\n\n\tfn schema_to_content(\n\t\ttypes: Vec,\n\t\tschema: ReferenceOr\n\t) -> IndexMap {\n\t\tlet mut content: IndexMap = IndexMap::new();\n\t\tfor ty in types {\n\t\t\tcontent.insert(ty.to_string(), MediaType {\n\t\t\t\tschema: Some(schema.clone()),\n\t\t\t\t..Default::default()\n\t\t\t});\n\t\t}\n\t\tcontent\n\t}\n\n\tpub(crate) fn into_operation(self) -> Operation {\n\t\t// this is unfortunately neccessary to prevent rust from complaining about partially moving self\n\t\tlet (\n\t\t\toperation_id,\n\t\t\tdescription,\n\t\t\taccepted_types,\n\t\t\tresponses,\n\t\t\tparams,\n\t\t\tbody_schema,\n\t\t\tsupported_types,\n\t\t\trequires_auth\n\t\t) = (\n\t\t\tself.operation_id,\n\t\t\tself.description,\n\t\t\tself.accepted_types,\n\t\t\tself.responses,\n\t\t\tself.params,\n\t\t\tself.body_schema,\n\t\t\tself.supported_types,\n\t\t\tself.requires_auth\n\t\t);\n\n\t\tlet responses: IndexMap> = responses\n\t\t\t.into_iter()\n\t\t\t.map(|(code, schema)| {\n\t\t\t\tlet content =\n\t\t\t\t\tSelf::schema_to_content(accepted_types.clone().or_all_types(), schema);\n\t\t\t\t(\n\t\t\t\t\tOAStatusCode::Code(code.as_u16()),\n\t\t\t\t\tItem(Response {\n\t\t\t\t\t\tdescription: code\n\t\t\t\t\t\t\t.canonical_reason()\n\t\t\t\t\t\t\t.map(|d| d.to_string())\n\t\t\t\t\t\t\t.unwrap_or_default(),\n\t\t\t\t\t\tcontent,\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t})\n\t\t\t.collect();\n\n\t\tlet request_body = body_schema.map(|schema| {\n\t\t\tItem(OARequestBody {\n\t\t\t\tcontent: Self::schema_to_content(supported_types.or_all_types(), schema),\n\t\t\t\trequired: true,\n\t\t\t\t..Default::default()\n\t\t\t})\n\t\t});\n\n\t\tlet mut security = None;\n\t\tif requires_auth {\n\t\t\tlet mut sec = IndexMap::new();\n\t\t\tsec.insert(SECURITY_NAME.to_owned(), Vec::new());\n\t\t\tsecurity = Some(vec![sec]);\n\t\t}\n\n\t\tOperation {\n\t\t\ttags: Vec::new(),\n\t\t\toperation_id,\n\t\t\tdescription,\n\t\t\tparameters: params.into_params(),\n\t\t\trequest_body,\n\t\t\tresponses: Responses {\n\t\t\t\tresponses,\n\t\t\t\t..Default::default()\n\t\t\t},\n\t\t\tdeprecated: false,\n\t\t\tsecurity,\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse crate::{NoContent, Raw, ResponseSchema};\n\n\t#[test]\n\tfn no_content_schema_to_content() {\n\t\tlet types = NoContent::accepted_types();\n\t\tlet schema = ::schema(StatusCode::NO_CONTENT);\n\t\tlet content =\n\t\t\tOperationDescription::schema_to_content(types.or_all_types(), Item(schema.schema));\n\t\tassert!(content.is_empty());\n\t}\n\n\t#[test]\n\tfn raw_schema_to_content() {\n\t\tlet types = Raw::<&str>::accepted_types();\n\t\tlet schema = as ResponseSchema>::schema(StatusCode::OK);\n\t\tlet content =\n\t\t\tOperationDescription::schema_to_content(types.or_all_types(), Item(schema.schema));\n\t\tassert_eq!(content.len(), 1);\n\t\tlet json = serde_json::to_string(&content.values().nth(0).unwrap()).unwrap();\n\t\tassert_eq!(json, r#\"{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}\"#);\n\t}\n}\n","traces":[{"line":15,"address":[8206336,8206903,8206947],"length":1,"stats":{"Line":1}},{"line":25,"address":[6467106,6467033],"length":1,"stats":{"Line":2}},{"line":27,"address":[6467116],"length":1,"stats":{"Line":1}},{"line":29,"address":[6300135],"length":1,"stats":{"Line":1}},{"line":42,"address":[8208094,8208123,8206960],"length":1,"stats":{"Line":2}},{"line":46,"address":[8207000],"length":1,"stats":{"Line":2}},{"line":47,"address":[7718276],"length":1,"stats":{"Line":1}},{"line":50,"address":[8175776,8175738],"length":1,"stats":{"Line":2}},{"line":51,"address":[8477449],"length":1,"stats":{"Line":1}},{"line":54,"address":[7958635,7958484,7958824,7958770,7959706],"length":1,"stats":{"Line":5}},{"line":55,"address":[7810824,7811287],"length":1,"stats":{"Line":2}},{"line":56,"address":[6468986],"length":1,"stats":{"Line":1}},{"line":57,"address":[6468810],"length":1,"stats":{"Line":1}},{"line":58,"address":[6301891],"length":1,"stats":{"Line":1}},{"line":65,"address":[7813035,7811872,7813006],"length":1,"stats":{"Line":2}},{"line":69,"address":[7720024],"length":1,"stats":{"Line":2}},{"line":70,"address":[6469460],"length":1,"stats":{"Line":1}},{"line":73,"address":[7812080,7812042],"length":1,"stats":{"Line":2}},{"line":74,"address":[8177641],"length":1,"stats":{"Line":1}},{"line":77,"address":[7813464,7812212,7812363,7812498,7812552],"length":1,"stats":{"Line":5}},{"line":78,"address":[6303080,6303543],"length":1,"stats":{"Line":2}},{"line":79,"address":[8480438],"length":1,"stats":{"Line":1}},{"line":80,"address":[8210030],"length":1,"stats":{"Line":1}},{"line":82,"address":[8178755],"length":1,"stats":{"Line":1}},{"line":83,"address":[7813310],"length":1,"stats":{"Line":1}},{"line":88,"address":[8179216,8179562,8179525],"length":1,"stats":{"Line":2}},{"line":89,"address":[6471206],"length":1,"stats":{"Line":2}},{"line":90,"address":[7961908],"length":1,"stats":{"Line":2}},{"line":91,"address":[7962000],"length":1,"stats":{"Line":2}},{"line":92,"address":[7962067],"length":1,"stats":{"Line":2}},{"line":122,"address":[7840312,7834736,7820576,7833716,7837012,7830424,7824848,7827168,7827124,7823828,7828144,7823872,7837056,7838032,7831440,7840352,7821552,7830464,7841328,7843608,7833760],"length":1,"stats":{"Line":8}},{"line":126,"address":[6720203,6719526,6719430],"length":1,"stats":{"Line":24}},{"line":127,"address":[7834418,7827826,7824072,7820776,7827368,7830664,7824530,7821234,7831122,7833960,7837256,7837714,7840552,7841010],"length":1,"stats":{"Line":0}},{"line":128,"address":[6719604],"length":1,"stats":{"Line":6}},{"line":129,"address":[],"length":0,"stats":{"Line":2}},{"line":131,"address":[6720371,6721687,6720315],"length":1,"stats":{"Line":20}},{"line":132,"address":[6720419],"length":1,"stats":{"Line":6}},{"line":133,"address":[],"length":0,"stats":{"Line":12}},{"line":134,"address":[],"length":0,"stats":{"Line":14}},{"line":135,"address":[],"length":0,"stats":{"Line":10}},{"line":137,"address":[7832350,7842238,7829054,7838805,7825758,7838942,7828917,7835646,7825621,7822325,7822462,7832213,7842101,7835509],"length":1,"stats":{"Line":2}},{"line":143,"address":[7841480,7821704,7825000,7831592,7834888,7828296,7838184],"length":1,"stats":{"Line":8}},{"line":145,"address":[6721845],"length":1,"stats":{"Line":8}},{"line":147,"address":[],"length":0,"stats":{"Line":8}},{"line":150,"address":[6722030],"length":1,"stats":{"Line":8}},{"line":154,"address":[8481216,8481292],"length":1,"stats":{"Line":1}},{"line":155,"address":[8211028,8211107],"length":1,"stats":{"Line":2}},{"line":158,"address":[8481360,8481450],"length":1,"stats":{"Line":1}},{"line":159,"address":[8481489,8481396],"length":1,"stats":{"Line":2}},{"line":162,"address":[],"length":0,"stats":{"Line":2}},{"line":163,"address":[7844356,7844020,7843684],"length":1,"stats":{"Line":2}},{"line":164,"address":[],"length":0,"stats":{"Line":2}},{"line":167,"address":[7814400,7815673],"length":1,"stats":{"Line":3}},{"line":171,"address":[8211338],"length":1,"stats":{"Line":3}},{"line":172,"address":[7722854,7722899,7722632,7722725],"length":1,"stats":{"Line":12}},{"line":173,"address":[8482003,8482379],"length":1,"stats":{"Line":6}},{"line":174,"address":[6472529,6472617],"length":1,"stats":{"Line":6}},{"line":175,"address":[7723291],"length":1,"stats":{"Line":3}},{"line":178,"address":[8180427],"length":1,"stats":{"Line":3}},{"line":181,"address":[7963792,7967836,7967574],"length":1,"stats":{"Line":2}},{"line":183,"address":[6473219,6473573],"length":1,"stats":{"Line":4}},{"line":184,"address":[8213279],"length":1,"stats":{"Line":2}},{"line":185,"address":[7724535],"length":1,"stats":{"Line":2}},{"line":186,"address":[6306911],"length":1,"stats":{"Line":2}},{"line":187,"address":[6306943],"length":1,"stats":{"Line":2}},{"line":188,"address":[7724663],"length":1,"stats":{"Line":2}},{"line":189,"address":[6307032],"length":1,"stats":{"Line":2}},{"line":190,"address":[7964682],"length":1,"stats":{"Line":2}},{"line":191,"address":[7724754],"length":1,"stats":{"Line":2}},{"line":193,"address":[8212755],"length":1,"stats":{"Line":2}},{"line":194,"address":[7724001],"length":1,"stats":{"Line":2}},{"line":195,"address":[6306367],"length":1,"stats":{"Line":2}},{"line":196,"address":[7964029],"length":1,"stats":{"Line":2}},{"line":197,"address":[6306477],"length":1,"stats":{"Line":2}},{"line":198,"address":[8212937],"length":1,"stats":{"Line":2}},{"line":199,"address":[8181597],"length":1,"stats":{"Line":2}},{"line":200,"address":[6473563],"length":1,"stats":{"Line":2}},{"line":203,"address":[8483782,8483906],"length":1,"stats":{"Line":4}},{"line":205,"address":[8217056,8217099,8218247,8218194],"length":1,"stats":{"Line":4}},{"line":207,"address":[7820263,7820326],"length":1,"stats":{"Line":4}},{"line":209,"address":[6477876,6477818],"length":1,"stats":{"Line":4}},{"line":210,"address":[7728851],"length":1,"stats":{"Line":2}},{"line":211,"address":[7728622],"length":1,"stats":{"Line":2}},{"line":213,"address":[8186918,8186896],"length":1,"stats":{"Line":4}},{"line":215,"address":[6477968],"length":1,"stats":{"Line":2}},{"line":216,"address":[6478064],"length":1,"stats":{"Line":2}},{"line":222,"address":[7821440,7816856,7821929],"length":1,"stats":{"Line":3}},{"line":223,"address":[6311910,6312147],"length":1,"stats":{"Line":2}},{"line":224,"address":[7821478,7821574],"length":1,"stats":{"Line":2}},{"line":226,"address":[7729755],"length":1,"stats":{"Line":1}},{"line":230,"address":[6474448],"length":1,"stats":{"Line":2}},{"line":231,"address":[8214610,8213938],"length":1,"stats":{"Line":3}},{"line":232,"address":[8182572],"length":1,"stats":{"Line":1}},{"line":233,"address":[6307579,6307652,6310299],"length":1,"stats":{"Line":2}},{"line":234,"address":[8484477],"length":1,"stats":{"Line":1}},{"line":238,"address":[7965126],"length":1,"stats":{"Line":2}},{"line":241,"address":[6475211],"length":1,"stats":{"Line":2}},{"line":243,"address":[6308539],"length":1,"stats":{"Line":2}}],"covered":97,"coverable":98},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","openapi","router.rs"],"content":"use super::{\n\tbuilder::OpenapiBuilder,\n\thandler::{OpenapiDocHandler, OpenapiSpecHandler},\n\toperation::OperationDescription\n};\nuse crate::{routing::*, EndpointWithSchema, ResourceWithSchema, ResponseSchema};\nuse gotham::{\n\thyper::{Method, StatusCode},\n\tpipeline::PipelineHandleChain,\n\tprelude::*,\n\trouter::builder::{RouterBuilder, ScopeBuilder}\n};\nuse lazy_regex::regex_replace_all;\nuse openapi_type::OpenapiType;\nuse std::{collections::HashMap, panic::RefUnwindSafe};\n\n/// This trait adds the `openapi_spec` and `openapi_doc` method to an OpenAPI-aware router.\npub trait GetOpenapi {\n\t/// Register a GET route to `path` that returns the OpenAPI specification in JSON format.\n\tfn openapi_spec(&mut self, path: &str);\n\n\t/// Register a GET route to `path` that returns the OpenAPI documentation in HTML format.\n\tfn openapi_doc(&mut self, path: &str);\n}\n\n#[derive(Debug)]\npub struct OpenapiRouter<'a, D> {\n\tpub(crate) router: &'a mut D,\n\tpub(crate) scope: Option<&'a str>,\n\tpub(crate) openapi_builder: &'a mut OpenapiBuilder\n}\n\nmacro_rules! implOpenapiRouter {\n\t($implType:ident) => {\n\t\timpl<'a, 'b, C, P> OpenapiRouter<'a, $implType<'b, C, P>>\n\t\twhere\n\t\t\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\t\t\tP: RefUnwindSafe + Send + Sync + 'static\n\t\t{\n\t\t\tpub fn scope(&mut self, path: &str, callback: F)\n\t\t\twhere\n\t\t\t\tF: FnOnce(&mut OpenapiRouter<'_, ScopeBuilder<'_, C, P>>)\n\t\t\t{\n\t\t\t\tlet mut openapi_builder = self.openapi_builder.clone();\n\t\t\t\tlet new_scope = self\n\t\t\t\t\t.scope\n\t\t\t\t\t.map(|scope| format!(\"{scope}/{path}\").replace(\"//\", \"/\"));\n\t\t\t\tself.router.scope(path, |router| {\n\t\t\t\t\tlet mut router = OpenapiRouter {\n\t\t\t\t\t\trouter,\n\t\t\t\t\t\tscope: Some(new_scope.as_ref().map(String::as_ref).unwrap_or(path)),\n\t\t\t\t\t\topenapi_builder: &mut openapi_builder\n\t\t\t\t\t};\n\t\t\t\t\tcallback(&mut router);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\timpl<'a, 'b, C, P> GetOpenapi for OpenapiRouter<'a, $implType<'b, C, P>>\n\t\twhere\n\t\t\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\t\t\tP: RefUnwindSafe + Send + Sync + 'static\n\t\t{\n\t\t\tfn openapi_spec(&mut self, path: &str) {\n\t\t\t\tself.router\n\t\t\t\t\t.get(path)\n\t\t\t\t\t.to_new_handler(OpenapiSpecHandler::new(\n\t\t\t\t\t\tself.openapi_builder.openapi.clone()\n\t\t\t\t\t));\n\t\t\t}\n\n\t\t\tfn openapi_doc(&mut self, path: &str) {\n\t\t\t\tself.router\n\t\t\t\t\t.get(path)\n\t\t\t\t\t.to_new_handler(OpenapiDocHandler::new(self.openapi_builder.openapi.clone()));\n\t\t\t}\n\t\t}\n\n\t\timpl<'a, 'b, C, P> DrawResourcesWithSchema for OpenapiRouter<'a, $implType<'b, C, P>>\n\t\twhere\n\t\t\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\t\t\tP: RefUnwindSafe + Send + Sync + 'static\n\t\t{\n\t\t\tfn resource(&mut self, mut path: &str) {\n\t\t\t\tif path.starts_with('/') {\n\t\t\t\t\tpath = &path[1..];\n\t\t\t\t}\n\t\t\t\tR::setup((self, path));\n\t\t\t}\n\t\t}\n\n\t\timpl<'a, 'b, C, P> DrawResourceRoutesWithSchema\n\t\t\tfor (&mut OpenapiRouter<'a, $implType<'b, C, P>>, &str)\n\t\twhere\n\t\t\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\t\t\tP: RefUnwindSafe + Send + Sync + 'static\n\t\t{\n\t\t\tfn endpoint(&mut self) {\n\t\t\t\tlet mut responses: HashMap = HashMap::new();\n\t\t\t\tfor code in E::Output::status_codes() {\n\t\t\t\t\tresponses.insert(\n\t\t\t\t\t\tcode,\n\t\t\t\t\t\t(self.0).openapi_builder.add_schema(E::Output::schema(code))\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tlet mut path = format!(\"{}/{}\", self.0.scope.unwrap_or_default(), self.1);\n\t\t\t\tlet mut descr = OperationDescription::new::(responses, &path);\n\t\t\t\tif E::has_placeholders() {\n\t\t\t\t\tdescr.set_path_params(E::Placeholders::schema());\n\t\t\t\t}\n\t\t\t\tif E::needs_params() {\n\t\t\t\t\tdescr.set_query_params(E::Params::schema());\n\t\t\t\t}\n\t\t\t\tif E::needs_body() {\n\t\t\t\t\tlet body_schema = (self.0).openapi_builder.add_schema(E::Body::schema());\n\t\t\t\t\tdescr.set_body::(body_schema);\n\t\t\t\t}\n\n\t\t\t\tlet uri: &str = &E::uri();\n\t\t\t\tlet uri =\n\t\t\t\t\tregex_replace_all!(r#\"(^|/):([^/]+)(/|$)\"#, uri, |_, prefix, name, suffix| {\n\t\t\t\t\t\tformat!(\"{prefix}{{{name}}}{suffix}\")\n\t\t\t\t\t});\n\t\t\t\tif !uri.is_empty() {\n\t\t\t\t\tpath = format!(\"{path}/{uri}\");\n\t\t\t\t}\n\n\t\t\t\tlet op = descr.into_operation();\n\t\t\t\tlet mut item = (self.0).openapi_builder.remove_path(&path);\n\t\t\t\tmatch E::http_method() {\n\t\t\t\t\tMethod::GET => item.get = Some(op),\n\t\t\t\t\tMethod::PUT => item.put = Some(op),\n\t\t\t\t\tMethod::POST => item.post = Some(op),\n\t\t\t\t\tMethod::DELETE => item.delete = Some(op),\n\t\t\t\t\tMethod::OPTIONS => item.options = Some(op),\n\t\t\t\t\tMethod::HEAD => item.head = Some(op),\n\t\t\t\t\tMethod::PATCH => item.patch = Some(op),\n\t\t\t\t\tMethod::TRACE => item.trace = Some(op),\n\t\t\t\t\tmethod => {\n\t\t\t\t\t\twarn!(\"Ignoring unsupported method '{method}' in OpenAPI Specification\")\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\t(self.0).openapi_builder.add_path(path, item);\n\n\t\t\t\t(&mut *(self.0).router, self.1).endpoint::()\n\t\t\t}\n\t\t}\n\t};\n}\n\nimplOpenapiRouter!(RouterBuilder);\nimplOpenapiRouter!(ScopeBuilder);\n","traces":[{"line":40,"address":[6717328,6717638,6718352,6718662],"length":1,"stats":{"Line":2}},{"line":44,"address":[6717424,6718448,6718378,6717354],"length":1,"stats":{"Line":4}},{"line":45,"address":[6717429,6718453],"length":1,"stats":{"Line":2}},{"line":47,"address":[6717920,6718020,6718190,6719044,6719214,6719105,6718944,6718081],"length":1,"stats":{"Line":4}},{"line":48,"address":[6717493,6718688,6717903,6717664,6718517,6718927],"length":1,"stats":{"Line":4}},{"line":49,"address":[6717848,6718710,6718872,6717686],"length":1,"stats":{"Line":4}},{"line":51,"address":[6718720,6717696,6717755,6718779],"length":1,"stats":{"Line":4}},{"line":52,"address":[6718868,6717844],"length":1,"stats":{"Line":2}},{"line":54,"address":[6718902,6717878],"length":1,"stats":{"Line":2}},{"line":64,"address":[7849826,7849648,7849804],"length":1,"stats":{"Line":2}},{"line":65,"address":[7849777,7849678],"length":1,"stats":{"Line":4}},{"line":67,"address":[6723116],"length":1,"stats":{"Line":2}},{"line":68,"address":[6723066],"length":1,"stats":{"Line":2}},{"line":84,"address":[7850128,7850272,7849984,7849840],"length":1,"stats":{"Line":6}},{"line":85,"address":[7850114,7849863,7850258,7850295,7850151,7850402,7849970,7850007],"length":1,"stats":{"Line":7}},{"line":86,"address":[7850076,7850364,7850220,7849932],"length":1,"stats":{"Line":1}},{"line":88,"address":[7850036,7849892,7850324,7850180],"length":1,"stats":{"Line":6}},{"line":98,"address":[7856612,7881572,7857280,7882240,7888480,7869760,7894688,7875336,7882196,7869716,7887816,7863520,7857236,7875960,7888440,7862860,7876000,7869092,7894064,7863484,7851040],"length":1,"stats":{"Line":9}},{"line":99,"address":[6730885,6723541],"length":1,"stats":{"Line":9}},{"line":100,"address":[7863722,7863928,7857550,7876408,7863985,7882652,7869962,7851505,7851306,7857749,7888674,7888750,7882510,7870168,7882709,7870026,7888892,7888949,7876202,7851242,7857474,7870225,7857692,7851448,7876266,7863786,7876465,7882434],"length":1,"stats":{"Line":36}},{"line":101,"address":[7857180,7875904,7869660,7863428,7882140,7888384,7894632],"length":1,"stats":{"Line":9}},{"line":103,"address":[7875869,7857773,7870249,7876489,7882105,7863393,7851529,7882733,7888349,7888973,7894597,7869625,7857145,7864009],"length":1,"stats":{"Line":18}},{"line":106,"address":[7851847,7876807,7864053,7870704,7870477,7858095,7864464,7851573,7864237,7882965,7851757,7883055,7883192,7870567,7876533,7889205,7851984,7876944,7857821,7858005,7864327,7858232,7876717,7870293,7882781,7889021,7889295,7889432],"length":1,"stats":{"Line":36}},{"line":107,"address":[7863344,7889440,7894548,7882056,7875820,7864472,7876952,7870712,7869576,7883200,7888300,7858240,7851992,7857096],"length":1,"stats":{"Line":9}},{"line":108,"address":[7889701,7877151,7870911,7864733,7852191,7852253,7883461,7858501,7858439,7877213,7883399,7864671,7889639,7870973],"length":1,"stats":{"Line":18}},{"line":109,"address":[6724721,6732065],"length":1,"stats":{"Line":4}},{"line":111,"address":[7883467,7864804,7852259,7889776,7858572,7877284,7864739,7871048,7889707,7858507,7870979,7877219,7852328,7883532],"length":1,"stats":{"Line":18}},{"line":112,"address":[7858592,7864824,7877304,7871068,7852356,7889796,7883552],"length":1,"stats":{"Line":1}},{"line":114,"address":[7871054,7889851,7883538,7852399,7871123,7858578,7877290,7858647,7877359,7864810,7852334,7883607,7889782,7864879],"length":1,"stats":{"Line":18}},{"line":115,"address":[7864908,7871152,7858676,7883636,7877388,7852428,7889880],"length":1,"stats":{"Line":2}},{"line":116,"address":[6732309,6724965],"length":1,"stats":{"Line":2}},{"line":119,"address":[6724865,6725069,6732318,6732209,6724974,6732413],"length":1,"stats":{"Line":27}},{"line":121,"address":[8220303,8219833,8219280,8219775,8219808,8219305,8219953,8219425],"length":1,"stats":{"Line":62}},{"line":122,"address":[6737979,6730635],"length":1,"stats":{"Line":4}},{"line":124,"address":[6733232,6725173,6732517,6725268,6732612,6725888],"length":1,"stats":{"Line":23}},{"line":125,"address":[7853278,7859526,7878238,7859389,7872002,7884345,7884482,7871865,7890593,7890730,7865758,7853141,7865621,7878101],"length":1,"stats":{"Line":10}},{"line":128,"address":[7877915,7865435,7859203,7871679,7884159,7852955,7859697,7878409,7884653,7890407,7872173,7865929,7853449,7890901],"length":1,"stats":{"Line":18}},{"line":129,"address":[7890909,7866048,7891020,7872292,7859705,7878417,7853457,7853568,7865937,7872181,7884772,7859816,7878528,7884661],"length":1,"stats":{"Line":18}},{"line":130,"address":[7884871,7878627,7878675,7884919,7891119,7891167,7872391,7866147,7866195,7859915,7853715,7853667,7859963,7872439],"length":1,"stats":{"Line":18}},{"line":131,"address":[6733771,6734675,6726427,6727331],"length":1,"stats":{"Line":7}},{"line":132,"address":[6726663,6734007,6727556,6734900],"length":1,"stats":{"Line":1}},{"line":133,"address":[7854990,7866573,7891545,7892442,7867470,7872817,7860341,7879053,7861238,7879950,7885297,7873714,7886194,7854093],"length":1,"stats":{"Line":0}},{"line":134,"address":[7880178,7891781,7873942,7885533,7873053,7861466,7866809,7886422,7892670,7860577,7867698,7854329,7855218,7879289],"length":1,"stats":{"Line":0}},{"line":135,"address":[6727187,6734531,6733653,6726309],"length":1,"stats":{"Line":0}},{"line":136,"address":[7861580,7879395,7873159,7860683,7854435,7866915,7874056,7880292,7885639,7886536,7891887,7892784,7867812,7855332],"length":1,"stats":{"Line":0}},{"line":137,"address":[7885839,7855560,7892087,7886764,7879595,7854635,7868040,7893012,7867115,7873359,7860883,7874284,7861808,7880520],"length":1,"stats":{"Line":1}},{"line":138,"address":[7867015,7855446,7860783,7880406,7885739,7879495,7867926,7886650,7891987,7892898,7854535,7873259,7874170,7861694],"length":1,"stats":{"Line":0}},{"line":139,"address":[7872493,7878729,7866249,7853769,7860017,7884973,7891221],"length":1,"stats":{"Line":0}},{"line":140,"address":[7891261,7853809,7861973,7880685,7886929,7893406,7860057,7868434,7878769,7855954,7868205,7862043,7874449,7874678,7880755,7880914,7885013,7886999,7866289,7887158,7893177,7893247,7855725,7872533,7855795,7862202,7868275,7874519],"length":1,"stats":{"Line":0}},{"line":143,"address":[7868648,7881128,7874892,7862416,7856168,7887372,7893620],"length":1,"stats":{"Line":9}},{"line":145,"address":[7893832,7875104,7887584,7856380,7868860,7881340,7862628],"length":1,"stats":{"Line":9}}],"covered":44,"coverable":51},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","response","auth_result.rs"],"content":"use crate::{IntoResponseError, Response};\nuse gotham::{hyper::StatusCode, mime::TEXT_PLAIN_UTF_8};\nuse gotham_restful_derive::ResourceError;\n#[cfg(feature = \"openapi\")]\nuse openapi_type::{OpenapiSchema, OpenapiType};\n\n/// This is an error type that always yields a _403 Forbidden_ response. This type\n/// is best used in combination with [`AuthSuccess`] or [`AuthResult`].\n#[derive(Clone, Debug)]\npub struct AuthError(String);\n\nimpl AuthError {\n\tpub fn new>(msg: T) -> Self {\n\t\tSelf(msg.into())\n\t}\n}\n\nimpl IntoResponseError for AuthError {\n\t// TODO why does this need to be serde_json::Error ?!?\n\ttype Err = serde_json::Error;\n\n\tfn into_response_error(self) -> Result {\n\t\tOk(Response::new(\n\t\t\tStatusCode::FORBIDDEN,\n\t\t\tself.0,\n\t\t\tSome(TEXT_PLAIN_UTF_8)\n\t\t))\n\t}\n\n\t#[cfg(feature = \"openapi\")]\n\tfn status_codes() -> Vec {\n\t\tvec![StatusCode::FORBIDDEN]\n\t}\n\n\t#[cfg(feature = \"openapi\")]\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tassert_eq!(code, StatusCode::FORBIDDEN);\n\t\t as OpenapiType>::schema()\n\t}\n}\n\n/// This return type can be used to wrap any type implementing [IntoResponse](crate::IntoResponse)\n/// that can only be returned if the client is authenticated. Otherwise, an empty _403 Forbidden_\n/// response will be issued.\n///\n/// Use can look something like this (assuming the `auth` feature is enabled):\n///\n/// ```rust\n/// # #[macro_use] extern crate gotham_restful_derive;\n/// # #[cfg(feature = \"auth\")]\n/// # mod auth_feature_enabled {\n/// # use gotham::state::State;\n/// # use gotham_restful::*;\n/// # use serde::Deserialize;\n/// #\n/// # #[derive(Resource)]\n/// # #[resource(read_all)]\n/// # struct MyResource;\n/// #\n/// # #[derive(Clone, Deserialize)]\n/// # struct MyAuthData { exp : u64 }\n/// #\n/// #[read_all]\n/// fn read_all(auth: AuthStatus) -> AuthSuccess {\n/// \tlet auth_data = auth.ok()?;\n/// \t// do something\n/// \tOk(NoContent::default())\n/// }\n/// # }\n/// ```\npub type AuthSuccess = Result;\n\n/// This is an error type that either yields a _403 Forbidden_ response if produced\n/// from an authentication error, or delegates to another error type. This type is\n/// best used with [`AuthResult`].\n#[derive(Debug, Clone, ResourceError)]\npub enum AuthErrorOrOther {\n\tForbidden(#[from] AuthError),\n\n\t#[status(INTERNAL_SERVER_ERROR)]\n\t#[display(\"{0}\")]\n\tOther(E)\n}\n\nmod private {\n\tuse gotham::handler::HandlerError;\n\tpub trait Sealed {}\n\timpl> Sealed for E {}\n}\n\nimpl From for AuthErrorOrOther\nwhere\n\t// TODO https://github.com/msrd0/gotham_restful/issues/20\n\tF: private::Sealed + Into\n{\n\tfn from(err: F) -> Self {\n\t\tSelf::Other(err.into())\n\t}\n}\n\n/// This return type can be used to wrap any type implementing [IntoResponse](crate::IntoResponse)\n/// that can only be returned if the client is authenticated. Otherwise, an empty _403 Forbidden_\n/// response will be issued.\n///\n/// Use can look something like this (assuming the `auth` feature is enabled):\n///\n/// ```\n/// # #[macro_use] extern crate gotham_restful_derive;\n/// # #[cfg(feature = \"auth\")]\n/// # mod auth_feature_enabled {\n/// # use gotham::state::State;\n/// # use gotham_restful::*;\n/// # use serde::Deserialize;\n/// # use std::io;\n/// #\n/// # #[derive(Resource)]\n/// # #[resource(read_all)]\n/// # struct MyResource;\n/// #\n/// # #[derive(Clone, Deserialize)]\n/// # struct MyAuthData { exp : u64 }\n/// #\n/// #[read_all]\n/// fn read_all(auth: AuthStatus) -> AuthResult {\n/// \tlet auth_data = auth.ok()?;\n/// \t// do something\n/// \tOk(NoContent::default().into())\n/// }\n/// # }\n/// ```\npub type AuthResult = Result>;\n","traces":[{"line":13,"address":[6350112,6350016],"length":1,"stats":{"Line":0}},{"line":14,"address":[],"length":0,"stats":{"Line":0}},{"line":22,"address":[7774144],"length":1,"stats":{"Line":0}},{"line":23,"address":[8171117],"length":1,"stats":{"Line":0}},{"line":25,"address":[8139665],"length":1,"stats":{"Line":0}},{"line":26,"address":[6148980],"length":1,"stats":{"Line":0}},{"line":31,"address":[8441408],"length":1,"stats":{"Line":1}},{"line":32,"address":[8441489,8441421],"length":1,"stats":{"Line":1}},{"line":36,"address":[7774400],"length":1,"stats":{"Line":1}},{"line":38,"address":[6149443],"length":1,"stats":{"Line":1}},{"line":96,"address":[],"length":0,"stats":{"Line":0}},{"line":97,"address":[],"length":0,"stats":{"Line":0}}],"covered":4,"coverable":12},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","response","mod.rs"],"content":"use futures_util::future::{self, BoxFuture, FutureExt};\nuse gotham::{\n\thandler::HandlerError,\n\thyper::{\n\t\theader::{HeaderMap, HeaderName, HeaderValue},\n\t\tBody, StatusCode\n\t},\n\tmime::{Mime, APPLICATION_JSON, STAR_STAR}\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::{OpenapiSchema, OpenapiType};\nuse serde::Serialize;\n#[cfg(feature = \"errorlog\")]\nuse std::fmt::Display;\nuse std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin};\n\nmod auth_result;\n#[allow(unreachable_pub)]\npub use auth_result::{AuthError, AuthErrorOrOther, AuthResult, AuthSuccess};\n\nmod no_content;\n#[allow(unreachable_pub)]\npub use no_content::NoContent;\n\nmod raw;\n#[allow(unreachable_pub)]\npub use raw::Raw;\n\nmod redirect;\n#[allow(unreachable_pub)]\npub use redirect::Redirect;\n\nmod result;\n#[allow(unreachable_pub)]\npub use result::IntoResponseError;\n\nmod success;\n#[allow(unreachable_pub)]\npub use success::Success;\n\npub(crate) trait OrAllTypes {\n\tfn or_all_types(self) -> Vec;\n}\n\nimpl OrAllTypes for Option> {\n\tfn or_all_types(self) -> Vec {\n\t\tself.unwrap_or_else(|| vec![STAR_STAR])\n\t}\n}\n\n/// A response, used to create the final gotham response from.\n///\n/// This type is not meant to be used as the return type of endpoint handlers. While it can be\n/// freely used without the `openapi` feature, it is more complicated to use when you enable it,\n/// since this type does not store any schema information. You can attach schema information\n/// like so:\n///\n/// ```rust\n/// # #[cfg(feature = \"openapi\")] mod example {\n/// # use gotham::hyper::StatusCode;\n/// # use gotham_restful::*;\n/// # use openapi_type::*;\n/// fn schema(code: StatusCode) -> OpenapiSchema {\n/// \tassert_eq!(code, StatusCode::ACCEPTED);\n/// \t<()>::schema()\n/// }\n///\n/// fn status_codes() -> Vec {\n/// \tvec![StatusCode::ACCEPTED]\n/// }\n///\n/// #[create(schema = \"schema\", status_codes = \"status_codes\")]\n/// fn create(body: Raw>) {}\n/// # }\n/// ```\n#[derive(Debug)]\npub struct Response {\n\tpub(crate) status: StatusCode,\n\tpub(crate) body: Body,\n\tpub(crate) mime: Option,\n\tpub(crate) headers: HeaderMap\n}\n\nimpl Response {\n\t/// Create a new [Response] from raw data.\n\t#[must_use = \"Creating a response is pointless if you don't use it\"]\n\tpub fn new>(status: StatusCode, body: B, mime: Option) -> Self {\n\t\tSelf {\n\t\t\tstatus,\n\t\t\tbody: body.into(),\n\t\t\tmime,\n\t\t\theaders: Default::default()\n\t\t}\n\t}\n\n\t/// Create a [Response] with mime type json from already serialized data.\n\t#[must_use = \"Creating a response is pointless if you don't use it\"]\n\tpub fn json>(status: StatusCode, body: B) -> Self {\n\t\tSelf {\n\t\t\tstatus,\n\t\t\tbody: body.into(),\n\t\t\tmime: Some(APPLICATION_JSON),\n\t\t\theaders: Default::default()\n\t\t}\n\t}\n\n\t/// Create a _204 No Content_ [Response].\n\t#[must_use = \"Creating a response is pointless if you don't use it\"]\n\tpub fn no_content() -> Self {\n\t\tSelf {\n\t\t\tstatus: StatusCode::NO_CONTENT,\n\t\t\tbody: Body::empty(),\n\t\t\tmime: None,\n\t\t\theaders: Default::default()\n\t\t}\n\t}\n\n\t/// Create an empty _403 Forbidden_ [Response].\n\t#[must_use = \"Creating a response is pointless if you don't use it\"]\n\tpub fn forbidden() -> Self {\n\t\tSelf {\n\t\t\tstatus: StatusCode::FORBIDDEN,\n\t\t\tbody: Body::empty(),\n\t\t\tmime: None,\n\t\t\theaders: Default::default()\n\t\t}\n\t}\n\n\t/// Return the status code of this [Response].\n\tpub fn status(&self) -> StatusCode {\n\t\tself.status\n\t}\n\n\t/// Return the mime type of this [Response].\n\tpub fn mime(&self) -> Option<&Mime> {\n\t\tself.mime.as_ref()\n\t}\n\n\t/// Add an HTTP header to the [Response].\n\tpub fn header(&mut self, name: HeaderName, value: HeaderValue) {\n\t\tself.headers.insert(name, value);\n\t}\n\n\tpub(crate) fn with_headers(mut self, headers: HeaderMap) -> Self {\n\t\tself.headers = headers;\n\t\tself\n\t}\n\n\t#[cfg(test)]\n\tpub(crate) fn full_body(\n\t\tmut self\n\t) -> Result, ::Error> {\n\t\tuse futures_executor::block_on;\n\t\tuse gotham::hyper::body::to_bytes;\n\n\t\tlet bytes: &[u8] = &block_on(to_bytes(&mut self.body))?;\n\t\tOk(bytes.to_vec())\n\t}\n}\n\nimpl IntoResponse for Response {\n\ttype Err = Infallible;\n\n\tfn into_response(self) -> BoxFuture<'static, Result> {\n\t\tfuture::ok(self).boxed()\n\t}\n}\n\n/// This trait needs to be implemented by every type returned from an endpoint to\n/// to provide the response.\npub trait IntoResponse {\n\ttype Err: Into + Send + Sync + 'static;\n\n\t/// Turn this into a response that can be returned to the browser. This api will likely\n\t/// change in the future.\n\tfn into_response(self) -> BoxFuture<'static, Result>;\n\n\t/// Return a list of supported mime types.\n\tfn accepted_types() -> Option> {\n\t\tNone\n\t}\n}\n\n/// Additional details for [IntoResponse] to be used with an OpenAPI-aware router.\n#[cfg(feature = \"openapi\")]\npub trait ResponseSchema {\n\t/// All status codes returned by this response. Returns `[StatusCode::OK]` by default.\n\tfn status_codes() -> Vec {\n\t\tvec![StatusCode::OK]\n\t}\n\n\t/// Return the schema of the response for the given status code. The code may\n\t/// only be one that was previously returned by [Self::status_codes]. The\n\t/// implementation should panic if that is not the case.\n\tfn schema(code: StatusCode) -> OpenapiSchema;\n}\n\n#[cfg(feature = \"openapi\")]\nmod private {\n\tpub trait Sealed {}\n}\n\n/// A trait provided to convert a resource's result to json, and provide an OpenAPI schema to the\n/// router. This trait is implemented for all types that implement [IntoResponse] and\n/// [ResponseSchema].\n#[cfg(feature = \"openapi\")]\npub trait IntoResponseWithSchema: IntoResponse + ResponseSchema + private::Sealed {}\n\n#[cfg(feature = \"openapi\")]\nimpl private::Sealed for R {}\n\n#[cfg(feature = \"openapi\")]\nimpl IntoResponseWithSchema for R {}\n\n/// The default json returned on an 500 Internal Server Error.\n#[derive(Debug, Serialize)]\n#[cfg_attr(feature = \"openapi\", derive(OpenapiType))]\npub(crate) struct ResourceError {\n\t/// This is always `true` and can be used to detect an error response without looking at the\n\t/// HTTP status code.\n\terror: bool,\n\t/// The error message.\n\tmessage: String\n}\n\nimpl From for ResourceError {\n\tfn from(message: T) -> Self {\n\t\tSelf {\n\t\t\terror: true,\n\t\t\tmessage: message.to_string()\n\t\t}\n\t}\n}\n\n#[cfg(feature = \"errorlog\")]\nfn errorlog(e: E) {\n\terror!(\"The handler encountered an error: {e}\");\n}\n\n#[cfg(not(feature = \"errorlog\"))]\nfn errorlog(_e: E) {}\n\nfn handle_error(e: E) -> Pin> + Send>>\nwhere\n\tE: Debug + IntoResponseError\n{\n\tlet msg = format!(\"{e:?}\");\n\tlet res = e.into_response_error();\n\tmatch &res {\n\t\tOk(res) if res.status.is_server_error() => errorlog(msg),\n\t\tErr(err) => {\n\t\t\terrorlog(msg);\n\t\t\terrorlog(format!(\"{err:?}\"));\n\t\t},\n\t\t_ => {}\n\t};\n\tfuture::ready(res).boxed()\n}\n\nimpl IntoResponse for Pin + Send>>\nwhere\n\tRes: IntoResponse + 'static\n{\n\ttype Err = Res::Err;\n\n\tfn into_response(self) -> Pin> + Send>> {\n\t\tself.then(IntoResponse::into_response).boxed()\n\t}\n\n\tfn accepted_types() -> Option> {\n\t\tRes::accepted_types()\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for Pin + Send>>\nwhere\n\tRes: ResponseSchema\n{\n\tfn status_codes() -> Vec {\n\t\tRes::status_codes()\n\t}\n\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tRes::schema(code)\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse futures_executor::block_on;\n\tuse thiserror::Error;\n\n\t#[derive(Debug, Default, Deserialize, Serialize)]\n\t#[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n\tstruct Msg {\n\t\tmsg: String\n\t}\n\n\t#[derive(Debug, Default, Error)]\n\t#[error(\"An Error\")]\n\tstruct MsgError;\n\n\t#[test]\n\tfn result_from_future() {\n\t\tlet nc = NoContent::default();\n\t\tlet res = block_on(nc.into_response()).unwrap();\n\n\t\tlet fut_nc = async move { NoContent::default() }.boxed();\n\t\tlet fut_res = block_on(fut_nc.into_response()).unwrap();\n\n\t\tassert_eq!(res.status, fut_res.status);\n\t\tassert_eq!(res.mime, fut_res.mime);\n\t\tassert_eq!(res.full_body().unwrap(), fut_res.full_body().unwrap());\n\t}\n}\n","traces":[{"line":46,"address":[6018736],"length":1,"stats":{"Line":3}},{"line":47,"address":[8330896,8330910],"length":1,"stats":{"Line":9}},{"line":87,"address":[7184887,7184915,7184496],"length":1,"stats":{"Line":8}},{"line":90,"address":[8060863,8061295],"length":1,"stats":{"Line":9}},{"line":92,"address":[6559757],"length":1,"stats":{"Line":9}},{"line":98,"address":[7847789,7847814,7847488],"length":1,"stats":{"Line":2}},{"line":101,"address":[5752872],"length":1,"stats":{"Line":2}},{"line":102,"address":[],"length":0,"stats":{"Line":2}},{"line":103,"address":[],"length":0,"stats":{"Line":2}},{"line":109,"address":[7573090,7572880,7573065],"length":1,"stats":{"Line":3}},{"line":112,"address":[6155237],"length":1,"stats":{"Line":4}},{"line":114,"address":[8061699],"length":1,"stats":{"Line":4}},{"line":120,"address":[8030496,8030681,8030706],"length":1,"stats":{"Line":0}},{"line":123,"address":[7665013],"length":1,"stats":{"Line":0}},{"line":125,"address":[7813107],"length":1,"stats":{"Line":0}},{"line":130,"address":[8332336],"length":1,"stats":{"Line":1}},{"line":131,"address":[8332341],"length":1,"stats":{"Line":1}},{"line":135,"address":[8030736],"length":1,"stats":{"Line":1}},{"line":136,"address":[7665237],"length":1,"stats":{"Line":1}},{"line":140,"address":[7573376],"length":1,"stats":{"Line":2}},{"line":141,"address":[6117058],"length":1,"stats":{"Line":2}},{"line":144,"address":[7813617,7813408],"length":1,"stats":{"Line":4}},{"line":145,"address":[6155808,6155938],"length":1,"stats":{"Line":8}},{"line":146,"address":[6155965],"length":1,"stats":{"Line":4}},{"line":150,"address":[6117727,6117748,6117328],"length":1,"stats":{"Line":1}},{"line":156,"address":[6117415,6117358,6117625],"length":1,"stats":{"Line":6}},{"line":157,"address":[6117641],"length":1,"stats":{"Line":2}},{"line":164,"address":[7665568],"length":1,"stats":{"Line":0}},{"line":165,"address":[6156026],"length":1,"stats":{"Line":0}},{"line":179,"address":[8062528],"length":1,"stats":{"Line":7}},{"line":180,"address":[],"length":0,"stats":{"Line":7}},{"line":188,"address":[6481088],"length":1,"stats":{"Line":4}},{"line":189,"address":[6444413,6444480],"length":1,"stats":{"Line":4}},{"line":227,"address":[6607359,6607328],"length":1,"stats":{"Line":2}},{"line":230,"address":[6490573],"length":1,"stats":{"Line":2}},{"line":236,"address":[7848289,7847840],"length":1,"stats":{"Line":1}},{"line":237,"address":[],"length":0,"stats":{"Line":3}},{"line":243,"address":[7844656,7845725,7845753],"length":1,"stats":{"Line":1}},{"line":247,"address":[7844928,7844829],"length":1,"stats":{"Line":2}},{"line":248,"address":[7844936,7845035],"length":1,"stats":{"Line":2}},{"line":249,"address":[],"length":0,"stats":{"Line":1}},{"line":250,"address":[6122322,6119034,6120178,6119106,6121015,6121250,6122087,6118871,6122250,6119943,6121178,6120106],"length":1,"stats":{"Line":3}},{"line":251,"address":[],"length":0,"stats":{"Line":0}},{"line":252,"address":[],"length":0,"stats":{"Line":0}},{"line":253,"address":[6119267,6120339,6121540,6122612,6121411,6120468,6122483,6119396],"length":1,"stats":{"Line":0}},{"line":255,"address":[],"length":0,"stats":{"Line":0}},{"line":257,"address":[],"length":0,"stats":{"Line":3}},{"line":266,"address":[],"length":0,"stats":{"Line":1}},{"line":267,"address":[],"length":0,"stats":{"Line":1}},{"line":270,"address":[],"length":0,"stats":{"Line":0}},{"line":271,"address":[],"length":0,"stats":{"Line":0}},{"line":280,"address":[],"length":0,"stats":{"Line":0}},{"line":281,"address":[],"length":0,"stats":{"Line":0}},{"line":284,"address":[],"length":0,"stats":{"Line":0}},{"line":285,"address":[],"length":0,"stats":{"Line":0}}],"covered":40,"coverable":55},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","response","no_content.rs"],"content":"use super::{handle_error, IntoResponse};\n#[cfg(feature = \"openapi\")]\nuse crate::ResponseSchema;\nuse crate::{IntoResponseError, Response};\nuse futures_util::{future, future::FutureExt};\n#[cfg(feature = \"openapi\")]\nuse gotham::hyper::StatusCode;\nuse gotham::{\n\thyper::header::{HeaderMap, HeaderValue, IntoHeaderName},\n\tmime::Mime\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::{OpenapiSchema, OpenapiType};\nuse std::{fmt::Debug, future::Future, pin::Pin};\n\n/// This is the return type of a resource that doesn't actually return something. It will result\n/// in a _204 No Content_ answer by default. You don't need to use this type directly if using\n/// the function attributes:\n///\n/// ```\n/// # #[macro_use] extern crate gotham_restful_derive;\n/// # mod doc_tests_are_broken {\n/// # use gotham::state::State;\n/// # use gotham_restful::*;\n/// #\n/// # #[derive(Resource)]\n/// # #[resource(read_all)]\n/// # struct MyResource;\n/// #\n/// #[read_all]\n/// fn read_all() {\n/// \t// do something\n/// }\n/// # }\n/// ```\n#[derive(Clone, Debug, Default)]\npub struct NoContent {\n\theaders: HeaderMap\n}\n\nimpl From<()> for NoContent {\n\tfn from(_: ()) -> Self {\n\t\tSelf::default()\n\t}\n}\n\nimpl NoContent {\n\t/// Set a custom HTTP header. If a header with this name was set before, its value is being updated.\n\tpub fn header(&mut self, name: K, value: HeaderValue) {\n\t\tself.headers.insert(name, value);\n\t}\n\n\t/// Allow manipulating HTTP headers.\n\tpub fn headers_mut(&mut self) -> &mut HeaderMap {\n\t\t&mut self.headers\n\t}\n}\n\nimpl IntoResponse for NoContent {\n\t// TODO this shouldn't be a serde_json::Error\n\ttype Err = serde_json::Error; // just for easier handling of `Result`\n\n\t/// This will always be a _204 No Content_ together with an empty string.\n\tfn into_response(self) -> Pin> + Send>> {\n\t\tfuture::ok(Response::no_content().with_headers(self.headers)).boxed()\n\t}\n\n\tfn accepted_types() -> Option> {\n\t\tSome(Vec::new())\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for NoContent {\n\tfn status_codes() -> Vec {\n\t\tvec![StatusCode::NO_CONTENT]\n\t}\n\n\t/// Returns the schema of the `()` type.\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tassert_eq!(code, StatusCode::NO_CONTENT);\n\t\t<()>::schema()\n\t}\n}\n\nimpl IntoResponse for Result\nwhere\n\tE: Debug + IntoResponseError\n{\n\ttype Err = serde_json::Error;\n\n\tfn into_response(\n\t\tself\n\t) -> Pin> + Send>> {\n\t\tmatch self {\n\t\t\tOk(nc) => nc.into_response(),\n\t\t\tErr(e) => handle_error(e)\n\t\t}\n\t}\n\n\tfn accepted_types() -> Option> {\n\t\tNoContent::accepted_types()\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for Result\nwhere\n\tE: Debug + IntoResponseError\n{\n\tfn status_codes() -> Vec {\n\t\tlet mut status_codes = E::status_codes();\n\t\tstatus_codes.push(StatusCode::NO_CONTENT);\n\t\tstatus_codes\n\t}\n\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tmatch code {\n\t\t\tStatusCode::NO_CONTENT => ::schema(StatusCode::NO_CONTENT),\n\t\t\tcode => E::schema(code)\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse futures_executor::block_on;\n\tuse gotham::hyper::{header::ACCESS_CONTROL_ALLOW_ORIGIN, StatusCode};\n\tuse thiserror::Error;\n\n\t#[derive(Debug, Default, Error)]\n\t#[error(\"An Error\")]\n\tstruct MsgError;\n\n\t#[test]\n\tfn no_content_has_empty_response() {\n\t\tlet no_content = NoContent::default();\n\t\tlet res = block_on(no_content.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::NO_CONTENT);\n\t\tassert_eq!(res.mime, None);\n\t\tassert_eq!(res.full_body().unwrap(), &[] as &[u8]);\n\n\t\t#[cfg(feature = \"openapi\")]\n\t\tassert_eq!(NoContent::status_codes(), vec![StatusCode::NO_CONTENT]);\n\t}\n\n\t#[test]\n\tfn no_content_result() {\n\t\tlet no_content: Result = Ok(NoContent::default());\n\t\tlet res = block_on(no_content.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::NO_CONTENT);\n\t\tassert_eq!(res.mime, None);\n\t\tassert_eq!(res.full_body().unwrap(), &[] as &[u8]);\n\n\t\t#[cfg(feature = \"openapi\")]\n\t\tassert_eq!(>::status_codes(), vec![\n\t\t\tStatusCode::INTERNAL_SERVER_ERROR,\n\t\t\tStatusCode::NO_CONTENT\n\t\t]);\n\t}\n\n\t#[test]\n\tfn no_content_custom_headers() {\n\t\tlet mut no_content = NoContent::default();\n\t\tno_content.header(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static(\"*\"));\n\t\tlet res = block_on(no_content.into_response()).expect(\"didn't expect error response\");\n\t\tlet cors = res.headers.get(ACCESS_CONTROL_ALLOW_ORIGIN);\n\t\tassert_eq!(cors.map(|value| value.to_str().unwrap()), Some(\"*\"));\n\t}\n}\n","traces":[{"line":42,"address":[6442480],"length":1,"stats":{"Line":0}},{"line":43,"address":[8013452],"length":1,"stats":{"Line":0}},{"line":49,"address":[6442512],"length":1,"stats":{"Line":1}},{"line":50,"address":[],"length":0,"stats":{"Line":1}},{"line":54,"address":[7773504],"length":1,"stats":{"Line":0}},{"line":64,"address":[8013763,8013488,8013792],"length":1,"stats":{"Line":2}},{"line":65,"address":[8230924,8231005],"length":1,"stats":{"Line":6}},{"line":68,"address":[8532848],"length":1,"stats":{"Line":3}},{"line":69,"address":[8231245],"length":1,"stats":{"Line":5}},{"line":75,"address":[8262688],"length":1,"stats":{"Line":2}},{"line":76,"address":[8532993,8532925],"length":1,"stats":{"Line":2}},{"line":80,"address":[7865904],"length":1,"stats":{"Line":2}},{"line":82,"address":[6356472],"length":1,"stats":{"Line":2}},{"line":92,"address":[6511312],"length":1,"stats":{"Line":1}},{"line":95,"address":[],"length":0,"stats":{"Line":2}},{"line":96,"address":[],"length":0,"stats":{"Line":1}},{"line":97,"address":[],"length":0,"stats":{"Line":0}},{"line":101,"address":[],"length":0,"stats":{"Line":0}},{"line":102,"address":[],"length":0,"stats":{"Line":0}},{"line":111,"address":[],"length":0,"stats":{"Line":1}},{"line":112,"address":[],"length":0,"stats":{"Line":1}},{"line":113,"address":[],"length":0,"stats":{"Line":1}},{"line":114,"address":[],"length":0,"stats":{"Line":1}},{"line":117,"address":[],"length":0,"stats":{"Line":0}},{"line":118,"address":[],"length":0,"stats":{"Line":0}},{"line":119,"address":[],"length":0,"stats":{"Line":0}},{"line":120,"address":[],"length":0,"stats":{"Line":0}}],"covered":17,"coverable":27},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","response","raw.rs"],"content":"use super::{handle_error, IntoResponse, IntoResponseError};\nuse crate::{types::ResourceType, FromBody, RequestBody, Response};\n#[cfg(feature = \"openapi\")]\nuse crate::{IntoResponseWithSchema, ResponseSchema};\nuse futures_core::future::Future;\nuse futures_util::{future, future::FutureExt};\nuse gotham::{\n\thyper::{\n\t\tbody::{Body, Bytes},\n\t\tStatusCode\n\t},\n\tmime::Mime\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::{OpenapiSchema, OpenapiType, Visitor};\nuse serde_json::error::Error as SerdeJsonError;\nuse std::{convert::Infallible, fmt::Debug, pin::Pin};\n\n/// This type can be used both as a raw request body, as well as as a raw response. However, all types\n/// of request bodies are accepted by this type. It is therefore recommended to derive your own type\n/// from [RequestBody] and only use this when you need to return a raw response. This is a usage\n/// example that simply returns its body:\n///\n/// ```rust,no_run\n/// # #[macro_use] extern crate gotham_restful_derive;\n/// # use gotham::router::builder::*;\n/// # use gotham_restful::*;\n/// #[derive(Resource)]\n/// #[resource(create)]\n/// struct ImageResource;\n///\n/// #[create]\n/// fn create(body: Raw>) -> Raw> {\n/// \tbody\n/// }\n/// # fn main() {\n/// # \tgotham::start(\"127.0.0.1:8080\", build_simple_router(|route| {\n/// # \t\troute.resource::(\"img\");\n/// # \t}));\n/// # }\n/// ```\n#[derive(Debug)]\npub struct Raw {\n\tpub raw: T,\n\tpub mime: Mime\n}\n\nimpl Raw {\n\tpub fn new(raw: T, mime: Mime) -> Self {\n\t\tSelf { raw, mime }\n\t}\n}\n\nimpl AsMut for Raw\nwhere\n\tT: AsMut\n{\n\tfn as_mut(&mut self) -> &mut U {\n\t\tself.raw.as_mut()\n\t}\n}\n\nimpl AsRef for Raw\nwhere\n\tT: AsRef\n{\n\tfn as_ref(&self) -> &U {\n\t\tself.raw.as_ref()\n\t}\n}\n\nimpl Clone for Raw {\n\tfn clone(&self) -> Self {\n\t\tSelf {\n\t\t\traw: self.raw.clone(),\n\t\t\tmime: self.mime.clone()\n\t\t}\n\t}\n}\n\nimpl From<&'a [u8]>> FromBody for Raw {\n\ttype Err = Infallible;\n\n\tfn from_body(body: Bytes, mime: Mime) -> Result {\n\t\tOk(Self::new(body.as_ref().into(), mime))\n\t}\n}\n\nimpl RequestBody for Raw where Raw: FromBody + ResourceType {}\n\n#[cfg(feature = \"openapi\")]\nimpl OpenapiType for Raw {\n\tfn visit_type(visitor: &mut V) {\n\t\tvisitor.visit_binary()\n\t}\n}\n\nimpl> IntoResponse for Raw\nwhere\n\tSelf: Send\n{\n\ttype Err = SerdeJsonError; // just for easier handling of `Result, E>`\n\n\tfn into_response(\n\t\tself\n\t) -> Pin> + Send>> {\n\t\tfuture::ok(Response::new(StatusCode::OK, self.raw, Some(self.mime))).boxed()\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl> ResponseSchema for Raw\nwhere\n\tSelf: Send\n{\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tassert_eq!(code, StatusCode::OK);\n\t\t::schema()\n\t}\n}\n\nimpl IntoResponse for Result, E>\nwhere\n\tRaw: IntoResponse,\n\tE: Debug + IntoResponseError as IntoResponse>::Err>\n{\n\ttype Err = E::Err;\n\n\tfn into_response(self) -> Pin> + Send>> {\n\t\tmatch self {\n\t\t\tOk(raw) => raw.into_response(),\n\t\t\tErr(e) => handle_error(e)\n\t\t}\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for Result, E>\nwhere\n\tRaw: IntoResponseWithSchema,\n\tE: Debug + IntoResponseError as IntoResponse>::Err>\n{\n\tfn status_codes() -> Vec {\n\t\tlet mut status_codes = E::status_codes();\n\t\tstatus_codes.push(StatusCode::OK);\n\t\tstatus_codes\n\t}\n\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tmatch code {\n\t\t\tStatusCode::OK => as ResponseSchema>::schema(StatusCode::OK),\n\t\t\tcode => E::schema(code)\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse futures_executor::block_on;\n\tuse gotham::mime::TEXT_PLAIN;\n\tuse thiserror::Error;\n\n\t#[derive(Debug, Default, Error)]\n\t#[error(\"An Error\")]\n\tstruct MsgError;\n\n\t#[test]\n\tfn raw_response() {\n\t\tlet msg = \"Test\";\n\t\tlet raw = Raw::new(msg, TEXT_PLAIN);\n\t\tlet res = block_on(raw.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::OK);\n\t\tassert_eq!(res.mime, Some(TEXT_PLAIN));\n\t\tassert_eq!(res.full_body().unwrap(), msg.as_bytes());\n\n\t\t#[cfg(feature = \"openapi\")]\n\t\tassert_eq!(>::status_codes(), vec![StatusCode::OK]);\n\t}\n\n\t#[test]\n\tfn raw_result() {\n\t\tlet msg = \"Test\";\n\t\tlet raw: Result, MsgError> = Ok(Raw::new(msg, TEXT_PLAIN));\n\t\tlet res = block_on(raw.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::OK);\n\t\tassert_eq!(res.mime, Some(TEXT_PLAIN));\n\t\tassert_eq!(res.full_body().unwrap(), msg.as_bytes());\n\n\t\t#[cfg(feature = \"openapi\")]\n\t\tassert_eq!(, MsgError>>::status_codes(), vec![\n\t\t\tStatusCode::INTERNAL_SERVER_ERROR,\n\t\t\tStatusCode::OK\n\t\t]);\n\t}\n}\n","traces":[{"line":49,"address":[6398928],"length":1,"stats":{"Line":7}},{"line":58,"address":[],"length":0,"stats":{"Line":0}},{"line":59,"address":[],"length":0,"stats":{"Line":0}},{"line":67,"address":[],"length":0,"stats":{"Line":0}},{"line":68,"address":[],"length":0,"stats":{"Line":0}},{"line":73,"address":[],"length":0,"stats":{"Line":0}},{"line":75,"address":[],"length":0,"stats":{"Line":0}},{"line":76,"address":[],"length":0,"stats":{"Line":0}},{"line":84,"address":[6472512,6472845],"length":1,"stats":{"Line":1}},{"line":85,"address":[6472547,6472631],"length":1,"stats":{"Line":2}},{"line":93,"address":[7262128],"length":1,"stats":{"Line":4}},{"line":94,"address":[7847845],"length":1,"stats":{"Line":4}},{"line":104,"address":[6299632],"length":1,"stats":{"Line":5}},{"line":107,"address":[],"length":0,"stats":{"Line":5}},{"line":116,"address":[],"length":0,"stats":{"Line":3}},{"line":118,"address":[6443955],"length":1,"stats":{"Line":3}},{"line":129,"address":[6511568],"length":1,"stats":{"Line":1}},{"line":130,"address":[],"length":0,"stats":{"Line":2}},{"line":131,"address":[6511604],"length":1,"stats":{"Line":1}},{"line":132,"address":[],"length":0,"stats":{"Line":0}},{"line":143,"address":[],"length":0,"stats":{"Line":1}},{"line":144,"address":[],"length":0,"stats":{"Line":1}},{"line":145,"address":[],"length":0,"stats":{"Line":1}},{"line":146,"address":[],"length":0,"stats":{"Line":1}},{"line":149,"address":[],"length":0,"stats":{"Line":0}},{"line":150,"address":[],"length":0,"stats":{"Line":0}},{"line":151,"address":[],"length":0,"stats":{"Line":0}},{"line":152,"address":[],"length":0,"stats":{"Line":0}}],"covered":16,"coverable":28},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","response","redirect.rs"],"content":"use super::{handle_error, IntoResponse};\nuse crate::{IntoResponseError, Response};\n#[cfg(feature = \"openapi\")]\nuse crate::{NoContent, ResponseSchema};\nuse futures_util::future::{BoxFuture, FutureExt, TryFutureExt};\nuse gotham::hyper::{\n\theader::{InvalidHeaderValue, LOCATION},\n\tBody, StatusCode\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::OpenapiSchema;\nuse std::{error::Error as StdError, fmt::Debug};\nuse thiserror::Error;\n\n/// This is the return type of a resource that only returns a redirect. It will result\n/// in a _303 See Other_ answer, meaning the redirect will always result in a GET request\n/// on the target.\n///\n/// ```\n/// # #[macro_use] extern crate gotham_restful_derive;\n/// # mod doc_tests_are_broken {\n/// # use gotham::state::State;\n/// # use gotham_restful::*;\n/// #\n/// # #[derive(Resource)]\n/// # #[resource(read_all)]\n/// # struct MyResource;\n/// #\n/// #[read_all]\n/// fn read_all() -> Redirect {\n/// \tRedirect {\n/// \t\tto: \"http://localhost:8080/cool/new/location\".to_owned()\n/// \t}\n/// }\n/// # }\n/// ```\n#[derive(Clone, Debug, Default)]\npub struct Redirect {\n\tpub to: String\n}\n\nimpl IntoResponse for Redirect {\n\ttype Err = InvalidHeaderValue;\n\n\tfn into_response(self) -> BoxFuture<'static, Result> {\n\t\tasync move {\n\t\t\tlet mut res = Response::new(StatusCode::SEE_OTHER, Body::empty(), None);\n\t\t\tres.header(LOCATION, self.to.parse()?);\n\t\t\tOk(res)\n\t\t}\n\t\t.boxed()\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for Redirect {\n\tfn status_codes() -> Vec {\n\t\tvec![StatusCode::SEE_OTHER]\n\t}\n\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tassert_eq!(code, StatusCode::SEE_OTHER);\n\t\t::schema(StatusCode::NO_CONTENT)\n\t}\n}\n\n// private type due to parent mod\n#[derive(Debug, Error)]\npub enum RedirectError {\n\t#[error(\"{0}\")]\n\tInvalidLocation(#[from] InvalidHeaderValue),\n\t#[error(\"{0}\")]\n\tOther(#[source] E)\n}\n\n#[allow(ambiguous_associated_items)] // an enum variant is not a type. never.\nimpl IntoResponse for Result\nwhere\n\tE: Debug + IntoResponseError,\n\t::Err: StdError + Sync\n{\n\ttype Err = RedirectError<::Err>;\n\n\tfn into_response(self) -> BoxFuture<'static, Result> {\n\t\tmatch self {\n\t\t\tOk(nc) => nc.into_response().map_err(Into::into).boxed(),\n\t\t\tErr(e) => handle_error(e).map_err(RedirectError::Other).boxed()\n\t\t}\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for Result\nwhere\n\tE: Debug + IntoResponseError,\n\t::Err: StdError + Sync\n{\n\tfn status_codes() -> Vec {\n\t\tlet mut status_codes = E::status_codes();\n\t\tstatus_codes.push(StatusCode::SEE_OTHER);\n\t\tstatus_codes\n\t}\n\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tmatch code {\n\t\t\tStatusCode::SEE_OTHER => ::schema(StatusCode::SEE_OTHER),\n\t\t\tcode => E::schema(code)\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse futures_executor::block_on;\n\tuse gotham::hyper::StatusCode;\n\tuse thiserror::Error;\n\n\t#[derive(Debug, Default, Error)]\n\t#[error(\"An Error\")]\n\tstruct MsgError;\n\n\t#[test]\n\tfn redirect_response() {\n\t\tlet redir = Redirect {\n\t\t\tto: \"http://localhost/foo\".to_owned()\n\t\t};\n\t\tlet res = block_on(redir.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::SEE_OTHER);\n\t\tassert_eq!(res.mime, None);\n\t\tassert_eq!(\n\t\t\tres.headers.get(LOCATION).map(|hdr| hdr.to_str().unwrap()),\n\t\t\tSome(\"http://localhost/foo\")\n\t\t);\n\t\tassert_eq!(res.full_body().unwrap(), &[] as &[u8]);\n\n\t\t#[cfg(feature = \"openapi\")]\n\t\tassert_eq!(Redirect::status_codes(), vec![StatusCode::SEE_OTHER]);\n\t}\n\n\t#[test]\n\tfn redirect_result() {\n\t\tlet redir: Result = Ok(Redirect {\n\t\t\tto: \"http://localhost/foo\".to_owned()\n\t\t});\n\t\tlet res = block_on(redir.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::SEE_OTHER);\n\t\tassert_eq!(res.mime, None);\n\t\tassert_eq!(\n\t\t\tres.headers.get(LOCATION).map(|hdr| hdr.to_str().unwrap()),\n\t\t\tSome(\"http://localhost/foo\")\n\t\t);\n\t\tassert_eq!(res.full_body().unwrap(), &[] as &[u8]);\n\n\t\t#[cfg(feature = \"openapi\")]\n\t\tassert_eq!(>::status_codes(), vec![\n\t\t\tStatusCode::INTERNAL_SERVER_ERROR,\n\t\t\tStatusCode::SEE_OTHER\n\t\t]);\n\t}\n}\n","traces":[{"line":45,"address":[7847856],"length":1,"stats":{"Line":1}},{"line":46,"address":[6191105,6190302,6190359,6191143,6190903,6190228,6190272],"length":1,"stats":{"Line":3}},{"line":47,"address":[7608000,7608086],"length":1,"stats":{"Line":4}},{"line":48,"address":[6191086,6190471,6191133,6190992],"length":1,"stats":{"Line":2}},{"line":49,"address":[6190861],"length":1,"stats":{"Line":2}},{"line":57,"address":[8066240],"length":1,"stats":{"Line":1}},{"line":58,"address":[7700749,7700817],"length":1,"stats":{"Line":1}},{"line":61,"address":[7700848],"length":1,"stats":{"Line":0}},{"line":63,"address":[6191416],"length":1,"stats":{"Line":0}},{"line":84,"address":[],"length":0,"stats":{"Line":1}},{"line":85,"address":[],"length":0,"stats":{"Line":2}},{"line":86,"address":[],"length":0,"stats":{"Line":1}},{"line":87,"address":[6511936],"length":1,"stats":{"Line":0}},{"line":98,"address":[],"length":0,"stats":{"Line":1}},{"line":99,"address":[],"length":0,"stats":{"Line":1}},{"line":100,"address":[],"length":0,"stats":{"Line":1}},{"line":101,"address":[],"length":0,"stats":{"Line":1}},{"line":104,"address":[],"length":0,"stats":{"Line":0}},{"line":105,"address":[],"length":0,"stats":{"Line":0}},{"line":106,"address":[],"length":0,"stats":{"Line":0}},{"line":107,"address":[],"length":0,"stats":{"Line":0}}],"covered":14,"coverable":21},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","response","result.rs"],"content":"use super::{handle_error, IntoResponse, ResourceError};\n#[cfg(feature = \"openapi\")]\nuse crate::ResponseSchema;\nuse crate::{Response, ResponseBody, Success};\nuse futures_core::future::Future;\nuse gotham::{\n\tanyhow::Error,\n\thyper::StatusCode,\n\tmime::{Mime, APPLICATION_JSON}\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::{OpenapiSchema, OpenapiType};\nuse std::{fmt::Debug, pin::Pin};\n\npub trait IntoResponseError {\n\ttype Err: Debug + Send + 'static;\n\n\tfn into_response_error(self) -> Result;\n\n\t#[cfg(feature = \"openapi\")]\n\tfn status_codes() -> Vec;\n\n\t#[cfg(feature = \"openapi\")]\n\tfn schema(code: StatusCode) -> OpenapiSchema;\n}\n\nimpl IntoResponseError for E\nwhere\n\tE: Into\n{\n\ttype Err = serde_json::Error;\n\n\tfn into_response_error(self) -> Result {\n\t\tlet err: Error = self.into();\n\t\tlet err: ResourceError = err.into();\n\t\tOk(Response::json(\n\t\t\tStatusCode::INTERNAL_SERVER_ERROR,\n\t\t\tserde_json::to_string(&err)?\n\t\t))\n\t}\n\n\t#[cfg(feature = \"openapi\")]\n\tfn status_codes() -> Vec {\n\t\tvec![StatusCode::INTERNAL_SERVER_ERROR]\n\t}\n\n\t#[cfg(feature = \"openapi\")]\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tassert_eq!(code, StatusCode::INTERNAL_SERVER_ERROR);\n\t\tResourceError::schema()\n\t}\n}\n\nimpl IntoResponse for Result\nwhere\n\tR: ResponseBody,\n\tE: Debug + IntoResponseError\n{\n\ttype Err = E::Err;\n\n\tfn into_response(self) -> Pin> + Send>> {\n\t\tmatch self {\n\t\t\tOk(r) => Success::from(r).into_response(),\n\t\t\tErr(e) => handle_error(e)\n\t\t}\n\t}\n\n\tfn accepted_types() -> Option> {\n\t\tSome(vec![APPLICATION_JSON])\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for Result\nwhere\n\tR: ResponseBody,\n\tE: Debug + IntoResponseError\n{\n\tfn status_codes() -> Vec {\n\t\tlet mut status_codes = E::status_codes();\n\t\tstatus_codes.push(StatusCode::OK);\n\t\tstatus_codes\n\t}\n\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tmatch code {\n\t\t\tStatusCode::OK => R::schema(),\n\t\t\tcode => E::schema(code)\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse crate::response::OrAllTypes;\n\tuse futures_executor::block_on;\n\tuse thiserror::Error;\n\n\t#[derive(Debug, Default, Deserialize, Serialize)]\n\t#[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n\tstruct Msg {\n\t\tmsg: String\n\t}\n\n\t#[derive(Debug, Default, Error)]\n\t#[error(\"An Error\")]\n\tstruct MsgError;\n\n\t#[test]\n\tfn result_ok() {\n\t\tlet ok: Result = Ok(Msg::default());\n\t\tlet res = block_on(ok.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::OK);\n\t\tassert_eq!(res.mime, Some(APPLICATION_JSON));\n\t\tassert_eq!(res.full_body().unwrap(), br#\"{\"msg\":\"\"}\"#);\n\t}\n\n\t#[test]\n\tfn result_err() {\n\t\tlet err: Result = Err(MsgError);\n\t\tlet res = block_on(err.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::INTERNAL_SERVER_ERROR);\n\t\tassert_eq!(res.mime, Some(APPLICATION_JSON));\n\t\tassert_eq!(\n\t\t\tres.full_body().unwrap(),\n\t\t\tformat!(r#\"{{\"error\":true,\"message\":\"{}\"}}\"#, MsgError).as_bytes()\n\t\t);\n\t}\n\n\t#[test]\n\tfn success_accepts_json() {\n\t\tassert!(>::accepted_types()\n\t\t\t.or_all_types()\n\t\t\t.contains(&APPLICATION_JSON))\n\t}\n}\n","traces":[{"line":33,"address":[6444346,6443984],"length":1,"stats":{"Line":2}},{"line":34,"address":[6444001],"length":1,"stats":{"Line":2}},{"line":35,"address":[],"length":0,"stats":{"Line":2}},{"line":36,"address":[5763207,5763122],"length":1,"stats":{"Line":4}},{"line":37,"address":[],"length":0,"stats":{"Line":0}},{"line":38,"address":[],"length":0,"stats":{"Line":4}},{"line":43,"address":[6268496],"length":1,"stats":{"Line":3}},{"line":44,"address":[6151197,6151264],"length":1,"stats":{"Line":3}},{"line":48,"address":[5763296],"length":1,"stats":{"Line":0}},{"line":50,"address":[5763416],"length":1,"stats":{"Line":0}},{"line":61,"address":[7650672,7650496],"length":1,"stats":{"Line":2}},{"line":62,"address":[],"length":0,"stats":{"Line":3}},{"line":63,"address":[6512191],"length":1,"stats":{"Line":1}},{"line":64,"address":[],"length":0,"stats":{"Line":1}},{"line":68,"address":[6512288],"length":1,"stats":{"Line":3}},{"line":69,"address":[],"length":0,"stats":{"Line":3}},{"line":79,"address":[7651168,7651431,7651287,7651312],"length":1,"stats":{"Line":2}},{"line":80,"address":[],"length":0,"stats":{"Line":2}},{"line":81,"address":[],"length":0,"stats":{"Line":2}},{"line":82,"address":[],"length":0,"stats":{"Line":2}},{"line":85,"address":[],"length":0,"stats":{"Line":2}},{"line":86,"address":[],"length":0,"stats":{"Line":2}},{"line":87,"address":[],"length":0,"stats":{"Line":2}},{"line":88,"address":[],"length":0,"stats":{"Line":2}}],"covered":21,"coverable":24},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","response","success.rs"],"content":"use super::IntoResponse;\n#[cfg(feature = \"openapi\")]\nuse crate::ResponseSchema;\nuse crate::{Response, ResponseBody};\nuse futures_util::future::{self, FutureExt};\nuse gotham::{\n\thyper::{\n\t\theader::{HeaderMap, HeaderValue, IntoHeaderName},\n\t\tStatusCode\n\t},\n\tmime::{Mime, APPLICATION_JSON}\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::OpenapiSchema;\nuse std::{fmt::Debug, future::Future, pin::Pin};\n\n/// This can be returned from a resource when there is no cause of an error.\n///\n/// Usage example:\n///\n/// ```\n/// # #[macro_use] extern crate gotham_restful_derive;\n/// # mod doc_tests_are_broken {\n/// # use gotham::state::State;\n/// # use gotham_restful::*;\n/// # use serde::{Deserialize, Serialize};\n/// #\n/// # #[derive(Resource)]\n/// # #[resource(read_all)]\n/// # struct MyResource;\n/// #\n/// #[derive(Deserialize, Serialize)]\n/// # #[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n/// struct MyResponse {\n/// \tmessage: &'static str\n/// }\n///\n/// #[read_all]\n/// fn read_all() -> Success {\n/// \tlet res = MyResponse {\n/// \t\tmessage: \"I'm always happy\"\n/// \t};\n/// \tres.into()\n/// }\n/// # }\n/// ```\n#[derive(Clone, Debug, Default)]\npub struct Success {\n\tvalue: T,\n\theaders: HeaderMap\n}\n\nimpl From for Success {\n\tfn from(t: T) -> Self {\n\t\tSelf {\n\t\t\tvalue: t,\n\t\t\theaders: HeaderMap::new()\n\t\t}\n\t}\n}\n\nimpl Success {\n\t/// Set a custom HTTP header. If a header with this name was set before, its value is being updated.\n\tpub fn header(&mut self, name: K, value: HeaderValue) {\n\t\tself.headers.insert(name, value);\n\t}\n\n\t/// Allow manipulating HTTP headers.\n\tpub fn headers_mut(&mut self) -> &mut HeaderMap {\n\t\t&mut self.headers\n\t}\n}\n\nimpl IntoResponse for Success {\n\ttype Err = serde_json::Error;\n\n\tfn into_response(self) -> Pin> + Send>> {\n\t\tlet res = serde_json::to_string(&self.value)\n\t\t\t.map(|body| Response::json(StatusCode::OK, body).with_headers(self.headers));\n\t\tfuture::ready(res).boxed()\n\t}\n\n\tfn accepted_types() -> Option> {\n\t\tSome(vec![APPLICATION_JSON])\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for Success {\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tassert_eq!(code, StatusCode::OK);\n\t\tT::schema()\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse crate::response::OrAllTypes;\n\tuse futures_executor::block_on;\n\tuse gotham::hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN;\n\n\t#[derive(Debug, Default, Serialize)]\n\t#[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n\tstruct Msg {\n\t\tmsg: String\n\t}\n\n\t#[test]\n\tfn success_always_successfull() {\n\t\tlet success: Success = Msg::default().into();\n\t\tlet res = block_on(success.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::OK);\n\t\tassert_eq!(res.mime, Some(APPLICATION_JSON));\n\t\tassert_eq!(res.full_body().unwrap(), br#\"{\"msg\":\"\"}\"#);\n\t\t#[cfg(feature = \"openapi\")]\n\t\tassert_eq!(>::status_codes(), vec![StatusCode::OK]);\n\t}\n\n\t#[test]\n\tfn success_custom_headers() {\n\t\tlet mut success: Success = Msg::default().into();\n\t\tsuccess.header(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static(\"*\"));\n\t\tlet res = block_on(success.into_response()).expect(\"didn't expect error response\");\n\t\tlet cors = res.headers.get(ACCESS_CONTROL_ALLOW_ORIGIN);\n\t\tassert_eq!(cors.map(|value| value.to_str().unwrap()), Some(\"*\"));\n\t}\n\n\t#[test]\n\tfn success_accepts_json() {\n\t\tassert!(>::accepted_types()\n\t\t\t.or_all_types()\n\t\t\t.contains(&APPLICATION_JSON))\n\t}\n}\n","traces":[{"line":54,"address":[7792592,7792544,7792510,7792368],"length":1,"stats":{"Line":3}},{"line":57,"address":[],"length":0,"stats":{"Line":3}},{"line":64,"address":[],"length":0,"stats":{"Line":1}},{"line":65,"address":[],"length":0,"stats":{"Line":1}},{"line":69,"address":[],"length":0,"stats":{"Line":0}},{"line":70,"address":[],"length":0,"stats":{"Line":0}},{"line":77,"address":[6480358,6479744,6480041,6480022,6480080,6480377],"length":1,"stats":{"Line":2}},{"line":78,"address":[],"length":0,"stats":{"Line":6}},{"line":79,"address":[],"length":0,"stats":{"Line":9}},{"line":80,"address":[],"length":0,"stats":{"Line":5}},{"line":83,"address":[],"length":0,"stats":{"Line":1}},{"line":84,"address":[],"length":0,"stats":{"Line":1}},{"line":90,"address":[],"length":0,"stats":{"Line":0}},{"line":92,"address":[],"length":0,"stats":{"Line":0}}],"covered":10,"coverable":14},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","routing.rs"],"content":"#[cfg(feature = \"openapi\")]\nuse crate::openapi::{\n\tbuilder::{OpenapiBuilder, OpenapiInfo},\n\trouter::OpenapiRouter\n};\nuse crate::{response::ResourceError, Endpoint, FromBody, IntoResponse, Resource, Response};\n#[cfg(feature = \"cors\")]\nuse gotham::router::route::matcher::AccessControlRequestMethodMatcher;\nuse gotham::{\n\thandler::HandlerError,\n\thelpers::http::response::{create_empty_response, create_response},\n\thyper::{body::to_bytes, header::CONTENT_TYPE, Body, HeaderMap, Method, StatusCode},\n\tmime::{Mime, APPLICATION_JSON},\n\tpipeline::PipelineHandleChain,\n\tprelude::*,\n\trouter::{\n\t\tbuilder::{RouterBuilder, ScopeBuilder},\n\t\troute::matcher::{AcceptHeaderRouteMatcher, ContentTypeHeaderRouteMatcher, RouteMatcher},\n\t\tRouteNonMatch\n\t},\n\tstate::{FromState, State}\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::OpenapiType;\nuse std::{any::TypeId, panic::RefUnwindSafe};\n\n/// Allow us to extract an id from a path.\n#[derive(Clone, Copy, Debug, Deserialize, StateData, StaticResponseExtender)]\n#[cfg_attr(feature = \"openapi\", derive(OpenapiType))]\npub struct PathExtractor {\n\tpub id: ID\n}\n\n/// This trait adds the `with_openapi` method to gotham's routing. It turns the default\n/// router into one that will only allow RESTful resources, but record them and generate\n/// an OpenAPI specification on request.\n#[cfg(feature = \"openapi\")]\npub trait WithOpenapi {\n\tfn with_openapi(&mut self, info: OpenapiInfo, block: F)\n\twhere\n\t\tF: FnOnce(OpenapiRouter<'_, D>);\n}\n\n/// This trait adds the `resource` method to gotham's routing. It allows you to register\n/// any RESTful [Resource] with a path.\n#[_private_openapi_trait(DrawResourcesWithSchema)]\npub trait DrawResources {\n\t#[openapi_bound(R: crate::ResourceWithSchema)]\n\t#[non_openapi_bound(R: crate::Resource)]\n\tfn resource(&mut self, path: &str);\n}\n\n/// This trait allows to draw routes within an resource. Use this only inside the\n/// [Resource::setup] method.\n#[_private_openapi_trait(DrawResourceRoutesWithSchema)]\npub trait DrawResourceRoutes {\n\t#[openapi_bound(E: crate::EndpointWithSchema)]\n\t#[non_openapi_bound(E: crate::Endpoint)]\n\tfn endpoint(&mut self);\n}\n\nfn response_from(res: Response, state: &State) -> gotham::hyper::Response {\n\tlet mut r = create_empty_response(state, res.status);\n\tlet headers = r.headers_mut();\n\tif let Some(mime) = res.mime {\n\t\theaders.insert(CONTENT_TYPE, mime.as_ref().parse().unwrap());\n\t}\n\tlet mut last_name = None;\n\tfor (name, value) in res.headers {\n\t\tif name.is_some() {\n\t\t\tlast_name = name;\n\t\t}\n\t\t// this unwrap is safe: the first item will always be Some\n\t\tlet name = last_name.clone().unwrap();\n\t\theaders.insert(name, value);\n\t}\n\n\tlet method = Method::borrow_from(state);\n\tif method != Method::HEAD {\n\t\t*r.body_mut() = res.body;\n\t}\n\n\t#[cfg(feature = \"cors\")]\n\tcrate::cors::handle_cors(state, &mut r);\n\n\tr\n}\n\nasync fn endpoint_handler(\n\tstate: &mut State\n) -> Result, HandlerError>\nwhere\n\tE: Endpoint,\n\t::Err: Into\n{\n\ttrace!(\"entering endpoint_handler\");\n\tlet placeholders = E::Placeholders::take_from(state);\n\t// workaround for E::Placeholders and E::Param being the same type\n\t// when fixed remove `Clone` requirement on endpoint\n\tif TypeId::of::() == TypeId::of::() {\n\t\tstate.put(placeholders.clone());\n\t}\n\tlet params = E::Params::take_from(state);\n\n\tlet body = match E::needs_body() {\n\t\ttrue => {\n\t\t\tlet body = to_bytes(Body::take_from(state)).await?;\n\n\t\t\tlet content_type: Mime = match HeaderMap::borrow_from(state).get(CONTENT_TYPE) {\n\t\t\t\tSome(content_type) => content_type.to_str().unwrap().parse().unwrap(),\n\t\t\t\tNone => {\n\t\t\t\t\tdebug!(\"Missing Content-Type: Returning 415 Response\");\n\t\t\t\t\tlet res = create_empty_response(state, StatusCode::UNSUPPORTED_MEDIA_TYPE);\n\t\t\t\t\treturn Ok(res);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tmatch E::Body::from_body(body, content_type) {\n\t\t\t\tOk(body) => Some(body),\n\t\t\t\tErr(e) => {\n\t\t\t\t\tdebug!(\"Invalid Body: Returning 400 Response\");\n\t\t\t\t\tlet error: ResourceError = e.into();\n\t\t\t\t\tlet json = serde_json::to_string(&error)?;\n\t\t\t\t\tlet res =\n\t\t\t\t\t\tcreate_response(state, StatusCode::BAD_REQUEST, APPLICATION_JSON, json);\n\t\t\t\t\treturn Ok(res);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tfalse => None\n\t};\n\n\tlet out = E::handle(state, placeholders, params, body).await;\n\tlet res = out.into_response().await.map_err(Into::into)?;\n\tdebug!(\"Returning response {res:?}\");\n\tOk(response_from(res, state))\n}\n\n#[derive(Clone)]\nstruct MaybeMatchAcceptHeader {\n\tmatcher: Option\n}\n\nimpl RouteMatcher for MaybeMatchAcceptHeader {\n\tfn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {\n\t\tmatch &self.matcher {\n\t\t\tSome(matcher) => matcher.is_match(state),\n\t\t\tNone => Ok(())\n\t\t}\n\t}\n}\n\nimpl MaybeMatchAcceptHeader {\n\tfn new(types: Option>) -> Self {\n\t\tlet types = match types {\n\t\t\tSome(types) if types.is_empty() => None,\n\t\t\ttypes => types\n\t\t};\n\t\tSelf {\n\t\t\tmatcher: types.map(AcceptHeaderRouteMatcher::new)\n\t\t}\n\t}\n}\n\nimpl From>> for MaybeMatchAcceptHeader {\n\tfn from(types: Option>) -> Self {\n\t\tSelf::new(types)\n\t}\n}\n\n#[derive(Clone)]\nstruct MaybeMatchContentTypeHeader {\n\tmatcher: Option\n}\n\nimpl RouteMatcher for MaybeMatchContentTypeHeader {\n\tfn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {\n\t\tmatch &self.matcher {\n\t\t\tSome(matcher) => matcher.is_match(state),\n\t\t\tNone => Ok(())\n\t\t}\n\t}\n}\n\nimpl MaybeMatchContentTypeHeader {\n\tfn new(types: Option>) -> Self {\n\t\tSelf {\n\t\t\tmatcher: types.map(|types| ContentTypeHeaderRouteMatcher::new(types).allow_no_type())\n\t\t}\n\t}\n}\n\nimpl From>> for MaybeMatchContentTypeHeader {\n\tfn from(types: Option>) -> Self {\n\t\tSelf::new(types)\n\t}\n}\n\nmacro_rules! implDrawResourceRoutes {\n\t($implType:ident) => {\n\t\t#[cfg(feature = \"openapi\")]\n\t\timpl<'a, C, P> WithOpenapi for $implType<'a, C, P>\n\t\twhere\n\t\t\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\t\t\tP: RefUnwindSafe + Send + Sync + 'static\n\t\t{\n\t\t\tfn with_openapi(&mut self, info: OpenapiInfo, block: F)\n\t\t\twhere\n\t\t\t\tF: FnOnce(OpenapiRouter<'_, $implType<'a, C, P>>)\n\t\t\t{\n\t\t\t\tlet router = OpenapiRouter {\n\t\t\t\t\trouter: self,\n\t\t\t\t\tscope: None,\n\t\t\t\t\topenapi_builder: &mut OpenapiBuilder::new(info)\n\t\t\t\t};\n\t\t\t\tblock(router);\n\t\t\t}\n\t\t}\n\n\t\timpl<'a, C, P> DrawResources for $implType<'a, C, P>\n\t\twhere\n\t\t\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\t\t\tP: RefUnwindSafe + Send + Sync + 'static\n\t\t{\n\t\t\tfn resource(&mut self, mut path: &str) {\n\t\t\t\tif path.starts_with('/') {\n\t\t\t\t\tpath = &path[1..];\n\t\t\t\t}\n\t\t\t\tR::setup((self, path));\n\t\t\t}\n\t\t}\n\n\t\timpl<'a, C, P> DrawResourceRoutes for (&mut $implType<'a, C, P>, &str)\n\t\twhere\n\t\t\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\t\t\tP: RefUnwindSafe + Send + Sync + 'static\n\t\t{\n\t\t\tfn endpoint(&mut self) {\n\t\t\t\tlet uri = format!(\"{}/{}\", self.1, E::uri());\n\t\t\t\tdebug!(\"Registering endpoint for {uri}\");\n\t\t\t\tself.0.associate(&uri, |assoc| {\n\t\t\t\t\tassoc\n\t\t\t\t\t\t.request(vec![E::http_method()])\n\t\t\t\t\t\t.add_route_matcher(MaybeMatchAcceptHeader::new(E::Output::accepted_types()))\n\t\t\t\t\t\t.with_path_extractor::()\n\t\t\t\t\t\t.with_query_string_extractor::()\n\t\t\t\t\t\t.to_async_borrowing(endpoint_handler::);\n\n\t\t\t\t\t#[cfg(feature = \"cors\")]\n\t\t\t\t\tif E::http_method() != Method::GET {\n\t\t\t\t\t\tassoc\n\t\t\t\t\t\t\t.options()\n\t\t\t\t\t\t\t.add_route_matcher(AccessControlRequestMethodMatcher::new(\n\t\t\t\t\t\t\t\tE::http_method()\n\t\t\t\t\t\t\t))\n\t\t\t\t\t\t\t.to(crate::cors::cors_preflight_handler);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t};\n}\n\nimplDrawResourceRoutes!(RouterBuilder);\nimplDrawResourceRoutes!(ScopeBuilder);\n","traces":[{"line":62,"address":[7610137,7609136,7611016],"length":1,"stats":{"Line":5}},{"line":63,"address":[6343963,6344114],"length":1,"stats":{"Line":10}},{"line":64,"address":[8066730,8066799],"length":1,"stats":{"Line":10}},{"line":65,"address":[7701303],"length":1,"stats":{"Line":5}},{"line":66,"address":[7701628,7701417,7702006,7701870],"length":1,"stats":{"Line":8}},{"line":68,"address":[7609600],"length":1,"stats":{"Line":5}},{"line":69,"address":[8369292,8368620,8369198,8369340,8370555],"length":1,"stats":{"Line":15}},{"line":70,"address":[8068698,8067836,8068508],"length":1,"stats":{"Line":0}},{"line":71,"address":[8370160],"length":1,"stats":{"Line":0}},{"line":74,"address":[8099906],"length":1,"stats":{"Line":0}},{"line":75,"address":[7703299],"length":1,"stats":{"Line":0}},{"line":78,"address":[8099259],"length":1,"stats":{"Line":5}},{"line":79,"address":[8067896,8068188],"length":1,"stats":{"Line":10}},{"line":80,"address":[6192909,6193385],"length":1,"stats":{"Line":5}},{"line":84,"address":[8067941],"length":1,"stats":{"Line":5}},{"line":86,"address":[6345566],"length":1,"stats":{"Line":5}},{"line":89,"address":[7345616,7345584,7345456,7345520,7345424,7345488,7345552],"length":1,"stats":{"Line":21}},{"line":96,"address":[7273969,7256555,7262251,7262533,7285286,7273707,7285099,7251185,7267947,7285381,7291169,7256742,7256837,7262438,7291094,7251110,7250923,7290907,7268134,7279526,7279601,7268209,7273894,7279339],"length":1,"stats":{"Line":63}},{"line":97,"address":[7346346,7351580,7374689,7357244,7363212,7357562,7380028,7346028,7374332,7380385,7368906,7351902,7362893,7368588],"length":1,"stats":{"Line":42}},{"line":100,"address":[6451574,6451655],"length":1,"stats":{"Line":42}},{"line":101,"address":[6451787],"length":1,"stats":{"Line":13}},{"line":103,"address":[7263011,7268665,7274425,7263131,7285859,7285979,7291733,7280057,7280161,7274529,7268769,7251745,7257435,7257315,7251641,7291625],"length":1,"stats":{"Line":42}},{"line":105,"address":[6451885,6451948],"length":1,"stats":{"Line":42}},{"line":106,"address":[],"length":0,"stats":{"Line":0}},{"line":107,"address":[6452521,6451129,6451964,6452070,6454910,6452192],"length":1,"stats":{"Line":16}},{"line":109,"address":[6452678,6452488],"length":1,"stats":{"Line":16}},{"line":110,"address":[6368413,6369070],"length":1,"stats":{"Line":16}},{"line":111,"address":[],"length":0,"stats":{"Line":0}},{"line":112,"address":[7252750,7269715,7264042,7264140,7275436,7252652,7264232,7269813,7292680,7287027,7269905,7292778,7292870,7258536,7281205,7258444,7287119,7281107,7275626,7281297,7275534,7252842,7258346,7286929],"length":1,"stats":{"Line":0}},{"line":113,"address":[],"length":0,"stats":{"Line":0}},{"line":114,"address":[7359264,7376361,7382096,7353607,7347995,7364918,7370594],"length":1,"stats":{"Line":0}},{"line":118,"address":[6453671],"length":1,"stats":{"Line":8}},{"line":119,"address":[7348563,7354175,7359832,7371152,7376929,7365486,7382621],"length":1,"stats":{"Line":8}},{"line":120,"address":[7460149,7465854,7483013,7471550,7488656,7454400,7477228,7500099,7494288],"length":1,"stats":{"Line":0}},{"line":121,"address":[7354412,7360069,7359869,7365812,7377166,7365723,7371277,7365523,7348600,7371572,7348889,7371477,7354212,7377261,7360158,7376966,7348800,7354507],"length":1,"stats":{"Line":0}},{"line":122,"address":[6454022],"length":1,"stats":{"Line":0}},{"line":123,"address":[6454700,6454863,6454322,6454381],"length":1,"stats":{"Line":0}},{"line":124,"address":[6454519],"length":1,"stats":{"Line":0}},{"line":125,"address":[],"length":0,"stats":{"Line":0}},{"line":126,"address":[7484006,7489643,7472543,7461142,7478215,7495275,7466847,7501086,7455387],"length":1,"stats":{"Line":0}},{"line":130,"address":[6451954],"length":1,"stats":{"Line":13}},{"line":133,"address":[],"length":0,"stats":{"Line":43}},{"line":134,"address":[6923239,6919619,6918080,6914003,6922857,6922787,6944568,6918006,6922713,6918532,6943880,6918150],"length":1,"stats":{"Line":42}},{"line":135,"address":[7456282,7496316,7502256,7484901,7456557,7490813,7485338,7485176,7456428,7467888,7473875,7479110,7462312,7479547,7496445,7462037,7502418,7496170,7473584,7479385,7490975,7496607,7468179,7473713,7462474,7490684,7502127,7467742,7501981,7490538,7485047,7473438,7479256,7468017,7462183,7456719],"length":1,"stats":{"Line":80}},{"line":136,"address":[7290509,7261856,7267094,7284695,7284237,7267552,7261398,7278482,7256154,7278938,7290053,7255698,7273303,7272845,7295726,7296182],"length":1,"stats":{"Line":42}},{"line":145,"address":[8100560],"length":1,"stats":{"Line":7}},{"line":146,"address":[7851777],"length":1,"stats":{"Line":5}},{"line":147,"address":[6194200],"length":1,"stats":{"Line":0}},{"line":148,"address":[6346596],"length":1,"stats":{"Line":7}},{"line":154,"address":[7852253,7852327,7851856],"length":1,"stats":{"Line":6}},{"line":155,"address":[7611910],"length":1,"stats":{"Line":8}},{"line":156,"address":[6346872,6346729],"length":1,"stats":{"Line":6}},{"line":157,"address":[8371013],"length":1,"stats":{"Line":5}},{"line":160,"address":[6346942],"length":1,"stats":{"Line":6}},{"line":166,"address":[8371376],"length":1,"stats":{"Line":0}},{"line":167,"address":[8101160],"length":1,"stats":{"Line":0}},{"line":177,"address":[8101184],"length":1,"stats":{"Line":0}},{"line":178,"address":[7612433],"length":1,"stats":{"Line":0}},{"line":179,"address":[8371496],"length":1,"stats":{"Line":0}},{"line":180,"address":[7704356],"length":1,"stats":{"Line":0}},{"line":186,"address":[8371520],"length":1,"stats":{"Line":0}},{"line":188,"address":[7612526,7612576,7612589],"length":1,"stats":{"Line":0}},{"line":194,"address":[7612640],"length":1,"stats":{"Line":0}},{"line":195,"address":[7612648],"length":1,"stats":{"Line":0}},{"line":207,"address":[7849360,7849613],"length":1,"stats":{"Line":2}},{"line":211,"address":[6643300,6643212],"length":1,"stats":{"Line":4}},{"line":213,"address":[7849398],"length":1,"stats":{"Line":2}},{"line":214,"address":[6643295,6643231],"length":1,"stats":{"Line":4}},{"line":216,"address":[6643335],"length":1,"stats":{"Line":2}},{"line":225,"address":[6202544],"length":1,"stats":{"Line":6}},{"line":226,"address":[7422754,7422647],"length":1,"stats":{"Line":5}},{"line":227,"address":[6974636],"length":1,"stats":{"Line":3}},{"line":229,"address":[6357172],"length":1,"stats":{"Line":6}},{"line":238,"address":[6364448,6365454],"length":1,"stats":{"Line":31}},{"line":239,"address":[6364468,6364683,6364558],"length":1,"stats":{"Line":62}},{"line":240,"address":[7812961,7814868,7811034,7812788,7815194,7817274,7811835,7817035,7817121,7812875,7814001,7811748,7815041,7815908,7816081,7813828,7810708,7813114,7815995,7816234,7811921,7810881,7810795,7816948,7812074,7814154,7813915,7814955],"length":1,"stats":{"Line":110}},{"line":241,"address":[7245516,7247964,7247148,7249596,7245584,7246360,7248780,7244700,7247216,7248032,7248808,7247992,7246400,7245544,7250412,7246332,7243952,7248848,7249624,7247176,7249664,7250440,7244768,7244728],"length":1,"stats":{"Line":93}},{"line":242,"address":[7450771,7447124,7445808,7445686,7447440,7445875,7449572,7446308,7444992,7446691,7449072,7444676,7446624,7444054,7450388,7448950,7443860,7444176,7444243,7447940,7448256,7449139,7445492,7448323,7446502,7450704,7447507,7448756,7449955,7449888,7449766,7450582,7447318,7448134,7444870,7445059],"length":1,"stats":{"Line":128}},{"line":243,"address":[6365707,6365534,6366288,6365735],"length":1,"stats":{"Line":62}},{"line":244,"address":[6913088,6912227,6913613,6912797,6912272,6913043,6913195,6912379],"length":1,"stats":{"Line":60}},{"line":250,"address":[6449650,6450466],"length":1,"stats":{"Line":29}},{"line":251,"address":[7340446,7340290,7342074,7345295,7344366,7342027,7342734,7343706,7343659,7341106,7343550,7340399,7345186,7341918,7344475,7341262,7341215,7342843,7342890,7344522,7345342],"length":1,"stats":{"Line":46}},{"line":253,"address":[7250320,7245424,7247872,7244608,7248688,7247056,7249504,7246240],"length":1,"stats":{"Line":16}},{"line":254,"address":[7248635,7250267,7245371,7249451,7247819,7244555,7247003,7246187],"length":1,"stats":{"Line":14}}],"covered":57,"coverable":84},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","types.rs"],"content":"use gotham::{\n\thyper::body::Bytes,\n\tmime::{Mime, APPLICATION_JSON}\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::OpenapiType;\nuse serde::{de::DeserializeOwned, Serialize};\nuse std::error::Error;\n\n#[cfg(not(feature = \"openapi\"))]\npub trait ResourceType {}\n\n#[cfg(not(feature = \"openapi\"))]\nimpl ResourceType for T {}\n\n#[cfg(feature = \"openapi\")]\npub trait ResourceType: OpenapiType {}\n\n#[cfg(feature = \"openapi\")]\nimpl ResourceType for T {}\n\n/// A type that can be used inside a response body. Implemented for every type that is\n/// serializable with serde. If the `openapi` feature is used, it must also be of type\n/// [OpenapiType].\npub trait ResponseBody: ResourceType + Serialize {}\n\nimpl ResponseBody for T {}\n\n/// This trait should be implemented for every type that can be built from an HTTP request body\n/// plus its media type.\n///\n/// For most use cases it is sufficient to derive this trait, you usually don't need to manually\n/// implement this. Therefore, make sure that the first variable of your struct can be built from\n/// [Bytes], and the second one can be build from [Mime]. If you have any additional variables, they\n/// need to be [Default]. This is an example of such a struct:\n///\n/// ```rust\n/// # use gotham::mime::{self, Mime};\n/// # use gotham_restful::{FromBody, RequestBody};\n/// #[derive(FromBody, RequestBody)]\n/// #[supported_types(mime::IMAGE_GIF, mime::IMAGE_JPEG, mime::IMAGE_PNG)]\n/// struct RawImage {\n/// \tcontent: Vec,\n/// \tcontent_type: Mime\n/// }\n/// ```\npub trait FromBody: Sized {\n\t/// The error type returned by the conversion if it was unsuccessfull. When using the derive\n\t/// macro, there is no way to trigger an error, so [std::convert::Infallible] is used here.\n\t/// However, this might change in the future.\n\ttype Err: Error;\n\n\t/// Perform the conversion.\n\tfn from_body(body: Bytes, content_type: Mime) -> Result;\n}\n\nimpl FromBody for T {\n\ttype Err = serde_json::Error;\n\n\tfn from_body(body: Bytes, _content_type: Mime) -> Result {\n\t\tserde_json::from_slice(&body)\n\t}\n}\n\n/// A type that can be used inside a request body. Implemented for every type that is deserializable\n/// with serde. If the `openapi` feature is used, it must also be of type [OpenapiType].\n///\n/// If you want a non-deserializable type to be used as a request body, e.g. because you'd like to\n/// get the raw data, you can derive it for your own type. All you need is to have a type implementing\n/// [FromBody] and optionally a list of supported media types:\n///\n/// ```rust\n/// # use gotham::mime::{self, Mime};\n/// # use gotham_restful::{FromBody, RequestBody};\n/// #[derive(FromBody, RequestBody)]\n/// #[supported_types(mime::IMAGE_GIF, mime::IMAGE_JPEG, mime::IMAGE_PNG)]\n/// struct RawImage {\n/// \tcontent: Vec,\n/// \tcontent_type: Mime\n/// }\n/// ```\npub trait RequestBody: ResourceType + FromBody {\n\t/// Return all types that are supported as content types. Use `None` if all types are supported.\n\tfn supported_types() -> Option> {\n\t\tNone\n\t}\n}\n\nimpl RequestBody for T {\n\tfn supported_types() -> Option> {\n\t\tSome(vec![APPLICATION_JSON])\n\t}\n}\n","traces":[{"line":60,"address":[6222840,6222688],"length":1,"stats":{"Line":2}},{"line":61,"address":[7300462,7300521],"length":1,"stats":{"Line":4}},{"line":84,"address":[],"length":0,"stats":{"Line":0}},{"line":85,"address":[],"length":0,"stats":{"Line":0}},{"line":90,"address":[],"length":0,"stats":{"Line":1}},{"line":91,"address":[],"length":0,"stats":{"Line":1}}],"covered":4,"coverable":6},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","async_methods.rs"],"content":"use gotham::{\n\thyper::{HeaderMap, Method},\n\tmime::{APPLICATION_JSON, TEXT_PLAIN},\n\tprelude::*,\n\trouter::build_simple_router,\n\tstate::State,\n\ttest::TestServer\n};\nuse gotham_restful::*;\n#[cfg(feature = \"openapi\")]\nuse openapi_type::OpenapiType;\nuse serde::Deserialize;\nuse simple_logger::SimpleLogger;\nuse tokio::time::{sleep, Duration};\n\nmod util {\n\tinclude!(\"util/mod.rs\");\n}\nuse util::{test_delete_response, test_get_response, test_post_response, test_put_response};\n\n#[derive(Resource)]\n#[resource(\n\tread_all, read, search, create, update_all, update, delete_all, delete, state_test\n)]\nstruct FooResource;\n\n#[derive(Deserialize)]\n#[cfg_attr(feature = \"openapi\", derive(OpenapiType))]\n#[allow(dead_code)]\nstruct FooBody {\n\tdata: String\n}\n\n#[derive(Clone, Deserialize, StateData, StaticResponseExtender)]\n#[cfg_attr(feature = \"openapi\", derive(OpenapiType))]\n#[allow(dead_code)]\nstruct FooSearch {\n\tquery: String\n}\n\nconst READ_ALL_RESPONSE: &[u8] = b\"1ARwwSPVyOKpJKrYwqGgECPVWDl1BqajAAj7g7WJ3e\";\n#[read_all]\nasync fn read_all() -> Raw<&'static [u8]> {\n\tRaw::new(READ_ALL_RESPONSE, TEXT_PLAIN)\n}\n\nconst READ_RESPONSE: &[u8] = b\"FEReHoeBKU17X2bBpVAd1iUvktFL43CDu0cFYHdaP9\";\n#[read]\nasync fn read(_id: u64) -> Raw<&'static [u8]> {\n\tRaw::new(READ_RESPONSE, TEXT_PLAIN)\n}\n\nconst SEARCH_RESPONSE: &[u8] = b\"AWqcQUdBRHXKh3at4u79mdupOAfEbnTcx71ogCVF0E\";\n#[search]\nasync fn search(_body: FooSearch) -> Raw<&'static [u8]> {\n\tRaw::new(SEARCH_RESPONSE, TEXT_PLAIN)\n}\n\nconst CREATE_RESPONSE: &[u8] = b\"y6POY7wOMAB0jBRBw0FJT7DOpUNbhmT8KdpQPLkI83\";\n#[create]\nasync fn create(_body: FooBody) -> Raw<&'static [u8]> {\n\tRaw::new(CREATE_RESPONSE, TEXT_PLAIN)\n}\n\nconst UPDATE_ALL_RESPONSE: &[u8] = b\"QlbYg8gHE9OQvvk3yKjXJLTSXlIrg9mcqhfMXJmQkv\";\n#[update_all]\nasync fn update_all(_body: FooBody) -> Raw<&'static [u8]> {\n\tRaw::new(UPDATE_ALL_RESPONSE, TEXT_PLAIN)\n}\n\nconst UPDATE_RESPONSE: &[u8] = b\"qGod55RUXkT1lgPO8h0uVM6l368O2S0GrwENZFFuRu\";\n#[update]\nasync fn update(_id: u64, _body: FooBody) -> Raw<&'static [u8]> {\n\tRaw::new(UPDATE_RESPONSE, TEXT_PLAIN)\n}\n\nconst DELETE_ALL_RESPONSE: &[u8] = b\"Y36kZ749MRk2Nem4BedJABOZiZWPLOtiwLfJlGTwm5\";\n#[delete_all]\nasync fn delete_all() -> Raw<&'static [u8]> {\n\tRaw::new(DELETE_ALL_RESPONSE, TEXT_PLAIN)\n}\n\nconst DELETE_RESPONSE: &[u8] = b\"CwRzBrKErsVZ1N7yeNfjZuUn1MacvgBqk4uPOFfDDq\";\n#[delete]\nasync fn delete(_id: u64) -> Raw<&'static [u8]> {\n\tRaw::new(DELETE_RESPONSE, TEXT_PLAIN)\n}\n\nconst STATE_TEST_RESPONSE: &[u8] = b\"xxJbxOuwioqR5DfzPuVqvaqRSfpdNQGluIvHU4n1LM\";\n#[endpoint(method = \"Method::GET\", uri = \"state_test\")]\nasync fn state_test(state: &mut State) -> Raw<&'static [u8]> {\n\tsleep(Duration::from_nanos(1)).await;\n\tstate.borrow::();\n\tsleep(Duration::from_nanos(1)).await;\n\tRaw::new(STATE_TEST_RESPONSE, TEXT_PLAIN)\n}\n\n#[test]\nfn async_methods() {\n\t// TODO no idea why with_local_timestamps fails here\n\t_ = SimpleLogger::new().env().with_utc_timestamps().init();\n\n\tlet server = TestServer::new(build_simple_router(|router| {\n\t\trouter.resource::(\"foo\");\n\t}))\n\t.unwrap();\n\n\ttest_get_response(&server, \"http://localhost/foo\", READ_ALL_RESPONSE);\n\ttest_get_response(&server, \"http://localhost/foo/1\", READ_RESPONSE);\n\ttest_get_response(\n\t\t&server,\n\t\t\"http://localhost/foo/search?query=hello+world\",\n\t\tSEARCH_RESPONSE\n\t);\n\ttest_post_response(\n\t\t&server,\n\t\t\"http://localhost/foo\",\n\t\tr#\"{\"data\":\"hello world\"}\"#,\n\t\tAPPLICATION_JSON,\n\t\tCREATE_RESPONSE\n\t);\n\ttest_put_response(\n\t\t&server,\n\t\t\"http://localhost/foo\",\n\t\tr#\"{\"data\":\"hello world\"}\"#,\n\t\tAPPLICATION_JSON,\n\t\tUPDATE_ALL_RESPONSE\n\t);\n\ttest_put_response(\n\t\t&server,\n\t\t\"http://localhost/foo/1\",\n\t\tr#\"{\"data\":\"hello world\"}\"#,\n\t\tAPPLICATION_JSON,\n\t\tUPDATE_RESPONSE\n\t);\n\ttest_delete_response(&server, \"http://localhost/foo\", DELETE_ALL_RESPONSE);\n\ttest_delete_response(&server, \"http://localhost/foo/1\", DELETE_RESPONSE);\n\ttest_get_response(\n\t\t&server,\n\t\t\"http://localhost/foo/state_test\",\n\t\tSTATE_TEST_RESPONSE\n\t);\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","cors_handling.rs"],"content":"#![cfg(feature = \"cors\")]\nuse gotham::{\n\thyper::{body::Body, client::connect::Connect, header::*, StatusCode},\n\tmime::TEXT_PLAIN,\n\tpipeline::{new_pipeline, single_pipeline},\n\trouter::build_router,\n\ttest::{Server, TestRequest, TestServer}\n};\nuse gotham_restful::{\n\tcors::{Headers, Origin},\n\tread_all, update_all, CorsConfig, DrawResources, Raw, Resource\n};\n\n#[derive(Resource)]\n#[resource(read_all, update_all)]\nstruct FooResource;\n\n#[read_all]\nfn read_all() {}\n\n#[update_all]\nfn update_all(_body: Raw>) {}\n\nfn test_server(cfg: CorsConfig) -> TestServer {\n\tlet (chain, pipeline) = single_pipeline(new_pipeline().add(cfg).build());\n\tTestServer::new(build_router(chain, pipeline, |router| {\n\t\trouter.resource::(\"/foo\")\n\t}))\n\t.unwrap()\n}\n\nfn test_response(\n\treq: TestRequest,\n\torigin: Option<&str>,\n\tvary: Option<&str>,\n\tcredentials: bool\n) where\n\tTS: Server + 'static,\n\tC: Connect + Clone + Send + Sync + 'static\n{\n\tlet res = req\n\t\t.with_header(ORIGIN, \"http://example.org\".parse().unwrap())\n\t\t.perform()\n\t\t.unwrap();\n\tassert_eq!(res.status(), StatusCode::NO_CONTENT);\n\tlet headers = res.headers();\n\tprintln!(\n\t\t\"{}\",\n\t\theaders\n\t\t\t.keys()\n\t\t\t.map(|name| name.as_str())\n\t\t\t.collect::>()\n\t\t\t.join(\",\")\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(ACCESS_CONTROL_ALLOW_ORIGIN)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.as_deref(),\n\t\torigin\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(VARY)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.as_deref(),\n\t\tvary\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(ACCESS_CONTROL_ALLOW_CREDENTIALS)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.map(|value| value == \"true\")\n\t\t\t.unwrap_or(false),\n\t\tcredentials\n\t);\n\tassert!(headers.get(ACCESS_CONTROL_MAX_AGE).is_none());\n}\n\nfn test_preflight(\n\tserver: &TestServer,\n\tmethod: &str,\n\torigin: Option<&str>,\n\tvary: &str,\n\tcredentials: bool,\n\tmax_age: u64\n) {\n\tlet res = server\n\t\t.client()\n\t\t.options(\"http://example.org/foo\")\n\t\t.with_header(ACCESS_CONTROL_REQUEST_METHOD, method.parse().unwrap())\n\t\t.with_header(ORIGIN, \"http://example.org\".parse().unwrap())\n\t\t.perform()\n\t\t.unwrap();\n\tassert_eq!(res.status(), StatusCode::NO_CONTENT);\n\tlet headers = res.headers();\n\tprintln!(\n\t\t\"{}\",\n\t\theaders\n\t\t\t.keys()\n\t\t\t.map(|name| name.as_str())\n\t\t\t.collect::>()\n\t\t\t.join(\",\")\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(ACCESS_CONTROL_ALLOW_METHODS)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.as_deref(),\n\t\tSome(method)\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(ACCESS_CONTROL_ALLOW_ORIGIN)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.as_deref(),\n\t\torigin\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(VARY)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.as_deref(),\n\t\tSome(vary)\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(ACCESS_CONTROL_ALLOW_CREDENTIALS)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.map(|value| value == \"true\")\n\t\t\t.unwrap_or(false),\n\t\tcredentials\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(ACCESS_CONTROL_MAX_AGE)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.and_then(|value| value.parse().ok()),\n\t\tSome(max_age)\n\t);\n}\n\nfn test_preflight_headers(\n\tserver: &TestServer,\n\tmethod: &str,\n\trequest_headers: Option<&str>,\n\tallowed_headers: Option<&str>,\n\tvary: &str\n) {\n\tlet client = server.client();\n\tlet mut res = client\n\t\t.options(\"http://example.org/foo\")\n\t\t.with_header(ACCESS_CONTROL_REQUEST_METHOD, method.parse().unwrap())\n\t\t.with_header(ORIGIN, \"http://example.org\".parse().unwrap());\n\tif let Some(hdr) = request_headers {\n\t\tres = res.with_header(ACCESS_CONTROL_REQUEST_HEADERS, hdr.parse().unwrap());\n\t}\n\tlet res = res.perform().unwrap();\n\tassert_eq!(res.status(), StatusCode::NO_CONTENT);\n\tlet headers = res.headers();\n\tprintln!(\n\t\t\"{}\",\n\t\theaders\n\t\t\t.keys()\n\t\t\t.map(|name| name.as_str())\n\t\t\t.collect::>()\n\t\t\t.join(\",\")\n\t);\n\tif let Some(hdr) = allowed_headers {\n\t\tassert_eq!(\n\t\t\theaders\n\t\t\t\t.get(ACCESS_CONTROL_ALLOW_HEADERS)\n\t\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t\t.as_deref(),\n\t\t\tSome(hdr)\n\t\t)\n\t} else {\n\t\tassert!(!headers.contains_key(ACCESS_CONTROL_ALLOW_HEADERS));\n\t}\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(VARY)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.as_deref(),\n\t\tSome(vary)\n\t);\n}\n\n#[test]\nfn cors_origin_none() {\n\tlet cfg = Default::default();\n\tlet server = test_server(cfg);\n\n\ttest_preflight(\n\t\t&server,\n\t\t\"PUT\",\n\t\tNone,\n\t\t\"access-control-request-method\",\n\t\tfalse,\n\t\t0\n\t);\n\n\ttest_response(\n\t\tserver.client().get(\"http://example.org/foo\"),\n\t\tNone,\n\t\tNone,\n\t\tfalse\n\t);\n\ttest_response(\n\t\tserver\n\t\t\t.client()\n\t\t\t.put(\"http://example.org/foo\", Body::empty(), TEXT_PLAIN),\n\t\tNone,\n\t\tNone,\n\t\tfalse\n\t);\n}\n\n#[test]\nfn cors_origin_star() {\n\tlet cfg = CorsConfig {\n\t\torigin: Origin::Star,\n\t\t..Default::default()\n\t};\n\tlet server = test_server(cfg);\n\n\ttest_preflight(\n\t\t&server,\n\t\t\"PUT\",\n\t\tSome(\"*\"),\n\t\t\"access-control-request-method\",\n\t\tfalse,\n\t\t0\n\t);\n\n\ttest_response(\n\t\tserver.client().get(\"http://example.org/foo\"),\n\t\tSome(\"*\"),\n\t\tNone,\n\t\tfalse\n\t);\n\ttest_response(\n\t\tserver\n\t\t\t.client()\n\t\t\t.put(\"http://example.org/foo\", Body::empty(), TEXT_PLAIN),\n\t\tSome(\"*\"),\n\t\tNone,\n\t\tfalse\n\t);\n}\n\n#[test]\nfn cors_origin_single() {\n\tlet cfg = CorsConfig {\n\t\torigin: Origin::Single(\"https://foo.com\".to_owned()),\n\t\t..Default::default()\n\t};\n\tlet server = test_server(cfg);\n\n\ttest_preflight(\n\t\t&server,\n\t\t\"PUT\",\n\t\tSome(\"https://foo.com\"),\n\t\t\"access-control-request-method\",\n\t\tfalse,\n\t\t0\n\t);\n\n\ttest_response(\n\t\tserver.client().get(\"http://example.org/foo\"),\n\t\tSome(\"https://foo.com\"),\n\t\tNone,\n\t\tfalse\n\t);\n\ttest_response(\n\t\tserver\n\t\t\t.client()\n\t\t\t.put(\"http://example.org/foo\", Body::empty(), TEXT_PLAIN),\n\t\tSome(\"https://foo.com\"),\n\t\tNone,\n\t\tfalse\n\t);\n}\n\n#[test]\nfn cors_origin_copy() {\n\tlet cfg = CorsConfig {\n\t\torigin: Origin::Copy,\n\t\t..Default::default()\n\t};\n\tlet server = test_server(cfg);\n\n\ttest_preflight(\n\t\t&server,\n\t\t\"PUT\",\n\t\tSome(\"http://example.org\"),\n\t\t\"access-control-request-method,origin\",\n\t\tfalse,\n\t\t0\n\t);\n\n\ttest_response(\n\t\tserver.client().get(\"http://example.org/foo\"),\n\t\tSome(\"http://example.org\"),\n\t\tSome(\"origin\"),\n\t\tfalse\n\t);\n\ttest_response(\n\t\tserver\n\t\t\t.client()\n\t\t\t.put(\"http://example.org/foo\", Body::empty(), TEXT_PLAIN),\n\t\tSome(\"http://example.org\"),\n\t\tSome(\"origin\"),\n\t\tfalse\n\t);\n}\n\n#[test]\nfn cors_headers_none() {\n\tlet cfg = Default::default();\n\tlet server = test_server(cfg);\n\n\ttest_preflight_headers(&server, \"PUT\", None, None, \"access-control-request-method\");\n\ttest_preflight_headers(\n\t\t&server,\n\t\t\"PUT\",\n\t\tSome(\"Content-Type\"),\n\t\tNone,\n\t\t\"access-control-request-method\"\n\t);\n}\n\n#[test]\nfn cors_headers_list() {\n\tlet cfg = CorsConfig {\n\t\theaders: Headers::List(vec![CONTENT_TYPE]),\n\t\t..Default::default()\n\t};\n\tlet server = test_server(cfg);\n\n\ttest_preflight_headers(\n\t\t&server,\n\t\t\"PUT\",\n\t\tNone,\n\t\tSome(\"content-type\"),\n\t\t\"access-control-request-method\"\n\t);\n\ttest_preflight_headers(\n\t\t&server,\n\t\t\"PUT\",\n\t\tSome(\"content-type\"),\n\t\tSome(\"content-type\"),\n\t\t\"access-control-request-method\"\n\t);\n}\n\n#[test]\nfn cors_headers_copy() {\n\tlet cfg = CorsConfig {\n\t\theaders: Headers::Copy,\n\t\t..Default::default()\n\t};\n\tlet server = test_server(cfg);\n\n\ttest_preflight_headers(\n\t\t&server,\n\t\t\"PUT\",\n\t\tNone,\n\t\tNone,\n\t\t\"access-control-request-method,access-control-request-headers\"\n\t);\n\ttest_preflight_headers(\n\t\t&server,\n\t\t\"PUT\",\n\t\tSome(\"content-type\"),\n\t\tSome(\"content-type\"),\n\t\t\"access-control-request-method,access-control-request-headers\"\n\t);\n}\n\n#[test]\nfn cors_credentials() {\n\tlet cfg = CorsConfig {\n\t\torigin: Origin::None,\n\t\tcredentials: true,\n\t\t..Default::default()\n\t};\n\tlet server = test_server(cfg);\n\n\ttest_preflight(\n\t\t&server,\n\t\t\"PUT\",\n\t\tNone,\n\t\t\"access-control-request-method\",\n\t\ttrue,\n\t\t0\n\t);\n\n\ttest_response(\n\t\tserver.client().get(\"http://example.org/foo\"),\n\t\tNone,\n\t\tNone,\n\t\ttrue\n\t);\n\ttest_response(\n\t\tserver\n\t\t\t.client()\n\t\t\t.put(\"http://example.org/foo\", Body::empty(), TEXT_PLAIN),\n\t\tNone,\n\t\tNone,\n\t\ttrue\n\t);\n}\n\n#[test]\nfn cors_max_age() {\n\tlet cfg = CorsConfig {\n\t\torigin: Origin::None,\n\t\tmax_age: 31536000,\n\t\t..Default::default()\n\t};\n\tlet server = test_server(cfg);\n\n\ttest_preflight(\n\t\t&server,\n\t\t\"PUT\",\n\t\tNone,\n\t\t\"access-control-request-method\",\n\t\tfalse,\n\t\t31536000\n\t);\n\n\ttest_response(\n\t\tserver.client().get(\"http://example.org/foo\"),\n\t\tNone,\n\t\tNone,\n\t\tfalse\n\t);\n\ttest_response(\n\t\tserver\n\t\t\t.client()\n\t\t\t.put(\"http://example.org/foo\", Body::empty(), TEXT_PLAIN),\n\t\tNone,\n\t\tNone,\n\t\tfalse\n\t);\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","custom_request_body.rs"],"content":"use gotham::{\n\thyper::header::CONTENT_TYPE,\n\tmime::{Mime, TEXT_PLAIN},\n\trouter::builder::*,\n\ttest::TestServer\n};\nuse gotham_restful::{create, DrawResources, FromBody, Raw, RequestBody, Resource};\n\nconst RESPONSE: &[u8] = b\"This is the only valid response.\";\n\n#[derive(Resource)]\n#[resource(create)]\nstruct FooResource;\n\n#[derive(FromBody, RequestBody)]\n#[supported_types(TEXT_PLAIN)]\nstruct Foo {\n\tcontent: Vec,\n\tcontent_type: Mime\n}\n\n#[create]\nfn create(body: Foo) -> Raw> {\n\tRaw::new(body.content, body.content_type)\n}\n\n#[test]\nfn custom_request_body() {\n\tlet server = TestServer::new(build_simple_router(|router| {\n\t\trouter.resource::(\"foo\");\n\t}))\n\t.unwrap();\n\n\tlet res = server\n\t\t.client()\n\t\t.post(\"http://localhost/foo\", RESPONSE, TEXT_PLAIN)\n\t\t.perform()\n\t\t.unwrap();\n\tassert_eq!(\n\t\tres.headers().get(CONTENT_TYPE).unwrap().to_str().unwrap(),\n\t\t\"text/plain\"\n\t);\n\tlet res = res.read_body().unwrap();\n\tlet body: &[u8] = res.as_ref();\n\tassert_eq!(body, RESPONSE);\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","openapi_specification.rs"],"content":"#![cfg(all(feature = \"auth\", feature = \"openapi\"))]\n#![allow(clippy::approx_constant)]\n\n#[macro_use]\nextern crate pretty_assertions;\n\nuse gotham::{\n\thyper::{Method, StatusCode},\n\tmime::{IMAGE_PNG, TEXT_PLAIN_UTF_8},\n\tpipeline::{new_pipeline, single_pipeline},\n\tprelude::*,\n\trouter::build_router,\n\ttest::TestServer\n};\nuse gotham_restful::*;\nuse openapi_type::{OpenapiSchema, OpenapiType, Visitor};\nuse serde::{Deserialize, Serialize};\n\n#[allow(dead_code)]\nmod util {\n\tinclude!(\"util/mod.rs\");\n}\nuse util::test_openapi_response;\n\nconst IMAGE_RESPONSE : &[u8] = b\"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUA/wA0XsCoAAAAAXRSTlN/gFy0ywAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=\";\n\n#[derive(Resource)]\n#[resource(get_image, set_image)]\nstruct ImageResource;\n\n#[derive(FromBody, RequestBody)]\n#[supported_types(IMAGE_PNG)]\nstruct Image(Vec);\n\n#[read(operation_id = \"getImage\")]\nfn get_image(_id: u64) -> Raw<&'static [u8]> {\n\tRaw::new(IMAGE_RESPONSE, \"image/png;base64\".parse().unwrap())\n}\n\n#[update(operation_id = \"setImage\")]\nfn set_image(_id: u64, _image: Image) {}\n\n#[derive(Resource)]\n#[resource(read_secret, search_secret)]\nstruct SecretResource;\n\n#[derive(Deserialize, Clone)]\nstruct AuthData {\n\tsub: String,\n\tiat: u64,\n\texp: u64\n}\n\ntype AuthStatus = gotham_restful::AuthStatus;\n\n#[derive(OpenapiType, Serialize)]\nstruct Secret {\n\tcode: f32\n}\n\n#[derive(OpenapiType, Serialize)]\nstruct Secrets {\n\tsecrets: Vec\n}\n\n#[derive(Clone, Deserialize, OpenapiType, StateData, StaticResponseExtender)]\nstruct SecretQuery {\n\tdate: String,\n\thour: Option,\n\tminute: Option\n}\n\n/// This endpoint gives access to the secret.\n///\n/// You need to be authenticated to call this endpoint.\n#[read]\nfn read_secret(auth: AuthStatus, _id: String) -> AuthSuccess {\n\tauth.ok()?;\n\tOk(Secret { code: 4.2 })\n}\n\n#[search]\nfn search_secret(auth: AuthStatus, _query: SecretQuery) -> AuthSuccess {\n\tauth.ok()?;\n\tOk(Secrets {\n\t\tsecrets: vec![Secret { code: 4.2 }, Secret { code: 3.14 }]\n\t})\n}\n\n#[derive(Resource)]\n#[resource(coffee_read_all)]\nstruct CoffeeResource;\n\nfn teapot_status_codes() -> Vec {\n\tvec![StatusCode::IM_A_TEAPOT]\n}\n\nfn teapot_schema(code: StatusCode) -> OpenapiSchema {\n\tassert_eq!(code, StatusCode::IM_A_TEAPOT);\n\n\tstruct Binary;\n\n\timpl OpenapiType for Binary {\n\t\tfn visit_type(visitor: &mut V) {\n\t\t\tvisitor.visit_binary();\n\t\t}\n\t}\n\n\tBinary::schema()\n}\n\n#[read_all(status_codes = \"teapot_status_codes\", schema = \"teapot_schema\")]\nfn coffee_read_all() -> Response {\n\tResponse::new(\n\t\tStatusCode::IM_A_TEAPOT,\n\t\t\"Sorry, this is just your fancy grandma's teapot. Can't make coffee.\",\n\t\tSome(TEXT_PLAIN_UTF_8)\n\t)\n}\n\n#[derive(Resource)]\n#[resource(custom_read_with, custom_patch)]\nstruct CustomResource;\n\n#[derive(Clone, Deserialize, OpenapiType, StateData, StaticResponseExtender)]\nstruct ReadWithPath {\n\tfrom: String,\n\tid: u64\n}\n\n#[endpoint(method = \"Method::GET\", uri = \"read/:from/with/:id\")]\nfn custom_read_with(_path: ReadWithPath) {}\n\n#[endpoint(method = \"Method::PATCH\", uri = \"\", body = true)]\nfn custom_patch(_body: String) {}\n\n#[test]\nfn openapi_specification() {\n\tlet info = OpenapiInfo {\n\t\ttitle: \"This is just a test\".to_owned(),\n\t\tversion: \"1.2.3\".to_owned(),\n\t\turls: vec![\"http://localhost:12345/api/v1\".to_owned()]\n\t};\n\tlet auth: AuthMiddleware = AuthMiddleware::new(\n\t\tAuthSource::AuthorizationHeader,\n\t\tAuthValidation::default(),\n\t\tStaticAuthHandler::from_array(b\"zlBsA2QXnkmpe0QTh8uCvtAEa4j33YAc\")\n\t);\n\tlet (chain, pipelines) = single_pipeline(new_pipeline().add(auth).build());\n\tlet server = TestServer::new(build_router(chain, pipelines, |router| {\n\t\trouter.with_openapi(info, |mut router| {\n\t\t\t// the leading slash tests that the spec doesn't contain '//img' nonsense\n\t\t\trouter.resource::(\"/img\");\n\t\t\trouter.resource::(\"secret\");\n\t\t\trouter.resource::(\"coffee\");\n\t\t\trouter.resource::(\"custom\");\n\t\t\trouter.openapi_spec(\"openapi\");\n\t\t});\n\t}))\n\t.unwrap();\n\n\ttest_openapi_response(\n\t\t&server,\n\t\t\"http://localhost/openapi\",\n\t\t\"tests/openapi_specification.json\"\n\t);\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","openapi_supports_scope.rs"],"content":"#![cfg(feature = \"openapi\")]\n\n#[macro_use]\nextern crate pretty_assertions;\n\nuse gotham::{mime::TEXT_PLAIN, router::builder::*, test::TestServer};\nuse gotham_restful::*;\n\n#[allow(dead_code)]\nmod util {\n\tinclude!(\"util/mod.rs\");\n}\nuse util::{test_get_response, test_openapi_response};\n\nconst RESPONSE: &[u8] = b\"This is the only valid response.\";\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all]\nfn read_all() -> Raw<&'static [u8]> {\n\tRaw::new(RESPONSE, TEXT_PLAIN)\n}\n\n#[test]\nfn openapi_supports_scope() {\n\tlet info = OpenapiInfo {\n\t\ttitle: \"Test\".to_owned(),\n\t\tversion: \"1.2.3\".to_owned(),\n\t\turls: Vec::new()\n\t};\n\tlet server = TestServer::new(build_simple_router(|router| {\n\t\trouter.with_openapi(info, |mut router| {\n\t\t\trouter.openapi_spec(\"openapi\");\n\t\t\trouter.resource::(\"foo1\");\n\t\t\trouter.scope(\"/bar\", |router| {\n\t\t\t\trouter.resource::(\"foo2\");\n\t\t\t\trouter.scope(\"/baz\", |router| {\n\t\t\t\t\trouter.resource::(\"foo3\");\n\t\t\t\t})\n\t\t\t});\n\t\t\trouter.resource::(\"foo4\");\n\t\t});\n\t}))\n\t.unwrap();\n\n\ttest_get_response(&server, \"http://localhost/foo1\", RESPONSE);\n\ttest_get_response(&server, \"http://localhost/bar/foo2\", RESPONSE);\n\ttest_get_response(&server, \"http://localhost/bar/baz/foo3\", RESPONSE);\n\ttest_get_response(&server, \"http://localhost/foo4\", RESPONSE);\n\ttest_openapi_response(\n\t\t&server,\n\t\t\"http://localhost/openapi\",\n\t\t\"tests/openapi_supports_scope.json\"\n\t);\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","resource_error.rs"],"content":"use gotham_restful::ResourceError;\n\n#[derive(ResourceError)]\nenum Error {\n\t#[display(\"I/O Error: {0}\")]\n\tIoError(#[from] std::io::Error),\n\n\t#[status(INTERNAL_SERVER_ERROR)]\n\t#[display(\"Internal Server Error: {0}\")]\n\tInternalServerError(String)\n}\n\n#[allow(deprecated)]\nmod resource_error {\n\tuse super::Error;\n\tuse gotham::{hyper::StatusCode, mime::APPLICATION_JSON};\n\tuse gotham_restful::IntoResponseError;\n\n\t#[test]\n\tfn io_error() {\n\t\tlet err = Error::IoError(std::io::Error::last_os_error());\n\t\tlet res = err.into_response_error().unwrap();\n\t\tassert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);\n\t\tassert_eq!(res.mime(), Some(&APPLICATION_JSON));\n\t}\n\n\t#[test]\n\tfn internal_server_error() {\n\t\tlet err = Error::InternalServerError(\"Brocken\".to_owned());\n\t\tassert_eq!(&format!(\"{err}\"), \"Internal Server Error: Brocken\");\n\n\t\tlet res = err.into_response_error().unwrap();\n\t\tassert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);\n\t\tassert_eq!(res.mime(), None); // TODO shouldn't this be a json error message?\n\t}\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","sync_methods.rs"],"content":"use gotham::{\n\tmime::{APPLICATION_JSON, TEXT_PLAIN},\n\tprelude::*,\n\trouter::build_simple_router,\n\ttest::TestServer\n};\nuse gotham_restful::*;\n#[cfg(feature = \"openapi\")]\nuse openapi_type::OpenapiType;\nuse serde::Deserialize;\nuse simple_logger::SimpleLogger;\n\nmod util {\n\tinclude!(\"util/mod.rs\");\n}\nuse util::{test_delete_response, test_get_response, test_post_response, test_put_response};\n\n#[derive(Resource)]\n#[resource(read_all, read, search, create, update_all, update, delete_all, delete)]\nstruct FooResource;\n\n#[derive(Deserialize)]\n#[cfg_attr(feature = \"openapi\", derive(OpenapiType))]\n#[allow(dead_code)]\nstruct FooBody {\n\tdata: String\n}\n\n#[derive(Clone, Deserialize, StateData, StaticResponseExtender)]\n#[cfg_attr(feature = \"openapi\", derive(OpenapiType))]\n#[allow(dead_code)]\nstruct FooSearch {\n\tquery: String\n}\n\nconst READ_ALL_RESPONSE: &[u8] = b\"1ARwwSPVyOKpJKrYwqGgECPVWDl1BqajAAj7g7WJ3e\";\n#[read_all]\nfn read_all() -> Raw<&'static [u8]> {\n\tRaw::new(READ_ALL_RESPONSE, TEXT_PLAIN)\n}\n\nconst READ_RESPONSE: &[u8] = b\"FEReHoeBKU17X2bBpVAd1iUvktFL43CDu0cFYHdaP9\";\n#[read]\nfn read(_id: u64) -> Raw<&'static [u8]> {\n\tRaw::new(READ_RESPONSE, TEXT_PLAIN)\n}\n\nconst SEARCH_RESPONSE: &[u8] = b\"AWqcQUdBRHXKh3at4u79mdupOAfEbnTcx71ogCVF0E\";\n#[search]\nfn search(_body: FooSearch) -> Raw<&'static [u8]> {\n\tRaw::new(SEARCH_RESPONSE, TEXT_PLAIN)\n}\n\nconst CREATE_RESPONSE: &[u8] = b\"y6POY7wOMAB0jBRBw0FJT7DOpUNbhmT8KdpQPLkI83\";\n#[create]\nfn create(_body: FooBody) -> Raw<&'static [u8]> {\n\tRaw::new(CREATE_RESPONSE, TEXT_PLAIN)\n}\n\nconst UPDATE_ALL_RESPONSE: &[u8] = b\"QlbYg8gHE9OQvvk3yKjXJLTSXlIrg9mcqhfMXJmQkv\";\n#[update_all]\nfn update_all(_body: FooBody) -> Raw<&'static [u8]> {\n\tRaw::new(UPDATE_ALL_RESPONSE, TEXT_PLAIN)\n}\n\nconst UPDATE_RESPONSE: &[u8] = b\"qGod55RUXkT1lgPO8h0uVM6l368O2S0GrwENZFFuRu\";\n#[update]\nfn update(_id: u64, _body: FooBody) -> Raw<&'static [u8]> {\n\tRaw::new(UPDATE_RESPONSE, TEXT_PLAIN)\n}\n\nconst DELETE_ALL_RESPONSE: &[u8] = b\"Y36kZ749MRk2Nem4BedJABOZiZWPLOtiwLfJlGTwm5\";\n#[delete_all]\nfn delete_all() -> Raw<&'static [u8]> {\n\tRaw::new(DELETE_ALL_RESPONSE, TEXT_PLAIN)\n}\n\nconst DELETE_RESPONSE: &[u8] = b\"CwRzBrKErsVZ1N7yeNfjZuUn1MacvgBqk4uPOFfDDq\";\n#[delete]\nfn delete(_id: u64) -> Raw<&'static [u8]> {\n\tRaw::new(DELETE_RESPONSE, TEXT_PLAIN)\n}\n\n#[test]\nfn sync_methods() {\n\t// TODO no idea why with_local_timestamps fails here\n\t_ = SimpleLogger::new().env().with_utc_timestamps().init();\n\n\tlet server = TestServer::new(build_simple_router(|router| {\n\t\trouter.resource::(\"foo\");\n\t}))\n\t.unwrap();\n\n\ttest_get_response(&server, \"http://localhost/foo\", READ_ALL_RESPONSE);\n\ttest_get_response(&server, \"http://localhost/foo/1\", READ_RESPONSE);\n\ttest_get_response(\n\t\t&server,\n\t\t\"http://localhost/foo/search?query=hello+world\",\n\t\tSEARCH_RESPONSE\n\t);\n\ttest_post_response(\n\t\t&server,\n\t\t\"http://localhost/foo\",\n\t\tr#\"{\"data\":\"hello world\"}\"#,\n\t\tAPPLICATION_JSON,\n\t\tCREATE_RESPONSE\n\t);\n\ttest_put_response(\n\t\t&server,\n\t\t\"http://localhost/foo\",\n\t\tr#\"{\"data\":\"hello world\"}\"#,\n\t\tAPPLICATION_JSON,\n\t\tUPDATE_ALL_RESPONSE\n\t);\n\ttest_put_response(\n\t\t&server,\n\t\t\"http://localhost/foo/1\",\n\t\tr#\"{\"data\":\"hello world\"}\"#,\n\t\tAPPLICATION_JSON,\n\t\tUPDATE_RESPONSE\n\t);\n\ttest_delete_response(&server, \"http://localhost/foo\", DELETE_ALL_RESPONSE);\n\ttest_delete_response(&server, \"http://localhost/foo/1\", DELETE_RESPONSE);\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","trybuild_ui.rs"],"content":"use trybuild::TestCases;\n\n#[test]\n#[ignore]\nfn trybuild_ui() {\n\tlet t = TestCases::new();\n\tt.compile_fail(\"tests/ui/endpoint/*.rs\");\n\tt.compile_fail(\"tests/ui/from_body/*.rs\");\n\tt.compile_fail(\"tests/ui/resource/*.rs\");\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","async_state.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\nuse gotham::state::State;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all]\nasync fn read_all(state: &State) {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","auth_data_non_clone.rs"],"content":"use gotham_restful::*;\nuse serde::Deserialize;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[derive(Deserialize)]\nstruct AuthData {\n\tiat: u64,\n\texp: u64\n}\n\n#[read_all]\nasync fn read_all(auth: AuthStatus) -> Result {\n\tauth.ok()?;\n\tOk(NoContent::default())\n}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","custom_method_invalid_expr.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[endpoint(method = \"I like pizza\", uri = \"custom_read\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","custom_method_invalid_type.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[endpoint(method = \"String::new()\", uri = \"custom_read\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","custom_method_missing.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[endpoint(uri = \"custom_read\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","custom_uri_missing.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[endpoint(method = \"gotham_restful::gotham::hyper::Method::GET\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","dynamic_schema_missing_schema.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\nuse gotham::hyper::StatusCode;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\nfn status_codes() -> Vec {\n\tunimplemented!()\n}\n\n#[read_all(status_codes = \"status_codes\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","dynamic_schema_missing_status_codes.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\nuse gotham::hyper::StatusCode;\nuse gotham_restful::private::OpenapiSchema;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\nfn schema(_: StatusCode) -> OpenapiSchema {\n\tunimplemented!()\n}\n\n#[read_all(schema = \"schema\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","dynamic_schema_wrong_type.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\nfn schema(_: u16) -> String {\n\tunimplemented!()\n}\n\nfn status_codes() -> Vec {\n\tunimplemented!()\n}\n\n#[read_all(schema = \"schema\", status_codes = \"status_codes\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","invalid_attribute.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all(FooResource)]\nfn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","invalid_body_ty.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\nuse gotham_restful::gotham::hyper::Method;\n\n#[derive(Resource)]\n#[resource(endpoint)]\nstruct FooResource;\n\n#[derive(Debug)]\nstruct FooBody {\n\tfoo: String\n}\n\n#[endpoint(method = \"Method::GET\", uri = \"\", body = true)]\nfn endpoint(_: FooBody) {\n\tunimplemented!()\n}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","invalid_params_ty.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\nuse gotham_restful::gotham::hyper::Method;\n\n#[derive(Resource)]\n#[resource(endpoint)]\nstruct FooResource;\n\n#[derive(Debug)]\nstruct FooParams {\n\tfoo: String\n}\n\n#[endpoint(method = \"Method::GET\", uri = \"\", params = true)]\nfn endpoint(_: FooParams) {\n\tunimplemented!()\n}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","invalid_placeholders_ty.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\nuse gotham_restful::gotham::hyper::Method;\n\n#[derive(Resource)]\n#[resource(endpoint)]\nstruct FooResource;\n\n#[derive(Debug)]\nstruct FooPlaceholders {\n\tfoo: String\n}\n\n#[endpoint(method = \"Method::GET\", uri = \":foo\")]\nfn endpoint(_: FooPlaceholders) {\n\tunimplemented!()\n}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","invalid_return_type.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\nuse gotham_restful::gotham::hyper::Method;\n\n#[derive(Resource)]\n#[resource(endpoint)]\nstruct FooResource;\n\nstruct FooResponse;\n\n#[endpoint(method = \"Method::GET\", uri = \"\")]\nfn endpoint() -> FooResponse {\n\tunimplemented!()\n}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","non_custom_body_attribute.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all(body = false)]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","non_custom_method_attribute.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all(method = \"gotham_restful::gotham::hyper::Method::GET\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","non_custom_params_attribute.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all(params = true)]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","non_custom_uri_attribute.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all(uri = \"custom_read\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","self.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all]\nfn read_all(self) {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","too_few_args.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read)]\nstruct FooResource;\n\n#[read]\nfn read() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","too_many_args.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all]\nfn read_all(_id: u64) {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","unknown_attribute.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all(pineapple = \"on pizza\")]\nfn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","unsafe.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all]\nunsafe fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","wants_auth_non_bool.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all(wants_auth = \"yes, please\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","from_body","enum.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(FromBody)]\nenum FromBodyEnum {\n\tSomeVariant(Vec),\n\tOtherVariant(String)\n}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","resource","unknown_method.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_any)]\nstruct FooResource;\n\n#[read_all]\nfn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","util","mod.rs"],"content":"use gotham::{\n\thyper::Body,\n\ttest::TestServer\n};\nuse log::info;\nuse gotham::mime::Mime;\n#[allow(unused_imports)]\nuse std::{fs::File, io::{Read, Write}, str};\n\npub fn test_get_response(server : &TestServer, path : &str, expected : &[u8])\n{\n\tinfo!(\"GET {path}\");\n\tlet res = server.client().get(path).perform().unwrap().read_body().unwrap();\n\tlet body : &[u8] = res.as_ref();\n\tassert_eq!(body, expected);\n}\n\npub fn test_post_response(server : &TestServer, path : &str, body : B, mime : Mime, expected : &[u8])\nwhere\n\tB : Into\n{\n\tinfo!(\"POST {path}\");\n\tlet res = server.client().post(path, body, mime).perform().unwrap().read_body().unwrap();\n\tlet body : &[u8] = res.as_ref();\n\tassert_eq!(body, expected);\n}\n\npub fn test_put_response(server : &TestServer, path : &str, body : B, mime : Mime, expected : &[u8])\nwhere\n\tB : Into\n{\n\tinfo!(\"PUT {path}\");\n\tlet res = server.client().put(path, body, mime).perform().unwrap().read_body().unwrap();\n\tlet body : &[u8] = res.as_ref();\n\tassert_eq!(body, expected);\n}\n\npub fn test_delete_response(server : &TestServer, path : &str, expected : &[u8])\n{\n\tinfo!(\"DELETE {path}\");\n\tlet res = server.client().delete(path).perform().unwrap().read_body().unwrap();\n\tlet body : &[u8] = res.as_ref();\n\tassert_eq!(body, expected);\n}\n\n#[cfg(feature = \"openapi\")]\npub fn test_openapi_response(server : &TestServer, path : &str, output_file : &str)\n{\n\tinfo!(\"GET {path}\");\n\tlet res = server.client().get(path).perform().unwrap().read_body().unwrap();\n\tlet body: serde_json::Value = serde_json::from_slice(&res).unwrap();\n\n\tlet mut file = File::open(output_file).unwrap();\n\tlet expected: serde_json::Value = serde_json::from_reader(&mut file).unwrap();\n\n\t//eprintln!(\"{body}\");\n\tassert_eq!(body, expected);\n}\n","traces":[],"covered":0,"coverable":0}],"coverage":73.21178120617111,"covered":522,"coverable":713} \ No newline at end of file +{"files":[{"path":["/","home","runner","work","gotham_restful","gotham_restful","redoc","src","lib.rs"],"content":"#![forbid(elided_lifetimes_in_paths, unsafe_code)]\n\n//! Private implementation detail of the `gotham_restful` crate.\n\nuse base64::prelude::*;\nuse either::Either;\nuse sha2::{Digest, Sha256};\nuse std::{io::Write, iter};\n\n#[doc(hidden)]\npub struct Redoc {\n\t/// HTML code.\n\tpub html: Vec,\n\n\t/// JS hash base64 encoded.\n\tpub script_hash: String\n}\n\n#[doc(hidden)]\npub fn html(spec: String) -> Redoc {\n\tlet encoded_spec = spec\n\t\t.chars()\n\t\t.flat_map(|c| match c {\n\t\t\t'&' => Either::Left(\"&\".chars()),\n\t\t\t'<' => Either::Left(\"<\".chars()),\n\t\t\t'>' => Either::Left(\">\".chars()),\n\t\t\tc => Either::Right(iter::once(c))\n\t\t})\n\t\t.collect::();\n\n\tlet script = include_str!(\"script.min.js\");\n\tlet mut script_hash = Sha256::new();\n\tscript_hash.update(script);\n\tlet script_hash = BASE64_STANDARD.encode(script_hash.finalize());\n\n\tlet mut html = Vec::::new();\n\twrite!(\n\t\thtml,\n\t\tconcat!(\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\tr#\"\"#,\n\t\t\tr#\"\"#,\n\t\t\t\"\",\n\t\t\tr#\"\"#,\n\t\t\tr#\"

{}
\"#,\n\t\t\tr#\"
\"#,\n\t\t\tr#\"\"#,\n\t\t\t\"\",\n\t\t\t\"\"\n\t\t),\n\t\tencoded_spec, script\n\t)\n\t.unwrap();\n\n\tRedoc { html, script_hash }\n}\n","traces":[{"line":20,"address":[8359360,8360587],"length":1,"stats":{"Line":0}},{"line":21,"address":[6452942,6453010],"length":1,"stats":{"Line":0}},{"line":23,"address":[8329195,8329168],"length":1,"stats":{"Line":0}},{"line":24,"address":[6875301],"length":1,"stats":{"Line":0}},{"line":25,"address":[7871959],"length":1,"stats":{"Line":0}},{"line":26,"address":[7871993],"length":1,"stats":{"Line":0}},{"line":27,"address":[7963788],"length":1,"stats":{"Line":0}},{"line":31,"address":[7962626],"length":1,"stats":{"Line":0}},{"line":32,"address":[6453101],"length":1,"stats":{"Line":0}},{"line":33,"address":[8629833],"length":1,"stats":{"Line":0}},{"line":34,"address":[7870860],"length":1,"stats":{"Line":0}},{"line":36,"address":[8111001],"length":1,"stats":{"Line":0}},{"line":37,"address":[6453709],"length":1,"stats":{"Line":0}}],"covered":0,"coverable":13},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","auth.rs"],"content":"use crate::AuthError;\n\nuse base64::prelude::*;\nuse futures_util::{\n\tfuture,\n\tfuture::{FutureExt, TryFutureExt}\n};\nuse gotham::{\n\tanyhow,\n\tcookie::CookieJar,\n\thandler::HandlerFuture,\n\thyper::header::{HeaderMap, HeaderName, AUTHORIZATION},\n\tmiddleware::{cookie::CookieParser, Middleware, NewMiddleware},\n\tprelude::*,\n\tstate::State\n};\nuse jsonwebtoken::DecodingKey;\nuse serde::de::DeserializeOwned;\nuse std::{marker::PhantomData, panic::RefUnwindSafe, pin::Pin};\n\npub type AuthValidation = jsonwebtoken::Validation;\n\n/// The authentication status returned by the auth middleware for each request.\n#[derive(Debug, StateData)]\npub enum AuthStatus {\n\t/// The auth status is unknown. This is likely because no secret was provided\n\t/// that could be used to verify the token of the client.\n\tUnknown,\n\n\t/// The request has been performed without any kind of authentication.\n\tUnauthenticated,\n\n\t/// The request has been performed with an invalid authentication. This\n\t/// includes expired tokens. Further details can be obtained from the\n\t/// included error.\n\tInvalid(jsonwebtoken::errors::Error),\n\n\t/// The request has been performed with a valid authentication. The claims\n\t/// that were decoded from the token are attached.\n\tAuthenticated(T)\n}\n\nimpl Clone for AuthStatus\nwhere\n\tT: Clone + Send + 'static\n{\n\tfn clone(&self) -> Self {\n\t\t// TODO why is this manually implemented?\n\t\tmatch self {\n\t\t\tSelf::Unknown => Self::Unknown,\n\t\t\tSelf::Unauthenticated => Self::Unauthenticated,\n\t\t\tSelf::Invalid(err) => Self::Invalid(err.clone()),\n\t\t\tSelf::Authenticated(data) => Self::Authenticated(data.clone())\n\t\t}\n\t}\n}\n\nimpl AuthStatus {\n\tpub fn ok(self) -> Result {\n\t\tmatch self {\n\t\t\tSelf::Unknown => Err(AuthError::new(\"The authentication could not be determined\")),\n\t\t\tSelf::Unauthenticated => Err(AuthError::new(\"Missing token\")),\n\t\t\tSelf::Invalid(err) => Err(AuthError::new(format!(\"Invalid token: {err}\"))),\n\t\t\tSelf::Authenticated(data) => Ok(data)\n\t\t}\n\t}\n}\n\n/// The source of the authentication token in the request.\n#[derive(Clone, Debug, StateData)]\npub enum AuthSource {\n\t/// Take the token from a cookie with the given name.\n\tCookie(String),\n\t/// Take the token from a header with the given name.\n\tHeader(HeaderName),\n\t/// Take the token from the HTTP Authorization header. This is different from `Header(\"Authorization\")`\n\t/// as it will follow the `scheme param` format from the HTTP specification. The `scheme` will\n\t/// be discarded, so its value doesn't matter.\n\tAuthorizationHeader\n}\n\n/// This trait will help the auth middleware to determine the validity of an authentication token.\n///\n/// A very basic implementation could look like this:\n///\n/// ```\n/// # use gotham_restful::{AuthHandler, gotham::state::State};\n/// #\n/// const SECRET: &'static [u8; 32] = b\"zlBsA2QXnkmpe0QTh8uCvtAEa4j33YAc\";\n///\n/// struct CustomAuthHandler;\n/// impl AuthHandler for CustomAuthHandler {\n/// \tfn jwt_secret Option>(\n/// \t\t&self,\n/// \t\t_state: &mut State,\n/// \t\t_decode_data: F\n/// \t) -> Option> {\n/// \t\tSome(SECRET.to_vec())\n/// \t}\n/// }\n/// ```\npub trait AuthHandler {\n\t/// Return the SHA256-HMAC secret used to verify the JWT token.\n\tfn jwt_secret Option>(\n\t\t&self,\n\t\tstate: &mut State,\n\t\tdecode_data: F\n\t) -> Option>;\n}\n\n/// An [AuthHandler] returning always the same secret. See [AuthMiddleware] for a usage example.\n#[derive(Clone, Debug)]\npub struct StaticAuthHandler {\n\tsecret: Vec\n}\n\nimpl StaticAuthHandler {\n\tpub fn from_vec(secret: Vec) -> Self {\n\t\tSelf { secret }\n\t}\n\n\tpub fn from_array(secret: &[u8]) -> Self {\n\t\tSelf::from_vec(secret.to_vec())\n\t}\n}\n\nimpl AuthHandler for StaticAuthHandler {\n\tfn jwt_secret Option>(\n\t\t&self,\n\t\t_state: &mut State,\n\t\t_decode_data: F\n\t) -> Option> {\n\t\tSome(self.secret.clone())\n\t}\n}\n\n/// This is the auth middleware. To use it, first make sure you have the `auth` feature enabled. Then\n/// simply add it to your pipeline and request it inside your handler:\n///\n/// ```rust,no_run\n/// # #[macro_use] extern crate gotham_restful_derive;\n/// # use gotham::{router::builder::*, pipeline::*, state::State};\n/// # use gotham_restful::*;\n/// # use serde::{Deserialize, Serialize};\n/// #\n/// #[derive(Resource)]\n/// #[resource(read_all)]\n/// struct AuthResource;\n///\n/// #[derive(Debug, Deserialize, Clone)]\n/// struct AuthData {\n/// \tsub: String,\n/// \texp: u64\n/// }\n///\n/// #[read_all]\n/// fn read_all(auth: &AuthStatus) -> Success {\n/// \tformat!(\"{auth:?}\").into()\n/// }\n///\n/// fn main() {\n/// \tlet auth: AuthMiddleware = AuthMiddleware::new(\n/// \t\tAuthSource::AuthorizationHeader,\n/// \t\tAuthValidation::default(),\n/// \t\tStaticAuthHandler::from_array(b\"zlBsA2QXnkmpe0QTh8uCvtAEa4j33YAc\")\n/// \t);\n/// \tlet (chain, pipelines) = single_pipeline(new_pipeline().add(auth).build());\n/// \tgotham::start(\n/// \t\t\"127.0.0.1:8080\",\n/// \t\tbuild_router(chain, pipelines, |route| {\n/// \t\t\troute.resource::(\"auth\");\n/// \t\t})\n/// \t);\n/// }\n/// ```\n#[derive(Debug)]\npub struct AuthMiddleware {\n\tsource: AuthSource,\n\tvalidation: AuthValidation,\n\thandler: Handler,\n\t_data: PhantomData\n}\n\nimpl Clone for AuthMiddleware\nwhere\n\tHandler: Clone\n{\n\tfn clone(&self) -> Self {\n\t\tSelf {\n\t\t\tsource: self.source.clone(),\n\t\t\tvalidation: self.validation.clone(),\n\t\t\thandler: self.handler.clone(),\n\t\t\t_data: self._data\n\t\t}\n\t}\n}\n\nimpl AuthMiddleware\nwhere\n\tData: DeserializeOwned + Send,\n\tHandler: AuthHandler + Default\n{\n\tpub fn from_source(source: AuthSource) -> Self {\n\t\tSelf {\n\t\t\tsource,\n\t\t\tvalidation: Default::default(),\n\t\t\thandler: Default::default(),\n\t\t\t_data: Default::default()\n\t\t}\n\t}\n}\n\nimpl AuthMiddleware\nwhere\n\tData: DeserializeOwned + Send,\n\tHandler: AuthHandler\n{\n\tpub fn new(source: AuthSource, validation: AuthValidation, handler: Handler) -> Self {\n\t\tSelf {\n\t\t\tsource,\n\t\t\tvalidation,\n\t\t\thandler,\n\t\t\t_data: Default::default()\n\t\t}\n\t}\n\n\tfn auth_status(&self, state: &mut State) -> AuthStatus {\n\t\t// extract the provided token, if any\n\t\tlet token = match &self.source {\n\t\t\tAuthSource::Cookie(name) => CookieJar::try_borrow_from(&state)\n\t\t\t\t.map(|jar| jar.get(&name).map(|cookie| cookie.value().to_owned()))\n\t\t\t\t.unwrap_or_else(|| {\n\t\t\t\t\tCookieParser::from_state(&state)\n\t\t\t\t\t\t.get(&name)\n\t\t\t\t\t\t.map(|cookie| cookie.value().to_owned())\n\t\t\t\t}),\n\t\t\tAuthSource::Header(name) => HeaderMap::try_borrow_from(&state)\n\t\t\t\t.and_then(|map| map.get(name))\n\t\t\t\t.and_then(|header| header.to_str().ok())\n\t\t\t\t.map(|value| value.to_owned()),\n\t\t\tAuthSource::AuthorizationHeader => HeaderMap::try_borrow_from(&state)\n\t\t\t\t.and_then(|map| map.get(AUTHORIZATION))\n\t\t\t\t.and_then(|header| header.to_str().ok())\n\t\t\t\t.and_then(|value| value.split_whitespace().nth(1))\n\t\t\t\t.map(|value| value.to_owned())\n\t\t};\n\n\t\t// unauthed if no token\n\t\tlet token = match token {\n\t\t\tSome(token) => token,\n\t\t\tNone => return AuthStatus::Unauthenticated\n\t\t};\n\n\t\t// get the secret from the handler, possibly decoding claims ourselves\n\t\tlet secret = self.handler.jwt_secret(state, || {\n\t\t\tlet b64 = token.split('.').nth(1)?;\n\t\t\tlet raw = BASE64_URL_SAFE_NO_PAD.decode(b64).ok()?;\n\t\t\tserde_json::from_slice(&raw).ok()?\n\t\t});\n\n\t\t// unknown if no secret\n\t\tlet secret = match secret {\n\t\t\tSome(secret) => secret,\n\t\t\tNone => return AuthStatus::Unknown\n\t\t};\n\n\t\t// validate the token\n\t\tlet data: Data = match jsonwebtoken::decode(\n\t\t\t&token,\n\t\t\t&DecodingKey::from_secret(&secret),\n\t\t\t&self.validation\n\t\t) {\n\t\t\tOk(data) => data.claims,\n\t\t\tErr(e) => return AuthStatus::Invalid(e)\n\t\t};\n\n\t\t// we found a valid token\n\t\tAuthStatus::Authenticated(data)\n\t}\n}\n\nimpl Middleware for AuthMiddleware\nwhere\n\tData: DeserializeOwned + Send + 'static,\n\tHandler: AuthHandler\n{\n\tfn call(self, mut state: State, chain: Chain) -> Pin>\n\twhere\n\t\tChain: FnOnce(State) -> Pin>\n\t{\n\t\t// put the source in our state, required for e.g. openapi\n\t\tstate.put(self.source.clone());\n\n\t\t// put the status in our state\n\t\tlet status = self.auth_status(&mut state);\n\t\tstate.put(status);\n\n\t\t// call the rest of the chain\n\t\tchain(state)\n\t\t\t.and_then(|(state, res)| future::ok((state, res)))\n\t\t\t.boxed()\n\t}\n}\n\nimpl NewMiddleware for AuthMiddleware\nwhere\n\tSelf: Clone + Middleware + Sync + RefUnwindSafe\n{\n\ttype Instance = Self;\n\n\tfn new_middleware(&self) -> anyhow::Result {\n\t\tlet c: Self = self.clone();\n\t\tOk(c)\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse gotham::{cookie::Cookie, hyper::header::COOKIE};\n\tuse jsonwebtoken::errors::ErrorKind;\n\tuse std::fmt::Debug;\n\n\t// 256-bit random string\n\tconst JWT_SECRET: &'static [u8; 32] = b\"Lyzsfnta0cdxyF0T9y6VGxp3jpgoMUuW\";\n\n\t// some known tokens\n\tconst VALID_TOKEN: &'static str = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9.8h8Ax-nnykqEQ62t7CxmM3ja6NzUQ4L0MLOOzddjLKk\";\n\tconst EXPIRED_TOKEN: &'static str = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjE1Nzc4MzcxMDB9.eV1snaGLYrJ7qUoMk74OvBY3WUU9M0Je5HTU2xtX1v0\";\n\tconst INVALID_TOKEN: &'static str = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9\";\n\n\t#[derive(Debug, Deserialize, PartialEq)]\n\tstruct TestData {\n\t\tiss: String,\n\t\tsub: String,\n\t\tiat: u64,\n\t\texp: u64\n\t}\n\n\timpl Default for TestData {\n\t\tfn default() -> Self {\n\t\t\tSelf {\n\t\t\t\tiss: \"msrd0\".to_owned(),\n\t\t\t\tsub: \"gotham-restful\".to_owned(),\n\t\t\t\tiat: 1577836800,\n\t\t\t\texp: 4102444800\n\t\t\t}\n\t\t}\n\t}\n\n\t#[derive(Default)]\n\tstruct NoneAuthHandler;\n\timpl AuthHandler for NoneAuthHandler {\n\t\tfn jwt_secret Option>(\n\t\t\t&self,\n\t\t\t_state: &mut State,\n\t\t\t_decode_data: F\n\t\t) -> Option> {\n\t\t\tNone\n\t\t}\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_none_secret() {\n\t\tlet middleware = >::from_source(\n\t\t\tAuthSource::AuthorizationHeader\n\t\t);\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut headers = HeaderMap::new();\n\t\t\theaders.insert(\n\t\t\t\tAUTHORIZATION,\n\t\t\t\tformat!(\"Bearer {VALID_TOKEN}\").parse().unwrap()\n\t\t\t);\n\t\t\tstate.put(headers);\n\t\t\tmiddleware.auth_status(&mut state);\n\t\t});\n\t}\n\n\t#[derive(Default)]\n\tstruct TestAssertingHandler;\n\timpl AuthHandler for TestAssertingHandler\n\twhere\n\t\tT: Debug + Default + PartialEq\n\t{\n\t\tfn jwt_secret Option>(\n\t\t\t&self,\n\t\t\t_state: &mut State,\n\t\t\tdecode_data: F\n\t\t) -> Option> {\n\t\t\tassert_eq!(decode_data(), Some(T::default()));\n\t\t\tSome(JWT_SECRET.to_vec())\n\t\t}\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_decode_data() {\n\t\tlet middleware = >::from_source(\n\t\t\tAuthSource::AuthorizationHeader\n\t\t);\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut headers = HeaderMap::new();\n\t\t\theaders.insert(\n\t\t\t\tAUTHORIZATION,\n\t\t\t\tformat!(\"Bearer {VALID_TOKEN}\").parse().unwrap()\n\t\t\t);\n\t\t\tstate.put(headers);\n\t\t\tmiddleware.auth_status(&mut state);\n\t\t});\n\t}\n\n\tfn new_middleware(source: AuthSource) -> AuthMiddleware\n\twhere\n\t\tT: DeserializeOwned + Send\n\t{\n\t\tAuthMiddleware::new(\n\t\t\tsource,\n\t\t\tDefault::default(),\n\t\t\tStaticAuthHandler::from_array(JWT_SECRET)\n\t\t)\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_no_token() {\n\t\tlet middleware = new_middleware::(AuthSource::AuthorizationHeader);\n\t\tState::with_new(|mut state| {\n\t\t\tlet status = middleware.auth_status(&mut state);\n\t\t\tmatch status {\n\t\t\t\tAuthStatus::Unauthenticated => {},\n\t\t\t\t_ => panic!(\"Expected AuthStatus::Unauthenticated, got {status:?}\")\n\t\t\t};\n\t\t});\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_expired_token() {\n\t\tlet middleware = new_middleware::(AuthSource::AuthorizationHeader);\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut headers = HeaderMap::new();\n\t\t\theaders.insert(\n\t\t\t\tAUTHORIZATION,\n\t\t\t\tformat!(\"Bearer {EXPIRED_TOKEN}\").parse().unwrap()\n\t\t\t);\n\t\t\tstate.put(headers);\n\t\t\tlet status = middleware.auth_status(&mut state);\n\t\t\tmatch status {\n\t\t\t\tAuthStatus::Invalid(err) if *err.kind() == ErrorKind::ExpiredSignature => {},\n\t\t\t\t_ => panic!(\n\t\t\t\t\t\"Expected AuthStatus::Invalid(..) with ErrorKind::ExpiredSignature, got {status:?}\"\n\t\t\t\t)\n\t\t\t};\n\t\t});\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_invalid_token() {\n\t\tlet middleware = new_middleware::(AuthSource::AuthorizationHeader);\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut headers = HeaderMap::new();\n\t\t\theaders.insert(\n\t\t\t\tAUTHORIZATION,\n\t\t\t\tformat!(\"Bearer {INVALID_TOKEN}\").parse().unwrap()\n\t\t\t);\n\t\t\tstate.put(headers);\n\t\t\tlet status = middleware.auth_status(&mut state);\n\t\t\tmatch status {\n\t\t\t\tAuthStatus::Invalid(err) if *err.kind() == ErrorKind::InvalidToken => {},\n\t\t\t\t_ => panic!(\n\t\t\t\t\t\"Expected AuthStatus::Invalid(..) with ErrorKind::InvalidToken, got {status:?}\"\n\t\t\t\t)\n\t\t\t};\n\t\t});\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_auth_header_token() {\n\t\tlet middleware = new_middleware::(AuthSource::AuthorizationHeader);\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut headers = HeaderMap::new();\n\t\t\theaders.insert(\n\t\t\t\tAUTHORIZATION,\n\t\t\t\tformat!(\"Bearer {VALID_TOKEN}\").parse().unwrap()\n\t\t\t);\n\t\t\tstate.put(headers);\n\t\t\tlet status = middleware.auth_status(&mut state);\n\t\t\tmatch status {\n\t\t\t\tAuthStatus::Authenticated(data) => assert_eq!(data, TestData::default()),\n\t\t\t\t_ => panic!(\"Expected AuthStatus::Authenticated, got {status:?}\")\n\t\t\t};\n\t\t})\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_header_token() {\n\t\tlet header_name = \"x-znoiprwmvfexju\";\n\t\tlet middleware =\n\t\t\tnew_middleware::(AuthSource::Header(HeaderName::from_static(header_name)));\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut headers = HeaderMap::new();\n\t\t\theaders.insert(header_name, VALID_TOKEN.parse().unwrap());\n\t\t\tstate.put(headers);\n\t\t\tlet status = middleware.auth_status(&mut state);\n\t\t\tmatch status {\n\t\t\t\tAuthStatus::Authenticated(data) => assert_eq!(data, TestData::default()),\n\t\t\t\t_ => panic!(\"Expected AuthStatus::Authenticated, got {status:?}\")\n\t\t\t};\n\t\t})\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_cookie_token() {\n\t\tlet cookie_name = \"znoiprwmvfexju\";\n\t\tlet middleware = new_middleware::(AuthSource::Cookie(cookie_name.to_owned()));\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut jar = CookieJar::new();\n\t\t\tjar.add_original(Cookie::new(cookie_name, VALID_TOKEN));\n\t\t\tstate.put(jar);\n\t\t\tlet status = middleware.auth_status(&mut state);\n\t\t\tmatch status {\n\t\t\t\tAuthStatus::Authenticated(data) => assert_eq!(data, TestData::default()),\n\t\t\t\t_ => panic!(\"Expected AuthStatus::Authenticated, got {status:?}\")\n\t\t\t};\n\t\t})\n\t}\n\n\t#[test]\n\tfn test_auth_middleware_cookie_no_jar() {\n\t\tlet cookie_name = \"znoiprwmvfexju\";\n\t\tlet middleware = new_middleware::(AuthSource::Cookie(cookie_name.to_owned()));\n\t\tState::with_new(|mut state| {\n\t\t\tlet mut headers = HeaderMap::new();\n\t\t\theaders.insert(\n\t\t\t\tCOOKIE,\n\t\t\t\tformat!(\"{cookie_name}={VALID_TOKEN}\").parse().unwrap()\n\t\t\t);\n\t\t\tstate.put(headers);\n\t\t\tlet status = middleware.auth_status(&mut state);\n\t\t\tmatch status {\n\t\t\t\tAuthStatus::Authenticated(data) => assert_eq!(data, TestData::default()),\n\t\t\t\t_ => panic!(\"Expected AuthStatus::Authenticated, got {status:?}\")\n\t\t\t};\n\t\t})\n\t}\n}\n","traces":[{"line":47,"address":[7936320],"length":1,"stats":{"Line":0}},{"line":49,"address":[],"length":0,"stats":{"Line":0}},{"line":50,"address":[],"length":0,"stats":{"Line":0}},{"line":51,"address":[],"length":0,"stats":{"Line":0}},{"line":52,"address":[],"length":0,"stats":{"Line":0}},{"line":53,"address":[7936495],"length":1,"stats":{"Line":0}},{"line":59,"address":[],"length":0,"stats":{"Line":0}},{"line":60,"address":[],"length":0,"stats":{"Line":0}},{"line":61,"address":[],"length":0,"stats":{"Line":0}},{"line":62,"address":[],"length":0,"stats":{"Line":0}},{"line":63,"address":[],"length":0,"stats":{"Line":0}},{"line":64,"address":[],"length":0,"stats":{"Line":0}},{"line":118,"address":[7832256],"length":1,"stats":{"Line":2}},{"line":122,"address":[8351328],"length":1,"stats":{"Line":2}},{"line":123,"address":[6174679],"length":1,"stats":{"Line":3}},{"line":128,"address":[6186957,6186912],"length":1,"stats":{"Line":1}},{"line":133,"address":[7792349,7792401],"length":1,"stats":{"Line":2}},{"line":188,"address":[7938318,7938048],"length":1,"stats":{"Line":1}},{"line":190,"address":[7938082],"length":1,"stats":{"Line":1}},{"line":191,"address":[7938092],"length":1,"stats":{"Line":1}},{"line":192,"address":[],"length":0,"stats":{"Line":1}},{"line":193,"address":[],"length":0,"stats":{"Line":0}},{"line":203,"address":[6187250,6187024,6187506,6187280],"length":1,"stats":{"Line":2}},{"line":206,"address":[6187066,6187322],"length":1,"stats":{"Line":2}},{"line":207,"address":[6187119,6187375],"length":1,"stats":{"Line":2}},{"line":208,"address":[6187164,6187420],"length":1,"stats":{"Line":2}},{"line":218,"address":[6187833,6187536,6187796],"length":1,"stats":{"Line":2}},{"line":223,"address":[],"length":0,"stats":{"Line":3}},{"line":227,"address":[7819110,7819079,7817856],"length":1,"stats":{"Line":4}},{"line":229,"address":[7817891],"length":1,"stats":{"Line":4}},{"line":230,"address":[],"length":0,"stats":{"Line":1}},{"line":231,"address":[],"length":0,"stats":{"Line":4}},{"line":232,"address":[],"length":0,"stats":{"Line":1}},{"line":233,"address":[7819403,7819498],"length":1,"stats":{"Line":2}},{"line":234,"address":[],"length":0,"stats":{"Line":1}},{"line":235,"address":[7819902,7819872],"length":1,"stats":{"Line":2}},{"line":237,"address":[6190637,6188013,6189325],"length":1,"stats":{"Line":1}},{"line":238,"address":[7819632,7819657],"length":1,"stats":{"Line":2}},{"line":239,"address":[7819769,7819760],"length":1,"stats":{"Line":2}},{"line":240,"address":[6193350,6193398,6193446,6193328,6193424,6193376],"length":1,"stats":{"Line":2}},{"line":241,"address":[7818074],"length":1,"stats":{"Line":4}},{"line":242,"address":[],"length":0,"stats":{"Line":8}},{"line":243,"address":[6193721,6193808,6193760,6193712,6193769,6193817],"length":1,"stats":{"Line":6}},{"line":244,"address":[],"length":0,"stats":{"Line":6}},{"line":245,"address":[7819328,7819350],"length":1,"stats":{"Line":6}},{"line":249,"address":[7818124],"length":1,"stats":{"Line":4}},{"line":250,"address":[7818181],"length":1,"stats":{"Line":3}},{"line":251,"address":[6188161,6190785,6189473],"length":1,"stats":{"Line":2}},{"line":255,"address":[7818239],"length":1,"stats":{"Line":4}},{"line":256,"address":[],"length":0,"stats":{"Line":1}},{"line":257,"address":[6194545,6194447,6194333],"length":1,"stats":{"Line":2}},{"line":258,"address":[6194511,6194617,6194775,6194209],"length":1,"stats":{"Line":3}},{"line":262,"address":[],"length":0,"stats":{"Line":3}},{"line":263,"address":[6191010,6188386,6189698],"length":1,"stats":{"Line":2}},{"line":264,"address":[7818371],"length":1,"stats":{"Line":1}},{"line":268,"address":[7818702,7818651],"length":1,"stats":{"Line":4}},{"line":269,"address":[],"length":0,"stats":{"Line":2}},{"line":270,"address":[7818558],"length":1,"stats":{"Line":2}},{"line":271,"address":[7818631],"length":1,"stats":{"Line":2}},{"line":273,"address":[6191389,6190077,6188765],"length":1,"stats":{"Line":2}},{"line":274,"address":[7818839],"length":1,"stats":{"Line":2}},{"line":278,"address":[],"length":0,"stats":{"Line":2}},{"line":287,"address":[7793152,7793119,7795664,7796463,7796080,7795215,7795248,7794367,7795631,7793568,7792736,7794777,7794832,7793535,7793984,7793951,7794400,7794814,7796047],"length":1,"stats":{"Line":1}},{"line":292,"address":[],"length":0,"stats":{"Line":2}},{"line":295,"address":[7794972,7793708,7794545,7793292,7795804,7796220,7795388,7794124,7792876],"length":1,"stats":{"Line":1}},{"line":296,"address":[],"length":0,"stats":{"Line":1}},{"line":299,"address":[],"length":0,"stats":{"Line":2}},{"line":300,"address":[],"length":0,"stats":{"Line":2}},{"line":311,"address":[],"length":0,"stats":{"Line":1}},{"line":312,"address":[],"length":0,"stats":{"Line":1}},{"line":313,"address":[],"length":0,"stats":{"Line":1}}],"covered":58,"coverable":71},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","cors.rs"],"content":"use gotham::{\n\thandler::HandlerFuture,\n\thelpers::http::response::create_empty_response,\n\thyper::{\n\t\theader::{\n\t\t\tHeaderMap, HeaderName, HeaderValue, ACCESS_CONTROL_ALLOW_CREDENTIALS,\n\t\t\tACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS,\n\t\t\tACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_MAX_AGE, ACCESS_CONTROL_REQUEST_HEADERS,\n\t\t\tACCESS_CONTROL_REQUEST_METHOD, ORIGIN, VARY\n\t\t},\n\t\tBody, Method, Response, StatusCode\n\t},\n\tmiddleware::Middleware,\n\tpipeline::PipelineHandleChain,\n\tprelude::*,\n\trouter::{builder::ExtendRouteMatcher, route::matcher::AccessControlRequestMethodMatcher},\n\tstate::State\n};\nuse std::{panic::RefUnwindSafe, pin::Pin};\n\n/// Specify the allowed origins of the request. It is up to the browser to check the validity of the\n/// origin. This, when sent to the browser, will indicate whether or not the request's origin was\n/// allowed to make the request.\n#[derive(Clone, Debug)]\npub enum Origin {\n\t/// Do not send any `Access-Control-Allow-Origin` headers.\n\tNone,\n\t/// Send `Access-Control-Allow-Origin: *`. Note that browser will not send credentials.\n\tStar,\n\t/// Set the `Access-Control-Allow-Origin` header to a single origin.\n\tSingle(String),\n\t/// Copy the `Origin` header into the `Access-Control-Allow-Origin` header.\n\tCopy\n}\n\nimpl Default for Origin {\n\tfn default() -> Self {\n\t\tSelf::None\n\t}\n}\n\nimpl Origin {\n\t/// Get the header value for the `Access-Control-Allow-Origin` header.\n\tfn header_value(&self, state: &State) -> Option {\n\t\tmatch self {\n\t\t\tSelf::None => None,\n\t\t\tSelf::Star => Some(\"*\".parse().unwrap()),\n\t\t\tSelf::Single(origin) => Some(origin.parse().unwrap()),\n\t\t\tSelf::Copy => {\n\t\t\t\tlet headers = HeaderMap::borrow_from(state);\n\t\t\t\theaders.get(ORIGIN).cloned()\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Returns true if the `Vary` header has to include `Origin`.\n\tfn varies(&self) -> bool {\n\t\tmatches!(self, Self::Copy)\n\t}\n}\n\n/// Specify the allowed headers of the request. It is up to the browser to check that only the allowed\n/// headers are sent with the request.\n#[derive(Clone, Debug)]\npub enum Headers {\n\t/// Do not send any `Access-Control-Allow-Headers` headers.\n\tNone,\n\t/// Set the `Access-Control-Allow-Headers` header to the following header list. If empty, this\n\t/// is treated as if it was [None].\n\tList(Vec),\n\t/// Copy the `Access-Control-Request-Headers` header into the `Access-Control-Allow-Header`\n\t/// header.\n\tCopy\n}\n\nimpl Default for Headers {\n\tfn default() -> Self {\n\t\tSelf::None\n\t}\n}\n\nimpl Headers {\n\t/// Get the header value for the `Access-Control-Allow-Headers` header.\n\tfn header_value(&self, state: &State) -> Option {\n\t\tmatch self {\n\t\t\tSelf::None => None,\n\t\t\tSelf::List(list) => Some(list.join(\",\").parse().unwrap()),\n\t\t\tSelf::Copy => {\n\t\t\t\tlet headers = HeaderMap::borrow_from(state);\n\t\t\t\theaders.get(ACCESS_CONTROL_REQUEST_HEADERS).cloned()\n\t\t\t}\n\t\t}\n\t}\n\n\t/// Returns true if the `Vary` header has to include `Origin`.\n\tfn varies(&self) -> bool {\n\t\tmatches!(self, Self::Copy)\n\t}\n}\n\n/// This is the configuration that the CORS handler will follow. Its default configuration is basically\n/// not to touch any responses, resulting in the browser's default behaviour.\n///\n/// To change settings, you need to put this type into gotham's [State]:\n///\n/// ```rust,no_run\n/// # use gotham::{router::builder::*, pipeline::*, state::State};\n/// # use gotham_restful::{*, cors::Origin};\n/// # #[cfg_attr(feature = \"cargo-clippy\", allow(clippy::needless_doctest_main))]\n/// fn main() {\n/// \tlet cors = CorsConfig {\n/// \t\torigin: Origin::Star,\n/// \t\t..Default::default()\n/// \t};\n/// \tlet (chain, pipelines) = single_pipeline(new_pipeline().add(cors).build());\n/// \tgotham::start(\n/// \t\t\"127.0.0.1:8080\",\n/// \t\tbuild_router(chain, pipelines, |route| {\n/// \t\t\t// your routing logic\n/// \t\t})\n/// \t);\n/// }\n/// ```\n///\n/// This easy approach allows you to have one global cors configuration. If you prefer to have separate\n/// configurations for different scopes, you need to register the middleware inside your routing logic:\n///\n/// ```rust,no_run\n/// # use gotham::{router::builder::*, pipeline::*, state::State};\n/// # use gotham_restful::{*, cors::Origin};\n/// let pipelines = new_pipeline_set();\n///\n/// // The first cors configuration\n/// let cors_a = CorsConfig {\n/// \torigin: Origin::Star,\n/// \t..Default::default()\n/// };\n/// let (pipelines, chain_a) = pipelines.add(new_pipeline().add(cors_a).build());\n///\n/// // The second cors configuration\n/// let cors_b = CorsConfig {\n/// \torigin: Origin::Copy,\n/// \t..Default::default()\n/// };\n/// let (pipelines, chain_b) = pipelines.add(new_pipeline().add(cors_b).build());\n///\n/// let pipeline_set = finalize_pipeline_set(pipelines);\n/// gotham::start(\n/// \t\"127.0.0.1:8080\",\n/// \tbuild_router((), pipeline_set, |route| {\n/// \t\t// routing without any cors config\n/// \t\troute.with_pipeline_chain((chain_a, ()), |route| {\n/// \t\t\t// routing with cors config a\n/// \t\t});\n/// \t\troute.with_pipeline_chain((chain_b, ()), |route| {\n/// \t\t\t// routing with cors config b\n/// \t\t});\n/// \t})\n/// );\n/// ```\n#[derive(Clone, Debug, Default, NewMiddleware, StateData)]\npub struct CorsConfig {\n\t/// The allowed origins.\n\tpub origin: Origin,\n\t/// The allowed headers.\n\tpub headers: Headers,\n\t/// The amount of seconds that the preflight request can be cached.\n\tpub max_age: u64,\n\t/// Whether or not the request may be made with supplying credentials.\n\tpub credentials: bool\n}\n\nimpl Middleware for CorsConfig {\n\tfn call(self, mut state: State, chain: Chain) -> Pin>\n\twhere\n\t\tChain: FnOnce(State) -> Pin>\n\t{\n\t\tstate.put(self);\n\t\tchain(state)\n\t}\n}\n\n/// Handle CORS for a non-preflight request. This means manipulating the `res` HTTP headers so that\n/// the response is aligned with the `state`'s [CorsConfig].\n///\n/// If you are using the [Resource](crate::Resource) type (which is the recommended way), you'll never\n/// have to call this method. However, if you are writing your own handler method, you might want to\n/// call this after your request to add the required CORS headers.\n///\n/// For further information on CORS, read .\npub fn handle_cors(state: &State, res: &mut Response) {\n\tlet config = CorsConfig::try_borrow_from(state);\n\tif let Some(cfg) = config {\n\t\tlet headers = res.headers_mut();\n\n\t\t// non-preflight requests require the Access-Control-Allow-Origin header\n\t\tif let Some(header) = cfg.origin.header_value(state) {\n\t\t\theaders.insert(ACCESS_CONTROL_ALLOW_ORIGIN, header);\n\t\t}\n\n\t\t// if the origin is copied over, we should tell the browser by specifying the Vary header\n\t\tif cfg.origin.varies() {\n\t\t\tlet vary = headers\n\t\t\t\t.get(VARY)\n\t\t\t\t.map(|vary| format!(\"{},origin\", vary.to_str().unwrap()));\n\t\t\theaders.insert(VARY, vary.as_deref().unwrap_or(\"origin\").parse().unwrap());\n\t\t}\n\n\t\t// if we allow credentials, tell the browser\n\t\tif cfg.credentials {\n\t\t\theaders.insert(\n\t\t\t\tACCESS_CONTROL_ALLOW_CREDENTIALS,\n\t\t\t\tHeaderValue::from_static(\"true\")\n\t\t\t);\n\t\t}\n\t}\n}\n\n/// Add CORS routing for your path. This is required for handling preflight requests.\n///\n/// Example:\n///\n/// ```rust,no_run\n/// # use gotham::{hyper::{Body, Method, Response}, router::builder::*};\n/// # use gotham_restful::*;\n/// build_simple_router(|router| {\n/// \t// The handler that needs preflight handling\n/// \trouter.post(\"/foo\").to(|state| {\n/// \t\tlet mut res: Response = unimplemented!();\n/// \t\thandle_cors(&state, &mut res);\n/// \t\t(state, res)\n/// \t});\n/// \t// Add preflight handling\n/// \trouter.cors(\"/foo\", Method::POST);\n/// });\n/// ```\npub trait CorsRoute\nwhere\n\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\tP: RefUnwindSafe + Send + Sync + 'static\n{\n\t/// Handle a preflight request on `path` for `method`. To configure the behaviour, use\n\t/// [CorsConfig].\n\tfn cors(&mut self, path: &str, method: Method);\n}\n\npub(crate) fn cors_preflight_handler(state: State) -> (State, Response) {\n\tlet config = CorsConfig::try_borrow_from(&state);\n\n\t// prepare the response\n\tlet mut res = create_empty_response(&state, StatusCode::NO_CONTENT);\n\tlet headers = res.headers_mut();\n\tlet mut vary: Vec = Vec::new();\n\n\t// copy the request method over to the response\n\tlet method = HeaderMap::borrow_from(&state)\n\t\t.get(ACCESS_CONTROL_REQUEST_METHOD)\n\t\t.unwrap()\n\t\t.clone();\n\theaders.insert(ACCESS_CONTROL_ALLOW_METHODS, method);\n\tvary.push(ACCESS_CONTROL_REQUEST_METHOD);\n\n\tif let Some(cfg) = config {\n\t\t// if we allow any headers, copy them over\n\t\tif let Some(header) = cfg.headers.header_value(&state) {\n\t\t\theaders.insert(ACCESS_CONTROL_ALLOW_HEADERS, header);\n\t\t}\n\n\t\t// if the headers are copied over, we should tell the browser by specifying the Vary header\n\t\tif cfg.headers.varies() {\n\t\t\tvary.push(ACCESS_CONTROL_REQUEST_HEADERS);\n\t\t}\n\n\t\t// set the max age for the preflight cache\n\t\tif let Some(age) = config.map(|cfg| cfg.max_age) {\n\t\t\theaders.insert(ACCESS_CONTROL_MAX_AGE, age.into());\n\t\t}\n\t}\n\n\t// make sure the browser knows that this request was based on the method\n\theaders.insert(VARY, vary.join(\",\").parse().unwrap());\n\n\thandle_cors(&state, &mut res);\n\t(state, res)\n}\n\nimpl CorsRoute for D\nwhere\n\tD: DrawRoutes,\n\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\tP: RefUnwindSafe + Send + Sync + 'static\n{\n\tfn cors(&mut self, path: &str, method: Method) {\n\t\tlet matcher = AccessControlRequestMethodMatcher::new(method);\n\t\tself.options(path)\n\t\t\t.extend_route_matcher(matcher)\n\t\t\t.to(cors_preflight_handler);\n\t}\n}\n","traces":[{"line":37,"address":[7647600],"length":1,"stats":{"Line":2}},{"line":38,"address":[7647603],"length":1,"stats":{"Line":1}},{"line":44,"address":[8136416],"length":1,"stats":{"Line":1}},{"line":45,"address":[7739563],"length":1,"stats":{"Line":3}},{"line":46,"address":[8105068],"length":1,"stats":{"Line":1}},{"line":47,"address":[8136533,8136691],"length":1,"stats":{"Line":2}},{"line":48,"address":[8407111,8406940],"length":1,"stats":{"Line":2}},{"line":50,"address":[8407136],"length":1,"stats":{"Line":1}},{"line":51,"address":[7648145],"length":1,"stats":{"Line":1}},{"line":57,"address":[7888224],"length":1,"stats":{"Line":1}},{"line":58,"address":[7888229],"length":1,"stats":{"Line":3}},{"line":77,"address":[7888272],"length":1,"stats":{"Line":3}},{"line":78,"address":[7888275],"length":1,"stats":{"Line":1}},{"line":84,"address":[6231274,6230672],"length":1,"stats":{"Line":1}},{"line":85,"address":[8105707],"length":1,"stats":{"Line":3}},{"line":86,"address":[8137229],"length":1,"stats":{"Line":2}},{"line":87,"address":[7888630,7888872,7888427],"length":1,"stats":{"Line":2}},{"line":89,"address":[8137328],"length":1,"stats":{"Line":1}},{"line":90,"address":[7888529],"length":1,"stats":{"Line":1}},{"line":96,"address":[7888928],"length":1,"stats":{"Line":2}},{"line":97,"address":[6261973],"length":1,"stats":{"Line":1}},{"line":174,"address":[6295286,6295088,6294672,6295260,6295078,6295052,6294870,6294844,6294880],"length":1,"stats":{"Line":3}},{"line":178,"address":[],"length":0,"stats":{"Line":3}},{"line":179,"address":[],"length":0,"stats":{"Line":10}},{"line":191,"address":[7650215,7649008,7650183],"length":1,"stats":{"Line":7}},{"line":192,"address":[8408049],"length":1,"stats":{"Line":5}},{"line":193,"address":[8106404],"length":1,"stats":{"Line":7}},{"line":194,"address":[7889093],"length":1,"stats":{"Line":1}},{"line":197,"address":[7741042,7741102],"length":1,"stats":{"Line":4}},{"line":198,"address":[7741138],"length":1,"stats":{"Line":1}},{"line":202,"address":[8408410],"length":1,"stats":{"Line":3}},{"line":203,"address":[8408463],"length":1,"stats":{"Line":1}},{"line":205,"address":[8409588,8409302,8409685,8409264,8409465],"length":1,"stats":{"Line":5}},{"line":206,"address":[6232212,6231868,6232560],"length":1,"stats":{"Line":2}},{"line":210,"address":[6231771],"length":1,"stats":{"Line":1}},{"line":211,"address":[7742001],"length":1,"stats":{"Line":1}},{"line":212,"address":[7650009],"length":1,"stats":{"Line":1}},{"line":213,"address":[7741935],"length":1,"stats":{"Line":1}},{"line":247,"address":[7742576,7744020,7744774],"length":1,"stats":{"Line":1}},{"line":248,"address":[6263858,6263738],"length":1,"stats":{"Line":4}},{"line":251,"address":[6263866],"length":1,"stats":{"Line":3}},{"line":252,"address":[7742782,7742858],"length":1,"stats":{"Line":4}},{"line":253,"address":[7890946],"length":1,"stats":{"Line":1}},{"line":256,"address":[8139789,8139993,8139851],"length":1,"stats":{"Line":7}},{"line":260,"address":[6264220],"length":1,"stats":{"Line":1}},{"line":261,"address":[8140092],"length":1,"stats":{"Line":3}},{"line":263,"address":[7891333],"length":1,"stats":{"Line":1}},{"line":265,"address":[7651508,7651431],"length":1,"stats":{"Line":5}},{"line":266,"address":[7651701,7651609],"length":1,"stats":{"Line":2}},{"line":270,"address":[6234079,6234113],"length":1,"stats":{"Line":3}},{"line":271,"address":[8410820],"length":1,"stats":{"Line":1}},{"line":275,"address":[8109111,8109209,8110261,8110256],"length":1,"stats":{"Line":8}},{"line":276,"address":[6264908],"length":1,"stats":{"Line":3}},{"line":281,"address":[8411149,8410446,8411850,8411505],"length":1,"stats":{"Line":4}},{"line":283,"address":[8411646],"length":1,"stats":{"Line":1}},{"line":284,"address":[6235001],"length":1,"stats":{"Line":2}},{"line":293,"address":[],"length":0,"stats":{"Line":0}},{"line":294,"address":[],"length":0,"stats":{"Line":0}},{"line":295,"address":[],"length":0,"stats":{"Line":0}},{"line":296,"address":[],"length":0,"stats":{"Line":0}},{"line":297,"address":[],"length":0,"stats":{"Line":0}}],"covered":56,"coverable":61},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","endpoint.rs"],"content":"#[cfg(feature = \"openapi\")]\nuse crate::openapi::operation::OperationId;\nuse crate::{IntoResponse, RequestBody};\nuse futures_util::future::BoxFuture;\nuse gotham::{\n\textractor::{PathExtractor, QueryStringExtractor},\n\thyper::{Body, Method, Response},\n\trouter::response::StaticResponseExtender,\n\tstate::{State, StateData}\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::{OpenapiType, Visitor};\nuse serde::{Deserialize, Deserializer};\nuse std::borrow::Cow;\n\n/// A no-op extractor that can be used as a default type for [Endpoint::Placeholders] and\n/// [Endpoint::Params].\n#[derive(Debug, Clone, Copy)]\npub struct NoopExtractor;\n\nimpl<'de> Deserialize<'de> for NoopExtractor {\n\tfn deserialize>(_: D) -> Result {\n\t\tOk(Self)\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl OpenapiType for NoopExtractor {\n\tfn visit_type(visitor: &mut V) {\n\t\twarn!(\n\t\t\t\"You're asking for the OpenAPI Schema for gotham_restful::NoopExtractor. This is probably not what you want.\"\n\t\t);\n\t\tvisitor.visit_unit();\n\t}\n}\n\nimpl StateData for NoopExtractor {}\n\nimpl StaticResponseExtender for NoopExtractor {\n\ttype ResBody = Body;\n\tfn extend(_: &mut State, _: &mut Response) {}\n}\n\n// TODO: Specify default types once https://github.com/rust-lang/rust/issues/29661 lands.\n#[_private_openapi_trait(EndpointWithSchema)]\npub trait Endpoint {\n\t/// The HTTP Verb of this endpoint.\n\tfn http_method() -> Method;\n\t/// The URI that this endpoint listens on in gotham's format.\n\tfn uri() -> Cow<'static, str>;\n\n\t/// The verb used for generating an operation id if [Self::operation_id] returns [None].\n\t/// For example `read`, `read_all`, `create`, `update` etc.\n\t#[openapi_only]\n\tfn operation_verb() -> Option<&'static str>;\n\n\t/// The output type that provides the response.\n\t#[openapi_bound(Output: crate::ResponseSchema)]\n\ttype Output: IntoResponse + Send;\n\n\t/// Returns `true` _iff_ the URI contains placeholders. `false` by default.\n\tfn has_placeholders() -> bool {\n\t\tfalse\n\t}\n\t/// The type that parses the URI placeholders. Use [NoopExtractor] if `has_placeholders()`\n\t/// returns `false`.\n\t#[openapi_bound(Placeholders: OpenapiType)]\n\ttype Placeholders: PathExtractor + Clone + Sync;\n\n\t/// Returns `true` _iff_ the request parameters should be parsed. `false` by default.\n\tfn needs_params() -> bool {\n\t\tfalse\n\t}\n\t/// The type that parses the request parameters. Use [NoopExtractor] if `needs_params()`\n\t/// returns `false`.\n\t#[openapi_bound(Params: OpenapiType)]\n\ttype Params: QueryStringExtractor + Clone + Sync;\n\n\t/// Returns `true` _iff_ the request body should be parsed. `false` by default.\n\tfn needs_body() -> bool {\n\t\tfalse\n\t}\n\t/// The type to parse the body into. Use `()` if `needs_body()` returns `false`.\n\ttype Body: RequestBody + Send;\n\n\t/// Returns `true` if the request wants to know the auth status of the client. `false` by default.\n\tfn wants_auth() -> bool {\n\t\tfalse\n\t}\n\n\t/// Replace the automatically generated operation id with a custom one. Only relevant for the\n\t/// OpenAPI Specification.\n\t#[openapi_only]\n\tfn operation_id() -> OperationId {\n\t\tOperationId::FullAuto\n\t}\n\n\t/// Add a description to the openapi specification. Usually taken from the rustdoc comment\n\t/// when using the proc macro.\n\t#[openapi_only]\n\tfn description() -> Option {\n\t\tNone\n\t}\n\n\t/// The handler for this endpoint.\n\tfn handle(\n\t\tstate: &mut State,\n\t\tplaceholders: Self::Placeholders,\n\t\tparams: Self::Params,\n\t\tbody: Option\n\t) -> BoxFuture<'_, Self::Output>;\n}\n\n#[cfg(feature = \"openapi\")]\nimpl Endpoint for E {\n\tfn http_method() -> Method {\n\t\tE::http_method()\n\t}\n\tfn uri() -> Cow<'static, str> {\n\t\tE::uri()\n\t}\n\n\ttype Output = E::Output;\n\n\tfn has_placeholders() -> bool {\n\t\tE::has_placeholders()\n\t}\n\ttype Placeholders = E::Placeholders;\n\n\tfn needs_params() -> bool {\n\t\tE::needs_params()\n\t}\n\ttype Params = E::Params;\n\n\tfn needs_body() -> bool {\n\t\tE::needs_body()\n\t}\n\ttype Body = E::Body;\n\n\tfn wants_auth() -> bool {\n\t\tE::wants_auth()\n\t}\n\n\tfn handle<'a>(\n\t\tstate: &'a mut State,\n\t\tplaceholders: Self::Placeholders,\n\t\tparams: Self::Params,\n\t\tbody: Option\n\t) -> BoxFuture<'a, Self::Output> {\n\t\tE::handle(state, placeholders, params, body)\n\t}\n}\n","traces":[{"line":22,"address":[7370256,7370240],"length":1,"stats":{"Line":10}},{"line":23,"address":[7261043,7261081],"length":1,"stats":{"Line":10}},{"line":29,"address":[7607536],"length":1,"stats":{"Line":0}},{"line":30,"address":[6189967,6189889],"length":1,"stats":{"Line":0}},{"line":31,"address":[],"length":0,"stats":{"Line":0}},{"line":33,"address":[7847557],"length":1,"stats":{"Line":0}},{"line":41,"address":[8366794,8366784],"length":1,"stats":{"Line":0}},{"line":62,"address":[],"length":0,"stats":{"Line":0}},{"line":63,"address":[],"length":0,"stats":{"Line":0}},{"line":71,"address":[],"length":0,"stats":{"Line":0}},{"line":72,"address":[],"length":0,"stats":{"Line":0}},{"line":80,"address":[],"length":0,"stats":{"Line":0}},{"line":81,"address":[],"length":0,"stats":{"Line":0}},{"line":87,"address":[],"length":0,"stats":{"Line":0}},{"line":88,"address":[],"length":0,"stats":{"Line":0}},{"line":94,"address":[],"length":0,"stats":{"Line":0}},{"line":95,"address":[],"length":0,"stats":{"Line":0}},{"line":101,"address":[],"length":0,"stats":{"Line":7}},{"line":102,"address":[],"length":0,"stats":{"Line":7}},{"line":116,"address":[],"length":0,"stats":{"Line":30}},{"line":117,"address":[6540856,6540824,6540664,6540632,6540696,6540888,6540792,6540760,6540728],"length":1,"stats":{"Line":30}},{"line":119,"address":[7003456,7003424],"length":1,"stats":{"Line":29}},{"line":120,"address":[7393256,7393320,7393416,7393288,7393224,7393384,7393352],"length":1,"stats":{"Line":30}},{"line":125,"address":[],"length":0,"stats":{"Line":0}},{"line":126,"address":[],"length":0,"stats":{"Line":0}},{"line":130,"address":[],"length":0,"stats":{"Line":0}},{"line":131,"address":[],"length":0,"stats":{"Line":0}},{"line":135,"address":[7392896,7392976,7392928,7392880,7392944,7392960,7392912],"length":1,"stats":{"Line":21}},{"line":136,"address":[7392961,7392945,7392977,7392897,7392913,7392881,7392929],"length":1,"stats":{"Line":21}},{"line":140,"address":[],"length":0,"stats":{"Line":0}},{"line":141,"address":[],"length":0,"stats":{"Line":0}},{"line":144,"address":[7393520,7393648,7393696,7393440,7393568,7393472,7393616],"length":1,"stats":{"Line":21}},{"line":150,"address":[],"length":0,"stats":{"Line":21}}],"covered":12,"coverable":33},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","lib.rs"],"content":"#![warn(missing_debug_implementations, rust_2018_idioms, unreachable_pub)]\n#![forbid(unsafe_code)]\n// deny warnings in CI\n#![cfg_attr(gotham_restful_deny_warnings, deny(warnings))]\n// clippy doesn't like our code style\n#![allow(clippy::tabs_in_doc_comments)]\n// intra-doc links only fully work when OpenAPI is enabled\n#![cfg_attr(feature = \"openapi\", deny(rustdoc::broken_intra_doc_links))]\n#![cfg_attr(not(feature = \"openapi\"), allow(rustdoc::broken_intra_doc_links))]\n\n//! This crate is an extension to the popular [gotham web framework][gotham] for Rust. It allows you to\n//! create resources with assigned endpoints that aim to be a more convenient way of creating handlers\n//! for requests.\n//!\n//! # Features\n//!\n//! - Automatically parse **JSON** request and produce response bodies\n//! - Allow using **raw** request and response bodies\n//! - Convenient **macros** to create responses that can be registered with gotham's router\n//! - Auto-Generate an **OpenAPI** specification for your API\n//! - Manage **CORS** headers so you don't have to\n//! - Manage **Authentication** with JWT\n//! - Integrate diesel connection pools for easy **database** integration\n//!\n//! # Safety\n//!\n//! This crate is just as safe as you'd expect from anything written in safe Rust - and\n//! `#![forbid(unsafe_code)]` ensures that no unsafe was used.\n//!\n//! # Endpoints\n//!\n//! There are a set of pre-defined endpoints that should cover the majority of REST APIs. However,\n//! it is also possible to define your own endpoints.\n//!\n//! ## Pre-defined Endpoints\n//!\n//! Assuming you assign `/foobar` to your resource, the following pre-defined endpoints exist:\n//!\n//! | Endpoint Name | Required Arguments | HTTP Verb | HTTP Path |\n//! | ------------- | ------------------ | --------- | -------------- |\n//! | read_all | | GET | /foobar |\n//! | read | id | GET | /foobar/:id |\n//! | search | query | GET | /foobar/search |\n//! | create | body | POST | /foobar |\n//! | update_all | body | PUT | /foobar |\n//! | update | id, body | PUT | /foobar/:id |\n//! | delete_all | | DELETE | /foobar |\n//! | delete | id | DELETE | /foobar/:id |\n//!\n//! Each of those endpoints has a macro that creates the neccessary boilerplate for the Resource. A\n//! simple example looks like this:\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # use gotham::router::builder::*;\n//! # use gotham_restful::*;\n//! # use serde::{Deserialize, Serialize};\n//! /// Our RESTful resource.\n//! #[derive(Resource)]\n//! #[resource(read)]\n//! struct FooResource;\n//!\n//! /// The return type of the foo read endpoint.\n//! #[derive(Serialize)]\n//! # #[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n//! struct Foo {\n//! \tid: u64\n//! }\n//!\n//! /// The foo read endpoint.\n//! #[read]\n//! fn read(id: u64) -> Success {\n//! \tFoo { id }.into()\n//! }\n//! # fn main() {\n//! # \tgotham::start(\"127.0.0.1:8080\", build_simple_router(|route| {\n//! # \t\troute.resource::(\"foo\");\n//! # \t})).expect(\"Failed to start gotham\");\n//! # }\n//! ```\n//!\n//! ## Custom Endpoints\n//!\n//! Defining custom endpoints is done with the `#[endpoint]` macro. The syntax is similar to that\n//! of the pre-defined endpoints, but you need to give it more context:\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # use gotham::{router::build_simple_router, prelude::*};\n//! # use gotham_restful::*;\n//! # use serde::{Deserialize, Serialize};\n//! use gotham_restful::gotham::hyper::Method;\n//!\n//! #[derive(Resource)]\n//! #[resource(custom_endpoint)]\n//! struct CustomResource;\n//!\n//! /// This type is used to parse path parameters.\n//! #[derive(Clone, Deserialize, StateData, StaticResponseExtender)]\n//! # #[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n//! struct CustomPath {\n//! \tname: String\n//! }\n//!\n//! #[endpoint(\n//! \turi = \"custom/:name/read\",\n//! \tmethod = \"Method::GET\",\n//! \tparams = false,\n//! \tbody = false\n//! )]\n//! fn custom_endpoint(path: CustomPath) -> Success {\n//! \tpath.name.into()\n//! }\n//! # fn main() {\n//! # \tgotham::start(\"127.0.0.1:8080\", build_simple_router(|route| {\n//! # \t\troute.resource::(\"custom\");\n//! # \t})).expect(\"Failed to start gotham\");\n//! # }\n//! ```\n//!\n//! # Arguments\n//!\n//! Some endpoints require arguments. Those should be\n//! * **id** Should be a deserializable json-primitive like [`i64`] or [`String`].\n//! * **body** Should be any deserializable object, or any type implementing [`RequestBody`].\n//! * **query** Should be any deserializable object whose variables are json-primitives. It will\n//! however not be parsed from json, but from HTTP GET parameters like in `search?id=1`. The\n//! type needs to implement [`QueryStringExtractor`](gotham::extractor::QueryStringExtractor).\n//!\n//! Additionally, all handlers may take a reference to gotham's [`State`]. Please note that for async\n//! handlers, it needs to be a mutable reference until rustc's lifetime checks across await bounds\n//! improve.\n//!\n//! # Uploads and Downloads\n//!\n//! By default, every request body is parsed from json, and every respone is converted to json using\n//! [serde_json]. However, you may also use raw bodies. This is an example where the request body\n//! is simply returned as the response again, no json parsing involved:\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # use gotham::{mime::{self, Mime}, router::builder::*};\n//! # use gotham_restful::*;\n//! # use serde::{Deserialize, Serialize};\n//! #[derive(Resource)]\n//! #[resource(create)]\n//! struct ImageResource;\n//!\n//! #[derive(FromBody, RequestBody)]\n//! #[supported_types(mime::IMAGE_GIF, mime::IMAGE_JPEG, mime::IMAGE_PNG)]\n//! struct RawImage {\n//! \tcontent: Vec,\n//! \tcontent_type: Mime\n//! }\n//!\n//! #[create]\n//! fn create(body: RawImage) -> Raw> {\n//! \tRaw::new(body.content, body.content_type)\n//! }\n//! # fn main() {\n//! # \tgotham::start(\"127.0.0.1:8080\", build_simple_router(|route| {\n//! # \t\troute.resource::(\"image\");\n//! # \t})).expect(\"Failed to start gotham\");\n//! # }\n//! ```\n//!\n//! # Custom HTTP Headers\n//!\n//! You can read request headers from the state as you would in any other gotham handler, and specify\n//! custom response headers using [Response::header].\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # use gotham::hyper::header::{ACCEPT, HeaderMap, VARY};\n//! # use gotham::{router::builder::*, state::State};\n//! # use gotham_restful::*;\n//! #[derive(Resource)]\n//! #[resource(read_all)]\n//! struct FooResource;\n//!\n//! #[read_all]\n//! async fn read_all(state: &mut State) -> NoContent {\n//! \tlet headers: &HeaderMap = state.borrow();\n//! \tlet accept = &headers[ACCEPT];\n//! # drop(accept);\n//!\n//! \tlet mut res = NoContent::default();\n//! \tres.header(VARY, \"accept\".parse().unwrap());\n//! \tres\n//! }\n//! # fn main() {\n//! # \tgotham::start(\"127.0.0.1:8080\", build_simple_router(|route| {\n//! # \t\troute.resource::(\"foo\");\n//! # \t})).expect(\"Failed to start gotham\");\n//! # }\n//! ```\n//!\n//! # Features\n//!\n//! To make life easier for common use-cases, this create offers a few features that might be helpful\n//! when you implement your web server. The complete feature list is\n//! - [`auth`](#authentication-feature) Advanced JWT middleware\n//! - [`cors`](#cors-feature) CORS handling for all endpoint handlers\n//! - [`database`](#database-feature) diesel middleware support\n//! - `errorlog` log errors returned from endpoint handlers\n//! - `full` enables all features except `without-openapi`\n//! - [`openapi`](#openapi-feature) router additions to generate an openapi spec\n//! - `without-openapi` (**default**) disables `openapi` support.\n//!\n//! ## Authentication Feature\n//!\n//! In order to enable authentication support, enable the `auth` feature gate. This allows you to\n//! register a middleware that can automatically check for the existence of an JWT authentication\n//! token. Besides being supported by the endpoint macros, it supports to lookup the required JWT secret\n//! with the JWT data, hence you can use several JWT secrets and decide on the fly which secret to use.\n//! None of this is currently supported by gotham's own JWT middleware.\n//!\n//! A simple example that uses only a single secret looks like this:\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # #[cfg(feature = \"auth\")]\n//! # mod auth_feature_enabled {\n//! # use gotham::{router::builder::*, pipeline::*, state::State};\n//! # use gotham_restful::*;\n//! # use serde::{Deserialize, Serialize};\n//! #[derive(Resource)]\n//! #[resource(read)]\n//! struct SecretResource;\n//!\n//! #[derive(Serialize)]\n//! # #[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n//! struct Secret {\n//! \tid: u64,\n//! \tintended_for: String\n//! }\n//!\n//! #[derive(Deserialize, Clone)]\n//! struct AuthData {\n//! \tsub: String,\n//! \texp: u64\n//! }\n//!\n//! #[read]\n//! fn read(auth: AuthStatus, id: u64) -> AuthSuccess {\n//! \tlet intended_for = auth.ok()?.sub;\n//! \tOk(Secret { id, intended_for })\n//! }\n//!\n//! fn main() {\n//! \tlet auth: AuthMiddleware = AuthMiddleware::new(\n//! \t\tAuthSource::AuthorizationHeader,\n//! \t\tAuthValidation::default(),\n//! \t\tStaticAuthHandler::from_array(b\"zlBsA2QXnkmpe0QTh8uCvtAEa4j33YAc\")\n//! \t);\n//! \tlet (chain, pipelines) = single_pipeline(new_pipeline().add(auth).build());\n//! \tgotham::start(\n//! \t\t\"127.0.0.1:8080\",\n//! \t\tbuild_router(chain, pipelines, |route| {\n//! \t\t\troute.resource::(\"secret\");\n//! \t\t})\n//! \t)\n//! \t.expect(\"Failed to start gotham\");\n//! }\n//! # }\n//! ```\n//!\n//! ## CORS Feature\n//!\n//! The cors feature allows an easy usage of this web server from other origins. By default, only\n//! the `Access-Control-Allow-Methods` header is touched. To change the behaviour, add your desired\n//! configuration as a middleware.\n//!\n//! A simple example that allows authentication from every origin (note that `*` always disallows\n//! authentication), and every content type, looks like this:\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # #[cfg(feature = \"cors\")]\n//! # mod cors_feature_enabled {\n//! # use gotham::{hyper::header::*, router::builder::*, pipeline::*, state::State};\n//! # use gotham_restful::{*, cors::*};\n//! # use serde::{Deserialize, Serialize};\n//! #[derive(Resource)]\n//! #[resource(read_all)]\n//! struct FooResource;\n//!\n//! #[read_all]\n//! fn read_all() {\n//! \t// your handler\n//! }\n//!\n//! fn main() {\n//! \tlet cors = CorsConfig {\n//! \t\torigin: Origin::Copy,\n//! \t\theaders: Headers::List(vec![CONTENT_TYPE]),\n//! \t\tmax_age: 0,\n//! \t\tcredentials: true\n//! \t};\n//! \tlet (chain, pipelines) = single_pipeline(new_pipeline().add(cors).build());\n//! \tgotham::start(\n//! \t\t\"127.0.0.1:8080\",\n//! \t\tbuild_router(chain, pipelines, |route| {\n//! \t\t\troute.resource::(\"foo\");\n//! \t\t})\n//! \t)\n//! \t.expect(\"Failed to start gotham\");\n//! }\n//! # }\n//! ```\n//!\n//! The cors feature can also be used for non-resource handlers. Take a look at [`CorsRoute`]\n//! for an example.\n//!\n//! ## Database Feature\n//!\n//! The database feature allows an easy integration of [diesel] into your handler functions. Please\n//! note however that due to the way gotham's diesel middleware implementation, it is not possible\n//! to run async code while holding a database connection. If you need to combine async and database,\n//! you'll need to borrow the connection from the [`State`] yourself and return a boxed future.\n//!\n//! A simple non-async example looks like this:\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate diesel;\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # #[cfg(feature = \"database\")]\n//! # mod database_feature_enabled {\n//! # use diesel::{table, PgConnection, QueryResult, RunQueryDsl};\n//! # use gotham::{router::builder::*, pipeline::*, state::State};\n//! # use gotham_middleware_diesel::DieselMiddleware;\n//! # use gotham_restful::*;\n//! # use serde::{Deserialize, Serialize};\n//! # use std::env;\n//! # table! {\n//! # foo (id) {\n//! # id -> Int8,\n//! # value -> Text,\n//! # }\n//! # }\n//! #[derive(Resource)]\n//! #[resource(read_all)]\n//! struct FooResource;\n//!\n//! #[derive(Queryable, Serialize)]\n//! # #[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n//! struct Foo {\n//! \tid: i64,\n//! \tvalue: String\n//! }\n//!\n//! #[read_all]\n//! fn read_all(conn: &mut PgConnection) -> QueryResult> {\n//! \tfoo::table.load(conn)\n//! }\n//!\n//! type Repo = gotham_middleware_diesel::Repo;\n//!\n//! fn main() {\n//! \tlet repo = Repo::new(&env::var(\"DATABASE_URL\").unwrap());\n//! \tlet diesel = DieselMiddleware::new(repo);\n//!\n//! \tlet (chain, pipelines) = single_pipeline(new_pipeline().add(diesel).build());\n//! \tgotham::start(\n//! \t\t\"127.0.0.1:8080\",\n//! \t\tbuild_router(chain, pipelines, |route| {\n//! \t\t\troute.resource::(\"foo\");\n//! \t\t})\n//! \t)\n//! \t.expect(\"Failed to start gotham\");\n//! }\n//! # }\n//! ```\n//!\n//! ## OpenAPI Feature\n//!\n//! The OpenAPI feature is probably the most powerful one of this crate. Definitely read this section\n//! carefully both as a binary as well as a library author to avoid unwanted suprises.\n//!\n//! In order to automatically create an openapi specification, gotham-restful needs knowledge over\n//! all routes and the types returned. `serde` does a great job at serialization but doesn't give\n//! enough type information, so all types used in the router need to implement\n//! [`OpenapiType`](openapi_type::OpenapiType). This can be derived for almoust any type and there\n//! should be no need to implement it manually. A simple example looks like this:\n//!\n//! ```rust,no_run\n//! # #[macro_use] extern crate gotham_restful_derive;\n//! # #[cfg(feature = \"openapi\")]\n//! # mod openapi_feature_enabled {\n//! # use gotham::{router::builder::*, state::State};\n//! # use gotham_restful::*;\n//! # use openapi_type::OpenapiType;\n//! # use serde::{Deserialize, Serialize};\n//! #[derive(Resource)]\n//! #[resource(read_all)]\n//! struct FooResource;\n//!\n//! #[derive(OpenapiType, Serialize)]\n//! struct Foo {\n//! \tbar: String\n//! }\n//!\n//! #[read_all]\n//! fn read_all() -> Success {\n//! \tFoo {\n//! \t\tbar: \"Hello World\".to_owned()\n//! \t}\n//! \t.into()\n//! }\n//!\n//! fn main() {\n//! \tgotham::start(\n//! \t\t\"127.0.0.1:8080\",\n//! \t\tbuild_simple_router(|route| {\n//! \t\t\tlet info = OpenapiInfo {\n//! \t\t\t\ttitle: \"My Foo API\".to_owned(),\n//! \t\t\t\tversion: \"0.1.0\".to_owned(),\n//! \t\t\t\turls: vec![\"https://example.org/foo/api/v1\".to_owned()]\n//! \t\t\t};\n//! \t\t\troute.with_openapi(info, |mut route| {\n//! \t\t\t\troute.resource::(\"foo\");\n//! \t\t\t\troute.openapi_spec(\"openapi\");\n//! \t\t\t\troute.openapi_doc(\"/\");\n//! \t\t\t});\n//! \t\t})\n//! \t)\n//! \t.expect(\"Failed to start gotham\");\n//! }\n//! # }\n//! ```\n//!\n//! Above example adds the resource as before, but adds two other endpoints as well: `/openapi` and `/`.\n//! The first one will return the generated openapi specification in JSON format, allowing you to easily\n//! generate clients in different languages without worying to exactly replicate your api in each of those\n//! languages. The second one will return documentation in HTML format, so you can easily view your\n//! api and share it with other people.\n//!\n//! ### Gotchas\n//!\n//! The openapi feature has some gotchas you should be aware of.\n//!\n//! - The name of a struct is used as a \"link\" in the openapi specification. Therefore, if you have two\n//! structs with the same name in your project, the openapi specification will be invalid as only one\n//! of the two will make it into the spec.\n//! - By default, the `without-openapi` feature of this crate is enabled. Disabling it in favour of the\n//! `openapi` feature will add additional type bounds and method requirements to some of the traits and\n//! \ttypes in this crate, for example instead of [`Endpoint`] you now have to implement\n//! \t[`EndpointWithSchema`]. This means that some code might only compile on either feature, but not\n//! on both. If you are writing a library that uses gotham-restful, it is strongly recommended to pass\n//! \tboth features through and conditionally enable the openapi code, like this:\n//!\n//! ```rust\n//! # #[macro_use] extern crate gotham_restful;\n//! # use serde::{Deserialize, Serialize};\n//! #[derive(Deserialize, Serialize)]\n//! #[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n//! struct Foo;\n//! ```\n//!\n//! [diesel]: https://diesel.rs/\n//! [`State`]: gotham::state::State\n\n#[cfg(all(feature = \"openapi\", feature = \"without-openapi\"))]\ncompile_error!(\"The 'openapi' and 'without-openapi' features cannot be combined\");\n\n#[cfg(all(not(feature = \"openapi\"), not(feature = \"without-openapi\")))]\ncompile_error!(\"Either the 'openapi' or 'without-openapi' feature needs to be enabled\");\n\n// weird proc macro issue\nextern crate self as gotham_restful;\n\n#[macro_use]\nextern crate gotham_restful_derive;\n#[macro_use]\nextern crate log;\n#[macro_use]\nextern crate serde;\n\n#[cfg(test)]\n#[macro_use]\nextern crate pretty_assertions;\n\n#[doc(no_inline)]\npub use gotham;\n\npub use gotham_restful_derive::*;\n\n/// Not public API\n#[doc(hidden)]\npub mod private {\n\tpub use crate::routing::PathExtractor as IdPlaceholder;\n\tpub use futures_util::future::{BoxFuture, FutureExt};\n\t#[cfg(feature = \"database\")]\n\tpub use gotham_middleware_diesel::Repo;\n\t#[cfg(feature = \"openapi\")]\n\tpub use openapi_type::{OpenapiSchema, OpenapiType, Visitor};\n\tpub use serde_json;\n\n\t#[cfg(feature = \"openapi\")]\n\tuse gotham::hyper::StatusCode;\n\t#[cfg(feature = \"auth\")]\n\tuse gotham::state::{FromState, State};\n\n\t/// This method is used by the endpoint macro to generate a good error message\n\t/// when the used AuthData type does not implement Clone.\n\t#[cfg(feature = \"auth\")]\n\t#[inline]\n\tpub fn clone_from_state(state: &State) -> T\n\twhere\n\t\tT: FromState + Clone\n\t{\n\t\tT::borrow_from(state).clone()\n\t}\n\n\t/// This trait is used by the endpoint macro to generate a good error message\n\t/// when the schema function has the wrong signature.\n\t#[cfg(feature = \"openapi\")]\n\tpub trait CustomSchema {\n\t\tfn schema(self, code: StatusCode) -> OpenapiSchema;\n\t}\n\n\t#[cfg(feature = \"openapi\")]\n\timpl CustomSchema for F\n\twhere\n\t\tF: FnOnce(StatusCode) -> OpenapiSchema\n\t{\n\t\t#[inline]\n\t\tfn schema(self, code: StatusCode) -> OpenapiSchema {\n\t\t\tself(code)\n\t\t}\n\t}\n\n\t/// This trait is used by the endpoint macro to generate a good error message\n\t/// when the status_codes function has the wrong signature.\n\t#[cfg(feature = \"openapi\")]\n\tpub trait CustomStatusCodes {\n\t\tfn status_codes(self) -> Vec;\n\t}\n\n\t#[cfg(feature = \"openapi\")]\n\timpl CustomStatusCodes for F\n\twhere\n\t\tF: FnOnce() -> Vec\n\t{\n\t\t#[inline]\n\t\tfn status_codes(self) -> Vec {\n\t\t\tself()\n\t\t}\n\t}\n}\n\n#[cfg(feature = \"auth\")]\nmod auth;\n#[cfg(feature = \"auth\")]\npub use auth::{\n\tAuthHandler, AuthMiddleware, AuthSource, AuthStatus, AuthValidation, StaticAuthHandler\n};\n\n#[cfg(feature = \"cors\")]\npub mod cors;\n#[cfg(feature = \"cors\")]\npub use cors::{handle_cors, CorsConfig, CorsRoute};\n\n#[cfg(feature = \"openapi\")]\nmod openapi;\n#[cfg(feature = \"openapi\")]\npub use openapi::{builder::OpenapiInfo, operation::OperationId, router::GetOpenapi};\n\nmod endpoint;\n#[cfg(feature = \"openapi\")]\npub use endpoint::EndpointWithSchema;\npub use endpoint::{Endpoint, NoopExtractor};\n\nmod response;\npub use response::{\n\tAuthError, AuthErrorOrOther, AuthResult, AuthSuccess, IntoResponse, IntoResponseError,\n\tNoContent, Raw, Redirect, Response, Success\n};\n#[cfg(feature = \"openapi\")]\npub use response::{IntoResponseWithSchema, ResponseSchema};\n\nmod routing;\npub use routing::{DrawResourceRoutes, DrawResources};\n#[cfg(feature = \"openapi\")]\npub use routing::{DrawResourceRoutesWithSchema, DrawResourcesWithSchema, WithOpenapi};\n\nmod types;\npub use types::{FromBody, RequestBody, ResponseBody};\n\n/// This trait must be implemented for every resource. It allows you to register the different\n/// endpoints that can be handled by this resource to be registered with the underlying router.\n///\n/// It is not recommended to implement this yourself, just use `#[derive(Resource)]`.\n#[_private_openapi_trait(ResourceWithSchema)]\npub trait Resource {\n\t/// Register all methods handled by this resource with the underlying router.\n\t#[openapi_bound(D: crate::DrawResourceRoutesWithSchema)]\n\t#[non_openapi_bound(D: crate::DrawResourceRoutes)]\n\tfn setup(route: D);\n}\n","traces":[{"line":463,"address":[10990570,10990557,10990425],"length":1,"stats":{"Line":0}},{"line":464,"address":[10990193,10990652,10989983,10990592],"length":1,"stats":{"Line":0}},{"line":466,"address":[15281637],"length":1,"stats":{"Line":2}},{"line":467,"address":[5131020],"length":1,"stats":{"Line":2}},{"line":508,"address":[18501370],"length":1,"stats":{"Line":0}},{"line":512,"address":[16178256],"length":1,"stats":{"Line":0}},{"line":518,"address":[1961292,1961040,1961314,1961283,1960956,1961049,1961064],"length":1,"stats":{"Line":0}},{"line":523,"address":[17191391],"length":1,"stats":{"Line":0}},{"line":528,"address":[15429949],"length":1,"stats":{"Line":9}},{"line":529,"address":[16869153],"length":1,"stats":{"Line":1}},{"line":536,"address":[7920731],"length":1,"stats":{"Line":6}},{"line":541,"address":[17898198],"length":1,"stats":{"Line":0}},{"line":546,"address":[17898187],"length":1,"stats":{"Line":15}},{"line":547,"address":[9454910,9454942],"length":1,"stats":{"Line":1}},{"line":595,"address":[5133522],"length":1,"stats":{"Line":0}}],"covered":7,"coverable":15},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","openapi","builder.rs"],"content":"use openapi_type::{\n\tindexmap::IndexMap,\n\topenapiv3::{\n\t\tself, Components, OpenAPI, PathItem, ReferenceOr,\n\t\tReferenceOr::{Item, Reference},\n\t\tSchema, Server\n\t},\n\tOpenapiSchema\n};\nuse parking_lot::RwLock;\nuse std::sync::Arc;\n\n#[derive(Clone, Debug)]\npub struct OpenapiInfo {\n\tpub title: String,\n\tpub version: String,\n\tpub urls: Vec\n}\n\n#[derive(Clone, Debug)]\npub(crate) struct OpenapiBuilder {\n\tpub(crate) openapi: Arc>\n}\n\nimpl OpenapiBuilder {\n\tpub(crate) fn new(info: OpenapiInfo) -> Self {\n\t\tSelf {\n\t\t\topenapi: Arc::new(RwLock::new(OpenAPI {\n\t\t\t\topenapi: \"3.0.2\".to_string(),\n\t\t\t\tinfo: openapiv3::Info {\n\t\t\t\t\ttitle: info.title,\n\t\t\t\t\tversion: info.version,\n\t\t\t\t\t..Default::default()\n\t\t\t\t},\n\t\t\t\tservers: info\n\t\t\t\t\t.urls\n\t\t\t\t\t.into_iter()\n\t\t\t\t\t.map(|url| Server {\n\t\t\t\t\t\turl,\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t})\n\t\t\t\t\t.collect(),\n\t\t\t\t..Default::default()\n\t\t\t}))\n\t\t}\n\t}\n\n\t/// Remove path from the OpenAPI spec, or return an empty one if not included. This is handy if you need to\n\t/// modify the path and add it back after the modification\n\tpub(crate) fn remove_path(&mut self, path: &str) -> PathItem {\n\t\tlet mut openapi = self.openapi.write();\n\t\tmatch openapi.paths.paths.swap_remove(path) {\n\t\t\tSome(Item(item)) => item,\n\t\t\t_ => PathItem::default()\n\t\t}\n\t}\n\n\tpub(crate) fn add_path(&mut self, path: Path, item: PathItem) {\n\t\tlet mut openapi = self.openapi.write();\n\t\topenapi.paths.paths.insert(path.to_string(), Item(item));\n\t}\n\n\tfn add_schema_impl(&mut self, name: String, mut schema: OpenapiSchema) {\n\t\tself.add_schema_dependencies(&mut schema.dependencies);\n\n\t\tlet mut openapi = self.openapi.write();\n\t\tmatch &mut openapi.components {\n\t\t\tSome(comp) => {\n\t\t\t\tcomp.schemas.insert(name, Item(schema.schema));\n\t\t\t},\n\t\t\tNone => {\n\t\t\t\tlet mut comp = Components::default();\n\t\t\t\tcomp.schemas.insert(name, Item(schema.schema));\n\t\t\t\topenapi.components = Some(comp);\n\t\t\t}\n\t\t};\n\t}\n\n\tfn add_schema_dependencies(&mut self, dependencies: &mut IndexMap) {\n\t\tlet keys: Vec = dependencies.keys().map(|k| k.to_string()).collect();\n\t\tfor dep in keys {\n\t\t\tlet dep_schema = dependencies.swap_remove(&dep);\n\t\t\tif let Some(dep_schema) = dep_schema {\n\t\t\t\tself.add_schema_impl(dep, dep_schema);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub(crate) fn add_schema(&mut self, mut schema: OpenapiSchema) -> ReferenceOr {\n\t\tmatch schema.schema.schema_data.title.clone() {\n\t\t\tSome(name) => {\n\t\t\t\tlet reference = Reference {\n\t\t\t\t\treference: format!(\"#/components/schemas/{name}\")\n\t\t\t\t};\n\t\t\t\tself.add_schema_impl(name, schema);\n\t\t\t\treference\n\t\t\t},\n\t\t\tNone => {\n\t\t\t\tself.add_schema_dependencies(&mut schema.dependencies);\n\t\t\t\tItem(schema.schema)\n\t\t\t}\n\t\t}\n\t}\n}\n\n#[cfg(test)]\n#[allow(dead_code)]\nmod test {\n\tuse super::*;\n\tuse openapi_type::OpenapiType;\n\n\t#[derive(OpenapiType)]\n\tstruct Message {\n\t\tmsg: String\n\t}\n\n\t#[derive(OpenapiType)]\n\tstruct Messages {\n\t\tmsgs: Vec\n\t}\n\n\tfn info() -> OpenapiInfo {\n\t\tOpenapiInfo {\n\t\t\ttitle: \"TEST CASE\".to_owned(),\n\t\t\tversion: \"1.2.3\".to_owned(),\n\t\t\turls: vec![\n\t\t\t\t\"http://localhost:1234\".to_owned(),\n\t\t\t\t\"https://example.org\".to_owned(),\n\t\t\t]\n\t\t}\n\t}\n\n\tfn openapi(builder: OpenapiBuilder) -> OpenAPI {\n\t\tArc::try_unwrap(builder.openapi).unwrap().into_inner()\n\t}\n\n\t#[test]\n\tfn new_builder() {\n\t\tlet info = info();\n\t\tlet builder = OpenapiBuilder::new(info.clone());\n\t\tlet openapi = openapi(builder);\n\n\t\tassert_eq!(info.title, openapi.info.title);\n\t\tassert_eq!(info.version, openapi.info.version);\n\t\tassert_eq!(info.urls.len(), openapi.servers.len());\n\t}\n\n\t#[test]\n\tfn add_schema() {\n\t\tlet mut builder = OpenapiBuilder::new(info());\n\t\tbuilder.add_schema(>::schema());\n\t\tlet openapi = openapi(builder);\n\n\t\tassert_eq!(\n\t\t\topenapi.components.clone().unwrap_or_default().schemas[\"Message\"],\n\t\t\tReferenceOr::Item(Message::schema().schema)\n\t\t);\n\t\tassert_eq!(\n\t\t\topenapi.components.clone().unwrap_or_default().schemas[\"Messages\"],\n\t\t\tReferenceOr::Item(Messages::schema().schema)\n\t\t);\n\t}\n}\n","traces":[{"line":26,"address":[7685921,7686116,7684272],"length":1,"stats":{"Line":3}},{"line":28,"address":[6108442,6108938,6107627],"length":1,"stats":{"Line":9}},{"line":50,"address":[7595321,7595278,7594640],"length":1,"stats":{"Line":2}},{"line":51,"address":[6109947],"length":1,"stats":{"Line":2}},{"line":52,"address":[8353805,8353881],"length":1,"stats":{"Line":4}},{"line":53,"address":[6110144],"length":1,"stats":{"Line":1}},{"line":54,"address":[8083872,8083757],"length":1,"stats":{"Line":4}},{"line":58,"address":[],"length":0,"stats":{"Line":2}},{"line":59,"address":[6887713,6887635],"length":1,"stats":{"Line":4}},{"line":60,"address":[6389403,6389472],"length":1,"stats":{"Line":4}},{"line":63,"address":[7596670,7595392,7596507],"length":1,"stats":{"Line":2}},{"line":64,"address":[7595465],"length":1,"stats":{"Line":2}},{"line":66,"address":[8052904],"length":1,"stats":{"Line":2}},{"line":67,"address":[8084416,8084489],"length":1,"stats":{"Line":4}},{"line":68,"address":[7687680],"length":1,"stats":{"Line":2}},{"line":69,"address":[8053128,8053866],"length":1,"stats":{"Line":4}},{"line":72,"address":[6110897],"length":1,"stats":{"Line":2}},{"line":73,"address":[8053485,8053290],"length":1,"stats":{"Line":4}},{"line":74,"address":[8053504,8053813],"length":1,"stats":{"Line":2}},{"line":79,"address":[8356358,8355712],"length":1,"stats":{"Line":3}},{"line":80,"address":[7596750,7597411,7597376],"length":1,"stats":{"Line":7}},{"line":81,"address":[8085582,8085756,8086088,8085700],"length":1,"stats":{"Line":10}},{"line":82,"address":[7837045,7836977],"length":1,"stats":{"Line":4}},{"line":83,"address":[6112188],"length":1,"stats":{"Line":2}},{"line":84,"address":[7837213,7837130],"length":1,"stats":{"Line":4}},{"line":89,"address":[6180671,6180615,6179776],"length":1,"stats":{"Line":3}},{"line":90,"address":[6112587,6112691],"length":1,"stats":{"Line":6}},{"line":91,"address":[6112750],"length":1,"stats":{"Line":1}},{"line":93,"address":[8055229],"length":1,"stats":{"Line":1}},{"line":95,"address":[8357070],"length":1,"stats":{"Line":1}},{"line":96,"address":[7690098],"length":1,"stats":{"Line":1}},{"line":99,"address":[7689517],"length":1,"stats":{"Line":3}},{"line":100,"address":[6180128],"length":1,"stats":{"Line":3}}],"covered":33,"coverable":33},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","openapi","handler","mod.rs"],"content":"#![cfg_attr(not(feature = \"auth\"), allow(unused_imports))]\nuse super::SECURITY_NAME;\nuse base64::prelude::*;\nuse futures_util::{future, future::FutureExt};\nuse gotham::{\n\tanyhow,\n\thandler::{Handler, HandlerError, HandlerFuture, NewHandler},\n\thelpers::http::response::{create_empty_response, create_response},\n\thyper::{\n\t\theader::{\n\t\t\tHeaderMap, HeaderValue, CACHE_CONTROL, CONTENT_SECURITY_POLICY, ETAG, IF_NONE_MATCH,\n\t\t\tREFERRER_POLICY, X_CONTENT_TYPE_OPTIONS\n\t\t},\n\t\tBody, Response, StatusCode\n\t},\n\tmime::{APPLICATION_JSON, TEXT_HTML_UTF_8, TEXT_PLAIN_UTF_8},\n\tstate::State\n};\nuse gotham_restful_redoc::Redoc;\nuse openapi_type::{\n\tindexmap::IndexMap,\n\topenapiv3::{APIKeyLocation, OpenAPI, ReferenceOr, SecurityScheme}\n};\nuse parking_lot::RwLock;\nuse sha2::{Digest, Sha256};\nuse std::{panic::RefUnwindSafe, pin::Pin, sync::Arc};\n\n#[cfg(feature = \"auth\")]\nfn get_security(state: &State) -> IndexMap> {\n\tuse crate::AuthSource;\n\tuse gotham::state::FromState;\n\n\tlet source = match AuthSource::try_borrow_from(state) {\n\t\tSome(source) => source,\n\t\tNone => return Default::default()\n\t};\n\n\tlet security_scheme = match source {\n\t\tAuthSource::Cookie(name) => SecurityScheme::APIKey {\n\t\t\tlocation: APIKeyLocation::Cookie,\n\t\t\tname: name.to_string(),\n\t\t\tdescription: None,\n\t\t\textensions: Default::default()\n\t\t},\n\t\tAuthSource::Header(name) => SecurityScheme::APIKey {\n\t\t\tlocation: APIKeyLocation::Header,\n\t\t\tname: name.to_string(),\n\t\t\tdescription: None,\n\t\t\textensions: Default::default()\n\t\t},\n\t\tAuthSource::AuthorizationHeader => SecurityScheme::HTTP {\n\t\t\tscheme: \"bearer\".to_owned(),\n\t\t\tbearer_format: Some(\"JWT\".to_owned()),\n\t\t\tdescription: None,\n\t\t\textensions: Default::default()\n\t\t}\n\t};\n\n\tlet mut security_schemes: IndexMap> = Default::default();\n\tsecurity_schemes.insert(SECURITY_NAME.to_owned(), ReferenceOr::Item(security_scheme));\n\n\tsecurity_schemes\n}\n\n#[cfg(not(feature = \"auth\"))]\nfn get_security(_state: &State) -> IndexMap> {\n\tDefault::default()\n}\n\nfn openapi_string(\n\tstate: &State,\n\topenapi: &Arc>\n) -> Result {\n\tlet openapi = openapi.read();\n\n\tlet mut openapi = openapi.clone();\n\tlet security_schemes = get_security(state);\n\tlet mut components = openapi.components.unwrap_or_default();\n\tcomponents.security_schemes = security_schemes;\n\topenapi.components = Some(components);\n\n\tserde_json::to_string(&openapi)\n}\n\nfn create_openapi_response(state: &State, openapi: &Arc>) -> Response {\n\tmatch openapi_string(state, openapi) {\n\t\tOk(body) => {\n\t\t\tlet mut res = create_response(state, StatusCode::OK, APPLICATION_JSON, body);\n\t\t\tlet headers = res.headers_mut();\n\t\t\theaders.insert(X_CONTENT_TYPE_OPTIONS, HeaderValue::from_static(\"nosniff\"));\n\t\t\tres\n\t\t},\n\t\tErr(e) => {\n\t\t\terror!(\"Unable to handle OpenAPI request due to error: {e}\");\n\t\t\tcreate_response(\n\t\t\t\tstate,\n\t\t\t\tStatusCode::INTERNAL_SERVER_ERROR,\n\t\t\t\tTEXT_PLAIN_UTF_8,\n\t\t\t\t\"\"\n\t\t\t)\n\t\t}\n\t}\n}\n\n#[derive(Clone)]\npub(crate) struct OpenapiSpecHandler {\n\topenapi: Arc>\n}\n\n// safety: the handler only ever aquires a read lock, so this usage of\n// RwLock is, in fact, unwind safe\nimpl RefUnwindSafe for OpenapiSpecHandler {}\n\nimpl OpenapiSpecHandler {\n\tpub(crate) fn new(openapi: Arc>) -> Self {\n\t\tSelf { openapi }\n\t}\n}\n\nimpl NewHandler for OpenapiSpecHandler {\n\ttype Instance = Self;\n\n\tfn new_handler(&self) -> anyhow::Result {\n\t\tOk(self.clone())\n\t}\n}\n\nimpl Handler for OpenapiSpecHandler {\n\tfn handle(self, mut state: State) -> Pin> {\n\t\tlet res = create_openapi_response(&mut state, &self.openapi);\n\t\tfuture::ok((state, res)).boxed()\n\t}\n}\n\n#[derive(Clone)]\npub(crate) struct OpenapiDocHandler {\n\topenapi: Arc>\n}\n\n// safety: the handler only ever aquires a read lock, so this usage of\n// RwLock is, in fact, unwind safe\nimpl RefUnwindSafe for OpenapiDocHandler {}\n\nimpl OpenapiDocHandler {\n\tpub(crate) fn new(openapi: Arc>) -> Self {\n\t\tSelf { openapi }\n\t}\n}\n\nimpl NewHandler for OpenapiDocHandler {\n\ttype Instance = Self;\n\n\tfn new_handler(&self) -> anyhow::Result {\n\t\tOk(self.clone())\n\t}\n}\n\nfn redoc_handler(\n\tstate: &State,\n\topenapi: &Arc>\n) -> Result, HandlerError> {\n\tlet spec = openapi_string(state, openapi)?;\n\tlet Redoc { html, script_hash } = gotham_restful_redoc::html(spec);\n\n\tlet mut etag = Sha256::new();\n\tetag.update(&html);\n\tlet etag = format!(\"\\\"{}\\\"\", BASE64_STANDARD.encode(etag.finalize()));\n\n\tif state\n\t\t.borrow::()\n\t\t.get(IF_NONE_MATCH)\n\t\t.map_or(false, |header| header.as_bytes() == etag.as_bytes())\n\t{\n\t\tlet res = create_empty_response(state, StatusCode::NOT_MODIFIED);\n\t\treturn Ok(res);\n\t}\n\n\tlet mut res = create_response(state, StatusCode::OK, TEXT_HTML_UTF_8, html);\n\tlet headers = res.headers_mut();\n\theaders.insert(\n\t\tCACHE_CONTROL,\n\t\tHeaderValue::from_static(\"public,max-age=2592000\")\n\t);\n\theaders.insert(\n\t\tCONTENT_SECURITY_POLICY,\n\t\tformat!(\n\t\t\t\"default-src 'none';base-uri 'none';script-src 'unsafe-inline' https://cdn.jsdelivr.net 'sha256-{script_hash}' 'strict-dynamic';style-src 'unsafe-inline' https://fonts.googleapis.com;font-src https://fonts.gstatic.com;connect-src 'self';img-src blob: data:\",\n\t\t).parse().unwrap()\n\t);\n\theaders.insert(ETAG, etag.parse().unwrap());\n\theaders.insert(REFERRER_POLICY, HeaderValue::from_static(\"no-referrer\"));\n\theaders.insert(X_CONTENT_TYPE_OPTIONS, HeaderValue::from_static(\"nosniff\"));\n\tOk(res)\n}\n\nimpl Handler for OpenapiDocHandler {\n\tfn handle(self, state: State) -> Pin> {\n\t\tmatch redoc_handler(&state, &self.openapi) {\n\t\t\tOk(res) => future::ok((state, res)).boxed(),\n\t\t\tErr(err) => future::err((state, err)).boxed()\n\t\t}\n\t}\n}\n","traces":[{"line":29,"address":[8056324,8055712,8056352],"length":1,"stats":{"Line":2}},{"line":33,"address":[7598437],"length":1,"stats":{"Line":2}},{"line":34,"address":[8087279],"length":1,"stats":{"Line":1}},{"line":35,"address":[8357495],"length":1,"stats":{"Line":1}},{"line":38,"address":[8087297],"length":1,"stats":{"Line":1}},{"line":41,"address":[8055922],"length":1,"stats":{"Line":0}},{"line":43,"address":[7838585],"length":1,"stats":{"Line":0}},{"line":47,"address":[7838632],"length":1,"stats":{"Line":0}},{"line":49,"address":[6181035],"length":1,"stats":{"Line":0}},{"line":52,"address":[8357729],"length":1,"stats":{"Line":1}},{"line":53,"address":[8357754,8358314],"length":1,"stats":{"Line":2}},{"line":55,"address":[6181692],"length":1,"stats":{"Line":1}},{"line":59,"address":[8087758],"length":1,"stats":{"Line":1}},{"line":60,"address":[7599721,7599653],"length":1,"stats":{"Line":2}},{"line":62,"address":[7839833],"length":1,"stats":{"Line":1}},{"line":70,"address":[7840867,7839920,7841333],"length":1,"stats":{"Line":2}},{"line":74,"address":[8088802],"length":1,"stats":{"Line":2}},{"line":76,"address":[8359084,8359156],"length":1,"stats":{"Line":4}},{"line":77,"address":[7600224,7600176],"length":1,"stats":{"Line":4}},{"line":78,"address":[6334376,6334477],"length":1,"stats":{"Line":4}},{"line":79,"address":[7692244],"length":1,"stats":{"Line":2}},{"line":80,"address":[8359627],"length":1,"stats":{"Line":2}},{"line":82,"address":[7840714],"length":1,"stats":{"Line":2}},{"line":85,"address":[8090176,8090789],"length":1,"stats":{"Line":2}},{"line":86,"address":[7841398],"length":1,"stats":{"Line":2}},{"line":87,"address":[6183825],"length":1,"stats":{"Line":2}},{"line":88,"address":[7601509],"length":1,"stats":{"Line":2}},{"line":89,"address":[8090545,8090425],"length":1,"stats":{"Line":4}},{"line":90,"address":[8059311,8059097],"length":1,"stats":{"Line":2}},{"line":91,"address":[6184291],"length":1,"stats":{"Line":2}},{"line":93,"address":[6183993],"length":1,"stats":{"Line":0}},{"line":94,"address":[6184598,6184760,6184408,6184006],"length":1,"stats":{"Line":0}},{"line":115,"address":[7842592],"length":1,"stats":{"Line":2}},{"line":123,"address":[7602640],"length":1,"stats":{"Line":2}},{"line":124,"address":[7602649],"length":1,"stats":{"Line":2}},{"line":129,"address":[8361696,8362000],"length":1,"stats":{"Line":2}},{"line":130,"address":[6185067],"length":1,"stats":{"Line":2}},{"line":131,"address":[8091568,8091489],"length":1,"stats":{"Line":4}},{"line":145,"address":[7694912],"length":1,"stats":{"Line":0}},{"line":153,"address":[7603040],"length":1,"stats":{"Line":0}},{"line":154,"address":[6337161],"length":1,"stats":{"Line":0}},{"line":158,"address":[7603088,7606527,7606754],"length":1,"stats":{"Line":0}},{"line":162,"address":[7843100,7843377],"length":1,"stats":{"Line":0}},{"line":163,"address":[7843302,7843452],"length":1,"stats":{"Line":0}},{"line":165,"address":[7695444],"length":1,"stats":{"Line":0}},{"line":166,"address":[7695507],"length":1,"stats":{"Line":0}},{"line":167,"address":[8092430,8092715],"length":1,"stats":{"Line":0}},{"line":169,"address":[7844180,7844245],"length":1,"stats":{"Line":0}},{"line":172,"address":[7846793,7846768],"length":1,"stats":{"Line":0}},{"line":174,"address":[7604555],"length":1,"stats":{"Line":0}},{"line":175,"address":[7698540],"length":1,"stats":{"Line":0}},{"line":178,"address":[7696268],"length":1,"stats":{"Line":0}},{"line":179,"address":[6186991,6186918],"length":1,"stats":{"Line":0}},{"line":180,"address":[6338877],"length":1,"stats":{"Line":0}},{"line":181,"address":[8093447],"length":1,"stats":{"Line":0}},{"line":182,"address":[6187037],"length":1,"stats":{"Line":0}},{"line":184,"address":[7697344],"length":1,"stats":{"Line":0}},{"line":185,"address":[6187180],"length":1,"stats":{"Line":0}},{"line":186,"address":[6187404,6187533,6187621],"length":1,"stats":{"Line":0}},{"line":190,"address":[6339997,6339692,6340661],"length":1,"stats":{"Line":0}},{"line":191,"address":[8365544,8364972],"length":1,"stats":{"Line":0}},{"line":192,"address":[7698033,7698393],"length":1,"stats":{"Line":0}},{"line":193,"address":[7698256],"length":1,"stats":{"Line":0}},{"line":197,"address":[6340992,6341587],"length":1,"stats":{"Line":0}},{"line":198,"address":[7698801,7698875,7699256],"length":1,"stats":{"Line":0}},{"line":199,"address":[6341150,6341411],"length":1,"stats":{"Line":0}},{"line":200,"address":[6189520,6189756],"length":1,"stats":{"Line":0}}],"covered":32,"coverable":67},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","openapi","mod.rs"],"content":"const SECURITY_NAME: &str = \"authToken\";\n\npub(crate) mod builder;\npub(crate) mod handler;\npub(crate) mod operation;\npub(crate) mod router;\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","openapi","operation.rs"],"content":"use super::SECURITY_NAME;\nuse crate::{response::OrAllTypes, EndpointWithSchema, IntoResponse, RequestBody};\nuse gotham::{hyper::StatusCode, mime::Mime};\nuse openapi_type::{\n\tindexmap::IndexMap,\n\topenapiv3::{\n\t\tMediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr,\n\t\tReferenceOr::Item, RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind,\n\t\tStatusCode as OAStatusCode, Type\n\t},\n\tOpenapiSchema\n};\nuse std::{borrow::Cow, collections::HashMap};\n\nfn new_parameter_data(\n\tname: String,\n\trequired: bool,\n\tschema: ReferenceOr>\n) -> ParameterData {\n\tParameterData {\n\t\tname,\n\t\tdescription: None,\n\t\trequired,\n\t\tdeprecated: None,\n\t\tformat: ParameterSchemaOrContent::Schema(schema.unbox()),\n\t\texample: None,\n\t\texamples: Default::default(),\n\t\texplode: None,\n\t\textensions: Default::default()\n\t}\n}\n\n#[derive(Default)]\nstruct OperationParams {\n\tpath_params: Option,\n\tquery_params: Option\n}\n\nimpl OperationParams {\n\t// TODO shouldn't this be a custom openapi_type::Visitor\n\t// rather than this hacky code?\n\tfn add_path_params(\n\t\tpath_params: Option,\n\t\tparams: &mut Vec>\n\t) {\n\t\tlet path_params = match path_params {\n\t\t\tSome(pp) => pp.schema,\n\t\t\tNone => return\n\t\t};\n\t\tlet path_params = match path_params.schema_kind {\n\t\t\tSchemaKind::Type(Type::Object(ty)) => ty,\n\t\t\t_ => panic!(\"Path Parameters needs to be a plain struct\")\n\t\t};\n\t\tfor (name, schema) in path_params.properties {\n\t\t\tlet required = path_params.required.contains(&name);\n\t\t\tparams.push(Item(Parameter::Path {\n\t\t\t\tparameter_data: new_parameter_data(name, required, schema),\n\t\t\t\tstyle: Default::default()\n\t\t\t}))\n\t\t}\n\t}\n\n\t// TODO shouldn't this be a custom openapi_type::Visitor\n\t// rather than this hacky code?\n\tfn add_query_params(\n\t\tquery_params: Option,\n\t\tparams: &mut Vec>\n\t) {\n\t\tlet query_params = match query_params {\n\t\t\tSome(qp) => qp.schema,\n\t\t\tNone => return\n\t\t};\n\t\tlet query_params = match query_params.schema_kind {\n\t\t\tSchemaKind::Type(Type::Object(ty)) => ty,\n\t\t\t_ => panic!(\"Query Parameters needs to be a plain struct\")\n\t\t};\n\t\tfor (name, schema) in query_params.properties {\n\t\t\tlet required = query_params.required.contains(&name);\n\t\t\tparams.push(Item(Parameter::Query {\n\t\t\t\tparameter_data: new_parameter_data(name, required, schema),\n\t\t\t\tallow_reserved: false,\n\t\t\t\tstyle: Default::default(),\n\t\t\t\tallow_empty_value: None\n\t\t\t}))\n\t\t}\n\t}\n\n\tfn into_params(self) -> Vec> {\n\t\tlet mut params: Vec> = Vec::new();\n\t\tSelf::add_path_params(self.path_params, &mut params);\n\t\tSelf::add_query_params(self.query_params, &mut params);\n\t\tparams\n\t}\n}\n\n#[derive(Debug, Default)]\npub enum OperationId {\n\t/// Automatically generate the operation id based on path and operation verb.\n\t#[default]\n\tFullAuto,\n\t/// Automatically generate the operation id based on path and the provided string.\n\tSemiAuto(Cow<'static, str>),\n\t/// Use the provided operation id.\n\tManual(String)\n}\n\npub(crate) struct OperationDescription {\n\toperation_id: Option,\n\tdescription: Option,\n\n\taccepted_types: Option>,\n\tresponses: HashMap>,\n\tparams: OperationParams,\n\tbody_schema: Option>,\n\tsupported_types: Option>,\n\trequires_auth: bool\n}\n\nimpl OperationDescription {\n\t/// Create a new operation description for the given endpoint type and schema. If the endpoint\n\t/// does not specify an operation id, the path is used to generate one.\n\tpub(crate) fn new(\n\t\tresponses: HashMap>,\n\t\tpath: &str\n\t) -> Self {\n\t\tlet (mut operation_id, op_id_verb) = match E::operation_id() {\n\t\t\tOperationId::FullAuto => (None, E::operation_verb().map(Cow::Borrowed)),\n\t\t\tOperationId::SemiAuto(verb) => (None, Some(verb)),\n\t\t\tOperationId::Manual(id) => (Some(id), None)\n\t\t};\n\t\tif let Some(verb) = op_id_verb {\n\t\t\tlet op_path = path.replace('/', \"_\");\n\t\t\tlet op_path = op_path.trim_start_matches('_');\n\t\t\tif verb.starts_with(op_path) || verb.ends_with(op_path) {\n\t\t\t\toperation_id = Some(verb.into_owned());\n\t\t\t} else {\n\t\t\t\toperation_id = Some(format!(\"{verb}_{op_path}\"));\n\t\t\t}\n\t\t}\n\n\t\tSelf {\n\t\t\toperation_id,\n\t\t\tdescription: E::description(),\n\n\t\t\taccepted_types: E::Output::accepted_types(),\n\t\t\tresponses,\n\t\t\tparams: Default::default(),\n\t\t\tbody_schema: None,\n\t\t\tsupported_types: None,\n\t\t\trequires_auth: E::wants_auth()\n\t\t}\n\t}\n\n\tpub(crate) fn set_path_params(&mut self, params: OpenapiSchema) {\n\t\tself.params.path_params = Some(params);\n\t}\n\n\tpub(crate) fn set_query_params(&mut self, params: OpenapiSchema) {\n\t\tself.params.query_params = Some(params);\n\t}\n\n\tpub(crate) fn set_body(&mut self, schema: ReferenceOr) {\n\t\tself.body_schema = Some(schema);\n\t\tself.supported_types = Body::supported_types();\n\t}\n\n\tfn schema_to_content(\n\t\ttypes: Vec,\n\t\tschema: ReferenceOr\n\t) -> IndexMap {\n\t\tlet mut content: IndexMap = IndexMap::new();\n\t\tfor ty in types {\n\t\t\tcontent.insert(ty.to_string(), MediaType {\n\t\t\t\tschema: Some(schema.clone()),\n\t\t\t\t..Default::default()\n\t\t\t});\n\t\t}\n\t\tcontent\n\t}\n\n\tpub(crate) fn into_operation(self) -> Operation {\n\t\t// this is unfortunately neccessary to prevent rust from complaining about partially moving self\n\t\tlet (\n\t\t\toperation_id,\n\t\t\tdescription,\n\t\t\taccepted_types,\n\t\t\tresponses,\n\t\t\tparams,\n\t\t\tbody_schema,\n\t\t\tsupported_types,\n\t\t\trequires_auth\n\t\t) = (\n\t\t\tself.operation_id,\n\t\t\tself.description,\n\t\t\tself.accepted_types,\n\t\t\tself.responses,\n\t\t\tself.params,\n\t\t\tself.body_schema,\n\t\t\tself.supported_types,\n\t\t\tself.requires_auth\n\t\t);\n\n\t\tlet responses: IndexMap> = responses\n\t\t\t.into_iter()\n\t\t\t.map(|(code, schema)| {\n\t\t\t\tlet content =\n\t\t\t\t\tSelf::schema_to_content(accepted_types.clone().or_all_types(), schema);\n\t\t\t\t(\n\t\t\t\t\tOAStatusCode::Code(code.as_u16()),\n\t\t\t\t\tItem(Response {\n\t\t\t\t\t\tdescription: code\n\t\t\t\t\t\t\t.canonical_reason()\n\t\t\t\t\t\t\t.map(|d| d.to_string())\n\t\t\t\t\t\t\t.unwrap_or_default(),\n\t\t\t\t\t\tcontent,\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t})\n\t\t\t.collect();\n\n\t\tlet request_body = body_schema.map(|schema| {\n\t\t\tItem(OARequestBody {\n\t\t\t\tcontent: Self::schema_to_content(supported_types.or_all_types(), schema),\n\t\t\t\trequired: true,\n\t\t\t\t..Default::default()\n\t\t\t})\n\t\t});\n\n\t\tlet mut security = None;\n\t\tif requires_auth {\n\t\t\tlet mut sec = IndexMap::new();\n\t\t\tsec.insert(SECURITY_NAME.to_owned(), Vec::new());\n\t\t\tsecurity = Some(vec![sec]);\n\t\t}\n\n\t\tOperation {\n\t\t\ttags: Vec::new(),\n\t\t\toperation_id,\n\t\t\tdescription,\n\t\t\tparameters: params.into_params(),\n\t\t\trequest_body,\n\t\t\tresponses: Responses {\n\t\t\t\tresponses,\n\t\t\t\t..Default::default()\n\t\t\t},\n\t\t\tdeprecated: false,\n\t\t\tsecurity,\n\t\t\t..Default::default()\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse crate::{NoContent, Raw, ResponseSchema};\n\n\t#[test]\n\tfn no_content_schema_to_content() {\n\t\tlet types = NoContent::accepted_types();\n\t\tlet schema = ::schema(StatusCode::NO_CONTENT);\n\t\tlet content =\n\t\t\tOperationDescription::schema_to_content(types.or_all_types(), Item(schema.schema));\n\t\tassert!(content.is_empty());\n\t}\n\n\t#[test]\n\tfn raw_schema_to_content() {\n\t\tlet types = Raw::<&str>::accepted_types();\n\t\tlet schema = as ResponseSchema>::schema(StatusCode::OK);\n\t\tlet content =\n\t\t\tOperationDescription::schema_to_content(types.or_all_types(), Item(schema.schema));\n\t\tassert_eq!(content.len(), 1);\n\t\tlet json = serde_json::to_string(&content.values().nth(0).unwrap()).unwrap();\n\t\tassert_eq!(json, r#\"{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}\"#);\n\t}\n}\n","traces":[{"line":15,"address":[8206336,8206903,8206947],"length":1,"stats":{"Line":1}},{"line":25,"address":[8476641,8476726],"length":1,"stats":{"Line":2}},{"line":27,"address":[6300064],"length":1,"stats":{"Line":1}},{"line":29,"address":[7717799],"length":1,"stats":{"Line":1}},{"line":42,"address":[8477184,8478318,8478347],"length":1,"stats":{"Line":2}},{"line":46,"address":[7810104],"length":1,"stats":{"Line":2}},{"line":47,"address":[8175604],"length":1,"stats":{"Line":1}},{"line":50,"address":[6300720,6300682],"length":1,"stats":{"Line":2}},{"line":51,"address":[7810329],"length":1,"stats":{"Line":1}},{"line":54,"address":[7958770,7958824,7958635,7958484,7959706],"length":1,"stats":{"Line":5}},{"line":55,"address":[7718936,7719399],"length":1,"stats":{"Line":2}},{"line":56,"address":[8478622],"length":1,"stats":{"Line":1}},{"line":57,"address":[8208222],"length":1,"stats":{"Line":1}},{"line":58,"address":[8208339],"length":1,"stats":{"Line":1}},{"line":65,"address":[8480126,8480155,8478992],"length":1,"stats":{"Line":2}},{"line":69,"address":[8177352],"length":1,"stats":{"Line":2}},{"line":70,"address":[6302420],"length":1,"stats":{"Line":1}},{"line":73,"address":[8479162,8479200],"length":1,"stats":{"Line":2}},{"line":74,"address":[6469577],"length":1,"stats":{"Line":1}},{"line":77,"address":[6470880,6469926,6469799,6469976,6469652],"length":1,"stats":{"Line":5}},{"line":78,"address":[6303543,6303080],"length":1,"stats":{"Line":2}},{"line":79,"address":[7721430],"length":1,"stats":{"Line":1}},{"line":80,"address":[8210030],"length":1,"stats":{"Line":1}},{"line":82,"address":[8210147],"length":1,"stats":{"Line":1}},{"line":83,"address":[8480430],"length":1,"stats":{"Line":1}},{"line":88,"address":[6304469,6304160,6304506],"length":1,"stats":{"Line":2}},{"line":89,"address":[7721846],"length":1,"stats":{"Line":2}},{"line":90,"address":[7961908],"length":1,"stats":{"Line":2}},{"line":91,"address":[7962000],"length":1,"stats":{"Line":2}},{"line":92,"address":[7962067],"length":1,"stats":{"Line":2}},{"line":122,"address":[7827824,7841008,7839992,7836692,7824528,7823508,7823552,7843284,7837712,7831120,7826848,7833440,7834416,7836736,7830100,7821232,7826808,7830144,7833400,7840032,7820256],"length":1,"stats":{"Line":8}},{"line":126,"address":[6719046,6719723,6718950],"length":1,"stats":{"Line":24}},{"line":127,"address":[],"length":0,"stats":{"Line":0}},{"line":128,"address":[6719124],"length":1,"stats":{"Line":6}},{"line":129,"address":[7840475,7827291,7823995,7820699,7833883,7830587,7837179],"length":1,"stats":{"Line":2}},{"line":131,"address":[6719835,6719891,6721207],"length":1,"stats":{"Line":20}},{"line":132,"address":[6719939],"length":1,"stats":{"Line":6}},{"line":133,"address":[7841219,7821516,7831404,7824812,7831331,7824739,7841292,7828108,7834627,7834700,7828035,7821443,7837923,7837996],"length":1,"stats":{"Line":12}},{"line":134,"address":[],"length":0,"stats":{"Line":14}},{"line":135,"address":[],"length":0,"stats":{"Line":10}},{"line":137,"address":[7828597,7825301,7822142,7832030,7838622,7841781,7841918,7835189,7825438,7828734,7835326,7831893,7838485,7822005],"length":1,"stats":{"Line":2}},{"line":143,"address":[],"length":0,"stats":{"Line":8}},{"line":145,"address":[6721365],"length":1,"stats":{"Line":8}},{"line":147,"address":[],"length":0,"stats":{"Line":8}},{"line":150,"address":[6721550],"length":1,"stats":{"Line":8}},{"line":154,"address":[7814096,7814172],"length":1,"stats":{"Line":1}},{"line":155,"address":[6304580,6304659],"length":1,"stats":{"Line":2}},{"line":158,"address":[7814240,7814330],"length":1,"stats":{"Line":1}},{"line":159,"address":[7814276,7814369],"length":1,"stats":{"Line":2}},{"line":162,"address":[7843972,7843636,7844000,7844308,7843328,7843664],"length":1,"stats":{"Line":2}},{"line":163,"address":[7843700,7843364,7844036],"length":1,"stats":{"Line":2}},{"line":164,"address":[7843479,7843815,7844151],"length":1,"stats":{"Line":2}},{"line":167,"address":[6473065,6471824],"length":1,"stats":{"Line":3}},{"line":171,"address":[6471866],"length":1,"stats":{"Line":3}},{"line":172,"address":[8180182,8179960,8180053,8180227],"length":1,"stats":{"Line":12}},{"line":173,"address":[7814883,7815259],"length":1,"stats":{"Line":6}},{"line":174,"address":[6472569,6472481],"length":1,"stats":{"Line":6}},{"line":175,"address":[8180619],"length":1,"stats":{"Line":3}},{"line":178,"address":[8211819],"length":1,"stats":{"Line":3}},{"line":181,"address":[7967574,7967836,7963792],"length":1,"stats":{"Line":2}},{"line":183,"address":[8482899,8483253],"length":1,"stats":{"Line":4}},{"line":184,"address":[8483503],"length":1,"stats":{"Line":2}},{"line":185,"address":[8181863],"length":1,"stats":{"Line":2}},{"line":186,"address":[6306911],"length":1,"stats":{"Line":2}},{"line":187,"address":[6306943],"length":1,"stats":{"Line":2}},{"line":188,"address":[8181991],"length":1,"stats":{"Line":2}},{"line":189,"address":[6473976],"length":1,"stats":{"Line":2}},{"line":190,"address":[7964682],"length":1,"stats":{"Line":2}},{"line":191,"address":[8182082],"length":1,"stats":{"Line":2}},{"line":193,"address":[6306307],"length":1,"stats":{"Line":2}},{"line":194,"address":[8181329],"length":1,"stats":{"Line":2}},{"line":195,"address":[7724031],"length":1,"stats":{"Line":2}},{"line":196,"address":[7964029],"length":1,"stats":{"Line":2}},{"line":197,"address":[6473421],"length":1,"stats":{"Line":2}},{"line":198,"address":[8212937],"length":1,"stats":{"Line":2}},{"line":199,"address":[6306541],"length":1,"stats":{"Line":2}},{"line":200,"address":[6306571],"length":1,"stats":{"Line":2}},{"line":203,"address":[7816786,7816662],"length":1,"stats":{"Line":4}},{"line":205,"address":[6310651,6311799,6310608,6311746],"length":1,"stats":{"Line":4}},{"line":207,"address":[8487383,8487446],"length":1,"stats":{"Line":4}},{"line":209,"address":[6477770,6477828],"length":1,"stats":{"Line":4}},{"line":210,"address":[8186179],"length":1,"stats":{"Line":2}},{"line":211,"address":[8185950],"length":1,"stats":{"Line":2}},{"line":213,"address":[8218310,8218288],"length":1,"stats":{"Line":4}},{"line":215,"address":[8487712],"length":1,"stats":{"Line":2}},{"line":216,"address":[8217584],"length":1,"stats":{"Line":2}},{"line":222,"address":[6479253,6478768,6474236],"length":1,"stats":{"Line":3}},{"line":223,"address":[8218358,8218595],"length":1,"stats":{"Line":2}},{"line":224,"address":[6478806,6478898],"length":1,"stats":{"Line":2}},{"line":226,"address":[8187083],"length":1,"stats":{"Line":1}},{"line":230,"address":[6307472],"length":1,"stats":{"Line":2}},{"line":231,"address":[6474418,6475078],"length":1,"stats":{"Line":3}},{"line":232,"address":[6307516],"length":1,"stats":{"Line":1}},{"line":233,"address":[7725316,7725243,7727963],"length":1,"stats":{"Line":2}},{"line":234,"address":[7817357],"length":1,"stats":{"Line":1}},{"line":238,"address":[7965126],"length":1,"stats":{"Line":2}},{"line":241,"address":[6475163],"length":1,"stats":{"Line":2}},{"line":243,"address":[6308539],"length":1,"stats":{"Line":2}}],"covered":97,"coverable":98},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","openapi","router.rs"],"content":"use super::{\n\tbuilder::OpenapiBuilder,\n\thandler::{OpenapiDocHandler, OpenapiSpecHandler},\n\toperation::OperationDescription\n};\nuse crate::{routing::*, EndpointWithSchema, ResourceWithSchema, ResponseSchema};\nuse gotham::{\n\thyper::{Method, StatusCode},\n\tpipeline::PipelineHandleChain,\n\tprelude::*,\n\trouter::builder::{RouterBuilder, ScopeBuilder}\n};\nuse lazy_regex::regex_replace_all;\nuse openapi_type::OpenapiType;\nuse std::{collections::HashMap, panic::RefUnwindSafe};\n\n/// This trait adds the `openapi_spec` and `openapi_doc` method to an OpenAPI-aware router.\npub trait GetOpenapi {\n\t/// Register a GET route to `path` that returns the OpenAPI specification in JSON format.\n\tfn openapi_spec(&mut self, path: &str);\n\n\t/// Register a GET route to `path` that returns the OpenAPI documentation in HTML format.\n\tfn openapi_doc(&mut self, path: &str);\n}\n\n#[derive(Debug)]\npub struct OpenapiRouter<'a, D> {\n\tpub(crate) router: &'a mut D,\n\tpub(crate) scope: Option<&'a str>,\n\tpub(crate) openapi_builder: &'a mut OpenapiBuilder\n}\n\nmacro_rules! implOpenapiRouter {\n\t($implType:ident) => {\n\t\timpl<'a, 'b, C, P> OpenapiRouter<'a, $implType<'b, C, P>>\n\t\twhere\n\t\t\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\t\t\tP: RefUnwindSafe + Send + Sync + 'static\n\t\t{\n\t\t\tpub fn scope(&mut self, path: &str, callback: F)\n\t\t\twhere\n\t\t\t\tF: FnOnce(&mut OpenapiRouter<'_, ScopeBuilder<'_, C, P>>)\n\t\t\t{\n\t\t\t\tlet mut openapi_builder = self.openapi_builder.clone();\n\t\t\t\tlet new_scope = self\n\t\t\t\t\t.scope\n\t\t\t\t\t.map(|scope| format!(\"{scope}/{path}\").replace(\"//\", \"/\"));\n\t\t\t\tself.router.scope(path, |router| {\n\t\t\t\t\tlet mut router = OpenapiRouter {\n\t\t\t\t\t\trouter,\n\t\t\t\t\t\tscope: Some(new_scope.as_ref().map(String::as_ref).unwrap_or(path)),\n\t\t\t\t\t\topenapi_builder: &mut openapi_builder\n\t\t\t\t\t};\n\t\t\t\t\tcallback(&mut router);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\timpl<'a, 'b, C, P> GetOpenapi for OpenapiRouter<'a, $implType<'b, C, P>>\n\t\twhere\n\t\t\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\t\t\tP: RefUnwindSafe + Send + Sync + 'static\n\t\t{\n\t\t\tfn openapi_spec(&mut self, path: &str) {\n\t\t\t\tself.router\n\t\t\t\t\t.get(path)\n\t\t\t\t\t.to_new_handler(OpenapiSpecHandler::new(\n\t\t\t\t\t\tself.openapi_builder.openapi.clone()\n\t\t\t\t\t));\n\t\t\t}\n\n\t\t\tfn openapi_doc(&mut self, path: &str) {\n\t\t\t\tself.router\n\t\t\t\t\t.get(path)\n\t\t\t\t\t.to_new_handler(OpenapiDocHandler::new(self.openapi_builder.openapi.clone()));\n\t\t\t}\n\t\t}\n\n\t\timpl<'a, 'b, C, P> DrawResourcesWithSchema for OpenapiRouter<'a, $implType<'b, C, P>>\n\t\twhere\n\t\t\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\t\t\tP: RefUnwindSafe + Send + Sync + 'static\n\t\t{\n\t\t\tfn resource(&mut self, mut path: &str) {\n\t\t\t\tif path.starts_with('/') {\n\t\t\t\t\tpath = &path[1..];\n\t\t\t\t}\n\t\t\t\tR::setup((self, path));\n\t\t\t}\n\t\t}\n\n\t\timpl<'a, 'b, C, P> DrawResourceRoutesWithSchema\n\t\t\tfor (&mut OpenapiRouter<'a, $implType<'b, C, P>>, &str)\n\t\twhere\n\t\t\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\t\t\tP: RefUnwindSafe + Send + Sync + 'static\n\t\t{\n\t\t\tfn endpoint(&mut self) {\n\t\t\t\tlet mut responses: HashMap = HashMap::new();\n\t\t\t\tfor code in E::Output::status_codes() {\n\t\t\t\t\tresponses.insert(\n\t\t\t\t\t\tcode,\n\t\t\t\t\t\t(self.0).openapi_builder.add_schema(E::Output::schema(code))\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tlet mut path = format!(\"{}/{}\", self.0.scope.unwrap_or_default(), self.1);\n\t\t\t\tlet mut descr = OperationDescription::new::(responses, &path);\n\t\t\t\tif E::has_placeholders() {\n\t\t\t\t\tdescr.set_path_params(E::Placeholders::schema());\n\t\t\t\t}\n\t\t\t\tif E::needs_params() {\n\t\t\t\t\tdescr.set_query_params(E::Params::schema());\n\t\t\t\t}\n\t\t\t\tif E::needs_body() {\n\t\t\t\t\tlet body_schema = (self.0).openapi_builder.add_schema(E::Body::schema());\n\t\t\t\t\tdescr.set_body::(body_schema);\n\t\t\t\t}\n\n\t\t\t\tlet uri: &str = &E::uri();\n\t\t\t\tlet uri =\n\t\t\t\t\tregex_replace_all!(r#\"(^|/):([^/]+)(/|$)\"#, uri, |_, prefix, name, suffix| {\n\t\t\t\t\t\tformat!(\"{prefix}{{{name}}}{suffix}\")\n\t\t\t\t\t});\n\t\t\t\tif !uri.is_empty() {\n\t\t\t\t\tpath = format!(\"{path}/{uri}\");\n\t\t\t\t}\n\n\t\t\t\tlet op = descr.into_operation();\n\t\t\t\tlet mut item = (self.0).openapi_builder.remove_path(&path);\n\t\t\t\tmatch E::http_method() {\n\t\t\t\t\tMethod::GET => item.get = Some(op),\n\t\t\t\t\tMethod::PUT => item.put = Some(op),\n\t\t\t\t\tMethod::POST => item.post = Some(op),\n\t\t\t\t\tMethod::DELETE => item.delete = Some(op),\n\t\t\t\t\tMethod::OPTIONS => item.options = Some(op),\n\t\t\t\t\tMethod::HEAD => item.head = Some(op),\n\t\t\t\t\tMethod::PATCH => item.patch = Some(op),\n\t\t\t\t\tMethod::TRACE => item.trace = Some(op),\n\t\t\t\t\tmethod => {\n\t\t\t\t\t\twarn!(\"Ignoring unsupported method '{method}' in OpenAPI Specification\")\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\t(self.0).openapi_builder.add_path(path, item);\n\n\t\t\t\t(&mut *(self.0).router, self.1).endpoint::()\n\t\t\t}\n\t\t}\n\t};\n}\n\nimplOpenapiRouter!(RouterBuilder);\nimplOpenapiRouter!(ScopeBuilder);\n","traces":[{"line":40,"address":[6716848,6717872,6717158,6718182],"length":1,"stats":{"Line":2}},{"line":44,"address":[6717968,6716944,6716874,6717898],"length":1,"stats":{"Line":4}},{"line":45,"address":[6716949,6717973],"length":1,"stats":{"Line":2}},{"line":47,"address":[6717284,6717345,6718734,6718464,6718625,6717184,6718564,6717454],"length":1,"stats":{"Line":4}},{"line":48,"address":[6717013,6717616,6717855,6718037,6718208,6718447],"length":1,"stats":{"Line":4}},{"line":49,"address":[6717800,6717638,6718392,6718230],"length":1,"stats":{"Line":4}},{"line":51,"address":[6717707,6718240,6717648,6718299],"length":1,"stats":{"Line":4}},{"line":52,"address":[6717796,6718388],"length":1,"stats":{"Line":2}},{"line":54,"address":[6718422,6717830],"length":1,"stats":{"Line":2}},{"line":64,"address":[7849548,7849392,7849570],"length":1,"stats":{"Line":2}},{"line":65,"address":[7849422,7849521],"length":1,"stats":{"Line":4}},{"line":67,"address":[6722636],"length":1,"stats":{"Line":2}},{"line":68,"address":[6722586],"length":1,"stats":{"Line":2}},{"line":84,"address":[7849584,7849728,7849872,7850016],"length":1,"stats":{"Line":6}},{"line":85,"address":[7849607,7849858,7849895,7849714,7849751,7850146,7850002,7850039],"length":1,"stats":{"Line":7}},{"line":86,"address":[7849676,7849820,7849964,7850108],"length":1,"stats":{"Line":1}},{"line":88,"address":[7850068,7849780,7849636,7849924],"length":1,"stats":{"Line":6}},{"line":98,"address":[7868836,7887556,7888224,7893800,7875708,7857024,7869504,7875744,7881984,7862600,7888180,7856368,7869460,7863264,7856992,7875084,7881940,7894424,7850784,7881316,7863224],"length":1,"stats":{"Line":9}},{"line":99,"address":[6730405,6723061],"length":1,"stats":{"Line":9}},{"line":100,"address":[7851196,7882392,7863672,7876010,7869973,7851253,7888693,7869698,7857290,7863729,7863466,7869916,7882186,7851054,7876209,7888494,7876152,7888418,7850978,7857226,7888636,7857432,7869774,7863530,7882250,7857489,7882449,7875946],"length":1,"stats":{"Line":36}},{"line":101,"address":[7888124,7881884,7863168,7856936,7875652,7894368,7869404],"length":1,"stats":{"Line":9}},{"line":103,"address":[7876233,7881849,7888089,7857513,7863133,7875617,7869369,7888717,7894333,7851277,7882473,7869997,7863753,7856901],"length":1,"stats":{"Line":18}},{"line":106,"address":[7851736,7851509,7882701,7882928,7889039,7857831,7857968,7876551,7851325,7882517,7864071,7876461,7876688,7864208,7870456,7888765,7889176,7870319,7863797,7851599,7857741,7863981,7870045,7876277,7870229,7857557,7882791,7888949],"length":1,"stats":{"Line":36}},{"line":107,"address":[7870464,7889184,7857976,7864216,7869320,7851744,7876696,7856852,7881800,7875568,7882936,7888040,7894284,7863084],"length":1,"stats":{"Line":9}},{"line":108,"address":[7864477,7852005,7883135,7864415,7870725,7858237,7870663,7883197,7889445,7889383,7851943,7858175,7876895,7876957],"length":1,"stats":{"Line":18}},{"line":109,"address":[6724241,6731585],"length":1,"stats":{"Line":4}},{"line":111,"address":[7889516,7876963,7858312,7889451,7864552,7870796,7852080,7883203,7858243,7864483,7870731,7883268,7852011,7877028],"length":1,"stats":{"Line":18}},{"line":112,"address":[7870816,7858332,7864580,7852100,7877048,7889536,7883288],"length":1,"stats":{"Line":1}},{"line":114,"address":[7889591,7877103,7883274,7883343,7852086,7889522,7864558,7864623,7870802,7858387,7870871,7877034,7858318,7852155],"length":1,"stats":{"Line":18}},{"line":115,"address":[7864652,7877132,7858416,7870900,7883372,7889620,7852184],"length":1,"stats":{"Line":2}},{"line":116,"address":[6724485,6731829],"length":1,"stats":{"Line":2}},{"line":119,"address":[6731838,6731933,6724589,6724385,6724494,6731729],"length":1,"stats":{"Line":27}},{"line":121,"address":[7902177,7877359,7898241,7898257,7900784,7902096,7902097,7902128,7898273,7899536,7900865,7898289,7852411,7900848,7858643,7899553,7897600,7900881,7898224,7898337,7902144,7897616,7898240,7898320,7902113,7894912,7900176,7900801,7897601,7900785,7898352,7898304,7898305,7898336,7899537,7864879,7899568,7895391,7899569,7899584,7900177,7895360,7900192,7900896,7894943,7900897,7900912,7898321,7896704,7895839,7896287,7898208,7902112,7902145,7902160,7902161,7901504,7894464,7898288,7900816,7902176,7889843,7902129,7898944,7896735,7900832,7900880,7897152,7899552,7896256,7900833,7871127,7898256,7900800,7898209,7894495,7895808,7898225,7897183,7898272,7900817,7883599,7900849,7900864],"length":1,"stats":{"Line":62}},{"line":122,"address":[6730171,6737483],"length":1,"stats":{"Line":4}},{"line":124,"address":[6725408,6724693,6724788,6732037,6732132,6732752],"length":1,"stats":{"Line":23}},{"line":125,"address":[7859129,7890466,7859266,7877982,7884085,7852897,7853034,7884222,7890329,7871613,7865365,7865502,7877845,7871750],"length":1,"stats":{"Line":10}},{"line":128,"address":[7890637,7858943,7871427,7871921,7878153,7859437,7865179,7852711,7853205,7865673,7883899,7877659,7884393,7890143],"length":1,"stats":{"Line":18}},{"line":129,"address":[7884512,7878272,7890645,7890756,7865792,7871929,7859445,7878161,7853324,7859556,7865681,7853213,7872040,7884401],"length":1,"stats":{"Line":18}},{"line":130,"address":[7872187,7853471,7853423,7865891,7865939,7878371,7884659,7859655,7878419,7884611,7859703,7872139,7890855,7890903],"length":1,"stats":{"Line":18}},{"line":131,"address":[6725947,6726851,6734195,6733291],"length":1,"stats":{"Line":7}},{"line":132,"address":[6733527,6726183,6727076,6734420],"length":1,"stats":{"Line":1}},{"line":133,"address":[7885934,7872565,7867214,7892178,7891281,7878797,7879694,7860978,7860081,7873462,7854746,7853849,7866317,7885037],"length":1,"stats":{"Line":0}},{"line":134,"address":[7879033,7860317,7866553,7854974,7861206,7854085,7873690,7891517,7892406,7886162,7885273,7879922,7867442,7872801],"length":1,"stats":{"Line":0}},{"line":135,"address":[6726707,6725829,6733173,6734051],"length":1,"stats":{"Line":0}},{"line":136,"address":[7860423,7873804,7879139,7880036,7885379,7886276,7891623,7892520,7872907,7866659,7867556,7861320,7854191,7855088],"length":1,"stats":{"Line":0}},{"line":137,"address":[7855316,7880264,7860623,7861548,7892748,7873107,7866859,7879339,7885579,7886504,7854391,7867784,7874032,7891823],"length":1,"stats":{"Line":1}},{"line":138,"address":[7867670,7866759,7873007,7879239,7880150,7892634,7885479,7891723,7860523,7854291,7873918,7886390,7855202,7861434],"length":1,"stats":{"Line":0}},{"line":139,"address":[7859757,7878473,7872241,7853525,7890957,7884713,7865993],"length":1,"stats":{"Line":0}},{"line":140,"address":[7855710,7874426,7861783,7859797,7861713,7866033,7874267,7878513,7855481,7874197,7855551,7884753,7886669,7886739,7886898,7853565,7890997,7892913,7892983,7893142,7872281,7880499,7868019,7880429,7861942,7880658,7868178,7867949],"length":1,"stats":{"Line":0}},{"line":143,"address":[7862156,7868392,7855924,7874640,7880872,7887112,7893356],"length":1,"stats":{"Line":9}},{"line":145,"address":[7862368,7887324,7893568,7874852,7856136,7881084,7868604],"length":1,"stats":{"Line":9}}],"covered":44,"coverable":51},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","response","auth_result.rs"],"content":"use crate::{IntoResponseError, Response};\nuse gotham::{hyper::StatusCode, mime::TEXT_PLAIN_UTF_8};\nuse gotham_restful_derive::ResourceError;\n#[cfg(feature = \"openapi\")]\nuse openapi_type::{OpenapiSchema, OpenapiType};\n\n/// This is an error type that always yields a _403 Forbidden_ response. This type\n/// is best used in combination with [`AuthSuccess`] or [`AuthResult`].\n#[derive(Clone, Debug)]\npub struct AuthError(String);\n\nimpl AuthError {\n\tpub fn new>(msg: T) -> Self {\n\t\tSelf(msg.into())\n\t}\n}\n\nimpl IntoResponseError for AuthError {\n\t// TODO why does this need to be serde_json::Error ?!?\n\ttype Err = serde_json::Error;\n\n\tfn into_response_error(self) -> Result {\n\t\tOk(Response::new(\n\t\t\tStatusCode::FORBIDDEN,\n\t\t\tself.0,\n\t\t\tSome(TEXT_PLAIN_UTF_8)\n\t\t))\n\t}\n\n\t#[cfg(feature = \"openapi\")]\n\tfn status_codes() -> Vec {\n\t\tvec![StatusCode::FORBIDDEN]\n\t}\n\n\t#[cfg(feature = \"openapi\")]\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tassert_eq!(code, StatusCode::FORBIDDEN);\n\t\t as OpenapiType>::schema()\n\t}\n}\n\n/// This return type can be used to wrap any type implementing [IntoResponse](crate::IntoResponse)\n/// that can only be returned if the client is authenticated. Otherwise, an empty _403 Forbidden_\n/// response will be issued.\n///\n/// Use can look something like this (assuming the `auth` feature is enabled):\n///\n/// ```rust\n/// # #[macro_use] extern crate gotham_restful_derive;\n/// # #[cfg(feature = \"auth\")]\n/// # mod auth_feature_enabled {\n/// # use gotham::state::State;\n/// # use gotham_restful::*;\n/// # use serde::Deserialize;\n/// #\n/// # #[derive(Resource)]\n/// # #[resource(read_all)]\n/// # struct MyResource;\n/// #\n/// # #[derive(Clone, Deserialize)]\n/// # struct MyAuthData { exp : u64 }\n/// #\n/// #[read_all]\n/// fn read_all(auth: AuthStatus) -> AuthSuccess {\n/// \tlet auth_data = auth.ok()?;\n/// \t// do something\n/// \tOk(NoContent::default())\n/// }\n/// # }\n/// ```\npub type AuthSuccess = Result;\n\n/// This is an error type that either yields a _403 Forbidden_ response if produced\n/// from an authentication error, or delegates to another error type. This type is\n/// best used with [`AuthResult`].\n#[derive(Debug, Clone, ResourceError)]\npub enum AuthErrorOrOther {\n\tForbidden(#[from] AuthError),\n\n\t#[status(INTERNAL_SERVER_ERROR)]\n\t#[display(\"{0}\")]\n\tOther(E)\n}\n\nmod private {\n\tuse gotham::handler::HandlerError;\n\tpub trait Sealed {}\n\timpl> Sealed for E {}\n}\n\nimpl From for AuthErrorOrOther\nwhere\n\t// TODO https://github.com/msrd0/gotham_restful/issues/20\n\tF: private::Sealed + Into\n{\n\tfn from(err: F) -> Self {\n\t\tSelf::Other(err.into())\n\t}\n}\n\n/// This return type can be used to wrap any type implementing [IntoResponse](crate::IntoResponse)\n/// that can only be returned if the client is authenticated. Otherwise, an empty _403 Forbidden_\n/// response will be issued.\n///\n/// Use can look something like this (assuming the `auth` feature is enabled):\n///\n/// ```\n/// # #[macro_use] extern crate gotham_restful_derive;\n/// # #[cfg(feature = \"auth\")]\n/// # mod auth_feature_enabled {\n/// # use gotham::state::State;\n/// # use gotham_restful::*;\n/// # use serde::Deserialize;\n/// # use std::io;\n/// #\n/// # #[derive(Resource)]\n/// # #[resource(read_all)]\n/// # struct MyResource;\n/// #\n/// # #[derive(Clone, Deserialize)]\n/// # struct MyAuthData { exp : u64 }\n/// #\n/// #[read_all]\n/// fn read_all(auth: AuthStatus) -> AuthResult {\n/// \tlet auth_data = auth.ok()?;\n/// \t// do something\n/// \tOk(NoContent::default().into())\n/// }\n/// # }\n/// ```\npub type AuthResult = Result>;\n","traces":[{"line":13,"address":[6712160,6712256],"length":1,"stats":{"Line":0}},{"line":14,"address":[],"length":0,"stats":{"Line":0}},{"line":22,"address":[8441296],"length":1,"stats":{"Line":0}},{"line":23,"address":[6264701],"length":1,"stats":{"Line":0}},{"line":25,"address":[8171089],"length":1,"stats":{"Line":0}},{"line":26,"address":[8441348],"length":1,"stats":{"Line":0}},{"line":31,"address":[7774320],"length":1,"stats":{"Line":1}},{"line":32,"address":[7774401,7774333],"length":1,"stats":{"Line":1}},{"line":36,"address":[8441552],"length":1,"stats":{"Line":1}},{"line":38,"address":[6148227],"length":1,"stats":{"Line":1}},{"line":96,"address":[],"length":0,"stats":{"Line":0}},{"line":97,"address":[],"length":0,"stats":{"Line":0}}],"covered":4,"coverable":12},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","response","mod.rs"],"content":"use futures_util::future::{self, BoxFuture, FutureExt};\nuse gotham::{\n\thandler::HandlerError,\n\thyper::{\n\t\theader::{HeaderMap, HeaderName, HeaderValue},\n\t\tBody, StatusCode\n\t},\n\tmime::{Mime, APPLICATION_JSON, STAR_STAR}\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::{OpenapiSchema, OpenapiType};\nuse serde::Serialize;\n#[cfg(feature = \"errorlog\")]\nuse std::fmt::Display;\nuse std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin};\n\nmod auth_result;\n#[allow(unreachable_pub)]\npub use auth_result::{AuthError, AuthErrorOrOther, AuthResult, AuthSuccess};\n\nmod no_content;\n#[allow(unreachable_pub)]\npub use no_content::NoContent;\n\nmod raw;\n#[allow(unreachable_pub)]\npub use raw::Raw;\n\nmod redirect;\n#[allow(unreachable_pub)]\npub use redirect::Redirect;\n\nmod result;\n#[allow(unreachable_pub)]\npub use result::IntoResponseError;\n\nmod success;\n#[allow(unreachable_pub)]\npub use success::Success;\n\npub(crate) trait OrAllTypes {\n\tfn or_all_types(self) -> Vec;\n}\n\nimpl OrAllTypes for Option> {\n\tfn or_all_types(self) -> Vec {\n\t\tself.unwrap_or_else(|| vec![STAR_STAR])\n\t}\n}\n\n/// A response, used to create the final gotham response from.\n///\n/// This type is not meant to be used as the return type of endpoint handlers. While it can be\n/// freely used without the `openapi` feature, it is more complicated to use when you enable it,\n/// since this type does not store any schema information. You can attach schema information\n/// like so:\n///\n/// ```rust\n/// # #[cfg(feature = \"openapi\")] mod example {\n/// # use gotham::hyper::StatusCode;\n/// # use gotham_restful::*;\n/// # use openapi_type::*;\n/// fn schema(code: StatusCode) -> OpenapiSchema {\n/// \tassert_eq!(code, StatusCode::ACCEPTED);\n/// \t<()>::schema()\n/// }\n///\n/// fn status_codes() -> Vec {\n/// \tvec![StatusCode::ACCEPTED]\n/// }\n///\n/// #[create(schema = \"schema\", status_codes = \"status_codes\")]\n/// fn create(body: Raw>) {}\n/// # }\n/// ```\n#[derive(Debug)]\npub struct Response {\n\tpub(crate) status: StatusCode,\n\tpub(crate) body: Body,\n\tpub(crate) mime: Option,\n\tpub(crate) headers: HeaderMap\n}\n\nimpl Response {\n\t/// Create a new [Response] from raw data.\n\t#[must_use = \"Creating a response is pointless if you don't use it\"]\n\tpub fn new>(status: StatusCode, body: B, mime: Option) -> Self {\n\t\tSelf {\n\t\t\tstatus,\n\t\t\tbody: body.into(),\n\t\t\tmime,\n\t\t\theaders: Default::default()\n\t\t}\n\t}\n\n\t/// Create a [Response] with mime type json from already serialized data.\n\t#[must_use = \"Creating a response is pointless if you don't use it\"]\n\tpub fn json>(status: StatusCode, body: B) -> Self {\n\t\tSelf {\n\t\t\tstatus,\n\t\t\tbody: body.into(),\n\t\t\tmime: Some(APPLICATION_JSON),\n\t\t\theaders: Default::default()\n\t\t}\n\t}\n\n\t/// Create a _204 No Content_ [Response].\n\t#[must_use = \"Creating a response is pointless if you don't use it\"]\n\tpub fn no_content() -> Self {\n\t\tSelf {\n\t\t\tstatus: StatusCode::NO_CONTENT,\n\t\t\tbody: Body::empty(),\n\t\t\tmime: None,\n\t\t\theaders: Default::default()\n\t\t}\n\t}\n\n\t/// Create an empty _403 Forbidden_ [Response].\n\t#[must_use = \"Creating a response is pointless if you don't use it\"]\n\tpub fn forbidden() -> Self {\n\t\tSelf {\n\t\t\tstatus: StatusCode::FORBIDDEN,\n\t\t\tbody: Body::empty(),\n\t\t\tmime: None,\n\t\t\theaders: Default::default()\n\t\t}\n\t}\n\n\t/// Return the status code of this [Response].\n\tpub fn status(&self) -> StatusCode {\n\t\tself.status\n\t}\n\n\t/// Return the mime type of this [Response].\n\tpub fn mime(&self) -> Option<&Mime> {\n\t\tself.mime.as_ref()\n\t}\n\n\t/// Add an HTTP header to the [Response].\n\tpub fn header(&mut self, name: HeaderName, value: HeaderValue) {\n\t\tself.headers.insert(name, value);\n\t}\n\n\tpub(crate) fn with_headers(mut self, headers: HeaderMap) -> Self {\n\t\tself.headers = headers;\n\t\tself\n\t}\n\n\t#[cfg(test)]\n\tpub(crate) fn full_body(\n\t\tmut self\n\t) -> Result, ::Error> {\n\t\tuse futures_executor::block_on;\n\t\tuse gotham::hyper::body::to_bytes;\n\n\t\tlet bytes: &[u8] = &block_on(to_bytes(&mut self.body))?;\n\t\tOk(bytes.to_vec())\n\t}\n}\n\nimpl IntoResponse for Response {\n\ttype Err = Infallible;\n\n\tfn into_response(self) -> BoxFuture<'static, Result> {\n\t\tfuture::ok(self).boxed()\n\t}\n}\n\n/// This trait needs to be implemented by every type returned from an endpoint to\n/// to provide the response.\npub trait IntoResponse {\n\ttype Err: Into + Send + Sync + 'static;\n\n\t/// Turn this into a response that can be returned to the browser. This api will likely\n\t/// change in the future.\n\tfn into_response(self) -> BoxFuture<'static, Result>;\n\n\t/// Return a list of supported mime types.\n\tfn accepted_types() -> Option> {\n\t\tNone\n\t}\n}\n\n/// Additional details for [IntoResponse] to be used with an OpenAPI-aware router.\n#[cfg(feature = \"openapi\")]\npub trait ResponseSchema {\n\t/// All status codes returned by this response. Returns `[StatusCode::OK]` by default.\n\tfn status_codes() -> Vec {\n\t\tvec![StatusCode::OK]\n\t}\n\n\t/// Return the schema of the response for the given status code. The code may\n\t/// only be one that was previously returned by [Self::status_codes]. The\n\t/// implementation should panic if that is not the case.\n\tfn schema(code: StatusCode) -> OpenapiSchema;\n}\n\n#[cfg(feature = \"openapi\")]\nmod private {\n\tpub trait Sealed {}\n}\n\n/// A trait provided to convert a resource's result to json, and provide an OpenAPI schema to the\n/// router. This trait is implemented for all types that implement [IntoResponse] and\n/// [ResponseSchema].\n#[cfg(feature = \"openapi\")]\npub trait IntoResponseWithSchema: IntoResponse + ResponseSchema + private::Sealed {}\n\n#[cfg(feature = \"openapi\")]\nimpl private::Sealed for R {}\n\n#[cfg(feature = \"openapi\")]\nimpl IntoResponseWithSchema for R {}\n\n/// The default json returned on an 500 Internal Server Error.\n#[derive(Debug, Serialize)]\n#[cfg_attr(feature = \"openapi\", derive(OpenapiType))]\npub(crate) struct ResourceError {\n\t/// This is always `true` and can be used to detect an error response without looking at the\n\t/// HTTP status code.\n\terror: bool,\n\t/// The error message.\n\tmessage: String\n}\n\nimpl From for ResourceError {\n\tfn from(message: T) -> Self {\n\t\tSelf {\n\t\t\terror: true,\n\t\t\tmessage: message.to_string()\n\t\t}\n\t}\n}\n\n#[cfg(feature = \"errorlog\")]\nfn errorlog(e: E) {\n\terror!(\"The handler encountered an error: {e}\");\n}\n\n#[cfg(not(feature = \"errorlog\"))]\nfn errorlog(_e: E) {}\n\nfn handle_error(e: E) -> Pin> + Send>>\nwhere\n\tE: Debug + IntoResponseError\n{\n\tlet msg = format!(\"{e:?}\");\n\tlet res = e.into_response_error();\n\tmatch &res {\n\t\tOk(res) if res.status.is_server_error() => errorlog(msg),\n\t\tErr(err) => {\n\t\t\terrorlog(msg);\n\t\t\terrorlog(format!(\"{err:?}\"));\n\t\t},\n\t\t_ => {}\n\t};\n\tfuture::ready(res).boxed()\n}\n\nimpl IntoResponse for Pin + Send>>\nwhere\n\tRes: IntoResponse + 'static\n{\n\ttype Err = Res::Err;\n\n\tfn into_response(self) -> Pin> + Send>> {\n\t\tself.then(IntoResponse::into_response).boxed()\n\t}\n\n\tfn accepted_types() -> Option> {\n\t\tRes::accepted_types()\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for Pin + Send>>\nwhere\n\tRes: ResponseSchema\n{\n\tfn status_codes() -> Vec {\n\t\tRes::status_codes()\n\t}\n\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tRes::schema(code)\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse futures_executor::block_on;\n\tuse thiserror::Error;\n\n\t#[derive(Debug, Default, Deserialize, Serialize)]\n\t#[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n\tstruct Msg {\n\t\tmsg: String\n\t}\n\n\t#[derive(Debug, Default, Error)]\n\t#[error(\"An Error\")]\n\tstruct MsgError;\n\n\t#[test]\n\tfn result_from_future() {\n\t\tlet nc = NoContent::default();\n\t\tlet res = block_on(nc.into_response()).unwrap();\n\n\t\tlet fut_nc = async move { NoContent::default() }.boxed();\n\t\tlet fut_res = block_on(fut_nc.into_response()).unwrap();\n\n\t\tassert_eq!(res.status, fut_res.status);\n\t\tassert_eq!(res.mime, fut_res.mime);\n\t\tassert_eq!(res.full_body().unwrap(), fut_res.full_body().unwrap());\n\t}\n}\n","traces":[{"line":46,"address":[7774576],"length":1,"stats":{"Line":3}},{"line":47,"address":[8330862,8330848],"length":1,"stats":{"Line":9}},{"line":87,"address":[6556760,6556368,6556732],"length":1,"stats":{"Line":8}},{"line":90,"address":[6114127,6114559,6113707],"length":1,"stats":{"Line":9}},{"line":92,"address":[7184536],"length":1,"stats":{"Line":9}},{"line":98,"address":[7847232,7847533,7847558],"length":1,"stats":{"Line":2}},{"line":101,"address":[6114968],"length":1,"stats":{"Line":2}},{"line":102,"address":[7847293],"length":1,"stats":{"Line":2}},{"line":103,"address":[7847374],"length":1,"stats":{"Line":2}},{"line":109,"address":[8030160,8030370,8030345],"length":1,"stats":{"Line":3}},{"line":112,"address":[6115301],"length":1,"stats":{"Line":4}},{"line":114,"address":[8061651],"length":1,"stats":{"Line":4}},{"line":120,"address":[8062025,8061840,8062050],"length":1,"stats":{"Line":0}},{"line":123,"address":[7664965],"length":1,"stats":{"Line":0}},{"line":125,"address":[7813059],"length":1,"stats":{"Line":0}},{"line":130,"address":[7573280],"length":1,"stats":{"Line":1}},{"line":131,"address":[7665173],"length":1,"stats":{"Line":1}},{"line":135,"address":[8062080],"length":1,"stats":{"Line":1}},{"line":136,"address":[6155637],"length":1,"stats":{"Line":1}},{"line":140,"address":[8030656],"length":1,"stats":{"Line":2}},{"line":141,"address":[6115778],"length":1,"stats":{"Line":2}},{"line":144,"address":[7813569,7813360],"length":1,"stats":{"Line":4}},{"line":145,"address":[8332432,8332562],"length":1,"stats":{"Line":8}},{"line":146,"address":[8062365],"length":1,"stats":{"Line":4}},{"line":150,"address":[6116048,6116447,6116468],"length":1,"stats":{"Line":1}},{"line":156,"address":[6116345,6116078,6116135],"length":1,"stats":{"Line":6}},{"line":157,"address":[6116361],"length":1,"stats":{"Line":2}},{"line":164,"address":[8332640],"length":1,"stats":{"Line":0}},{"line":165,"address":[8062426],"length":1,"stats":{"Line":0}},{"line":179,"address":[6444240],"length":1,"stats":{"Line":7}},{"line":180,"address":[6230163],"length":1,"stats":{"Line":7}},{"line":188,"address":[],"length":0,"stats":{"Line":4}},{"line":189,"address":[6481053,6481120],"length":1,"stats":{"Line":4}},{"line":227,"address":[7241888,7242012],"length":1,"stats":{"Line":2}},{"line":230,"address":[6604088],"length":1,"stats":{"Line":2}},{"line":236,"address":[],"length":0,"stats":{"Line":1}},{"line":237,"address":[],"length":0,"stats":{"Line":3}},{"line":243,"address":[6118257,6121473,6121445,6120401,6120373,6118272,6119329,6119344,6120416,6117200,6118229,6119301],"length":1,"stats":{"Line":1}},{"line":247,"address":[7844672,7844573],"length":1,"stats":{"Line":2}},{"line":248,"address":[6118621,6117549,6120701,6117485,6118557,6119629,6119693,6120765],"length":1,"stats":{"Line":2}},{"line":249,"address":[7844787],"length":1,"stats":{"Line":1}},{"line":250,"address":[6120807,6117591,6121042,6118826,6119735,6118663,6117754,6119898,6119970,6120970,6118898,6117826],"length":1,"stats":{"Line":3}},{"line":251,"address":[],"length":0,"stats":{"Line":0}},{"line":252,"address":[],"length":0,"stats":{"Line":0}},{"line":253,"address":[],"length":0,"stats":{"Line":0}},{"line":255,"address":[],"length":0,"stats":{"Line":0}},{"line":257,"address":[],"length":0,"stats":{"Line":3}},{"line":266,"address":[6266400],"length":1,"stats":{"Line":1}},{"line":267,"address":[],"length":0,"stats":{"Line":1}},{"line":270,"address":[],"length":0,"stats":{"Line":0}},{"line":271,"address":[],"length":0,"stats":{"Line":0}},{"line":280,"address":[],"length":0,"stats":{"Line":0}},{"line":281,"address":[],"length":0,"stats":{"Line":0}},{"line":284,"address":[],"length":0,"stats":{"Line":0}},{"line":285,"address":[],"length":0,"stats":{"Line":0}}],"covered":40,"coverable":55},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","response","no_content.rs"],"content":"use super::{handle_error, IntoResponse};\n#[cfg(feature = \"openapi\")]\nuse crate::ResponseSchema;\nuse crate::{IntoResponseError, Response};\nuse futures_util::{future, future::FutureExt};\n#[cfg(feature = \"openapi\")]\nuse gotham::hyper::StatusCode;\nuse gotham::{\n\thyper::header::{HeaderMap, HeaderValue, IntoHeaderName},\n\tmime::Mime\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::{OpenapiSchema, OpenapiType};\nuse std::{fmt::Debug, future::Future, pin::Pin};\n\n/// This is the return type of a resource that doesn't actually return something. It will result\n/// in a _204 No Content_ answer by default. You don't need to use this type directly if using\n/// the function attributes:\n///\n/// ```\n/// # #[macro_use] extern crate gotham_restful_derive;\n/// # mod doc_tests_are_broken {\n/// # use gotham::state::State;\n/// # use gotham_restful::*;\n/// #\n/// # #[derive(Resource)]\n/// # #[resource(read_all)]\n/// # struct MyResource;\n/// #\n/// #[read_all]\n/// fn read_all() {\n/// \t// do something\n/// }\n/// # }\n/// ```\n#[derive(Clone, Debug, Default)]\npub struct NoContent {\n\theaders: HeaderMap\n}\n\nimpl From<()> for NoContent {\n\tfn from(_: ()) -> Self {\n\t\tSelf::default()\n\t}\n}\n\nimpl NoContent {\n\t/// Set a custom HTTP header. If a header with this name was set before, its value is being updated.\n\tpub fn header(&mut self, name: K, value: HeaderValue) {\n\t\tself.headers.insert(name, value);\n\t}\n\n\t/// Allow manipulating HTTP headers.\n\tpub fn headers_mut(&mut self) -> &mut HeaderMap {\n\t\t&mut self.headers\n\t}\n}\n\nimpl IntoResponse for NoContent {\n\t// TODO this shouldn't be a serde_json::Error\n\ttype Err = serde_json::Error; // just for easier handling of `Result`\n\n\t/// This will always be a _204 No Content_ together with an empty string.\n\tfn into_response(self) -> Pin> + Send>> {\n\t\tfuture::ok(Response::no_content().with_headers(self.headers)).boxed()\n\t}\n\n\tfn accepted_types() -> Option> {\n\t\tSome(Vec::new())\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for NoContent {\n\tfn status_codes() -> Vec {\n\t\tvec![StatusCode::NO_CONTENT]\n\t}\n\n\t/// Returns the schema of the `()` type.\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tassert_eq!(code, StatusCode::NO_CONTENT);\n\t\t<()>::schema()\n\t}\n}\n\nimpl IntoResponse for Result\nwhere\n\tE: Debug + IntoResponseError\n{\n\ttype Err = serde_json::Error;\n\n\tfn into_response(\n\t\tself\n\t) -> Pin> + Send>> {\n\t\tmatch self {\n\t\t\tOk(nc) => nc.into_response(),\n\t\t\tErr(e) => handle_error(e)\n\t\t}\n\t}\n\n\tfn accepted_types() -> Option> {\n\t\tNoContent::accepted_types()\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for Result\nwhere\n\tE: Debug + IntoResponseError\n{\n\tfn status_codes() -> Vec {\n\t\tlet mut status_codes = E::status_codes();\n\t\tstatus_codes.push(StatusCode::NO_CONTENT);\n\t\tstatus_codes\n\t}\n\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tmatch code {\n\t\t\tStatusCode::NO_CONTENT => ::schema(StatusCode::NO_CONTENT),\n\t\t\tcode => E::schema(code)\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse futures_executor::block_on;\n\tuse gotham::hyper::{header::ACCESS_CONTROL_ALLOW_ORIGIN, StatusCode};\n\tuse thiserror::Error;\n\n\t#[derive(Debug, Default, Error)]\n\t#[error(\"An Error\")]\n\tstruct MsgError;\n\n\t#[test]\n\tfn no_content_has_empty_response() {\n\t\tlet no_content = NoContent::default();\n\t\tlet res = block_on(no_content.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::NO_CONTENT);\n\t\tassert_eq!(res.mime, None);\n\t\tassert_eq!(res.full_body().unwrap(), &[] as &[u8]);\n\n\t\t#[cfg(feature = \"openapi\")]\n\t\tassert_eq!(NoContent::status_codes(), vec![StatusCode::NO_CONTENT]);\n\t}\n\n\t#[test]\n\tfn no_content_result() {\n\t\tlet no_content: Result = Ok(NoContent::default());\n\t\tlet res = block_on(no_content.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::NO_CONTENT);\n\t\tassert_eq!(res.mime, None);\n\t\tassert_eq!(res.full_body().unwrap(), &[] as &[u8]);\n\n\t\t#[cfg(feature = \"openapi\")]\n\t\tassert_eq!(>::status_codes(), vec![\n\t\t\tStatusCode::INTERNAL_SERVER_ERROR,\n\t\t\tStatusCode::NO_CONTENT\n\t\t]);\n\t}\n\n\t#[test]\n\tfn no_content_custom_headers() {\n\t\tlet mut no_content = NoContent::default();\n\t\tno_content.header(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static(\"*\"));\n\t\tlet res = block_on(no_content.into_response()).expect(\"didn't expect error response\");\n\t\tlet cors = res.headers.get(ACCESS_CONTROL_ALLOW_ORIGIN);\n\t\tassert_eq!(cors.map(|value| value.to_str().unwrap()), Some(\"*\"));\n\t}\n}\n","traces":[{"line":42,"address":[6442352],"length":1,"stats":{"Line":0}},{"line":43,"address":[8013452],"length":1,"stats":{"Line":0}},{"line":49,"address":[6442384],"length":1,"stats":{"Line":1}},{"line":50,"address":[],"length":0,"stats":{"Line":1}},{"line":54,"address":[8230832],"length":1,"stats":{"Line":0}},{"line":64,"address":[8013488,8013763,8013792],"length":1,"stats":{"Line":2}},{"line":65,"address":[8262397,8262316],"length":1,"stats":{"Line":6}},{"line":68,"address":[7865728],"length":1,"stats":{"Line":3}},{"line":69,"address":[8262637],"length":1,"stats":{"Line":5}},{"line":75,"address":[7773904],"length":1,"stats":{"Line":2}},{"line":76,"address":[7865805,7865873],"length":1,"stats":{"Line":2}},{"line":80,"address":[8533024],"length":1,"stats":{"Line":2}},{"line":82,"address":[6356472],"length":1,"stats":{"Line":2}},{"line":92,"address":[6511312],"length":1,"stats":{"Line":1}},{"line":95,"address":[],"length":0,"stats":{"Line":2}},{"line":96,"address":[],"length":0,"stats":{"Line":1}},{"line":97,"address":[],"length":0,"stats":{"Line":0}},{"line":101,"address":[],"length":0,"stats":{"Line":0}},{"line":102,"address":[],"length":0,"stats":{"Line":0}},{"line":111,"address":[],"length":0,"stats":{"Line":1}},{"line":112,"address":[],"length":0,"stats":{"Line":1}},{"line":113,"address":[],"length":0,"stats":{"Line":1}},{"line":114,"address":[],"length":0,"stats":{"Line":1}},{"line":117,"address":[],"length":0,"stats":{"Line":0}},{"line":118,"address":[],"length":0,"stats":{"Line":0}},{"line":119,"address":[],"length":0,"stats":{"Line":0}},{"line":120,"address":[],"length":0,"stats":{"Line":0}}],"covered":17,"coverable":27},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","response","raw.rs"],"content":"use super::{handle_error, IntoResponse, IntoResponseError};\nuse crate::{types::ResourceType, FromBody, RequestBody, Response};\n#[cfg(feature = \"openapi\")]\nuse crate::{IntoResponseWithSchema, ResponseSchema};\nuse futures_core::future::Future;\nuse futures_util::{future, future::FutureExt};\nuse gotham::{\n\thyper::{\n\t\tbody::{Body, Bytes},\n\t\tStatusCode\n\t},\n\tmime::Mime\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::{OpenapiSchema, OpenapiType, Visitor};\nuse serde_json::error::Error as SerdeJsonError;\nuse std::{convert::Infallible, fmt::Debug, pin::Pin};\n\n/// This type can be used both as a raw request body, as well as as a raw response. However, all types\n/// of request bodies are accepted by this type. It is therefore recommended to derive your own type\n/// from [RequestBody] and only use this when you need to return a raw response. This is a usage\n/// example that simply returns its body:\n///\n/// ```rust,no_run\n/// # #[macro_use] extern crate gotham_restful_derive;\n/// # use gotham::router::builder::*;\n/// # use gotham_restful::*;\n/// #[derive(Resource)]\n/// #[resource(create)]\n/// struct ImageResource;\n///\n/// #[create]\n/// fn create(body: Raw>) -> Raw> {\n/// \tbody\n/// }\n/// # fn main() {\n/// # \tgotham::start(\"127.0.0.1:8080\", build_simple_router(|route| {\n/// # \t\troute.resource::(\"img\");\n/// # \t}));\n/// # }\n/// ```\n#[derive(Debug)]\npub struct Raw {\n\tpub raw: T,\n\tpub mime: Mime\n}\n\nimpl Raw {\n\tpub fn new(raw: T, mime: Mime) -> Self {\n\t\tSelf { raw, mime }\n\t}\n}\n\nimpl AsMut for Raw\nwhere\n\tT: AsMut\n{\n\tfn as_mut(&mut self) -> &mut U {\n\t\tself.raw.as_mut()\n\t}\n}\n\nimpl AsRef for Raw\nwhere\n\tT: AsRef\n{\n\tfn as_ref(&self) -> &U {\n\t\tself.raw.as_ref()\n\t}\n}\n\nimpl Clone for Raw {\n\tfn clone(&self) -> Self {\n\t\tSelf {\n\t\t\traw: self.raw.clone(),\n\t\t\tmime: self.mime.clone()\n\t\t}\n\t}\n}\n\nimpl From<&'a [u8]>> FromBody for Raw {\n\ttype Err = Infallible;\n\n\tfn from_body(body: Bytes, mime: Mime) -> Result {\n\t\tOk(Self::new(body.as_ref().into(), mime))\n\t}\n}\n\nimpl RequestBody for Raw where Raw: FromBody + ResourceType {}\n\n#[cfg(feature = \"openapi\")]\nimpl OpenapiType for Raw {\n\tfn visit_type(visitor: &mut V) {\n\t\tvisitor.visit_binary()\n\t}\n}\n\nimpl> IntoResponse for Raw\nwhere\n\tSelf: Send\n{\n\ttype Err = SerdeJsonError; // just for easier handling of `Result, E>`\n\n\tfn into_response(\n\t\tself\n\t) -> Pin> + Send>> {\n\t\tfuture::ok(Response::new(StatusCode::OK, self.raw, Some(self.mime))).boxed()\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl> ResponseSchema for Raw\nwhere\n\tSelf: Send\n{\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tassert_eq!(code, StatusCode::OK);\n\t\t::schema()\n\t}\n}\n\nimpl IntoResponse for Result, E>\nwhere\n\tRaw: IntoResponse,\n\tE: Debug + IntoResponseError as IntoResponse>::Err>\n{\n\ttype Err = E::Err;\n\n\tfn into_response(self) -> Pin> + Send>> {\n\t\tmatch self {\n\t\t\tOk(raw) => raw.into_response(),\n\t\t\tErr(e) => handle_error(e)\n\t\t}\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for Result, E>\nwhere\n\tRaw: IntoResponseWithSchema,\n\tE: Debug + IntoResponseError as IntoResponse>::Err>\n{\n\tfn status_codes() -> Vec {\n\t\tlet mut status_codes = E::status_codes();\n\t\tstatus_codes.push(StatusCode::OK);\n\t\tstatus_codes\n\t}\n\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tmatch code {\n\t\t\tStatusCode::OK => as ResponseSchema>::schema(StatusCode::OK),\n\t\t\tcode => E::schema(code)\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse futures_executor::block_on;\n\tuse gotham::mime::TEXT_PLAIN;\n\tuse thiserror::Error;\n\n\t#[derive(Debug, Default, Error)]\n\t#[error(\"An Error\")]\n\tstruct MsgError;\n\n\t#[test]\n\tfn raw_response() {\n\t\tlet msg = \"Test\";\n\t\tlet raw = Raw::new(msg, TEXT_PLAIN);\n\t\tlet res = block_on(raw.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::OK);\n\t\tassert_eq!(res.mime, Some(TEXT_PLAIN));\n\t\tassert_eq!(res.full_body().unwrap(), msg.as_bytes());\n\n\t\t#[cfg(feature = \"openapi\")]\n\t\tassert_eq!(>::status_codes(), vec![StatusCode::OK]);\n\t}\n\n\t#[test]\n\tfn raw_result() {\n\t\tlet msg = \"Test\";\n\t\tlet raw: Result, MsgError> = Ok(Raw::new(msg, TEXT_PLAIN));\n\t\tlet res = block_on(raw.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::OK);\n\t\tassert_eq!(res.mime, Some(TEXT_PLAIN));\n\t\tassert_eq!(res.full_body().unwrap(), msg.as_bytes());\n\n\t\t#[cfg(feature = \"openapi\")]\n\t\tassert_eq!(, MsgError>>::status_codes(), vec![\n\t\t\tStatusCode::INTERNAL_SERVER_ERROR,\n\t\t\tStatusCode::OK\n\t\t]);\n\t}\n}\n","traces":[{"line":49,"address":[6443216],"length":1,"stats":{"Line":7}},{"line":58,"address":[],"length":0,"stats":{"Line":0}},{"line":59,"address":[],"length":0,"stats":{"Line":0}},{"line":67,"address":[],"length":0,"stats":{"Line":0}},{"line":68,"address":[],"length":0,"stats":{"Line":0}},{"line":73,"address":[],"length":0,"stats":{"Line":0}},{"line":75,"address":[],"length":0,"stats":{"Line":0}},{"line":76,"address":[],"length":0,"stats":{"Line":0}},{"line":84,"address":[6342605,6342272],"length":1,"stats":{"Line":1}},{"line":85,"address":[],"length":0,"stats":{"Line":2}},{"line":93,"address":[7847760],"length":1,"stats":{"Line":4}},{"line":94,"address":[7261525],"length":1,"stats":{"Line":4}},{"line":104,"address":[6443312],"length":1,"stats":{"Line":5}},{"line":107,"address":[6443319],"length":1,"stats":{"Line":5}},{"line":116,"address":[6443568],"length":1,"stats":{"Line":3}},{"line":118,"address":[6443827],"length":1,"stats":{"Line":3}},{"line":129,"address":[6511568],"length":1,"stats":{"Line":1}},{"line":130,"address":[6511579,6511656],"length":1,"stats":{"Line":2}},{"line":131,"address":[],"length":0,"stats":{"Line":1}},{"line":132,"address":[],"length":0,"stats":{"Line":0}},{"line":143,"address":[],"length":0,"stats":{"Line":1}},{"line":144,"address":[],"length":0,"stats":{"Line":1}},{"line":145,"address":[],"length":0,"stats":{"Line":1}},{"line":146,"address":[],"length":0,"stats":{"Line":1}},{"line":149,"address":[],"length":0,"stats":{"Line":0}},{"line":150,"address":[],"length":0,"stats":{"Line":0}},{"line":151,"address":[],"length":0,"stats":{"Line":0}},{"line":152,"address":[],"length":0,"stats":{"Line":0}}],"covered":16,"coverable":28},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","response","redirect.rs"],"content":"use super::{handle_error, IntoResponse};\nuse crate::{IntoResponseError, Response};\n#[cfg(feature = \"openapi\")]\nuse crate::{NoContent, ResponseSchema};\nuse futures_util::future::{BoxFuture, FutureExt, TryFutureExt};\nuse gotham::hyper::{\n\theader::{InvalidHeaderValue, LOCATION},\n\tBody, StatusCode\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::OpenapiSchema;\nuse std::{error::Error as StdError, fmt::Debug};\nuse thiserror::Error;\n\n/// This is the return type of a resource that only returns a redirect. It will result\n/// in a _303 See Other_ answer, meaning the redirect will always result in a GET request\n/// on the target.\n///\n/// ```\n/// # #[macro_use] extern crate gotham_restful_derive;\n/// # mod doc_tests_are_broken {\n/// # use gotham::state::State;\n/// # use gotham_restful::*;\n/// #\n/// # #[derive(Resource)]\n/// # #[resource(read_all)]\n/// # struct MyResource;\n/// #\n/// #[read_all]\n/// fn read_all() -> Redirect {\n/// \tRedirect {\n/// \t\tto: \"http://localhost:8080/cool/new/location\".to_owned()\n/// \t}\n/// }\n/// # }\n/// ```\n#[derive(Clone, Debug, Default)]\npub struct Redirect {\n\tpub to: String\n}\n\nimpl IntoResponse for Redirect {\n\ttype Err = InvalidHeaderValue;\n\n\tfn into_response(self) -> BoxFuture<'static, Result> {\n\t\tasync move {\n\t\t\tlet mut res = Response::new(StatusCode::SEE_OTHER, Body::empty(), None);\n\t\t\tres.header(LOCATION, self.to.parse()?);\n\t\t\tOk(res)\n\t\t}\n\t\t.boxed()\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for Redirect {\n\tfn status_codes() -> Vec {\n\t\tvec![StatusCode::SEE_OTHER]\n\t}\n\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tassert_eq!(code, StatusCode::SEE_OTHER);\n\t\t::schema(StatusCode::NO_CONTENT)\n\t}\n}\n\n// private type due to parent mod\n#[derive(Debug, Error)]\npub enum RedirectError {\n\t#[error(\"{0}\")]\n\tInvalidLocation(#[from] InvalidHeaderValue),\n\t#[error(\"{0}\")]\n\tOther(#[source] E)\n}\n\n#[allow(ambiguous_associated_items)] // an enum variant is not a type. never.\nimpl IntoResponse for Result\nwhere\n\tE: Debug + IntoResponseError,\n\t::Err: StdError + Sync\n{\n\ttype Err = RedirectError<::Err>;\n\n\tfn into_response(self) -> BoxFuture<'static, Result> {\n\t\tmatch self {\n\t\t\tOk(nc) => nc.into_response().map_err(Into::into).boxed(),\n\t\t\tErr(e) => handle_error(e).map_err(RedirectError::Other).boxed()\n\t\t}\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for Result\nwhere\n\tE: Debug + IntoResponseError,\n\t::Err: StdError + Sync\n{\n\tfn status_codes() -> Vec {\n\t\tlet mut status_codes = E::status_codes();\n\t\tstatus_codes.push(StatusCode::SEE_OTHER);\n\t\tstatus_codes\n\t}\n\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tmatch code {\n\t\t\tStatusCode::SEE_OTHER => ::schema(StatusCode::SEE_OTHER),\n\t\t\tcode => E::schema(code)\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse futures_executor::block_on;\n\tuse gotham::hyper::StatusCode;\n\tuse thiserror::Error;\n\n\t#[derive(Debug, Default, Error)]\n\t#[error(\"An Error\")]\n\tstruct MsgError;\n\n\t#[test]\n\tfn redirect_response() {\n\t\tlet redir = Redirect {\n\t\t\tto: \"http://localhost/foo\".to_owned()\n\t\t};\n\t\tlet res = block_on(redir.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::SEE_OTHER);\n\t\tassert_eq!(res.mime, None);\n\t\tassert_eq!(\n\t\t\tres.headers.get(LOCATION).map(|hdr| hdr.to_str().unwrap()),\n\t\t\tSome(\"http://localhost/foo\")\n\t\t);\n\t\tassert_eq!(res.full_body().unwrap(), &[] as &[u8]);\n\n\t\t#[cfg(feature = \"openapi\")]\n\t\tassert_eq!(Redirect::status_codes(), vec![StatusCode::SEE_OTHER]);\n\t}\n\n\t#[test]\n\tfn redirect_result() {\n\t\tlet redir: Result = Ok(Redirect {\n\t\t\tto: \"http://localhost/foo\".to_owned()\n\t\t});\n\t\tlet res = block_on(redir.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::SEE_OTHER);\n\t\tassert_eq!(res.mime, None);\n\t\tassert_eq!(\n\t\t\tres.headers.get(LOCATION).map(|hdr| hdr.to_str().unwrap()),\n\t\t\tSome(\"http://localhost/foo\")\n\t\t);\n\t\tassert_eq!(res.full_body().unwrap(), &[] as &[u8]);\n\n\t\t#[cfg(feature = \"openapi\")]\n\t\tassert_eq!(>::status_codes(), vec![\n\t\t\tStatusCode::INTERNAL_SERVER_ERROR,\n\t\t\tStatusCode::SEE_OTHER\n\t\t]);\n\t}\n}\n","traces":[{"line":45,"address":[7847776],"length":1,"stats":{"Line":1}},{"line":46,"address":[6191025,6190823,6191063,6190279,6190148,6190192,6190222],"length":1,"stats":{"Line":3}},{"line":47,"address":[8065334,8065248],"length":1,"stats":{"Line":4}},{"line":48,"address":[6191006,6190391,6190912,6191053],"length":1,"stats":{"Line":2}},{"line":49,"address":[6190781],"length":1,"stats":{"Line":2}},{"line":57,"address":[8097552],"length":1,"stats":{"Line":1}},{"line":58,"address":[8367789,8367857],"length":1,"stats":{"Line":1}},{"line":61,"address":[8367888],"length":1,"stats":{"Line":0}},{"line":63,"address":[6191336],"length":1,"stats":{"Line":0}},{"line":84,"address":[],"length":0,"stats":{"Line":1}},{"line":85,"address":[],"length":0,"stats":{"Line":2}},{"line":86,"address":[],"length":0,"stats":{"Line":1}},{"line":87,"address":[6511936],"length":1,"stats":{"Line":0}},{"line":98,"address":[],"length":0,"stats":{"Line":1}},{"line":99,"address":[],"length":0,"stats":{"Line":1}},{"line":100,"address":[],"length":0,"stats":{"Line":1}},{"line":101,"address":[],"length":0,"stats":{"Line":1}},{"line":104,"address":[],"length":0,"stats":{"Line":0}},{"line":105,"address":[],"length":0,"stats":{"Line":0}},{"line":106,"address":[],"length":0,"stats":{"Line":0}},{"line":107,"address":[],"length":0,"stats":{"Line":0}}],"covered":14,"coverable":21},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","response","result.rs"],"content":"use super::{handle_error, IntoResponse, ResourceError};\n#[cfg(feature = \"openapi\")]\nuse crate::ResponseSchema;\nuse crate::{Response, ResponseBody, Success};\nuse futures_core::future::Future;\nuse gotham::{\n\tanyhow::Error,\n\thyper::StatusCode,\n\tmime::{Mime, APPLICATION_JSON}\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::{OpenapiSchema, OpenapiType};\nuse std::{fmt::Debug, pin::Pin};\n\npub trait IntoResponseError {\n\ttype Err: Debug + Send + 'static;\n\n\tfn into_response_error(self) -> Result;\n\n\t#[cfg(feature = \"openapi\")]\n\tfn status_codes() -> Vec;\n\n\t#[cfg(feature = \"openapi\")]\n\tfn schema(code: StatusCode) -> OpenapiSchema;\n}\n\nimpl IntoResponseError for E\nwhere\n\tE: Into\n{\n\ttype Err = serde_json::Error;\n\n\tfn into_response_error(self) -> Result {\n\t\tlet err: Error = self.into();\n\t\tlet err: ResourceError = err.into();\n\t\tOk(Response::json(\n\t\t\tStatusCode::INTERNAL_SERVER_ERROR,\n\t\t\tserde_json::to_string(&err)?\n\t\t))\n\t}\n\n\t#[cfg(feature = \"openapi\")]\n\tfn status_codes() -> Vec {\n\t\tvec![StatusCode::INTERNAL_SERVER_ERROR]\n\t}\n\n\t#[cfg(feature = \"openapi\")]\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tassert_eq!(code, StatusCode::INTERNAL_SERVER_ERROR);\n\t\tResourceError::schema()\n\t}\n}\n\nimpl IntoResponse for Result\nwhere\n\tR: ResponseBody,\n\tE: Debug + IntoResponseError\n{\n\ttype Err = E::Err;\n\n\tfn into_response(self) -> Pin> + Send>> {\n\t\tmatch self {\n\t\t\tOk(r) => Success::from(r).into_response(),\n\t\t\tErr(e) => handle_error(e)\n\t\t}\n\t}\n\n\tfn accepted_types() -> Option> {\n\t\tSome(vec![APPLICATION_JSON])\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for Result\nwhere\n\tR: ResponseBody,\n\tE: Debug + IntoResponseError\n{\n\tfn status_codes() -> Vec {\n\t\tlet mut status_codes = E::status_codes();\n\t\tstatus_codes.push(StatusCode::OK);\n\t\tstatus_codes\n\t}\n\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tmatch code {\n\t\t\tStatusCode::OK => R::schema(),\n\t\t\tcode => E::schema(code)\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse crate::response::OrAllTypes;\n\tuse futures_executor::block_on;\n\tuse thiserror::Error;\n\n\t#[derive(Debug, Default, Deserialize, Serialize)]\n\t#[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n\tstruct Msg {\n\t\tmsg: String\n\t}\n\n\t#[derive(Debug, Default, Error)]\n\t#[error(\"An Error\")]\n\tstruct MsgError;\n\n\t#[test]\n\tfn result_ok() {\n\t\tlet ok: Result = Ok(Msg::default());\n\t\tlet res = block_on(ok.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::OK);\n\t\tassert_eq!(res.mime, Some(APPLICATION_JSON));\n\t\tassert_eq!(res.full_body().unwrap(), br#\"{\"msg\":\"\"}\"#);\n\t}\n\n\t#[test]\n\tfn result_err() {\n\t\tlet err: Result = Err(MsgError);\n\t\tlet res = block_on(err.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::INTERNAL_SERVER_ERROR);\n\t\tassert_eq!(res.mime, Some(APPLICATION_JSON));\n\t\tassert_eq!(\n\t\t\tres.full_body().unwrap(),\n\t\t\tformat!(r#\"{{\"error\":true,\"message\":\"{}\"}}\"#, MsgError).as_bytes()\n\t\t);\n\t}\n\n\t#[test]\n\tfn success_accepts_json() {\n\t\tassert!(>::accepted_types()\n\t\t\t.or_all_types()\n\t\t\t.contains(&APPLICATION_JSON))\n\t}\n}\n","traces":[{"line":33,"address":[6265904,6266266],"length":1,"stats":{"Line":2}},{"line":34,"address":[6443873],"length":1,"stats":{"Line":2}},{"line":35,"address":[],"length":0,"stats":{"Line":2}},{"line":36,"address":[5763207,5763122],"length":1,"stats":{"Line":4}},{"line":37,"address":[],"length":0,"stats":{"Line":0}},{"line":38,"address":[],"length":0,"stats":{"Line":4}},{"line":43,"address":[6266288],"length":1,"stats":{"Line":3}},{"line":44,"address":[6149981,6150048],"length":1,"stats":{"Line":3}},{"line":48,"address":[5763296],"length":1,"stats":{"Line":0}},{"line":50,"address":[5763416],"length":1,"stats":{"Line":0}},{"line":61,"address":[7650496,7650320],"length":1,"stats":{"Line":2}},{"line":62,"address":[],"length":0,"stats":{"Line":3}},{"line":63,"address":[6512191],"length":1,"stats":{"Line":1}},{"line":64,"address":[],"length":0,"stats":{"Line":1}},{"line":68,"address":[6512288],"length":1,"stats":{"Line":3}},{"line":69,"address":[],"length":0,"stats":{"Line":3}},{"line":79,"address":[7651255,7651136,7650992,7651111],"length":1,"stats":{"Line":2}},{"line":80,"address":[],"length":0,"stats":{"Line":2}},{"line":81,"address":[],"length":0,"stats":{"Line":2}},{"line":82,"address":[],"length":0,"stats":{"Line":2}},{"line":85,"address":[],"length":0,"stats":{"Line":2}},{"line":86,"address":[],"length":0,"stats":{"Line":2}},{"line":87,"address":[],"length":0,"stats":{"Line":2}},{"line":88,"address":[],"length":0,"stats":{"Line":2}}],"covered":21,"coverable":24},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","response","success.rs"],"content":"use super::IntoResponse;\n#[cfg(feature = \"openapi\")]\nuse crate::ResponseSchema;\nuse crate::{Response, ResponseBody};\nuse futures_util::future::{self, FutureExt};\nuse gotham::{\n\thyper::{\n\t\theader::{HeaderMap, HeaderValue, IntoHeaderName},\n\t\tStatusCode\n\t},\n\tmime::{Mime, APPLICATION_JSON}\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::OpenapiSchema;\nuse std::{fmt::Debug, future::Future, pin::Pin};\n\n/// This can be returned from a resource when there is no cause of an error.\n///\n/// Usage example:\n///\n/// ```\n/// # #[macro_use] extern crate gotham_restful_derive;\n/// # mod doc_tests_are_broken {\n/// # use gotham::state::State;\n/// # use gotham_restful::*;\n/// # use serde::{Deserialize, Serialize};\n/// #\n/// # #[derive(Resource)]\n/// # #[resource(read_all)]\n/// # struct MyResource;\n/// #\n/// #[derive(Deserialize, Serialize)]\n/// # #[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n/// struct MyResponse {\n/// \tmessage: &'static str\n/// }\n///\n/// #[read_all]\n/// fn read_all() -> Success {\n/// \tlet res = MyResponse {\n/// \t\tmessage: \"I'm always happy\"\n/// \t};\n/// \tres.into()\n/// }\n/// # }\n/// ```\n#[derive(Clone, Debug, Default)]\npub struct Success {\n\tvalue: T,\n\theaders: HeaderMap\n}\n\nimpl From for Success {\n\tfn from(t: T) -> Self {\n\t\tSelf {\n\t\t\tvalue: t,\n\t\t\theaders: HeaderMap::new()\n\t\t}\n\t}\n}\n\nimpl Success {\n\t/// Set a custom HTTP header. If a header with this name was set before, its value is being updated.\n\tpub fn header(&mut self, name: K, value: HeaderValue) {\n\t\tself.headers.insert(name, value);\n\t}\n\n\t/// Allow manipulating HTTP headers.\n\tpub fn headers_mut(&mut self) -> &mut HeaderMap {\n\t\t&mut self.headers\n\t}\n}\n\nimpl IntoResponse for Success {\n\ttype Err = serde_json::Error;\n\n\tfn into_response(self) -> Pin> + Send>> {\n\t\tlet res = serde_json::to_string(&self.value)\n\t\t\t.map(|body| Response::json(StatusCode::OK, body).with_headers(self.headers));\n\t\tfuture::ready(res).boxed()\n\t}\n\n\tfn accepted_types() -> Option> {\n\t\tSome(vec![APPLICATION_JSON])\n\t}\n}\n\n#[cfg(feature = \"openapi\")]\nimpl ResponseSchema for Success {\n\tfn schema(code: StatusCode) -> OpenapiSchema {\n\t\tassert_eq!(code, StatusCode::OK);\n\t\tT::schema()\n\t}\n}\n\n#[cfg(test)]\nmod test {\n\tuse super::*;\n\tuse crate::response::OrAllTypes;\n\tuse futures_executor::block_on;\n\tuse gotham::hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN;\n\n\t#[derive(Debug, Default, Serialize)]\n\t#[cfg_attr(feature = \"openapi\", derive(openapi_type::OpenapiType))]\n\tstruct Msg {\n\t\tmsg: String\n\t}\n\n\t#[test]\n\tfn success_always_successfull() {\n\t\tlet success: Success = Msg::default().into();\n\t\tlet res = block_on(success.into_response()).expect(\"didn't expect error response\");\n\t\tassert_eq!(res.status, StatusCode::OK);\n\t\tassert_eq!(res.mime, Some(APPLICATION_JSON));\n\t\tassert_eq!(res.full_body().unwrap(), br#\"{\"msg\":\"\"}\"#);\n\t\t#[cfg(feature = \"openapi\")]\n\t\tassert_eq!(>::status_codes(), vec![StatusCode::OK]);\n\t}\n\n\t#[test]\n\tfn success_custom_headers() {\n\t\tlet mut success: Success = Msg::default().into();\n\t\tsuccess.header(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static(\"*\"));\n\t\tlet res = block_on(success.into_response()).expect(\"didn't expect error response\");\n\t\tlet cors = res.headers.get(ACCESS_CONTROL_ALLOW_ORIGIN);\n\t\tassert_eq!(cors.map(|value| value.to_str().unwrap()), Some(\"*\"));\n\t}\n\n\t#[test]\n\tfn success_accepts_json() {\n\t\tassert!(>::accepted_types()\n\t\t\t.or_all_types()\n\t\t\t.contains(&APPLICATION_JSON))\n\t}\n}\n","traces":[{"line":54,"address":[7792144,7792286,7792016,7792064],"length":1,"stats":{"Line":3}},{"line":57,"address":[],"length":0,"stats":{"Line":3}},{"line":64,"address":[],"length":0,"stats":{"Line":1}},{"line":65,"address":[],"length":0,"stats":{"Line":1}},{"line":69,"address":[],"length":0,"stats":{"Line":0}},{"line":70,"address":[],"length":0,"stats":{"Line":0}},{"line":77,"address":[6480310,6480329,6479696,6479974,6480032,6479993],"length":1,"stats":{"Line":2}},{"line":78,"address":[],"length":0,"stats":{"Line":6}},{"line":79,"address":[],"length":0,"stats":{"Line":9}},{"line":80,"address":[],"length":0,"stats":{"Line":5}},{"line":83,"address":[],"length":0,"stats":{"Line":1}},{"line":84,"address":[],"length":0,"stats":{"Line":1}},{"line":90,"address":[],"length":0,"stats":{"Line":0}},{"line":92,"address":[],"length":0,"stats":{"Line":0}}],"covered":10,"coverable":14},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","routing.rs"],"content":"#[cfg(feature = \"openapi\")]\nuse crate::openapi::{\n\tbuilder::{OpenapiBuilder, OpenapiInfo},\n\trouter::OpenapiRouter\n};\nuse crate::{response::ResourceError, Endpoint, FromBody, IntoResponse, Resource, Response};\n#[cfg(feature = \"cors\")]\nuse gotham::router::route::matcher::AccessControlRequestMethodMatcher;\nuse gotham::{\n\thandler::HandlerError,\n\thelpers::http::response::{create_empty_response, create_response},\n\thyper::{body::to_bytes, header::CONTENT_TYPE, Body, HeaderMap, Method, StatusCode},\n\tmime::{Mime, APPLICATION_JSON},\n\tpipeline::PipelineHandleChain,\n\tprelude::*,\n\trouter::{\n\t\tbuilder::{RouterBuilder, ScopeBuilder},\n\t\troute::matcher::{AcceptHeaderRouteMatcher, ContentTypeHeaderRouteMatcher, RouteMatcher},\n\t\tRouteNonMatch\n\t},\n\tstate::{FromState, State}\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::OpenapiType;\nuse std::{any::TypeId, panic::RefUnwindSafe};\n\n/// Allow us to extract an id from a path.\n#[derive(Clone, Copy, Debug, Deserialize, StateData, StaticResponseExtender)]\n#[cfg_attr(feature = \"openapi\", derive(OpenapiType))]\npub struct PathExtractor {\n\tpub id: ID\n}\n\n/// This trait adds the `with_openapi` method to gotham's routing. It turns the default\n/// router into one that will only allow RESTful resources, but record them and generate\n/// an OpenAPI specification on request.\n#[cfg(feature = \"openapi\")]\npub trait WithOpenapi {\n\tfn with_openapi(&mut self, info: OpenapiInfo, block: F)\n\twhere\n\t\tF: FnOnce(OpenapiRouter<'_, D>);\n}\n\n/// This trait adds the `resource` method to gotham's routing. It allows you to register\n/// any RESTful [Resource] with a path.\n#[_private_openapi_trait(DrawResourcesWithSchema)]\npub trait DrawResources {\n\t#[openapi_bound(R: crate::ResourceWithSchema)]\n\t#[non_openapi_bound(R: crate::Resource)]\n\tfn resource(&mut self, path: &str);\n}\n\n/// This trait allows to draw routes within an resource. Use this only inside the\n/// [Resource::setup] method.\n#[_private_openapi_trait(DrawResourceRoutesWithSchema)]\npub trait DrawResourceRoutes {\n\t#[openapi_bound(E: crate::EndpointWithSchema)]\n\t#[non_openapi_bound(E: crate::Endpoint)]\n\tfn endpoint(&mut self);\n}\n\nfn response_from(res: Response, state: &State) -> gotham::hyper::Response {\n\tlet mut r = create_empty_response(state, res.status);\n\tlet headers = r.headers_mut();\n\tif let Some(mime) = res.mime {\n\t\theaders.insert(CONTENT_TYPE, mime.as_ref().parse().unwrap());\n\t}\n\tlet mut last_name = None;\n\tfor (name, value) in res.headers {\n\t\tif name.is_some() {\n\t\t\tlast_name = name;\n\t\t}\n\t\t// this unwrap is safe: the first item will always be Some\n\t\tlet name = last_name.clone().unwrap();\n\t\theaders.insert(name, value);\n\t}\n\n\tlet method = Method::borrow_from(state);\n\tif method != Method::HEAD {\n\t\t*r.body_mut() = res.body;\n\t}\n\n\t#[cfg(feature = \"cors\")]\n\tcrate::cors::handle_cors(state, &mut r);\n\n\tr\n}\n\nasync fn endpoint_handler(\n\tstate: &mut State\n) -> Result, HandlerError>\nwhere\n\tE: Endpoint,\n\t::Err: Into\n{\n\ttrace!(\"entering endpoint_handler\");\n\tlet placeholders = E::Placeholders::take_from(state);\n\t// workaround for E::Placeholders and E::Param being the same type\n\t// when fixed remove `Clone` requirement on endpoint\n\tif TypeId::of::() == TypeId::of::() {\n\t\tstate.put(placeholders.clone());\n\t}\n\tlet params = E::Params::take_from(state);\n\n\tlet body = match E::needs_body() {\n\t\ttrue => {\n\t\t\tlet body = to_bytes(Body::take_from(state)).await?;\n\n\t\t\tlet content_type: Mime = match HeaderMap::borrow_from(state).get(CONTENT_TYPE) {\n\t\t\t\tSome(content_type) => content_type.to_str().unwrap().parse().unwrap(),\n\t\t\t\tNone => {\n\t\t\t\t\tdebug!(\"Missing Content-Type: Returning 415 Response\");\n\t\t\t\t\tlet res = create_empty_response(state, StatusCode::UNSUPPORTED_MEDIA_TYPE);\n\t\t\t\t\treturn Ok(res);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tmatch E::Body::from_body(body, content_type) {\n\t\t\t\tOk(body) => Some(body),\n\t\t\t\tErr(e) => {\n\t\t\t\t\tdebug!(\"Invalid Body: Returning 400 Response\");\n\t\t\t\t\tlet error: ResourceError = e.into();\n\t\t\t\t\tlet json = serde_json::to_string(&error)?;\n\t\t\t\t\tlet res =\n\t\t\t\t\t\tcreate_response(state, StatusCode::BAD_REQUEST, APPLICATION_JSON, json);\n\t\t\t\t\treturn Ok(res);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tfalse => None\n\t};\n\n\tlet out = E::handle(state, placeholders, params, body).await;\n\tlet res = out.into_response().await.map_err(Into::into)?;\n\tdebug!(\"Returning response {res:?}\");\n\tOk(response_from(res, state))\n}\n\n#[derive(Clone)]\nstruct MaybeMatchAcceptHeader {\n\tmatcher: Option\n}\n\nimpl RouteMatcher for MaybeMatchAcceptHeader {\n\tfn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {\n\t\tmatch &self.matcher {\n\t\t\tSome(matcher) => matcher.is_match(state),\n\t\t\tNone => Ok(())\n\t\t}\n\t}\n}\n\nimpl MaybeMatchAcceptHeader {\n\tfn new(types: Option>) -> Self {\n\t\tlet types = match types {\n\t\t\tSome(types) if types.is_empty() => None,\n\t\t\ttypes => types\n\t\t};\n\t\tSelf {\n\t\t\tmatcher: types.map(AcceptHeaderRouteMatcher::new)\n\t\t}\n\t}\n}\n\nimpl From>> for MaybeMatchAcceptHeader {\n\tfn from(types: Option>) -> Self {\n\t\tSelf::new(types)\n\t}\n}\n\n#[derive(Clone)]\nstruct MaybeMatchContentTypeHeader {\n\tmatcher: Option\n}\n\nimpl RouteMatcher for MaybeMatchContentTypeHeader {\n\tfn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {\n\t\tmatch &self.matcher {\n\t\t\tSome(matcher) => matcher.is_match(state),\n\t\t\tNone => Ok(())\n\t\t}\n\t}\n}\n\nimpl MaybeMatchContentTypeHeader {\n\tfn new(types: Option>) -> Self {\n\t\tSelf {\n\t\t\tmatcher: types.map(|types| ContentTypeHeaderRouteMatcher::new(types).allow_no_type())\n\t\t}\n\t}\n}\n\nimpl From>> for MaybeMatchContentTypeHeader {\n\tfn from(types: Option>) -> Self {\n\t\tSelf::new(types)\n\t}\n}\n\nmacro_rules! implDrawResourceRoutes {\n\t($implType:ident) => {\n\t\t#[cfg(feature = \"openapi\")]\n\t\timpl<'a, C, P> WithOpenapi for $implType<'a, C, P>\n\t\twhere\n\t\t\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\t\t\tP: RefUnwindSafe + Send + Sync + 'static\n\t\t{\n\t\t\tfn with_openapi(&mut self, info: OpenapiInfo, block: F)\n\t\t\twhere\n\t\t\t\tF: FnOnce(OpenapiRouter<'_, $implType<'a, C, P>>)\n\t\t\t{\n\t\t\t\tlet router = OpenapiRouter {\n\t\t\t\t\trouter: self,\n\t\t\t\t\tscope: None,\n\t\t\t\t\topenapi_builder: &mut OpenapiBuilder::new(info)\n\t\t\t\t};\n\t\t\t\tblock(router);\n\t\t\t}\n\t\t}\n\n\t\timpl<'a, C, P> DrawResources for $implType<'a, C, P>\n\t\twhere\n\t\t\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\t\t\tP: RefUnwindSafe + Send + Sync + 'static\n\t\t{\n\t\t\tfn resource(&mut self, mut path: &str) {\n\t\t\t\tif path.starts_with('/') {\n\t\t\t\t\tpath = &path[1..];\n\t\t\t\t}\n\t\t\t\tR::setup((self, path));\n\t\t\t}\n\t\t}\n\n\t\timpl<'a, C, P> DrawResourceRoutes for (&mut $implType<'a, C, P>, &str)\n\t\twhere\n\t\t\tC: PipelineHandleChain

+ Copy + Send + Sync + 'static,\n\t\t\tP: RefUnwindSafe + Send + Sync + 'static\n\t\t{\n\t\t\tfn endpoint(&mut self) {\n\t\t\t\tlet uri = format!(\"{}/{}\", self.1, E::uri());\n\t\t\t\tdebug!(\"Registering endpoint for {uri}\");\n\t\t\t\tself.0.associate(&uri, |assoc| {\n\t\t\t\t\tassoc\n\t\t\t\t\t\t.request(vec![E::http_method()])\n\t\t\t\t\t\t.add_route_matcher(MaybeMatchAcceptHeader::new(E::Output::accepted_types()))\n\t\t\t\t\t\t.with_path_extractor::()\n\t\t\t\t\t\t.with_query_string_extractor::()\n\t\t\t\t\t\t.to_async_borrowing(endpoint_handler::);\n\n\t\t\t\t\t#[cfg(feature = \"cors\")]\n\t\t\t\t\tif E::http_method() != Method::GET {\n\t\t\t\t\t\tassoc\n\t\t\t\t\t\t\t.options()\n\t\t\t\t\t\t\t.add_route_matcher(AccessControlRequestMethodMatcher::new(\n\t\t\t\t\t\t\t\tE::http_method()\n\t\t\t\t\t\t\t))\n\t\t\t\t\t\t\t.to(crate::cors::cors_preflight_handler);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t};\n}\n\nimplDrawResourceRoutes!(RouterBuilder);\nimplDrawResourceRoutes!(ScopeBuilder);\n","traces":[{"line":62,"address":[7609056,7610936,7610057],"length":1,"stats":{"Line":5}},{"line":63,"address":[6342066,6341915],"length":1,"stats":{"Line":10}},{"line":64,"address":[8098042,8098111],"length":1,"stats":{"Line":10}},{"line":65,"address":[7849303],"length":1,"stats":{"Line":5}},{"line":66,"address":[8368910,8368457,8369046,8368668],"length":1,"stats":{"Line":8}},{"line":68,"address":[7609520],"length":1,"stats":{"Line":5}},{"line":69,"address":[8369260,8369118,8368540,8369212,8370475],"length":1,"stats":{"Line":15}},{"line":70,"address":[8370234,8369372,8370044],"length":1,"stats":{"Line":0}},{"line":71,"address":[8370080],"length":1,"stats":{"Line":0}},{"line":74,"address":[7702930],"length":1,"stats":{"Line":0}},{"line":75,"address":[7703219],"length":1,"stats":{"Line":0}},{"line":78,"address":[6343191],"length":1,"stats":{"Line":5}},{"line":79,"address":[8068044,8067752],"length":1,"stats":{"Line":10}},{"line":80,"address":[6193305,6192829],"length":1,"stats":{"Line":5}},{"line":84,"address":[8369477],"length":1,"stats":{"Line":5}},{"line":86,"address":[8369734],"length":1,"stats":{"Line":5}},{"line":89,"address":[6363488],"length":1,"stats":{"Line":21}},{"line":96,"address":[6363707,6363894,6363969],"length":1,"stats":{"Line":63}},{"line":97,"address":[6451722,6451404],"length":1,"stats":{"Line":42}},{"line":100,"address":[6920422,6914887,6920503,6914806],"length":1,"stats":{"Line":42}},{"line":101,"address":[7453423,7487653,7476347,7481979,7459131,7493419,7464891,7499093,7470693],"length":1,"stats":{"Line":13}},{"line":103,"address":[],"length":0,"stats":{"Line":42}},{"line":105,"address":[7266487,7283581,7295212,7260784,7255148,7272188,7266550,7283644,7295149,7289399,7278006,7260721,7255085,7289462,7277943,7272125],"length":1,"stats":{"Line":42}},{"line":106,"address":[],"length":0,"stats":{"Line":0}},{"line":107,"address":[6960138],"length":1,"stats":{"Line":16}},{"line":109,"address":[7272957,7272767,7261554,7284413,7295942,7255688,7278740,7295752,7267094,7261364,7290045,7278550,7267284,7284223,7255878,7290235],"length":1,"stats":{"Line":16}},{"line":110,"address":[6921789,6922446,6916057,6916714],"length":1,"stats":{"Line":16}},{"line":111,"address":[],"length":0,"stats":{"Line":0}},{"line":112,"address":[],"length":0,"stats":{"Line":0}},{"line":113,"address":[6453044],"length":1,"stats":{"Line":0}},{"line":114,"address":[7460594,7494843,7483403,7472137,7454887,7477771,7489136,7466354,7500537],"length":1,"stats":{"Line":0}},{"line":118,"address":[7500997,7478231,7461054,7472597,7483863,7495303,7455347,7466814,7489596],"length":1,"stats":{"Line":8}},{"line":119,"address":[6453939],"length":1,"stats":{"Line":8}},{"line":120,"address":[7495424,7501118,7467013,7455468,7472718,7478352,7461253,7489795,7483984],"length":1,"stats":{"Line":0}},{"line":121,"address":[7501437,7461477,7455781,7467037,7473037,7495448,7472742,7478376,7501342,7455492,7461572,7478665,7484297,7484008,7489819,7461277,7467237,7501142,7495737,7490019,7495648,7455692,7478576,7472942,7467332,7484208,7490108],"length":1,"stats":{"Line":0}},{"line":122,"address":[7355458,7361124,7372571,7349803,7366817,7378198],"length":1,"stats":{"Line":0}},{"line":123,"address":[7490866,7467549,7456539,7456376,7496013,7473254,7479423,7502195,7462167,7461789,7484892,7473313,7467927,7473632,7473795,7490703,7461848,7478882,7479260,7496332,7501713,7485055,7462330,7496495,7501654,7490384,7495954,7484514,7456057,7490325,7478941,7467608,7502032,7468090,7484573,7455998],"length":1,"stats":{"Line":0}},{"line":124,"address":[6454679],"length":1,"stats":{"Line":0}},{"line":125,"address":[],"length":0,"stats":{"Line":0}},{"line":126,"address":[6918011],"length":1,"stats":{"Line":0}},{"line":130,"address":[6364610],"length":1,"stats":{"Line":13}},{"line":133,"address":[6454040,6455075,6452150,6451310],"length":1,"stats":{"Line":43}},{"line":134,"address":[7579688,7578316,7579068,7580392,7581000,7581624,7577976],"length":1,"stats":{"Line":42}},{"line":135,"address":[7480380,7480509,7469047,7474606,7486141,7486303,7463141,7463578,7491823,7497452,7503152,7491952,7463287,7474881,7492114,7469176,7497306,7497743,7491677,7486012,7457787,7463416,7503006,7457350,7485866,7457625,7497581,7469338,7503443,7457496,7474752,7480234,7468901,7503281,7480671,7475043],"length":1,"stats":{"Line":80}},{"line":136,"address":[7384658,7363392,7351979,7385114,7368606,7357651,7357193,7369063,7362934,7374365,7379916,7374823,7351523,7380372],"length":1,"stats":{"Line":42}},{"line":145,"address":[6344480],"length":1,"stats":{"Line":7}},{"line":146,"address":[7703617],"length":1,"stats":{"Line":5}},{"line":147,"address":[6344568],"length":1,"stats":{"Line":0}},{"line":148,"address":[6194100],"length":1,"stats":{"Line":7}},{"line":154,"address":[8069533,8069607,8069136],"length":1,"stats":{"Line":6}},{"line":155,"address":[8370838],"length":1,"stats":{"Line":8}},{"line":156,"address":[8100681,8100828],"length":1,"stats":{"Line":6}},{"line":157,"address":[7611925],"length":1,"stats":{"Line":5}},{"line":160,"address":[7852082],"length":1,"stats":{"Line":6}},{"line":166,"address":[8371296],"length":1,"stats":{"Line":0}},{"line":167,"address":[6345080],"length":1,"stats":{"Line":0}},{"line":177,"address":[8371328],"length":1,"stats":{"Line":0}},{"line":178,"address":[8101137],"length":1,"stats":{"Line":0}},{"line":179,"address":[6345192],"length":1,"stats":{"Line":0}},{"line":180,"address":[8101172],"length":1,"stats":{"Line":0}},{"line":186,"address":[8069760],"length":1,"stats":{"Line":0}},{"line":188,"address":[8069824,8069837,8069774],"length":1,"stats":{"Line":0}},{"line":194,"address":[8371568],"length":1,"stats":{"Line":0}},{"line":195,"address":[7852536],"length":1,"stats":{"Line":0}},{"line":207,"address":[7849104,7849357],"length":1,"stats":{"Line":2}},{"line":211,"address":[7849220,7849132],"length":1,"stats":{"Line":4}},{"line":213,"address":[6643446],"length":1,"stats":{"Line":2}},{"line":214,"address":[6643455,6643519],"length":1,"stats":{"Line":4}},{"line":216,"address":[7849255],"length":1,"stats":{"Line":2}},{"line":225,"address":[6354368],"length":1,"stats":{"Line":5}},{"line":226,"address":[6202983,6203090],"length":1,"stats":{"Line":5}},{"line":227,"address":[7423388],"length":1,"stats":{"Line":3}},{"line":229,"address":[6974388],"length":1,"stats":{"Line":5}},{"line":238,"address":[7436510,7441710,7440704,7438590,7444830,7443790,7442750,7437550,7436544,7435504,7438624,7439630,7442784,7441744,7443824,7440670,7439664,7437584],"length":1,"stats":{"Line":31}},{"line":239,"address":[6982523,6984478,6989588,6985428,6986468,6983348,6983438,6982398,6984388,6983563,6987598,6987508,6987723,6986558,6986683,6989803,6984603,6985518,6988638,6988548,6989678,6982308,6985643,6988763],"length":1,"stats":{"Line":62}},{"line":240,"address":[6642545,6641419,6641658,6642372,6641332,6641505,6642459,6642698],"length":1,"stats":{"Line":109}},{"line":241,"address":[7252952,7251360,7253768,7249728,7248912,7248056,7250544,7253740,7252992,7248872,7249688,7252108,7248096,7252924,7247280,7249660,7251292,7248028,7250476,7252136,7248844,7251320,7252176,7250504],"length":1,"stats":{"Line":93}},{"line":242,"address":[6449763,6450196,6449574,6449380,6450390,6449696,6450512,6450579],"length":1,"stats":{"Line":128}},{"line":243,"address":[7252160,7251406,7253792,7251344,7248896,7249947,7253239,7247326,7250763,7249774,7248343,7247527,7250528,7248142,7248080,7249712,7249131,7248958,7249159,7250590,7250791,7252222,7252976,7247499,7253038,7248315,7252423,7251579,7252395,7249975,7251607,7253211],"length":1,"stats":{"Line":62}},{"line":244,"address":[6450969,6450411,6449735,6450153,6450444,6449628,6450551,6449595],"length":1,"stats":{"Line":60}},{"line":250,"address":[6449810,6450626],"length":1,"stats":{"Line":29}},{"line":251,"address":[6449934,6450043,6450090,6450750,6450906,6450859],"length":1,"stats":{"Line":46}},{"line":253,"address":[7447968,7447152,7452048,7450416,7445520,7446336,7449600,7451232,7448784],"length":1,"stats":{"Line":16}},{"line":254,"address":[7445467,7448731,7447915,7451179,7451995,7449547,7450363,7447099,7446283],"length":1,"stats":{"Line":14}}],"covered":57,"coverable":84},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","types.rs"],"content":"use gotham::{\n\thyper::body::Bytes,\n\tmime::{Mime, APPLICATION_JSON}\n};\n#[cfg(feature = \"openapi\")]\nuse openapi_type::OpenapiType;\nuse serde::{de::DeserializeOwned, Serialize};\nuse std::error::Error;\n\n#[cfg(not(feature = \"openapi\"))]\npub trait ResourceType {}\n\n#[cfg(not(feature = \"openapi\"))]\nimpl ResourceType for T {}\n\n#[cfg(feature = \"openapi\")]\npub trait ResourceType: OpenapiType {}\n\n#[cfg(feature = \"openapi\")]\nimpl ResourceType for T {}\n\n/// A type that can be used inside a response body. Implemented for every type that is\n/// serializable with serde. If the `openapi` feature is used, it must also be of type\n/// [OpenapiType].\npub trait ResponseBody: ResourceType + Serialize {}\n\nimpl ResponseBody for T {}\n\n/// This trait should be implemented for every type that can be built from an HTTP request body\n/// plus its media type.\n///\n/// For most use cases it is sufficient to derive this trait, you usually don't need to manually\n/// implement this. Therefore, make sure that the first variable of your struct can be built from\n/// [Bytes], and the second one can be build from [Mime]. If you have any additional variables, they\n/// need to be [Default]. This is an example of such a struct:\n///\n/// ```rust\n/// # use gotham::mime::{self, Mime};\n/// # use gotham_restful::{FromBody, RequestBody};\n/// #[derive(FromBody, RequestBody)]\n/// #[supported_types(mime::IMAGE_GIF, mime::IMAGE_JPEG, mime::IMAGE_PNG)]\n/// struct RawImage {\n/// \tcontent: Vec,\n/// \tcontent_type: Mime\n/// }\n/// ```\npub trait FromBody: Sized {\n\t/// The error type returned by the conversion if it was unsuccessfull. When using the derive\n\t/// macro, there is no way to trigger an error, so [std::convert::Infallible] is used here.\n\t/// However, this might change in the future.\n\ttype Err: Error;\n\n\t/// Perform the conversion.\n\tfn from_body(body: Bytes, content_type: Mime) -> Result;\n}\n\nimpl FromBody for T {\n\ttype Err = serde_json::Error;\n\n\tfn from_body(body: Bytes, _content_type: Mime) -> Result {\n\t\tserde_json::from_slice(&body)\n\t}\n}\n\n/// A type that can be used inside a request body. Implemented for every type that is deserializable\n/// with serde. If the `openapi` feature is used, it must also be of type [OpenapiType].\n///\n/// If you want a non-deserializable type to be used as a request body, e.g. because you'd like to\n/// get the raw data, you can derive it for your own type. All you need is to have a type implementing\n/// [FromBody] and optionally a list of supported media types:\n///\n/// ```rust\n/// # use gotham::mime::{self, Mime};\n/// # use gotham_restful::{FromBody, RequestBody};\n/// #[derive(FromBody, RequestBody)]\n/// #[supported_types(mime::IMAGE_GIF, mime::IMAGE_JPEG, mime::IMAGE_PNG)]\n/// struct RawImage {\n/// \tcontent: Vec,\n/// \tcontent_type: Mime\n/// }\n/// ```\npub trait RequestBody: ResourceType + FromBody {\n\t/// Return all types that are supported as content types. Use `None` if all types are supported.\n\tfn supported_types() -> Option> {\n\t\tNone\n\t}\n}\n\nimpl RequestBody for T {\n\tfn supported_types() -> Option> {\n\t\tSome(vec![APPLICATION_JSON])\n\t}\n}\n","traces":[{"line":60,"address":[6461272,6461120],"length":1,"stats":{"Line":2}},{"line":61,"address":[6461193,6461134],"length":1,"stats":{"Line":4}},{"line":84,"address":[],"length":0,"stats":{"Line":0}},{"line":85,"address":[],"length":0,"stats":{"Line":0}},{"line":90,"address":[6943488],"length":1,"stats":{"Line":1}},{"line":91,"address":[],"length":0,"stats":{"Line":1}}],"covered":4,"coverable":6},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","async_methods.rs"],"content":"use gotham::{\n\thyper::{HeaderMap, Method},\n\tmime::{APPLICATION_JSON, TEXT_PLAIN},\n\tprelude::*,\n\trouter::build_simple_router,\n\tstate::State,\n\ttest::TestServer\n};\nuse gotham_restful::*;\n#[cfg(feature = \"openapi\")]\nuse openapi_type::OpenapiType;\nuse serde::Deserialize;\nuse simple_logger::SimpleLogger;\nuse tokio::time::{sleep, Duration};\n\nmod util {\n\tinclude!(\"util/mod.rs\");\n}\nuse util::{test_delete_response, test_get_response, test_post_response, test_put_response};\n\n#[derive(Resource)]\n#[resource(\n\tread_all, read, search, create, update_all, update, delete_all, delete, state_test\n)]\nstruct FooResource;\n\n#[derive(Deserialize)]\n#[cfg_attr(feature = \"openapi\", derive(OpenapiType))]\n#[allow(dead_code)]\nstruct FooBody {\n\tdata: String\n}\n\n#[derive(Clone, Deserialize, StateData, StaticResponseExtender)]\n#[cfg_attr(feature = \"openapi\", derive(OpenapiType))]\n#[allow(dead_code)]\nstruct FooSearch {\n\tquery: String\n}\n\nconst READ_ALL_RESPONSE: &[u8] = b\"1ARwwSPVyOKpJKrYwqGgECPVWDl1BqajAAj7g7WJ3e\";\n#[read_all]\nasync fn read_all() -> Raw<&'static [u8]> {\n\tRaw::new(READ_ALL_RESPONSE, TEXT_PLAIN)\n}\n\nconst READ_RESPONSE: &[u8] = b\"FEReHoeBKU17X2bBpVAd1iUvktFL43CDu0cFYHdaP9\";\n#[read]\nasync fn read(_id: u64) -> Raw<&'static [u8]> {\n\tRaw::new(READ_RESPONSE, TEXT_PLAIN)\n}\n\nconst SEARCH_RESPONSE: &[u8] = b\"AWqcQUdBRHXKh3at4u79mdupOAfEbnTcx71ogCVF0E\";\n#[search]\nasync fn search(_body: FooSearch) -> Raw<&'static [u8]> {\n\tRaw::new(SEARCH_RESPONSE, TEXT_PLAIN)\n}\n\nconst CREATE_RESPONSE: &[u8] = b\"y6POY7wOMAB0jBRBw0FJT7DOpUNbhmT8KdpQPLkI83\";\n#[create]\nasync fn create(_body: FooBody) -> Raw<&'static [u8]> {\n\tRaw::new(CREATE_RESPONSE, TEXT_PLAIN)\n}\n\nconst UPDATE_ALL_RESPONSE: &[u8] = b\"QlbYg8gHE9OQvvk3yKjXJLTSXlIrg9mcqhfMXJmQkv\";\n#[update_all]\nasync fn update_all(_body: FooBody) -> Raw<&'static [u8]> {\n\tRaw::new(UPDATE_ALL_RESPONSE, TEXT_PLAIN)\n}\n\nconst UPDATE_RESPONSE: &[u8] = b\"qGod55RUXkT1lgPO8h0uVM6l368O2S0GrwENZFFuRu\";\n#[update]\nasync fn update(_id: u64, _body: FooBody) -> Raw<&'static [u8]> {\n\tRaw::new(UPDATE_RESPONSE, TEXT_PLAIN)\n}\n\nconst DELETE_ALL_RESPONSE: &[u8] = b\"Y36kZ749MRk2Nem4BedJABOZiZWPLOtiwLfJlGTwm5\";\n#[delete_all]\nasync fn delete_all() -> Raw<&'static [u8]> {\n\tRaw::new(DELETE_ALL_RESPONSE, TEXT_PLAIN)\n}\n\nconst DELETE_RESPONSE: &[u8] = b\"CwRzBrKErsVZ1N7yeNfjZuUn1MacvgBqk4uPOFfDDq\";\n#[delete]\nasync fn delete(_id: u64) -> Raw<&'static [u8]> {\n\tRaw::new(DELETE_RESPONSE, TEXT_PLAIN)\n}\n\nconst STATE_TEST_RESPONSE: &[u8] = b\"xxJbxOuwioqR5DfzPuVqvaqRSfpdNQGluIvHU4n1LM\";\n#[endpoint(method = \"Method::GET\", uri = \"state_test\")]\nasync fn state_test(state: &mut State) -> Raw<&'static [u8]> {\n\tsleep(Duration::from_nanos(1)).await;\n\tstate.borrow::();\n\tsleep(Duration::from_nanos(1)).await;\n\tRaw::new(STATE_TEST_RESPONSE, TEXT_PLAIN)\n}\n\n#[test]\nfn async_methods() {\n\t// TODO no idea why with_local_timestamps fails here\n\t_ = SimpleLogger::new().env().with_utc_timestamps().init();\n\n\tlet server = TestServer::new(build_simple_router(|router| {\n\t\trouter.resource::(\"foo\");\n\t}))\n\t.unwrap();\n\n\ttest_get_response(&server, \"http://localhost/foo\", READ_ALL_RESPONSE);\n\ttest_get_response(&server, \"http://localhost/foo/1\", READ_RESPONSE);\n\ttest_get_response(\n\t\t&server,\n\t\t\"http://localhost/foo/search?query=hello+world\",\n\t\tSEARCH_RESPONSE\n\t);\n\ttest_post_response(\n\t\t&server,\n\t\t\"http://localhost/foo\",\n\t\tr#\"{\"data\":\"hello world\"}\"#,\n\t\tAPPLICATION_JSON,\n\t\tCREATE_RESPONSE\n\t);\n\ttest_put_response(\n\t\t&server,\n\t\t\"http://localhost/foo\",\n\t\tr#\"{\"data\":\"hello world\"}\"#,\n\t\tAPPLICATION_JSON,\n\t\tUPDATE_ALL_RESPONSE\n\t);\n\ttest_put_response(\n\t\t&server,\n\t\t\"http://localhost/foo/1\",\n\t\tr#\"{\"data\":\"hello world\"}\"#,\n\t\tAPPLICATION_JSON,\n\t\tUPDATE_RESPONSE\n\t);\n\ttest_delete_response(&server, \"http://localhost/foo\", DELETE_ALL_RESPONSE);\n\ttest_delete_response(&server, \"http://localhost/foo/1\", DELETE_RESPONSE);\n\ttest_get_response(\n\t\t&server,\n\t\t\"http://localhost/foo/state_test\",\n\t\tSTATE_TEST_RESPONSE\n\t);\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","cors_handling.rs"],"content":"#![cfg(feature = \"cors\")]\nuse gotham::{\n\thyper::{body::Body, client::connect::Connect, header::*, StatusCode},\n\tmime::TEXT_PLAIN,\n\tpipeline::{new_pipeline, single_pipeline},\n\trouter::build_router,\n\ttest::{Server, TestRequest, TestServer}\n};\nuse gotham_restful::{\n\tcors::{Headers, Origin},\n\tread_all, update_all, CorsConfig, DrawResources, Raw, Resource\n};\n\n#[derive(Resource)]\n#[resource(read_all, update_all)]\nstruct FooResource;\n\n#[read_all]\nfn read_all() {}\n\n#[update_all]\nfn update_all(_body: Raw>) {}\n\nfn test_server(cfg: CorsConfig) -> TestServer {\n\tlet (chain, pipeline) = single_pipeline(new_pipeline().add(cfg).build());\n\tTestServer::new(build_router(chain, pipeline, |router| {\n\t\trouter.resource::(\"/foo\")\n\t}))\n\t.unwrap()\n}\n\nfn test_response(\n\treq: TestRequest,\n\torigin: Option<&str>,\n\tvary: Option<&str>,\n\tcredentials: bool\n) where\n\tTS: Server + 'static,\n\tC: Connect + Clone + Send + Sync + 'static\n{\n\tlet res = req\n\t\t.with_header(ORIGIN, \"http://example.org\".parse().unwrap())\n\t\t.perform()\n\t\t.unwrap();\n\tassert_eq!(res.status(), StatusCode::NO_CONTENT);\n\tlet headers = res.headers();\n\tprintln!(\n\t\t\"{}\",\n\t\theaders\n\t\t\t.keys()\n\t\t\t.map(|name| name.as_str())\n\t\t\t.collect::>()\n\t\t\t.join(\",\")\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(ACCESS_CONTROL_ALLOW_ORIGIN)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.as_deref(),\n\t\torigin\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(VARY)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.as_deref(),\n\t\tvary\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(ACCESS_CONTROL_ALLOW_CREDENTIALS)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.map(|value| value == \"true\")\n\t\t\t.unwrap_or(false),\n\t\tcredentials\n\t);\n\tassert!(headers.get(ACCESS_CONTROL_MAX_AGE).is_none());\n}\n\nfn test_preflight(\n\tserver: &TestServer,\n\tmethod: &str,\n\torigin: Option<&str>,\n\tvary: &str,\n\tcredentials: bool,\n\tmax_age: u64\n) {\n\tlet res = server\n\t\t.client()\n\t\t.options(\"http://example.org/foo\")\n\t\t.with_header(ACCESS_CONTROL_REQUEST_METHOD, method.parse().unwrap())\n\t\t.with_header(ORIGIN, \"http://example.org\".parse().unwrap())\n\t\t.perform()\n\t\t.unwrap();\n\tassert_eq!(res.status(), StatusCode::NO_CONTENT);\n\tlet headers = res.headers();\n\tprintln!(\n\t\t\"{}\",\n\t\theaders\n\t\t\t.keys()\n\t\t\t.map(|name| name.as_str())\n\t\t\t.collect::>()\n\t\t\t.join(\",\")\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(ACCESS_CONTROL_ALLOW_METHODS)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.as_deref(),\n\t\tSome(method)\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(ACCESS_CONTROL_ALLOW_ORIGIN)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.as_deref(),\n\t\torigin\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(VARY)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.as_deref(),\n\t\tSome(vary)\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(ACCESS_CONTROL_ALLOW_CREDENTIALS)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.map(|value| value == \"true\")\n\t\t\t.unwrap_or(false),\n\t\tcredentials\n\t);\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(ACCESS_CONTROL_MAX_AGE)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.and_then(|value| value.parse().ok()),\n\t\tSome(max_age)\n\t);\n}\n\nfn test_preflight_headers(\n\tserver: &TestServer,\n\tmethod: &str,\n\trequest_headers: Option<&str>,\n\tallowed_headers: Option<&str>,\n\tvary: &str\n) {\n\tlet client = server.client();\n\tlet mut res = client\n\t\t.options(\"http://example.org/foo\")\n\t\t.with_header(ACCESS_CONTROL_REQUEST_METHOD, method.parse().unwrap())\n\t\t.with_header(ORIGIN, \"http://example.org\".parse().unwrap());\n\tif let Some(hdr) = request_headers {\n\t\tres = res.with_header(ACCESS_CONTROL_REQUEST_HEADERS, hdr.parse().unwrap());\n\t}\n\tlet res = res.perform().unwrap();\n\tassert_eq!(res.status(), StatusCode::NO_CONTENT);\n\tlet headers = res.headers();\n\tprintln!(\n\t\t\"{}\",\n\t\theaders\n\t\t\t.keys()\n\t\t\t.map(|name| name.as_str())\n\t\t\t.collect::>()\n\t\t\t.join(\",\")\n\t);\n\tif let Some(hdr) = allowed_headers {\n\t\tassert_eq!(\n\t\t\theaders\n\t\t\t\t.get(ACCESS_CONTROL_ALLOW_HEADERS)\n\t\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t\t.as_deref(),\n\t\t\tSome(hdr)\n\t\t)\n\t} else {\n\t\tassert!(!headers.contains_key(ACCESS_CONTROL_ALLOW_HEADERS));\n\t}\n\tassert_eq!(\n\t\theaders\n\t\t\t.get(VARY)\n\t\t\t.and_then(|value| value.to_str().ok())\n\t\t\t.as_deref(),\n\t\tSome(vary)\n\t);\n}\n\n#[test]\nfn cors_origin_none() {\n\tlet cfg = Default::default();\n\tlet server = test_server(cfg);\n\n\ttest_preflight(\n\t\t&server,\n\t\t\"PUT\",\n\t\tNone,\n\t\t\"access-control-request-method\",\n\t\tfalse,\n\t\t0\n\t);\n\n\ttest_response(\n\t\tserver.client().get(\"http://example.org/foo\"),\n\t\tNone,\n\t\tNone,\n\t\tfalse\n\t);\n\ttest_response(\n\t\tserver\n\t\t\t.client()\n\t\t\t.put(\"http://example.org/foo\", Body::empty(), TEXT_PLAIN),\n\t\tNone,\n\t\tNone,\n\t\tfalse\n\t);\n}\n\n#[test]\nfn cors_origin_star() {\n\tlet cfg = CorsConfig {\n\t\torigin: Origin::Star,\n\t\t..Default::default()\n\t};\n\tlet server = test_server(cfg);\n\n\ttest_preflight(\n\t\t&server,\n\t\t\"PUT\",\n\t\tSome(\"*\"),\n\t\t\"access-control-request-method\",\n\t\tfalse,\n\t\t0\n\t);\n\n\ttest_response(\n\t\tserver.client().get(\"http://example.org/foo\"),\n\t\tSome(\"*\"),\n\t\tNone,\n\t\tfalse\n\t);\n\ttest_response(\n\t\tserver\n\t\t\t.client()\n\t\t\t.put(\"http://example.org/foo\", Body::empty(), TEXT_PLAIN),\n\t\tSome(\"*\"),\n\t\tNone,\n\t\tfalse\n\t);\n}\n\n#[test]\nfn cors_origin_single() {\n\tlet cfg = CorsConfig {\n\t\torigin: Origin::Single(\"https://foo.com\".to_owned()),\n\t\t..Default::default()\n\t};\n\tlet server = test_server(cfg);\n\n\ttest_preflight(\n\t\t&server,\n\t\t\"PUT\",\n\t\tSome(\"https://foo.com\"),\n\t\t\"access-control-request-method\",\n\t\tfalse,\n\t\t0\n\t);\n\n\ttest_response(\n\t\tserver.client().get(\"http://example.org/foo\"),\n\t\tSome(\"https://foo.com\"),\n\t\tNone,\n\t\tfalse\n\t);\n\ttest_response(\n\t\tserver\n\t\t\t.client()\n\t\t\t.put(\"http://example.org/foo\", Body::empty(), TEXT_PLAIN),\n\t\tSome(\"https://foo.com\"),\n\t\tNone,\n\t\tfalse\n\t);\n}\n\n#[test]\nfn cors_origin_copy() {\n\tlet cfg = CorsConfig {\n\t\torigin: Origin::Copy,\n\t\t..Default::default()\n\t};\n\tlet server = test_server(cfg);\n\n\ttest_preflight(\n\t\t&server,\n\t\t\"PUT\",\n\t\tSome(\"http://example.org\"),\n\t\t\"access-control-request-method,origin\",\n\t\tfalse,\n\t\t0\n\t);\n\n\ttest_response(\n\t\tserver.client().get(\"http://example.org/foo\"),\n\t\tSome(\"http://example.org\"),\n\t\tSome(\"origin\"),\n\t\tfalse\n\t);\n\ttest_response(\n\t\tserver\n\t\t\t.client()\n\t\t\t.put(\"http://example.org/foo\", Body::empty(), TEXT_PLAIN),\n\t\tSome(\"http://example.org\"),\n\t\tSome(\"origin\"),\n\t\tfalse\n\t);\n}\n\n#[test]\nfn cors_headers_none() {\n\tlet cfg = Default::default();\n\tlet server = test_server(cfg);\n\n\ttest_preflight_headers(&server, \"PUT\", None, None, \"access-control-request-method\");\n\ttest_preflight_headers(\n\t\t&server,\n\t\t\"PUT\",\n\t\tSome(\"Content-Type\"),\n\t\tNone,\n\t\t\"access-control-request-method\"\n\t);\n}\n\n#[test]\nfn cors_headers_list() {\n\tlet cfg = CorsConfig {\n\t\theaders: Headers::List(vec![CONTENT_TYPE]),\n\t\t..Default::default()\n\t};\n\tlet server = test_server(cfg);\n\n\ttest_preflight_headers(\n\t\t&server,\n\t\t\"PUT\",\n\t\tNone,\n\t\tSome(\"content-type\"),\n\t\t\"access-control-request-method\"\n\t);\n\ttest_preflight_headers(\n\t\t&server,\n\t\t\"PUT\",\n\t\tSome(\"content-type\"),\n\t\tSome(\"content-type\"),\n\t\t\"access-control-request-method\"\n\t);\n}\n\n#[test]\nfn cors_headers_copy() {\n\tlet cfg = CorsConfig {\n\t\theaders: Headers::Copy,\n\t\t..Default::default()\n\t};\n\tlet server = test_server(cfg);\n\n\ttest_preflight_headers(\n\t\t&server,\n\t\t\"PUT\",\n\t\tNone,\n\t\tNone,\n\t\t\"access-control-request-method,access-control-request-headers\"\n\t);\n\ttest_preflight_headers(\n\t\t&server,\n\t\t\"PUT\",\n\t\tSome(\"content-type\"),\n\t\tSome(\"content-type\"),\n\t\t\"access-control-request-method,access-control-request-headers\"\n\t);\n}\n\n#[test]\nfn cors_credentials() {\n\tlet cfg = CorsConfig {\n\t\torigin: Origin::None,\n\t\tcredentials: true,\n\t\t..Default::default()\n\t};\n\tlet server = test_server(cfg);\n\n\ttest_preflight(\n\t\t&server,\n\t\t\"PUT\",\n\t\tNone,\n\t\t\"access-control-request-method\",\n\t\ttrue,\n\t\t0\n\t);\n\n\ttest_response(\n\t\tserver.client().get(\"http://example.org/foo\"),\n\t\tNone,\n\t\tNone,\n\t\ttrue\n\t);\n\ttest_response(\n\t\tserver\n\t\t\t.client()\n\t\t\t.put(\"http://example.org/foo\", Body::empty(), TEXT_PLAIN),\n\t\tNone,\n\t\tNone,\n\t\ttrue\n\t);\n}\n\n#[test]\nfn cors_max_age() {\n\tlet cfg = CorsConfig {\n\t\torigin: Origin::None,\n\t\tmax_age: 31536000,\n\t\t..Default::default()\n\t};\n\tlet server = test_server(cfg);\n\n\ttest_preflight(\n\t\t&server,\n\t\t\"PUT\",\n\t\tNone,\n\t\t\"access-control-request-method\",\n\t\tfalse,\n\t\t31536000\n\t);\n\n\ttest_response(\n\t\tserver.client().get(\"http://example.org/foo\"),\n\t\tNone,\n\t\tNone,\n\t\tfalse\n\t);\n\ttest_response(\n\t\tserver\n\t\t\t.client()\n\t\t\t.put(\"http://example.org/foo\", Body::empty(), TEXT_PLAIN),\n\t\tNone,\n\t\tNone,\n\t\tfalse\n\t);\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","custom_request_body.rs"],"content":"use gotham::{\n\thyper::header::CONTENT_TYPE,\n\tmime::{Mime, TEXT_PLAIN},\n\trouter::builder::*,\n\ttest::TestServer\n};\nuse gotham_restful::{create, DrawResources, FromBody, Raw, RequestBody, Resource};\n\nconst RESPONSE: &[u8] = b\"This is the only valid response.\";\n\n#[derive(Resource)]\n#[resource(create)]\nstruct FooResource;\n\n#[derive(FromBody, RequestBody)]\n#[supported_types(TEXT_PLAIN)]\nstruct Foo {\n\tcontent: Vec,\n\tcontent_type: Mime\n}\n\n#[create]\nfn create(body: Foo) -> Raw> {\n\tRaw::new(body.content, body.content_type)\n}\n\n#[test]\nfn custom_request_body() {\n\tlet server = TestServer::new(build_simple_router(|router| {\n\t\trouter.resource::(\"foo\");\n\t}))\n\t.unwrap();\n\n\tlet res = server\n\t\t.client()\n\t\t.post(\"http://localhost/foo\", RESPONSE, TEXT_PLAIN)\n\t\t.perform()\n\t\t.unwrap();\n\tassert_eq!(\n\t\tres.headers().get(CONTENT_TYPE).unwrap().to_str().unwrap(),\n\t\t\"text/plain\"\n\t);\n\tlet res = res.read_body().unwrap();\n\tlet body: &[u8] = res.as_ref();\n\tassert_eq!(body, RESPONSE);\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","openapi_specification.rs"],"content":"#![cfg(all(feature = \"auth\", feature = \"openapi\"))]\n#![allow(clippy::approx_constant)]\n\n#[macro_use]\nextern crate pretty_assertions;\n\nuse gotham::{\n\thyper::{Method, StatusCode},\n\tmime::{IMAGE_PNG, TEXT_PLAIN_UTF_8},\n\tpipeline::{new_pipeline, single_pipeline},\n\tprelude::*,\n\trouter::build_router,\n\ttest::TestServer\n};\nuse gotham_restful::*;\nuse openapi_type::{OpenapiSchema, OpenapiType, Visitor};\nuse serde::{Deserialize, Serialize};\n\n#[allow(dead_code)]\nmod util {\n\tinclude!(\"util/mod.rs\");\n}\nuse util::test_openapi_response;\n\nconst IMAGE_RESPONSE : &[u8] = b\"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUA/wA0XsCoAAAAAXRSTlN/gFy0ywAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=\";\n\n#[derive(Resource)]\n#[resource(get_image, set_image)]\nstruct ImageResource;\n\n#[derive(FromBody, RequestBody)]\n#[supported_types(IMAGE_PNG)]\nstruct Image(Vec);\n\n#[read(operation_id = \"getImage\")]\nfn get_image(_id: u64) -> Raw<&'static [u8]> {\n\tRaw::new(IMAGE_RESPONSE, \"image/png;base64\".parse().unwrap())\n}\n\n#[update(operation_id = \"setImage\")]\nfn set_image(_id: u64, _image: Image) {}\n\n#[derive(Resource)]\n#[resource(read_secret, search_secret)]\nstruct SecretResource;\n\n#[derive(Deserialize, Clone)]\nstruct AuthData {\n\tsub: String,\n\tiat: u64,\n\texp: u64\n}\n\ntype AuthStatus = gotham_restful::AuthStatus;\n\n#[derive(OpenapiType, Serialize)]\nstruct Secret {\n\tcode: f32\n}\n\n#[derive(OpenapiType, Serialize)]\nstruct Secrets {\n\tsecrets: Vec\n}\n\n#[derive(Clone, Deserialize, OpenapiType, StateData, StaticResponseExtender)]\nstruct SecretQuery {\n\tdate: String,\n\thour: Option,\n\tminute: Option\n}\n\n/// This endpoint gives access to the secret.\n///\n/// You need to be authenticated to call this endpoint.\n#[read]\nfn read_secret(auth: AuthStatus, _id: String) -> AuthSuccess {\n\tauth.ok()?;\n\tOk(Secret { code: 4.2 })\n}\n\n#[search]\nfn search_secret(auth: AuthStatus, _query: SecretQuery) -> AuthSuccess {\n\tauth.ok()?;\n\tOk(Secrets {\n\t\tsecrets: vec![Secret { code: 4.2 }, Secret { code: 3.14 }]\n\t})\n}\n\n#[derive(Resource)]\n#[resource(coffee_read_all)]\nstruct CoffeeResource;\n\nfn teapot_status_codes() -> Vec {\n\tvec![StatusCode::IM_A_TEAPOT]\n}\n\nfn teapot_schema(code: StatusCode) -> OpenapiSchema {\n\tassert_eq!(code, StatusCode::IM_A_TEAPOT);\n\n\tstruct Binary;\n\n\timpl OpenapiType for Binary {\n\t\tfn visit_type(visitor: &mut V) {\n\t\t\tvisitor.visit_binary();\n\t\t}\n\t}\n\n\tBinary::schema()\n}\n\n#[read_all(status_codes = \"teapot_status_codes\", schema = \"teapot_schema\")]\nfn coffee_read_all() -> Response {\n\tResponse::new(\n\t\tStatusCode::IM_A_TEAPOT,\n\t\t\"Sorry, this is just your fancy grandma's teapot. Can't make coffee.\",\n\t\tSome(TEXT_PLAIN_UTF_8)\n\t)\n}\n\n#[derive(Resource)]\n#[resource(custom_read_with, custom_patch)]\nstruct CustomResource;\n\n#[derive(Clone, Deserialize, OpenapiType, StateData, StaticResponseExtender)]\nstruct ReadWithPath {\n\tfrom: String,\n\tid: u64\n}\n\n#[endpoint(method = \"Method::GET\", uri = \"read/:from/with/:id\")]\nfn custom_read_with(_path: ReadWithPath) {}\n\n#[endpoint(method = \"Method::PATCH\", uri = \"\", body = true)]\nfn custom_patch(_body: String) {}\n\n#[test]\nfn openapi_specification() {\n\tlet info = OpenapiInfo {\n\t\ttitle: \"This is just a test\".to_owned(),\n\t\tversion: \"1.2.3\".to_owned(),\n\t\turls: vec![\"http://localhost:12345/api/v1\".to_owned()]\n\t};\n\tlet auth: AuthMiddleware = AuthMiddleware::new(\n\t\tAuthSource::AuthorizationHeader,\n\t\tAuthValidation::default(),\n\t\tStaticAuthHandler::from_array(b\"zlBsA2QXnkmpe0QTh8uCvtAEa4j33YAc\")\n\t);\n\tlet (chain, pipelines) = single_pipeline(new_pipeline().add(auth).build());\n\tlet server = TestServer::new(build_router(chain, pipelines, |router| {\n\t\trouter.with_openapi(info, |mut router| {\n\t\t\t// the leading slash tests that the spec doesn't contain '//img' nonsense\n\t\t\trouter.resource::(\"/img\");\n\t\t\trouter.resource::(\"secret\");\n\t\t\trouter.resource::(\"coffee\");\n\t\t\trouter.resource::(\"custom\");\n\t\t\trouter.openapi_spec(\"openapi\");\n\t\t});\n\t}))\n\t.unwrap();\n\n\ttest_openapi_response(\n\t\t&server,\n\t\t\"http://localhost/openapi\",\n\t\t\"tests/openapi_specification.json\"\n\t);\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","openapi_supports_scope.rs"],"content":"#![cfg(feature = \"openapi\")]\n\n#[macro_use]\nextern crate pretty_assertions;\n\nuse gotham::{mime::TEXT_PLAIN, router::builder::*, test::TestServer};\nuse gotham_restful::*;\n\n#[allow(dead_code)]\nmod util {\n\tinclude!(\"util/mod.rs\");\n}\nuse util::{test_get_response, test_openapi_response};\n\nconst RESPONSE: &[u8] = b\"This is the only valid response.\";\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all]\nfn read_all() -> Raw<&'static [u8]> {\n\tRaw::new(RESPONSE, TEXT_PLAIN)\n}\n\n#[test]\nfn openapi_supports_scope() {\n\tlet info = OpenapiInfo {\n\t\ttitle: \"Test\".to_owned(),\n\t\tversion: \"1.2.3\".to_owned(),\n\t\turls: Vec::new()\n\t};\n\tlet server = TestServer::new(build_simple_router(|router| {\n\t\trouter.with_openapi(info, |mut router| {\n\t\t\trouter.openapi_spec(\"openapi\");\n\t\t\trouter.resource::(\"foo1\");\n\t\t\trouter.scope(\"/bar\", |router| {\n\t\t\t\trouter.resource::(\"foo2\");\n\t\t\t\trouter.scope(\"/baz\", |router| {\n\t\t\t\t\trouter.resource::(\"foo3\");\n\t\t\t\t})\n\t\t\t});\n\t\t\trouter.resource::(\"foo4\");\n\t\t});\n\t}))\n\t.unwrap();\n\n\ttest_get_response(&server, \"http://localhost/foo1\", RESPONSE);\n\ttest_get_response(&server, \"http://localhost/bar/foo2\", RESPONSE);\n\ttest_get_response(&server, \"http://localhost/bar/baz/foo3\", RESPONSE);\n\ttest_get_response(&server, \"http://localhost/foo4\", RESPONSE);\n\ttest_openapi_response(\n\t\t&server,\n\t\t\"http://localhost/openapi\",\n\t\t\"tests/openapi_supports_scope.json\"\n\t);\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","resource_error.rs"],"content":"use gotham_restful::ResourceError;\n\n#[derive(ResourceError)]\nenum Error {\n\t#[display(\"I/O Error: {0}\")]\n\tIoError(#[from] std::io::Error),\n\n\t#[status(INTERNAL_SERVER_ERROR)]\n\t#[display(\"Internal Server Error: {0}\")]\n\tInternalServerError(String)\n}\n\n#[allow(deprecated)]\nmod resource_error {\n\tuse super::Error;\n\tuse gotham::{hyper::StatusCode, mime::APPLICATION_JSON};\n\tuse gotham_restful::IntoResponseError;\n\n\t#[test]\n\tfn io_error() {\n\t\tlet err = Error::IoError(std::io::Error::last_os_error());\n\t\tlet res = err.into_response_error().unwrap();\n\t\tassert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);\n\t\tassert_eq!(res.mime(), Some(&APPLICATION_JSON));\n\t}\n\n\t#[test]\n\tfn internal_server_error() {\n\t\tlet err = Error::InternalServerError(\"Brocken\".to_owned());\n\t\tassert_eq!(&format!(\"{err}\"), \"Internal Server Error: Brocken\");\n\n\t\tlet res = err.into_response_error().unwrap();\n\t\tassert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);\n\t\tassert_eq!(res.mime(), None); // TODO shouldn't this be a json error message?\n\t}\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","sync_methods.rs"],"content":"use gotham::{\n\tmime::{APPLICATION_JSON, TEXT_PLAIN},\n\tprelude::*,\n\trouter::build_simple_router,\n\ttest::TestServer\n};\nuse gotham_restful::*;\n#[cfg(feature = \"openapi\")]\nuse openapi_type::OpenapiType;\nuse serde::Deserialize;\nuse simple_logger::SimpleLogger;\n\nmod util {\n\tinclude!(\"util/mod.rs\");\n}\nuse util::{test_delete_response, test_get_response, test_post_response, test_put_response};\n\n#[derive(Resource)]\n#[resource(read_all, read, search, create, update_all, update, delete_all, delete)]\nstruct FooResource;\n\n#[derive(Deserialize)]\n#[cfg_attr(feature = \"openapi\", derive(OpenapiType))]\n#[allow(dead_code)]\nstruct FooBody {\n\tdata: String\n}\n\n#[derive(Clone, Deserialize, StateData, StaticResponseExtender)]\n#[cfg_attr(feature = \"openapi\", derive(OpenapiType))]\n#[allow(dead_code)]\nstruct FooSearch {\n\tquery: String\n}\n\nconst READ_ALL_RESPONSE: &[u8] = b\"1ARwwSPVyOKpJKrYwqGgECPVWDl1BqajAAj7g7WJ3e\";\n#[read_all]\nfn read_all() -> Raw<&'static [u8]> {\n\tRaw::new(READ_ALL_RESPONSE, TEXT_PLAIN)\n}\n\nconst READ_RESPONSE: &[u8] = b\"FEReHoeBKU17X2bBpVAd1iUvktFL43CDu0cFYHdaP9\";\n#[read]\nfn read(_id: u64) -> Raw<&'static [u8]> {\n\tRaw::new(READ_RESPONSE, TEXT_PLAIN)\n}\n\nconst SEARCH_RESPONSE: &[u8] = b\"AWqcQUdBRHXKh3at4u79mdupOAfEbnTcx71ogCVF0E\";\n#[search]\nfn search(_body: FooSearch) -> Raw<&'static [u8]> {\n\tRaw::new(SEARCH_RESPONSE, TEXT_PLAIN)\n}\n\nconst CREATE_RESPONSE: &[u8] = b\"y6POY7wOMAB0jBRBw0FJT7DOpUNbhmT8KdpQPLkI83\";\n#[create]\nfn create(_body: FooBody) -> Raw<&'static [u8]> {\n\tRaw::new(CREATE_RESPONSE, TEXT_PLAIN)\n}\n\nconst UPDATE_ALL_RESPONSE: &[u8] = b\"QlbYg8gHE9OQvvk3yKjXJLTSXlIrg9mcqhfMXJmQkv\";\n#[update_all]\nfn update_all(_body: FooBody) -> Raw<&'static [u8]> {\n\tRaw::new(UPDATE_ALL_RESPONSE, TEXT_PLAIN)\n}\n\nconst UPDATE_RESPONSE: &[u8] = b\"qGod55RUXkT1lgPO8h0uVM6l368O2S0GrwENZFFuRu\";\n#[update]\nfn update(_id: u64, _body: FooBody) -> Raw<&'static [u8]> {\n\tRaw::new(UPDATE_RESPONSE, TEXT_PLAIN)\n}\n\nconst DELETE_ALL_RESPONSE: &[u8] = b\"Y36kZ749MRk2Nem4BedJABOZiZWPLOtiwLfJlGTwm5\";\n#[delete_all]\nfn delete_all() -> Raw<&'static [u8]> {\n\tRaw::new(DELETE_ALL_RESPONSE, TEXT_PLAIN)\n}\n\nconst DELETE_RESPONSE: &[u8] = b\"CwRzBrKErsVZ1N7yeNfjZuUn1MacvgBqk4uPOFfDDq\";\n#[delete]\nfn delete(_id: u64) -> Raw<&'static [u8]> {\n\tRaw::new(DELETE_RESPONSE, TEXT_PLAIN)\n}\n\n#[test]\nfn sync_methods() {\n\t// TODO no idea why with_local_timestamps fails here\n\t_ = SimpleLogger::new().env().with_utc_timestamps().init();\n\n\tlet server = TestServer::new(build_simple_router(|router| {\n\t\trouter.resource::(\"foo\");\n\t}))\n\t.unwrap();\n\n\ttest_get_response(&server, \"http://localhost/foo\", READ_ALL_RESPONSE);\n\ttest_get_response(&server, \"http://localhost/foo/1\", READ_RESPONSE);\n\ttest_get_response(\n\t\t&server,\n\t\t\"http://localhost/foo/search?query=hello+world\",\n\t\tSEARCH_RESPONSE\n\t);\n\ttest_post_response(\n\t\t&server,\n\t\t\"http://localhost/foo\",\n\t\tr#\"{\"data\":\"hello world\"}\"#,\n\t\tAPPLICATION_JSON,\n\t\tCREATE_RESPONSE\n\t);\n\ttest_put_response(\n\t\t&server,\n\t\t\"http://localhost/foo\",\n\t\tr#\"{\"data\":\"hello world\"}\"#,\n\t\tAPPLICATION_JSON,\n\t\tUPDATE_ALL_RESPONSE\n\t);\n\ttest_put_response(\n\t\t&server,\n\t\t\"http://localhost/foo/1\",\n\t\tr#\"{\"data\":\"hello world\"}\"#,\n\t\tAPPLICATION_JSON,\n\t\tUPDATE_RESPONSE\n\t);\n\ttest_delete_response(&server, \"http://localhost/foo\", DELETE_ALL_RESPONSE);\n\ttest_delete_response(&server, \"http://localhost/foo/1\", DELETE_RESPONSE);\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","trybuild_ui.rs"],"content":"use trybuild::TestCases;\n\n#[test]\n#[ignore]\nfn trybuild_ui() {\n\tlet t = TestCases::new();\n\tt.compile_fail(\"tests/ui/endpoint/*.rs\");\n\tt.compile_fail(\"tests/ui/from_body/*.rs\");\n\tt.compile_fail(\"tests/ui/resource/*.rs\");\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","async_state.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\nuse gotham::state::State;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all]\nasync fn read_all(state: &State) {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","auth_data_non_clone.rs"],"content":"use gotham_restful::*;\nuse serde::Deserialize;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[derive(Deserialize)]\nstruct AuthData {\n\tiat: u64,\n\texp: u64\n}\n\n#[read_all]\nasync fn read_all(auth: AuthStatus) -> Result {\n\tauth.ok()?;\n\tOk(NoContent::default())\n}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","custom_method_invalid_expr.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[endpoint(method = \"I like pizza\", uri = \"custom_read\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","custom_method_invalid_type.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[endpoint(method = \"String::new()\", uri = \"custom_read\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","custom_method_missing.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[endpoint(uri = \"custom_read\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","custom_uri_missing.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[endpoint(method = \"gotham_restful::gotham::hyper::Method::GET\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","dynamic_schema_missing_schema.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\nuse gotham::hyper::StatusCode;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\nfn status_codes() -> Vec {\n\tunimplemented!()\n}\n\n#[read_all(status_codes = \"status_codes\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","dynamic_schema_missing_status_codes.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\nuse gotham::hyper::StatusCode;\nuse gotham_restful::private::OpenapiSchema;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\nfn schema(_: StatusCode) -> OpenapiSchema {\n\tunimplemented!()\n}\n\n#[read_all(schema = \"schema\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","dynamic_schema_wrong_type.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\nfn schema(_: u16) -> String {\n\tunimplemented!()\n}\n\nfn status_codes() -> Vec {\n\tunimplemented!()\n}\n\n#[read_all(schema = \"schema\", status_codes = \"status_codes\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","invalid_attribute.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all(FooResource)]\nfn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","invalid_body_ty.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\nuse gotham_restful::gotham::hyper::Method;\n\n#[derive(Resource)]\n#[resource(endpoint)]\nstruct FooResource;\n\n#[derive(Debug)]\nstruct FooBody {\n\tfoo: String\n}\n\n#[endpoint(method = \"Method::GET\", uri = \"\", body = true)]\nfn endpoint(_: FooBody) {\n\tunimplemented!()\n}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","invalid_params_ty.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\nuse gotham_restful::gotham::hyper::Method;\n\n#[derive(Resource)]\n#[resource(endpoint)]\nstruct FooResource;\n\n#[derive(Debug)]\nstruct FooParams {\n\tfoo: String\n}\n\n#[endpoint(method = \"Method::GET\", uri = \"\", params = true)]\nfn endpoint(_: FooParams) {\n\tunimplemented!()\n}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","invalid_placeholders_ty.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\nuse gotham_restful::gotham::hyper::Method;\n\n#[derive(Resource)]\n#[resource(endpoint)]\nstruct FooResource;\n\n#[derive(Debug)]\nstruct FooPlaceholders {\n\tfoo: String\n}\n\n#[endpoint(method = \"Method::GET\", uri = \":foo\")]\nfn endpoint(_: FooPlaceholders) {\n\tunimplemented!()\n}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","invalid_return_type.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\nuse gotham_restful::gotham::hyper::Method;\n\n#[derive(Resource)]\n#[resource(endpoint)]\nstruct FooResource;\n\nstruct FooResponse;\n\n#[endpoint(method = \"Method::GET\", uri = \"\")]\nfn endpoint() -> FooResponse {\n\tunimplemented!()\n}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","non_custom_body_attribute.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all(body = false)]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","non_custom_method_attribute.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all(method = \"gotham_restful::gotham::hyper::Method::GET\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","non_custom_params_attribute.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all(params = true)]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","non_custom_uri_attribute.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all(uri = \"custom_read\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","self.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all]\nfn read_all(self) {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","too_few_args.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read)]\nstruct FooResource;\n\n#[read]\nfn read() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","too_many_args.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all]\nfn read_all(_id: u64) {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","unknown_attribute.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all(pineapple = \"on pizza\")]\nfn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","unsafe.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all]\nunsafe fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","endpoint","wants_auth_non_bool.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_all)]\nstruct FooResource;\n\n#[read_all(wants_auth = \"yes, please\")]\nasync fn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","from_body","enum.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(FromBody)]\nenum FromBodyEnum {\n\tSomeVariant(Vec),\n\tOtherVariant(String)\n}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","ui","resource","unknown_method.rs"],"content":"#[macro_use]\nextern crate gotham_restful;\n\n#[derive(Resource)]\n#[resource(read_any)]\nstruct FooResource;\n\n#[read_all]\nfn read_all() {}\n\nfn main() {}\n","traces":[],"covered":0,"coverable":0},{"path":["/","home","runner","work","gotham_restful","gotham_restful","tests","util","mod.rs"],"content":"use gotham::{\n\thyper::Body,\n\ttest::TestServer\n};\nuse log::info;\nuse gotham::mime::Mime;\n#[allow(unused_imports)]\nuse std::{fs::File, io::{Read, Write}, str};\n\npub fn test_get_response(server : &TestServer, path : &str, expected : &[u8])\n{\n\tinfo!(\"GET {path}\");\n\tlet res = server.client().get(path).perform().unwrap().read_body().unwrap();\n\tlet body : &[u8] = res.as_ref();\n\tassert_eq!(body, expected);\n}\n\npub fn test_post_response(server : &TestServer, path : &str, body : B, mime : Mime, expected : &[u8])\nwhere\n\tB : Into\n{\n\tinfo!(\"POST {path}\");\n\tlet res = server.client().post(path, body, mime).perform().unwrap().read_body().unwrap();\n\tlet body : &[u8] = res.as_ref();\n\tassert_eq!(body, expected);\n}\n\npub fn test_put_response(server : &TestServer, path : &str, body : B, mime : Mime, expected : &[u8])\nwhere\n\tB : Into\n{\n\tinfo!(\"PUT {path}\");\n\tlet res = server.client().put(path, body, mime).perform().unwrap().read_body().unwrap();\n\tlet body : &[u8] = res.as_ref();\n\tassert_eq!(body, expected);\n}\n\npub fn test_delete_response(server : &TestServer, path : &str, expected : &[u8])\n{\n\tinfo!(\"DELETE {path}\");\n\tlet res = server.client().delete(path).perform().unwrap().read_body().unwrap();\n\tlet body : &[u8] = res.as_ref();\n\tassert_eq!(body, expected);\n}\n\n#[cfg(feature = \"openapi\")]\npub fn test_openapi_response(server : &TestServer, path : &str, output_file : &str)\n{\n\tinfo!(\"GET {path}\");\n\tlet res = server.client().get(path).perform().unwrap().read_body().unwrap();\n\tlet body: serde_json::Value = serde_json::from_slice(&res).unwrap();\n\n\tlet mut file = File::open(output_file).unwrap();\n\tlet expected: serde_json::Value = serde_json::from_reader(&mut file).unwrap();\n\n\t//eprintln!(\"{body}\");\n\tassert_eq!(body, expected);\n}\n","traces":[],"covered":0,"coverable":0}],"coverage":73.21178120617111,"covered":522,"coverable":713} \ No newline at end of file