diff --git a/doc/.timestamp b/doc/.timestamp index ce7798402c..514435aa51 100644 --- a/doc/.timestamp +++ b/doc/.timestamp @@ -1 +1 @@ -Mon May 13 14:56:58 UTC 2024 +Mon May 13 15:00:04 UTC 2024 diff --git a/tarpaulin-report.html b/tarpaulin-report.html index 3896c4eba4..f902a29b91 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":[7008563,7007312],"length":1,"stats":{"Line":0},"fn_name":"html"},{"line":21,"address":[7007369,7007437],"length":1,"stats":{"Line":0},"fn_name":null},{"line":23,"address":[7991024,7991051],"length":1,"stats":{"Line":0},"fn_name":"{closure#0}"},{"line":24,"address":[7008677],"length":1,"stats":{"Line":0},"fn_name":null},{"line":25,"address":[7991143],"length":1,"stats":{"Line":0},"fn_name":null},{"line":26,"address":[7991177],"length":1,"stats":{"Line":0},"fn_name":null},{"line":27,"address":[7991084],"length":1,"stats":{"Line":0},"fn_name":null},{"line":31,"address":[7007342,7007510],"length":1,"stats":{"Line":0},"fn_name":null},{"line":32,"address":[7989969],"length":1,"stats":{"Line":0},"fn_name":null},{"line":33,"address":[6585424],"length":1,"stats":{"Line":0},"fn_name":null},{"line":34,"address":[7007635],"length":1,"stats":{"Line":0},"fn_name":null},{"line":36,"address":[7007808],"length":1,"stats":{"Line":0},"fn_name":null},{"line":37,"address":[7008123],"length":1,"stats":{"Line":0},"fn_name":null}],"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},"fn_name":null},{"line":49,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":50,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":51,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":52,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":53,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":60,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":61,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":62,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":63,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":64,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":118,"address":[6224848],"length":1,"stats":{"Line":3},"fn_name":null},{"line":122,"address":[6224880],"length":1,"stats":{"Line":2},"fn_name":null},{"line":123,"address":[6224903],"length":1,"stats":{"Line":2},"fn_name":null},{"line":128,"address":[6224944,6224989],"length":1,"stats":{"Line":1},"fn_name":"jwt_secret>"},{"line":133,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":188,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":190,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":191,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":192,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":193,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":203,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":206,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":207,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":208,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":218,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":223,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":227,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":229,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":230,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":231,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":232,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":233,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":234,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":235,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":237,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":238,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":239,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":240,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":241,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":242,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":243,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":244,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":245,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":249,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":250,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":251,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":255,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":256,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":257,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":258,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":262,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":263,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":264,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":268,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":269,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":270,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":271,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":273,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":274,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":278,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":287,"address":[7650640,7652304,7652681,7652718,7652736,7653535,7651023,7650607,7651439,7650224,7651888,7650191,7649808,7651472,7651855,7653119,7651056,7653152,7652271],"length":1,"stats":{"Line":1},"fn_name":"call, gotham::router::route::matcher::access_control_request_method::AccessControlRequestMethodMatcher>, (borrow_bag::handle::Handle, ())>, borrow_bag::handle::Take>, ()), (gotham::pipeline::Pipeline<(gotham_restful::auth::AuthMiddleware, ())>, ()), gotham::extractor::path::NoopPathExtractor, gotham::extractor::query_string::NoopQueryStringExtractor, fn(gotham::state::State) -> (gotham::state::State, http::response::Response)>, (borrow_bag::handle::Handle, ())>, borrow_bag::handle::Take>, ()), (gotham::pipeline::Pipeline<(gotham_restful::auth::AuthMiddleware, ())>, ())>>"},{"line":292,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":295,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":296,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":299,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":300,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":311,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":312,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":313,"address":[],"length":0,"stats":{"Line":1},"fn_name":null}],"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":[6313184],"length":1,"stats":{"Line":2},"fn_name":"default"},{"line":38,"address":[6313187],"length":1,"stats":{"Line":1},"fn_name":null},{"line":44,"address":[6313216],"length":1,"stats":{"Line":1},"fn_name":null},{"line":45,"address":[6313259],"length":1,"stats":{"Line":3},"fn_name":null},{"line":46,"address":[6313324],"length":1,"stats":{"Line":1},"fn_name":null},{"line":47,"address":[6313333,6313491],"length":1,"stats":{"Line":2},"fn_name":null},{"line":48,"address":[6313687,6313516],"length":1,"stats":{"Line":2},"fn_name":null},{"line":50,"address":[6313712],"length":1,"stats":{"Line":1},"fn_name":null},{"line":51,"address":[6313729],"length":1,"stats":{"Line":1},"fn_name":null},{"line":57,"address":[6313840],"length":1,"stats":{"Line":1},"fn_name":null},{"line":58,"address":[6313845],"length":1,"stats":{"Line":3},"fn_name":null},{"line":77,"address":[6313888],"length":1,"stats":{"Line":3},"fn_name":"default"},{"line":78,"address":[6313891],"length":1,"stats":{"Line":1},"fn_name":null},{"line":84,"address":[6314513,6313920],"length":1,"stats":{"Line":1},"fn_name":null},{"line":85,"address":[6313963],"length":1,"stats":{"Line":3},"fn_name":null},{"line":86,"address":[6314029],"length":1,"stats":{"Line":2},"fn_name":null},{"line":87,"address":[6314237,6314479,6314043],"length":1,"stats":{"Line":2},"fn_name":null},{"line":89,"address":[6314120],"length":1,"stats":{"Line":1},"fn_name":null},{"line":90,"address":[6314137],"length":1,"stats":{"Line":1},"fn_name":null},{"line":96,"address":[6314544],"length":1,"stats":{"Line":2},"fn_name":null},{"line":97,"address":[6314549],"length":1,"stats":{"Line":1},"fn_name":null},{"line":174,"address":[6667510,6667692,6667728,6667900,6667926,6667312,6667520,6667484,6667718],"length":1,"stats":{"Line":5},"fn_name":"call, gotham_restful::routing::MaybeMatchAcceptHeader>, (borrow_bag::handle::Handle, borrow_bag::handle::Take>, ()), (gotham::pipeline::Pipeline<(gotham_restful::cors::CorsConfig, ())>, ()), gotham_restful::endpoint::NoopExtractor, gotham_restful::endpoint::NoopExtractor, fn(&mut gotham::state::State) -> gotham_restful::routing::endpoint_handler::{async_fn_env#0}>, (borrow_bag::handle::Handle, borrow_bag::handle::Take>, ()), (gotham::pipeline::Pipeline<(gotham_restful::cors::CorsConfig, ())>, ())>>"},{"line":178,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":179,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":191,"address":[6314592,6315760,6315792],"length":1,"stats":{"Line":7},"fn_name":"handle_cors"},{"line":192,"address":[6314625],"length":1,"stats":{"Line":5},"fn_name":null},{"line":193,"address":[6314659],"length":1,"stats":{"Line":7},"fn_name":null},{"line":194,"address":[6314708],"length":1,"stats":{"Line":1},"fn_name":null},{"line":197,"address":[6314737,6314797],"length":1,"stats":{"Line":4},"fn_name":null},{"line":198,"address":[6314833],"length":1,"stats":{"Line":1},"fn_name":null},{"line":202,"address":[6314985],"length":1,"stats":{"Line":3},"fn_name":null},{"line":203,"address":[6315068],"length":1,"stats":{"Line":1},"fn_name":null},{"line":204,"address":[6315038],"length":1,"stats":{"Line":1},"fn_name":null},{"line":205,"address":[6315824,6315859,6316092,6316022],"length":1,"stats":{"Line":4},"fn_name":"{closure#0}"},{"line":206,"address":[6315801,6315111,6315453],"length":1,"stats":{"Line":2},"fn_name":null},{"line":210,"address":[6315018],"length":1,"stats":{"Line":1},"fn_name":null},{"line":211,"address":[6315690],"length":1,"stats":{"Line":1},"fn_name":null},{"line":212,"address":[6315586],"length":1,"stats":{"Line":1},"fn_name":null},{"line":213,"address":[6315624],"length":1,"stats":{"Line":1},"fn_name":null},{"line":247,"address":[6316176,6318334,6317592],"length":1,"stats":{"Line":3},"fn_name":"cors_preflight_handler"},{"line":248,"address":[6316338,6316218],"length":1,"stats":{"Line":4},"fn_name":null},{"line":251,"address":[6316346],"length":1,"stats":{"Line":1},"fn_name":null},{"line":252,"address":[6316378,6316454],"length":1,"stats":{"Line":4},"fn_name":null},{"line":253,"address":[6316470],"length":1,"stats":{"Line":3},"fn_name":null},{"line":256,"address":[6316573,6316485,6316685],"length":1,"stats":{"Line":5},"fn_name":null},{"line":257,"address":[6316543],"length":1,"stats":{"Line":3},"fn_name":null},{"line":260,"address":[6316700],"length":1,"stats":{"Line":1},"fn_name":null},{"line":261,"address":[6316784],"length":1,"stats":{"Line":3},"fn_name":null},{"line":263,"address":[6316837],"length":1,"stats":{"Line":1},"fn_name":null},{"line":265,"address":[6316976,6316903],"length":1,"stats":{"Line":5},"fn_name":null},{"line":266,"address":[6317077,6317169],"length":1,"stats":{"Line":2},"fn_name":null},{"line":270,"address":[6317245,6317211],"length":1,"stats":{"Line":3},"fn_name":null},{"line":271,"address":[6317276],"length":1,"stats":{"Line":1},"fn_name":null},{"line":275,"address":[6317341,6318368,6317251,6318373],"length":1,"stats":{"Line":8},"fn_name":"{closure#0}"},{"line":276,"address":[6317388],"length":1,"stats":{"Line":3},"fn_name":null},{"line":281,"address":[6316910,6317601,6317953,6318290],"length":1,"stats":{"Line":4},"fn_name":null},{"line":283,"address":[6318102],"length":1,"stats":{"Line":1},"fn_name":null},{"line":284,"address":[6318117],"length":1,"stats":{"Line":2},"fn_name":null},{"line":293,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":294,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":295,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":296,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":297,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"covered":58,"coverable":63},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","endpoint.rs"],"content":"use 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() -> Option {\n\t\tNone\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":20,"address":[7059216,7059232],"length":1,"stats":{"Line":10},"fn_name":"deserialize>, gotham::extractor::internal::from_query_string_mapping::{closure_env#0}>, &alloc::vec::Vec, gotham::helpers::http::FormUrlDecoded>>>"},{"line":21,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":27,"address":[6318704],"length":1,"stats":{"Line":0},"fn_name":"visit_type"},{"line":28,"address":[6318785,6318717],"length":1,"stats":{"Line":0},"fn_name":null},{"line":29,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":31,"address":[7633296],"length":1,"stats":{"Line":0},"fn_name":null},{"line":39,"address":[7633418,7633408],"length":1,"stats":{"Line":0},"fn_name":"extend"},{"line":60,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":61,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":69,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":70,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":78,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":79,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":85,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":92,"address":[7027968],"length":1,"stats":{"Line":6},"fn_name":"operation_id"},{"line":93,"address":[7027971],"length":1,"stats":{"Line":6},"fn_name":null},{"line":99,"address":[7027936],"length":1,"stats":{"Line":7},"fn_name":"description"},{"line":100,"address":[7027939],"length":1,"stats":{"Line":7},"fn_name":null},{"line":114,"address":[],"length":0,"stats":{"Line":31},"fn_name":null},{"line":115,"address":[],"length":0,"stats":{"Line":29},"fn_name":null},{"line":117,"address":[],"length":0,"stats":{"Line":30},"fn_name":null},{"line":118,"address":[],"length":0,"stats":{"Line":29},"fn_name":null},{"line":123,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":124,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":128,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":129,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":133,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":134,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":138,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":139,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":142,"address":[7028688],"length":1,"stats":{"Line":21},"fn_name":"handle"},{"line":148,"address":[],"length":0,"stats":{"Line":21},"fn_name":null}],"covered":14,"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, 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":[16206189],"length":1,"stats":{"Line":0},"fn_name":null},{"line":464,"address":[15783990],"length":1,"stats":{"Line":0},"fn_name":null},{"line":466,"address":[14578997],"length":1,"stats":{"Line":2},"fn_name":null},{"line":467,"address":[19133984],"length":1,"stats":{"Line":2},"fn_name":"eq"},{"line":508,"address":[5589451],"length":1,"stats":{"Line":14},"fn_name":null},{"line":512,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":518,"address":[1888289,1888299,1888324,1888086,1887978,1888070,1888060],"length":1,"stats":{"Line":0},"fn_name":null},{"line":523,"address":[16209120],"length":1,"stats":{"Line":8},"fn_name":null},{"line":528,"address":[5589744],"length":1,"stats":{"Line":9},"fn_name":null},{"line":529,"address":[5589800],"length":1,"stats":{"Line":9},"fn_name":null},{"line":536,"address":[10474864],"length":1,"stats":{"Line":0},"fn_name":null},{"line":541,"address":[15625216],"length":1,"stats":{"Line":0},"fn_name":"next"},{"line":546,"address":[4949133],"length":1,"stats":{"Line":1},"fn_name":null},{"line":547,"address":[4949166],"length":1,"stats":{"Line":1},"fn_name":null},{"line":595,"address":[7906528],"length":1,"stats":{"Line":0},"fn_name":"blink"}],"covered":8,"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":[6286512,6288345,6288150],"length":1,"stats":{"Line":4},"fn_name":null},{"line":28,"address":[6480843,6481673,6482168],"length":1,"stats":{"Line":12},"fn_name":null},{"line":50,"address":[6483769,6483088,6483726],"length":1,"stats":{"Line":2},"fn_name":null},{"line":51,"address":[6483211],"length":1,"stats":{"Line":2},"fn_name":null},{"line":52,"address":[6483245,6483321],"length":1,"stats":{"Line":4},"fn_name":null},{"line":53,"address":[6483428],"length":1,"stats":{"Line":1},"fn_name":null},{"line":54,"address":[6289065,6289172],"length":1,"stats":{"Line":4},"fn_name":null},{"line":58,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":60,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":63,"address":[6290718,6289472,6290555],"length":1,"stats":{"Line":2},"fn_name":null},{"line":64,"address":[6483913],"length":1,"stats":{"Line":2},"fn_name":null},{"line":66,"address":[6484024],"length":1,"stats":{"Line":2},"fn_name":null},{"line":67,"address":[6484153,6484080],"length":1,"stats":{"Line":4},"fn_name":null},{"line":68,"address":[6484240],"length":1,"stats":{"Line":2},"fn_name":null},{"line":69,"address":[6484986,6484248],"length":1,"stats":{"Line":4},"fn_name":null},{"line":72,"address":[6484209],"length":1,"stats":{"Line":2},"fn_name":null},{"line":73,"address":[6484410,6484605],"length":1,"stats":{"Line":4},"fn_name":null},{"line":74,"address":[6484624,6484933],"length":1,"stats":{"Line":2},"fn_name":null},{"line":79,"address":[6291381,6290752],"length":1,"stats":{"Line":3},"fn_name":null},{"line":80,"address":[6290798,6291408,6291443],"length":1,"stats":{"Line":7},"fn_name":"{closure#0}"},{"line":81,"address":[6291007,6291335,6290843],"length":1,"stats":{"Line":7},"fn_name":null},{"line":82,"address":[6291060,6291108],"length":1,"stats":{"Line":4},"fn_name":null},{"line":83,"address":[6291116],"length":1,"stats":{"Line":2},"fn_name":null},{"line":84,"address":[6291193,6291276],"length":1,"stats":{"Line":4},"fn_name":null},{"line":89,"address":[6291472,6292235,6292291],"length":1,"stats":{"Line":3},"fn_name":null},{"line":90,"address":[6291619,6291515],"length":1,"stats":{"Line":6},"fn_name":null},{"line":91,"address":[6291675],"length":1,"stats":{"Line":1},"fn_name":null},{"line":93,"address":[6291899],"length":1,"stats":{"Line":1},"fn_name":null},{"line":95,"address":[6292020],"length":1,"stats":{"Line":1},"fn_name":null},{"line":96,"address":[6292166],"length":1,"stats":{"Line":1},"fn_name":null},{"line":99,"address":[6291661],"length":1,"stats":{"Line":3},"fn_name":null},{"line":100,"address":[6291780],"length":1,"stats":{"Line":3},"fn_name":null}],"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":[6318991,6319019,6318384],"length":1,"stats":{"Line":2},"fn_name":"get_security"},{"line":33,"address":[6318558,6318437],"length":1,"stats":{"Line":2},"fn_name":null},{"line":34,"address":[6318494],"length":1,"stats":{"Line":1},"fn_name":null},{"line":35,"address":[6318486],"length":1,"stats":{"Line":1},"fn_name":null},{"line":38,"address":[6318512],"length":1,"stats":{"Line":1},"fn_name":null},{"line":41,"address":[6318593],"length":1,"stats":{"Line":0},"fn_name":null},{"line":43,"address":[6318616],"length":1,"stats":{"Line":0},"fn_name":null},{"line":47,"address":[6318671],"length":1,"stats":{"Line":0},"fn_name":null},{"line":49,"address":[6318694],"length":1,"stats":{"Line":0},"fn_name":null},{"line":52,"address":[6318716],"length":1,"stats":{"Line":1},"fn_name":null},{"line":53,"address":[6318741,6319301],"length":1,"stats":{"Line":2},"fn_name":null},{"line":55,"address":[6319351],"length":1,"stats":{"Line":1},"fn_name":null},{"line":59,"address":[6318969],"length":1,"stats":{"Line":1},"fn_name":null},{"line":60,"address":[6319648,6319716],"length":1,"stats":{"Line":2},"fn_name":null},{"line":62,"address":[6319852],"length":1,"stats":{"Line":1},"fn_name":null},{"line":70,"address":[6319936,6320855,6321321],"length":1,"stats":{"Line":2},"fn_name":"openapi_string"},{"line":74,"address":[6320002],"length":1,"stats":{"Line":2},"fn_name":null},{"line":76,"address":[6320116,6320057],"length":1,"stats":{"Line":4},"fn_name":null},{"line":77,"address":[6320192,6320144],"length":1,"stats":{"Line":4},"fn_name":null},{"line":78,"address":[6320301,6320200],"length":1,"stats":{"Line":4},"fn_name":null},{"line":79,"address":[6320317],"length":1,"stats":{"Line":2},"fn_name":null},{"line":80,"address":[6320580],"length":1,"stats":{"Line":2},"fn_name":null},{"line":82,"address":[6320712],"length":1,"stats":{"Line":2},"fn_name":null},{"line":85,"address":[6321344,6321969],"length":1,"stats":{"Line":2},"fn_name":"create_openapi_response"},{"line":86,"address":[6321382],"length":1,"stats":{"Line":2},"fn_name":null},{"line":87,"address":[6321441],"length":1,"stats":{"Line":2},"fn_name":null},{"line":88,"address":[6321464],"length":1,"stats":{"Line":2},"fn_name":null},{"line":89,"address":[6321722,6321599],"length":1,"stats":{"Line":4},"fn_name":null},{"line":90,"address":[6321947,6321730],"length":1,"stats":{"Line":2},"fn_name":null},{"line":91,"address":[6321916],"length":1,"stats":{"Line":2},"fn_name":null},{"line":93,"address":[6321615],"length":1,"stats":{"Line":0},"fn_name":null},{"line":94,"address":[6322362,6322036,6321628,6322227],"length":1,"stats":{"Line":0},"fn_name":null},{"line":98,"address":[6322056],"length":1,"stats":{"Line":0},"fn_name":null},{"line":115,"address":[6322480],"length":1,"stats":{"Line":2},"fn_name":null},{"line":123,"address":[6322496],"length":1,"stats":{"Line":2},"fn_name":"new_handler"},{"line":124,"address":[6322505],"length":1,"stats":{"Line":2},"fn_name":null},{"line":129,"address":[6322840,6322544],"length":1,"stats":{"Line":2},"fn_name":"handle"},{"line":130,"address":[6322587],"length":1,"stats":{"Line":2},"fn_name":null},{"line":131,"address":[6322640,6322561],"length":1,"stats":{"Line":4},"fn_name":null},{"line":145,"address":[6322864],"length":1,"stats":{"Line":0},"fn_name":null},{"line":153,"address":[6322880],"length":1,"stats":{"Line":0},"fn_name":"new_handler"},{"line":154,"address":[6322889],"length":1,"stats":{"Line":0},"fn_name":null},{"line":158,"address":[6322928,6326254,6326481],"length":1,"stats":{"Line":0},"fn_name":"redoc_handler"},{"line":162,"address":[6322975,6323252],"length":1,"stats":{"Line":0},"fn_name":null},{"line":163,"address":[6323177,6323326],"length":1,"stats":{"Line":0},"fn_name":null},{"line":165,"address":[6323398],"length":1,"stats":{"Line":0},"fn_name":null},{"line":166,"address":[6323477],"length":1,"stats":{"Line":0},"fn_name":null},{"line":167,"address":[6323484,6323738],"length":1,"stats":{"Line":0},"fn_name":null},{"line":169,"address":[6323981,6324082,6324127],"length":1,"stats":{"Line":0},"fn_name":null},{"line":171,"address":[6324052],"length":1,"stats":{"Line":0},"fn_name":null},{"line":172,"address":[6324119,6326553,6326528],"length":1,"stats":{"Line":0},"fn_name":"{closure#0}"},{"line":174,"address":[6324348],"length":1,"stats":{"Line":0},"fn_name":null},{"line":175,"address":[6326379],"length":1,"stats":{"Line":0},"fn_name":null},{"line":178,"address":[6324170],"length":1,"stats":{"Line":0},"fn_name":null},{"line":179,"address":[6324375,6324448],"length":1,"stats":{"Line":0},"fn_name":null},{"line":180,"address":[6324567],"length":1,"stats":{"Line":0},"fn_name":null},{"line":181,"address":[6324456],"length":1,"stats":{"Line":0},"fn_name":null},{"line":182,"address":[6324494],"length":1,"stats":{"Line":0},"fn_name":null},{"line":184,"address":[6325183],"length":1,"stats":{"Line":0},"fn_name":null},{"line":185,"address":[6324637],"length":1,"stats":{"Line":0},"fn_name":null},{"line":186,"address":[6325012,6324804],"length":1,"stats":{"Line":0},"fn_name":null},{"line":190,"address":[6325621,6326285,6325316],"length":1,"stats":{"Line":0},"fn_name":null},{"line":191,"address":[6326263,6325691],"length":1,"stats":{"Line":0},"fn_name":null},{"line":192,"address":[6326232,6325872],"length":1,"stats":{"Line":0},"fn_name":null},{"line":193,"address":[6326095],"length":1,"stats":{"Line":0},"fn_name":null},{"line":197,"address":[6326624,6327219],"length":1,"stats":{"Line":0},"fn_name":"handle"},{"line":198,"address":[6326715,6326641,6327092],"length":1,"stats":{"Line":0},"fn_name":null},{"line":199,"address":[6326782,6327043],"length":1,"stats":{"Line":0},"fn_name":null},{"line":200,"address":[6326912,6327140],"length":1,"stats":{"Line":0},"fn_name":null}],"covered":32,"coverable":69},{"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::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\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 operation_id = E::operation_id().or_else(|| {\n\t\t\tE::operation_verb()\n\t\t\t\t.map(|verb| format!(\"{verb}_{}\", path.replace(\"/\", \"_\").trim_start_matches('_')))\n\t\t});\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":[6182815,6182771,6182208],"length":1,"stats":{"Line":1},"fn_name":"new_parameter_data"},{"line":25,"address":[6182297,6182370],"length":1,"stats":{"Line":2},"fn_name":null},{"line":27,"address":[6182380],"length":1,"stats":{"Line":1},"fn_name":null},{"line":29,"address":[6182451],"length":1,"stats":{"Line":1},"fn_name":null},{"line":42,"address":[6183980,6182832,6183951],"length":1,"stats":{"Line":2},"fn_name":null},{"line":46,"address":[6182872],"length":1,"stats":{"Line":2},"fn_name":null},{"line":47,"address":[6182932],"length":1,"stats":{"Line":1},"fn_name":null},{"line":50,"address":[6183040,6183002],"length":1,"stats":{"Line":2},"fn_name":null},{"line":51,"address":[6183098],"length":1,"stats":{"Line":1},"fn_name":null},{"line":54,"address":[6183173,6184371,6183320],"length":1,"stats":{"Line":3},"fn_name":null},{"line":55,"address":[6183577,6184048],"length":1,"stats":{"Line":2},"fn_name":null},{"line":56,"address":[6184251],"length":1,"stats":{"Line":1},"fn_name":null},{"line":57,"address":[6184075],"length":1,"stats":{"Line":1},"fn_name":null},{"line":58,"address":[6184192],"length":1,"stats":{"Line":1},"fn_name":null},{"line":65,"address":[6185743,6184624,6185772],"length":1,"stats":{"Line":2},"fn_name":null},{"line":69,"address":[6184664],"length":1,"stats":{"Line":2},"fn_name":null},{"line":70,"address":[6184724],"length":1,"stats":{"Line":1},"fn_name":null},{"line":73,"address":[6184794,6184832],"length":1,"stats":{"Line":2},"fn_name":null},{"line":74,"address":[6184890],"length":1,"stats":{"Line":1},"fn_name":null},{"line":77,"address":[6184965,6185112,6186193],"length":1,"stats":{"Line":3},"fn_name":null},{"line":78,"address":[6185369,6185840],"length":1,"stats":{"Line":2},"fn_name":null},{"line":79,"address":[6186051],"length":1,"stats":{"Line":1},"fn_name":null},{"line":80,"address":[6185867],"length":1,"stats":{"Line":1},"fn_name":null},{"line":82,"address":[6185984],"length":1,"stats":{"Line":1},"fn_name":null},{"line":83,"address":[6186043],"length":1,"stats":{"Line":1},"fn_name":null},{"line":88,"address":[6186790,6186753,6186448],"length":1,"stats":{"Line":2},"fn_name":null},{"line":89,"address":[6186470],"length":1,"stats":{"Line":2},"fn_name":null},{"line":90,"address":[6186560],"length":1,"stats":{"Line":2},"fn_name":null},{"line":91,"address":[6186652],"length":1,"stats":{"Line":2},"fn_name":null},{"line":92,"address":[6186719],"length":1,"stats":{"Line":2},"fn_name":null},{"line":111,"address":[7275635,7274784,7275563],"length":1,"stats":{"Line":8},"fn_name":null},{"line":115,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":116,"address":[],"length":0,"stats":{"Line":12},"fn_name":null},{"line":117,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":121,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":123,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":125,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":128,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":132,"address":[6186832,6186908],"length":1,"stats":{"Line":1},"fn_name":null},{"line":133,"address":[6186868,6186947],"length":1,"stats":{"Line":2},"fn_name":null},{"line":136,"address":[6186976,6187066],"length":1,"stats":{"Line":1},"fn_name":null},{"line":137,"address":[6187012,6187105],"length":1,"stats":{"Line":2},"fn_name":null},{"line":140,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":141,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":142,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":145,"address":[6188377,6187136],"length":1,"stats":{"Line":3},"fn_name":null},{"line":149,"address":[6187178],"length":1,"stats":{"Line":3},"fn_name":null},{"line":150,"address":[6187252,6187341],"length":1,"stats":{"Line":6},"fn_name":null},{"line":151,"address":[6187615,6187971],"length":1,"stats":{"Line":6},"fn_name":null},{"line":152,"address":[6187793,6187881],"length":1,"stats":{"Line":6},"fn_name":null},{"line":153,"address":[6187891],"length":1,"stats":{"Line":3},"fn_name":null},{"line":156,"address":[6187635],"length":1,"stats":{"Line":3},"fn_name":null},{"line":159,"address":[6192442,6192184,6188416],"length":1,"stats":{"Line":2},"fn_name":null},{"line":161,"address":[6188483,6188837],"length":1,"stats":{"Line":4},"fn_name":null},{"line":162,"address":[6189084],"length":1,"stats":{"Line":2},"fn_name":null},{"line":163,"address":[6189124],"length":1,"stats":{"Line":2},"fn_name":null},{"line":164,"address":[6189164],"length":1,"stats":{"Line":2},"fn_name":null},{"line":165,"address":[6189196],"length":1,"stats":{"Line":2},"fn_name":null},{"line":166,"address":[6189252],"length":1,"stats":{"Line":2},"fn_name":null},{"line":167,"address":[6189285],"length":1,"stats":{"Line":2},"fn_name":null},{"line":168,"address":[6189303],"length":1,"stats":{"Line":2},"fn_name":null},{"line":169,"address":[6189343],"length":1,"stats":{"Line":2},"fn_name":null},{"line":171,"address":[6188563],"length":1,"stats":{"Line":2},"fn_name":null},{"line":172,"address":[6188593],"length":1,"stats":{"Line":2},"fn_name":null},{"line":173,"address":[6188623],"length":1,"stats":{"Line":2},"fn_name":null},{"line":174,"address":[6188653],"length":1,"stats":{"Line":2},"fn_name":null},{"line":175,"address":[6188733],"length":1,"stats":{"Line":2},"fn_name":null},{"line":176,"address":[6188745],"length":1,"stats":{"Line":2},"fn_name":null},{"line":177,"address":[6188797],"length":1,"stats":{"Line":2},"fn_name":null},{"line":178,"address":[6188827],"length":1,"stats":{"Line":2},"fn_name":null},{"line":181,"address":[6189497,6189361],"length":1,"stats":{"Line":4},"fn_name":null},{"line":183,"address":[6192832,6193954,6194007,6189489,6192875],"length":1,"stats":{"Line":6},"fn_name":"{closure#0}"},{"line":185,"address":[6192935,6193010],"length":1,"stats":{"Line":4},"fn_name":null},{"line":187,"address":[6193098,6193156],"length":1,"stats":{"Line":4},"fn_name":null},{"line":188,"address":[6193395],"length":1,"stats":{"Line":2},"fn_name":null},{"line":189,"address":[6193174],"length":1,"stats":{"Line":2},"fn_name":null},{"line":191,"address":[6194070,6194048],"length":1,"stats":{"Line":4},"fn_name":"{closure#0}"},{"line":193,"address":[6193248],"length":1,"stats":{"Line":2},"fn_name":null},{"line":194,"address":[6193344],"length":1,"stats":{"Line":2},"fn_name":null},{"line":200,"address":[6194581,6189559,6194096],"length":1,"stats":{"Line":3},"fn_name":"{closure#1}"},{"line":201,"address":[6194118,6194351],"length":1,"stats":{"Line":2},"fn_name":null},{"line":202,"address":[6194134,6194226],"length":1,"stats":{"Line":2},"fn_name":null},{"line":204,"address":[6194295],"length":1,"stats":{"Line":1},"fn_name":null},{"line":208,"address":[6189723],"length":1,"stats":{"Line":2},"fn_name":null},{"line":209,"address":[6190408,6189741],"length":1,"stats":{"Line":3},"fn_name":null},{"line":210,"address":[6189775],"length":1,"stats":{"Line":1},"fn_name":null},{"line":211,"address":[6189826,6189899,6192537],"length":1,"stats":{"Line":2},"fn_name":null},{"line":212,"address":[6190048],"length":1,"stats":{"Line":1},"fn_name":null},{"line":216,"address":[6189745],"length":1,"stats":{"Line":2},"fn_name":null},{"line":219,"address":[6190493],"length":1,"stats":{"Line":2},"fn_name":null},{"line":221,"address":[6190785],"length":1,"stats":{"Line":2},"fn_name":null}],"covered":91,"coverable":91},{"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":[7172384,7172706,7171650,7171328],"length":1,"stats":{"Line":2},"fn_name":null},{"line":44,"address":[7172411,7171355,7172485,7171429],"length":1,"stats":{"Line":4},"fn_name":null},{"line":45,"address":[7171452,7172508,7171434,7172490],"length":1,"stats":{"Line":4},"fn_name":null},{"line":47,"address":[7171772,7171825,7173008,7171680,7173100,7171447,7173153,7172503],"length":1,"stats":{"Line":5},"fn_name":"{closure#0}<(), (), openapi_supports_scope::openapi_supports_scope::{closure#0}::{closure#0}::{closure#0}::{closure_env#0}>"},{"line":48,"address":[7172736,7171512,7172993,7172369,7172112,7172568],"length":1,"stats":{"Line":4},"fn_name":"{closure#1}<(), (), openapi_supports_scope::openapi_supports_scope::{closure#0}::{closure#0}::{closure#0}::{closure_env#0}>"},{"line":49,"address":[7172137,7172304,7172761,7172928],"length":1,"stats":{"Line":4},"fn_name":null},{"line":51,"address":[7172833,7172209,7172771,7172147],"length":1,"stats":{"Line":4},"fn_name":null},{"line":52,"address":[7172924,7172300],"length":1,"stats":{"Line":2},"fn_name":null},{"line":54,"address":[7172334,7172958],"length":1,"stats":{"Line":2},"fn_name":null},{"line":64,"address":[7174316,7174338,7174160],"length":1,"stats":{"Line":2},"fn_name":"openapi_spec<(), ()>"},{"line":65,"address":[7174190,7174289],"length":1,"stats":{"Line":4},"fn_name":null},{"line":67,"address":[7174268],"length":1,"stats":{"Line":2},"fn_name":null},{"line":68,"address":[7174218],"length":1,"stats":{"Line":2},"fn_name":null},{"line":84,"address":[7174352,7174496],"length":1,"stats":{"Line":6},"fn_name":"resource<(), (), openapi_supports_scope::FooResource>"},{"line":85,"address":[7174637,7174493,7174520,7174376],"length":1,"stats":{"Line":7},"fn_name":null},{"line":86,"address":[7174590,7174446],"length":1,"stats":{"Line":1},"fn_name":null},{"line":88,"address":[7174406,7174550],"length":1,"stats":{"Line":6},"fn_name":null},{"line":98,"address":[7187761,7174640,7187137,7180721,7181680,7180097],"length":1,"stats":{"Line":9},"fn_name":"endpoint<(), (), openapi_supports_scope::read_all___gotham_restful_endpoint>"},{"line":99,"address":[7181733,7174693],"length":1,"stats":{"Line":9},"fn_name":null},{"line":100,"address":[7181946,7174842,7174906,7181882],"length":1,"stats":{"Line":18},"fn_name":null},{"line":101,"address":[7180665,7187705],"length":1,"stats":{"Line":9},"fn_name":null},{"line":103,"address":[7187670,7180630,7182169,7175129],"length":1,"stats":{"Line":18},"fn_name":null},{"line":106,"address":[7175173,7182213,7182398,7175465,7182505,7175358],"length":1,"stats":{"Line":27},"fn_name":null},{"line":107,"address":[7175599,7180581,7187621,7182639],"length":1,"stats":{"Line":9},"fn_name":null},{"line":108,"address":[7175860,7182838,7175798,7182900],"length":1,"stats":{"Line":18},"fn_name":null},{"line":109,"address":[7182920,7175880],"length":1,"stats":{"Line":4},"fn_name":null},{"line":111,"address":[7175935,7182906,7182975,7175866],"length":1,"stats":{"Line":18},"fn_name":null},{"line":112,"address":[7175955,7182995],"length":1,"stats":{"Line":1},"fn_name":null},{"line":114,"address":[7183050,7175941,7176010,7182981],"length":1,"stats":{"Line":18},"fn_name":null},{"line":115,"address":[7183079,7176039],"length":1,"stats":{"Line":2},"fn_name":null},{"line":116,"address":[7176124,7183164],"length":1,"stats":{"Line":2},"fn_name":null},{"line":119,"address":[7176133,7183064,7183173,7183268,7176228,7176024],"length":1,"stats":{"Line":27},"fn_name":null},{"line":121,"address":[6264353,6264881,6264208,6264736,6264703,6265231,6264761,6264233],"length":1,"stats":{"Line":62},"fn_name":"{closure#0}"},{"line":122,"address":[7181506,7188610],"length":1,"stats":{"Line":4},"fn_name":null},{"line":124,"address":[7183372,7176427,7183467,7184046,7176332,7177006],"length":1,"stats":{"Line":23},"fn_name":null},{"line":125,"address":[7183794,7176754],"length":1,"stats":{"Line":5},"fn_name":null},{"line":128,"address":[7184059,7176535,7177019,7183575],"length":1,"stats":{"Line":18},"fn_name":null},{"line":129,"address":[7184067,7184178,7177138,7177027],"length":1,"stats":{"Line":18},"fn_name":null},{"line":130,"address":[7177237,7184325,7184277,7177285],"length":1,"stats":{"Line":18},"fn_name":null},{"line":131,"address":[7177545,7185501,7184585,7178461],"length":1,"stats":{"Line":7},"fn_name":null},{"line":132,"address":[7178686,7184821,7185726,7177781],"length":1,"stats":{"Line":1},"fn_name":null},{"line":133,"address":[7178572,7185612,7177663,7184703],"length":1,"stats":{"Line":0},"fn_name":null},{"line":134,"address":[7178800,7184939,7185840,7177899],"length":1,"stats":{"Line":0},"fn_name":null},{"line":135,"address":[7184467,7177427,7178317,7185357],"length":1,"stats":{"Line":0},"fn_name":null},{"line":136,"address":[7185954,7178017,7178914,7185057],"length":1,"stats":{"Line":0},"fn_name":null},{"line":137,"address":[7179142,7186182,7178217,7185257],"length":1,"stats":{"Line":1},"fn_name":null},{"line":138,"address":[7186068,7178117,7185157,7179028],"length":1,"stats":{"Line":0},"fn_name":null},{"line":139,"address":[7177339,7184379],"length":1,"stats":{"Line":0},"fn_name":null},{"line":140,"address":[7179509,7186347,7186417,7179377,7177379,7179307,7186549,7184419],"length":1,"stats":{"Line":0},"fn_name":null},{"line":143,"address":[7179651,7186691],"length":1,"stats":{"Line":9},"fn_name":null},{"line":145,"address":[7186905,7179865],"length":1,"stats":{"Line":9},"fn_name":null}],"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":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":14,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":22,"address":[8010416],"length":1,"stats":{"Line":0},"fn_name":"into_response_error"},{"line":23,"address":[7891462],"length":1,"stats":{"Line":0},"fn_name":null},{"line":25,"address":[7801312],"length":1,"stats":{"Line":0},"fn_name":null},{"line":26,"address":[6146835],"length":1,"stats":{"Line":0},"fn_name":null},{"line":31,"address":[6146960],"length":1,"stats":{"Line":1},"fn_name":"status_codes"},{"line":32,"address":[7801485,7801560],"length":1,"stats":{"Line":1},"fn_name":null},{"line":36,"address":[6487040],"length":1,"stats":{"Line":1},"fn_name":"schema"},{"line":38,"address":[6147363],"length":1,"stats":{"Line":1},"fn_name":null},{"line":96,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":97,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"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":[6115648],"length":1,"stats":{"Line":3},"fn_name":"or_all_types"},{"line":47,"address":[6292336,6292349],"length":1,"stats":{"Line":9},"fn_name":"{closure#0}"},{"line":87,"address":[6293292,6293751,6292496,6292856,6292884,6293320,6293360,6292928,6293779],"length":1,"stats":{"Line":8},"fn_name":null},{"line":90,"address":[6292991,6292559,6293451],"length":1,"stats":{"Line":9},"fn_name":null},{"line":92,"address":[6292697,6293592,6293133],"length":1,"stats":{"Line":9},"fn_name":null},{"line":98,"address":[6294230,6294205,6293808],"length":1,"stats":{"Line":2},"fn_name":null},{"line":101,"address":[6293848],"length":1,"stats":{"Line":2},"fn_name":null},{"line":102,"address":[6293869],"length":1,"stats":{"Line":2},"fn_name":null},{"line":103,"address":[6294046],"length":1,"stats":{"Line":3},"fn_name":null},{"line":109,"address":[6294441,6294466,6294256],"length":1,"stats":{"Line":3},"fn_name":null},{"line":112,"address":[6294277],"length":1,"stats":{"Line":4},"fn_name":null},{"line":114,"address":[6294291],"length":1,"stats":{"Line":4},"fn_name":null},{"line":120,"address":[6294665,6294480,6294690],"length":1,"stats":{"Line":0},"fn_name":null},{"line":123,"address":[6294501],"length":1,"stats":{"Line":0},"fn_name":null},{"line":125,"address":[6294515],"length":1,"stats":{"Line":0},"fn_name":null},{"line":130,"address":[6294704],"length":1,"stats":{"Line":1},"fn_name":null},{"line":131,"address":[6294709],"length":1,"stats":{"Line":1},"fn_name":null},{"line":135,"address":[6294720],"length":1,"stats":{"Line":1},"fn_name":null},{"line":136,"address":[6294725],"length":1,"stats":{"Line":1},"fn_name":null},{"line":140,"address":[6294736],"length":1,"stats":{"Line":2},"fn_name":null},{"line":141,"address":[6294754],"length":1,"stats":{"Line":2},"fn_name":null},{"line":144,"address":[6295005,6294800],"length":1,"stats":{"Line":4},"fn_name":null},{"line":145,"address":[6294832,6294958],"length":1,"stats":{"Line":8},"fn_name":null},{"line":146,"address":[6294985],"length":1,"stats":{"Line":4},"fn_name":null},{"line":150,"address":[6295444,6295423,6295024],"length":1,"stats":{"Line":1},"fn_name":null},{"line":156,"address":[6295054,6295111,6295321],"length":1,"stats":{"Line":6},"fn_name":null},{"line":157,"address":[6295337],"length":1,"stats":{"Line":2},"fn_name":null},{"line":164,"address":[6295472],"length":1,"stats":{"Line":0},"fn_name":"into_response"},{"line":165,"address":[6295491],"length":1,"stats":{"Line":0},"fn_name":null},{"line":179,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":180,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":188,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":189,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":227,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":230,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":236,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":237,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":243,"address":[6300028,6298016,6300000,6298064,6299008,6297052,6296080,6299036,6299056,6297072,6297024,6298044],"length":1,"stats":{"Line":1},"fn_name":"handle_error"},{"line":247,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":248,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":249,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":250,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":251,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":252,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":253,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":255,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":257,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":266,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":267,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":270,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":271,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":280,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":281,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":284,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":285,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"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":[6147392],"length":1,"stats":{"Line":0},"fn_name":"from"},{"line":43,"address":[6147404],"length":1,"stats":{"Line":0},"fn_name":null},{"line":49,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":50,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":54,"address":[6487248],"length":1,"stats":{"Line":0},"fn_name":null},{"line":64,"address":[6487264,6487568,6487539],"length":1,"stats":{"Line":2},"fn_name":"into_response"},{"line":65,"address":[6147577,6147500],"length":1,"stats":{"Line":6},"fn_name":null},{"line":68,"address":[6147792],"length":1,"stats":{"Line":4},"fn_name":"accepted_types"},{"line":69,"address":[6147805],"length":1,"stats":{"Line":4},"fn_name":null},{"line":75,"address":[6147856],"length":1,"stats":{"Line":2},"fn_name":"status_codes"},{"line":76,"address":[6147943,6147869],"length":1,"stats":{"Line":2},"fn_name":null},{"line":80,"address":[6487760],"length":1,"stats":{"Line":2},"fn_name":"schema"},{"line":82,"address":[6148259],"length":1,"stats":{"Line":2},"fn_name":null},{"line":92,"address":[6148288],"length":1,"stats":{"Line":1},"fn_name":"into_response"},{"line":95,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":96,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":97,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":101,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":102,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":111,"address":[6148515,6148400],"length":1,"stats":{"Line":1},"fn_name":"status_codes"},{"line":112,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":113,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":114,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":117,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":118,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":119,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":120,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"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":[6647776],"length":1,"stats":{"Line":7},"fn_name":null},{"line":58,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":67,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":68,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":73,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":75,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":76,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":85,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":93,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":94,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":104,"address":[6647872],"length":1,"stats":{"Line":5},"fn_name":"into_response<&str>"},{"line":107,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":116,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":118,"address":[6648419],"length":1,"stats":{"Line":3},"fn_name":null},{"line":129,"address":[6148544],"length":1,"stats":{"Line":1},"fn_name":"into_response<&str, gotham_restful::response::raw::test::MsgError>"},{"line":130,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":131,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":132,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":143,"address":[6148771,6148656],"length":1,"stats":{"Line":1},"fn_name":"status_codes"},{"line":144,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":145,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":146,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":149,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":150,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":151,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":152,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"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":[6432448],"length":1,"stats":{"Line":1},"fn_name":"into_response"},{"line":46,"address":[7837735,7837937,7837060,7837191,7837134,7837104,7837975],"length":1,"stats":{"Line":3},"fn_name":"{async_block#0}"},{"line":47,"address":[6648560,6648646],"length":1,"stats":{"Line":4},"fn_name":null},{"line":48,"address":[6649200,6649290,6648558,6648691,6649333],"length":1,"stats":{"Line":2},"fn_name":null},{"line":49,"address":[7747629],"length":1,"stats":{"Line":2},"fn_name":null},{"line":57,"address":[6433424],"length":1,"stats":{"Line":1},"fn_name":"status_codes"},{"line":58,"address":[7748056,7747981],"length":1,"stats":{"Line":1},"fn_name":null},{"line":61,"address":[6649520],"length":1,"stats":{"Line":0},"fn_name":"schema"},{"line":63,"address":[7748224],"length":1,"stats":{"Line":0},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":85,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":87,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":98,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":99,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":100,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":101,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":104,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":105,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":106,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":107,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"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!(\n\t\t\t>::accepted_types()\n\t\t\t\t.or_all_types()\n\t\t\t\t.contains(&APPLICATION_JSON)\n\t\t)\n\t}\n}\n","traces":[{"line":33,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":34,"address":[6649857],"length":1,"stats":{"Line":2},"fn_name":null},{"line":35,"address":[6649880],"length":1,"stats":{"Line":2},"fn_name":null},{"line":36,"address":[6505719,6505800],"length":1,"stats":{"Line":4},"fn_name":null},{"line":37,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":38,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":43,"address":[6505872],"length":1,"stats":{"Line":3},"fn_name":"status_codes"},{"line":44,"address":[6505959,6505885],"length":1,"stats":{"Line":3},"fn_name":null},{"line":48,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":50,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":61,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":62,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":63,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":64,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":68,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":69,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":79,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":80,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":81,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":82,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":85,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":87,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":88,"address":[],"length":0,"stats":{"Line":2},"fn_name":null}],"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!(\n\t\t\t>::accepted_types()\n\t\t\t\t.or_all_types()\n\t\t\t\t.contains(&APPLICATION_JSON)\n\t\t)\n\t}\n}\n","traces":[{"line":54,"address":[6712704,6712832,6712752,6712974],"length":1,"stats":{"Line":3},"fn_name":"from"},{"line":57,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":64,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":65,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":69,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":70,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":77,"address":[6713276,6713328,6713629,6713008,6713610,6713305],"length":1,"stats":{"Line":2},"fn_name":"into_response"},{"line":78,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":79,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":80,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":83,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":92,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"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":[6330309,6329312,6331168],"length":1,"stats":{"Line":5},"fn_name":"response_from"},{"line":63,"address":[6329355,6329506],"length":1,"stats":{"Line":10},"fn_name":null},{"line":64,"address":[6329583,6329514],"length":1,"stats":{"Line":10},"fn_name":null},{"line":65,"address":[6329591],"length":1,"stats":{"Line":5},"fn_name":null},{"line":66,"address":[6329705,6330154,6330290,6329912],"length":1,"stats":{"Line":8},"fn_name":null},{"line":68,"address":[6329776],"length":1,"stats":{"Line":5},"fn_name":null},{"line":69,"address":[6330362,6329788,6331695],"length":1,"stats":{"Line":10},"fn_name":null},{"line":70,"address":[6330612,6331268,6331454],"length":1,"stats":{"Line":0},"fn_name":null},{"line":71,"address":[6331300],"length":1,"stats":{"Line":0},"fn_name":null},{"line":74,"address":[6331290],"length":1,"stats":{"Line":0},"fn_name":null},{"line":75,"address":[6331559],"length":1,"stats":{"Line":0},"fn_name":null},{"line":78,"address":[6330631],"length":1,"stats":{"Line":5},"fn_name":null},{"line":79,"address":[6330660,6330948],"length":1,"stats":{"Line":10},"fn_name":null},{"line":80,"address":[6330725,6331201],"length":1,"stats":{"Line":5},"fn_name":null},{"line":84,"address":[6330710],"length":1,"stats":{"Line":5},"fn_name":null},{"line":86,"address":[6330958],"length":1,"stats":{"Line":5},"fn_name":null},{"line":89,"address":[7023376],"length":1,"stats":{"Line":21},"fn_name":"endpoint_handler"},{"line":96,"address":[],"length":0,"stats":{"Line":63},"fn_name":null},{"line":97,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":100,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":101,"address":[],"length":0,"stats":{"Line":13},"fn_name":null},{"line":103,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":105,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":106,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":107,"address":[],"length":0,"stats":{"Line":16},"fn_name":null},{"line":109,"address":[],"length":0,"stats":{"Line":16},"fn_name":null},{"line":110,"address":[],"length":0,"stats":{"Line":16},"fn_name":null},{"line":111,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":112,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":113,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":114,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":118,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":119,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":120,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":121,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":122,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":123,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":124,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":125,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":126,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":130,"address":[],"length":0,"stats":{"Line":13},"fn_name":null},{"line":133,"address":[],"length":0,"stats":{"Line":43},"fn_name":null},{"line":134,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":135,"address":[],"length":0,"stats":{"Line":80},"fn_name":null},{"line":136,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":145,"address":[6331920],"length":1,"stats":{"Line":5},"fn_name":"is_match"},{"line":146,"address":[6331953],"length":1,"stats":{"Line":7},"fn_name":null},{"line":147,"address":[6332008],"length":1,"stats":{"Line":0},"fn_name":null},{"line":148,"address":[6331988],"length":1,"stats":{"Line":5},"fn_name":null},{"line":154,"address":[6332032,6332421,6332495],"length":1,"stats":{"Line":7},"fn_name":null},{"line":155,"address":[6332054],"length":1,"stats":{"Line":7},"fn_name":null},{"line":156,"address":[6332264,6332121],"length":1,"stats":{"Line":6},"fn_name":null},{"line":157,"address":[6332145],"length":1,"stats":{"Line":5},"fn_name":null},{"line":160,"address":[6332334],"length":1,"stats":{"Line":7},"fn_name":null},{"line":166,"address":[6332512],"length":1,"stats":{"Line":0},"fn_name":"from"},{"line":167,"address":[6332520],"length":1,"stats":{"Line":0},"fn_name":null},{"line":177,"address":[6332544],"length":1,"stats":{"Line":0},"fn_name":"is_match"},{"line":178,"address":[6332577],"length":1,"stats":{"Line":0},"fn_name":null},{"line":179,"address":[6332632],"length":1,"stats":{"Line":0},"fn_name":null},{"line":180,"address":[6332612],"length":1,"stats":{"Line":0},"fn_name":null},{"line":186,"address":[6332656],"length":1,"stats":{"Line":0},"fn_name":null},{"line":188,"address":[6332670,6332733,6332720],"length":1,"stats":{"Line":0},"fn_name":"{closure#0}"},{"line":194,"address":[6332784],"length":1,"stats":{"Line":0},"fn_name":"from"},{"line":195,"address":[6332792],"length":1,"stats":{"Line":0},"fn_name":null},{"line":207,"address":[7173872,7174125],"length":1,"stats":{"Line":2},"fn_name":"with_openapi<(), (), openapi_supports_scope::openapi_supports_scope::{closure#0}::{closure_env#0}>"},{"line":211,"address":[7173988,7173900],"length":1,"stats":{"Line":4},"fn_name":null},{"line":213,"address":[7173910],"length":1,"stats":{"Line":2},"fn_name":null},{"line":214,"address":[7173983,7173919],"length":1,"stats":{"Line":4},"fn_name":null},{"line":216,"address":[7174023],"length":1,"stats":{"Line":2},"fn_name":null},{"line":225,"address":[6661040],"length":1,"stats":{"Line":5},"fn_name":"resource<(), (), custom_request_body::FooResource>"},{"line":226,"address":[6661064,6661181],"length":1,"stats":{"Line":5},"fn_name":null},{"line":227,"address":[6661134],"length":1,"stats":{"Line":3},"fn_name":null},{"line":229,"address":[6661094],"length":1,"stats":{"Line":5},"fn_name":null},{"line":238,"address":[6669736,6668864],"length":1,"stats":{"Line":30},"fn_name":"endpoint<(), (), custom_request_body::create___gotham_restful_endpoint>"},{"line":239,"address":[6669043,6668884,6668966],"length":1,"stats":{"Line":62},"fn_name":null},{"line":240,"address":[6669380,6669466,6669596,6669293],"length":1,"stats":{"Line":110},"fn_name":null},{"line":241,"address":[6669391,6669710],"length":1,"stats":{"Line":92},"fn_name":null},{"line":242,"address":[7022132,7022522,7022333,7022455],"length":1,"stats":{"Line":124},"fn_name":null},{"line":243,"address":[7022919,7022338,7022366,7022158],"length":1,"stats":{"Line":61},"fn_name":null},{"line":244,"address":[7022494,7022387,7022912,7022354],"length":1,"stats":{"Line":62},"fn_name":null},{"line":250,"address":[7022569],"length":1,"stats":{"Line":31},"fn_name":null},{"line":251,"address":[7022693,7022802,7022849],"length":1,"stats":{"Line":45},"fn_name":null},{"line":253,"address":[7022775],"length":1,"stats":{"Line":15},"fn_name":null},{"line":254,"address":[7022722],"length":1,"stats":{"Line":15},"fn_name":null}],"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":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":61,"address":[6608025,6607966],"length":1,"stats":{"Line":4},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":85,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":91,"address":[],"length":0,"stats":{"Line":1},"fn_name":null}],"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.38028169014085,"covered":521,"coverable":710} \ 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":[7007312,7008563],"length":1,"stats":{"Line":0},"fn_name":"html"},{"line":21,"address":[7007369,7007437],"length":1,"stats":{"Line":0},"fn_name":null},{"line":23,"address":[8110107,8110080],"length":1,"stats":{"Line":0},"fn_name":"{closure#0}"},{"line":24,"address":[7008677],"length":1,"stats":{"Line":0},"fn_name":null},{"line":25,"address":[8110199],"length":1,"stats":{"Line":0},"fn_name":null},{"line":26,"address":[8110233],"length":1,"stats":{"Line":0},"fn_name":null},{"line":27,"address":[8110140],"length":1,"stats":{"Line":0},"fn_name":null},{"line":31,"address":[7007510,7007342],"length":1,"stats":{"Line":0},"fn_name":null},{"line":32,"address":[8109025],"length":1,"stats":{"Line":0},"fn_name":null},{"line":33,"address":[7899968],"length":1,"stats":{"Line":0},"fn_name":null},{"line":34,"address":[7007635],"length":1,"stats":{"Line":0},"fn_name":null},{"line":36,"address":[7007808],"length":1,"stats":{"Line":0},"fn_name":null},{"line":37,"address":[7008123],"length":1,"stats":{"Line":0},"fn_name":null}],"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},"fn_name":null},{"line":49,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":50,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":51,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":52,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":53,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":60,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":61,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":62,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":63,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":64,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":118,"address":[6224848],"length":1,"stats":{"Line":3},"fn_name":null},{"line":122,"address":[6224880],"length":1,"stats":{"Line":2},"fn_name":null},{"line":123,"address":[6224903],"length":1,"stats":{"Line":2},"fn_name":null},{"line":128,"address":[6224989,6224944],"length":1,"stats":{"Line":1},"fn_name":"jwt_secret>"},{"line":133,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":188,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":190,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":191,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":192,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":193,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":203,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":206,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":207,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":208,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":218,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":223,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":227,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":229,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":230,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":231,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":232,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":233,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":234,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":235,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":237,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":238,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":239,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":240,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":241,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":242,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":243,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":244,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":245,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":249,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":250,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":251,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":255,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":256,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":257,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":258,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":262,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":263,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":264,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":268,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":269,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":270,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":271,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":273,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":274,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":278,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":287,"address":[7650191,7650607,7651023,7651855,7651472,7652271,7653152,7652304,7652736,7651888,7651439,7649808,7650640,7652681,7653119,7653535,7651056,7650224,7652718],"length":1,"stats":{"Line":1},"fn_name":"call, gotham::router::route::matcher::access_control_request_method::AccessControlRequestMethodMatcher>, (borrow_bag::handle::Handle, ())>, borrow_bag::handle::Take>, ()), (gotham::pipeline::Pipeline<(gotham_restful::auth::AuthMiddleware, ())>, ()), gotham::extractor::path::NoopPathExtractor, gotham::extractor::query_string::NoopQueryStringExtractor, fn(gotham::state::State) -> (gotham::state::State, http::response::Response)>, (borrow_bag::handle::Handle, ())>, borrow_bag::handle::Take>, ()), (gotham::pipeline::Pipeline<(gotham_restful::auth::AuthMiddleware, ())>, ())>>"},{"line":292,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":295,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":296,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":299,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":300,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":311,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":312,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":313,"address":[],"length":0,"stats":{"Line":1},"fn_name":null}],"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":[6313184],"length":1,"stats":{"Line":3},"fn_name":"default"},{"line":38,"address":[6313187],"length":1,"stats":{"Line":1},"fn_name":null},{"line":44,"address":[6313216],"length":1,"stats":{"Line":3},"fn_name":null},{"line":45,"address":[6313259],"length":1,"stats":{"Line":1},"fn_name":null},{"line":46,"address":[6313324],"length":1,"stats":{"Line":3},"fn_name":null},{"line":47,"address":[6313333,6313491],"length":1,"stats":{"Line":2},"fn_name":null},{"line":48,"address":[6313687,6313516],"length":1,"stats":{"Line":2},"fn_name":null},{"line":50,"address":[6313712],"length":1,"stats":{"Line":1},"fn_name":null},{"line":51,"address":[6313729],"length":1,"stats":{"Line":1},"fn_name":null},{"line":57,"address":[6313840],"length":1,"stats":{"Line":3},"fn_name":null},{"line":58,"address":[6313845],"length":1,"stats":{"Line":1},"fn_name":null},{"line":77,"address":[6313888],"length":1,"stats":{"Line":3},"fn_name":"default"},{"line":78,"address":[6313891],"length":1,"stats":{"Line":1},"fn_name":null},{"line":84,"address":[6313920,6314513],"length":1,"stats":{"Line":1},"fn_name":null},{"line":85,"address":[6313963],"length":1,"stats":{"Line":3},"fn_name":null},{"line":86,"address":[6314029],"length":1,"stats":{"Line":1},"fn_name":null},{"line":87,"address":[6314043,6314237,6314479],"length":1,"stats":{"Line":2},"fn_name":null},{"line":89,"address":[6314120],"length":1,"stats":{"Line":1},"fn_name":null},{"line":90,"address":[6314137],"length":1,"stats":{"Line":1},"fn_name":null},{"line":96,"address":[6314544],"length":1,"stats":{"Line":1},"fn_name":null},{"line":97,"address":[6314549],"length":1,"stats":{"Line":1},"fn_name":null},{"line":174,"address":[6667484,6667900,6667510,6667728,6667718,6667312,6667926,6667520,6667692],"length":1,"stats":{"Line":5},"fn_name":"call, gotham_restful::routing::MaybeMatchAcceptHeader>, (borrow_bag::handle::Handle, borrow_bag::handle::Take>, ()), (gotham::pipeline::Pipeline<(gotham_restful::cors::CorsConfig, ())>, ()), gotham_restful::endpoint::NoopExtractor, gotham_restful::endpoint::NoopExtractor, fn(&mut gotham::state::State) -> gotham_restful::routing::endpoint_handler::{async_fn_env#0}>, (borrow_bag::handle::Handle, borrow_bag::handle::Take>, ()), (gotham::pipeline::Pipeline<(gotham_restful::cors::CorsConfig, ())>, ())>>"},{"line":178,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":179,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":191,"address":[6315792,6314592,6315760],"length":1,"stats":{"Line":5},"fn_name":"handle_cors"},{"line":192,"address":[6314625],"length":1,"stats":{"Line":7},"fn_name":null},{"line":193,"address":[6314659],"length":1,"stats":{"Line":5},"fn_name":null},{"line":194,"address":[6314708],"length":1,"stats":{"Line":3},"fn_name":null},{"line":197,"address":[6314797,6314737],"length":1,"stats":{"Line":2},"fn_name":null},{"line":198,"address":[6314833],"length":1,"stats":{"Line":1},"fn_name":null},{"line":202,"address":[6314985],"length":1,"stats":{"Line":1},"fn_name":null},{"line":203,"address":[6315068],"length":1,"stats":{"Line":1},"fn_name":null},{"line":204,"address":[6315038],"length":1,"stats":{"Line":1},"fn_name":null},{"line":205,"address":[6316022,6315824,6315859,6316092],"length":1,"stats":{"Line":4},"fn_name":"{closure#0}"},{"line":206,"address":[6315111,6315453,6315801],"length":1,"stats":{"Line":2},"fn_name":null},{"line":210,"address":[6315018],"length":1,"stats":{"Line":3},"fn_name":null},{"line":211,"address":[6315690],"length":1,"stats":{"Line":1},"fn_name":null},{"line":212,"address":[6315586],"length":1,"stats":{"Line":1},"fn_name":null},{"line":213,"address":[6315624],"length":1,"stats":{"Line":1},"fn_name":null},{"line":247,"address":[6318334,6316176,6317592],"length":1,"stats":{"Line":3},"fn_name":"cors_preflight_handler"},{"line":248,"address":[6316338,6316218],"length":1,"stats":{"Line":4},"fn_name":null},{"line":251,"address":[6316346],"length":1,"stats":{"Line":1},"fn_name":null},{"line":252,"address":[6316454,6316378],"length":1,"stats":{"Line":4},"fn_name":null},{"line":253,"address":[6316470],"length":1,"stats":{"Line":3},"fn_name":null},{"line":256,"address":[6316685,6316485,6316573],"length":1,"stats":{"Line":5},"fn_name":null},{"line":257,"address":[6316543],"length":1,"stats":{"Line":3},"fn_name":null},{"line":260,"address":[6316700],"length":1,"stats":{"Line":1},"fn_name":null},{"line":261,"address":[6316784],"length":1,"stats":{"Line":3},"fn_name":null},{"line":263,"address":[6316837],"length":1,"stats":{"Line":1},"fn_name":null},{"line":265,"address":[6316903,6316976],"length":1,"stats":{"Line":4},"fn_name":null},{"line":266,"address":[6317077,6317169],"length":1,"stats":{"Line":2},"fn_name":null},{"line":270,"address":[6317245,6317211],"length":1,"stats":{"Line":3},"fn_name":null},{"line":271,"address":[6317276],"length":1,"stats":{"Line":1},"fn_name":null},{"line":275,"address":[6318373,6317341,6317251,6318368],"length":1,"stats":{"Line":6},"fn_name":"{closure#0}"},{"line":276,"address":[6317388],"length":1,"stats":{"Line":1},"fn_name":null},{"line":281,"address":[6316910,6317601,6317953,6318290],"length":1,"stats":{"Line":4},"fn_name":null},{"line":283,"address":[6318102],"length":1,"stats":{"Line":3},"fn_name":null},{"line":284,"address":[6318117],"length":1,"stats":{"Line":3},"fn_name":null},{"line":293,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":294,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":295,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":296,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":297,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"covered":58,"coverable":63},{"path":["/","home","runner","work","gotham_restful","gotham_restful","src","endpoint.rs"],"content":"use 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() -> Option {\n\t\tNone\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":20,"address":[],"length":0,"stats":{"Line":10},"fn_name":null},{"line":21,"address":[7059257,7059219],"length":1,"stats":{"Line":10},"fn_name":null},{"line":27,"address":[7633248],"length":1,"stats":{"Line":0},"fn_name":"visit_type"},{"line":28,"address":[7633329,7633261],"length":1,"stats":{"Line":0},"fn_name":null},{"line":29,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":31,"address":[6327296],"length":1,"stats":{"Line":0},"fn_name":null},{"line":39,"address":[6327408,6327418],"length":1,"stats":{"Line":0},"fn_name":"extend"},{"line":60,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":61,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":69,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":70,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":78,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":79,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":85,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":92,"address":[7027968],"length":1,"stats":{"Line":6},"fn_name":"operation_id"},{"line":93,"address":[7027971],"length":1,"stats":{"Line":6},"fn_name":null},{"line":99,"address":[7027936],"length":1,"stats":{"Line":7},"fn_name":"description"},{"line":100,"address":[7027939],"length":1,"stats":{"Line":7},"fn_name":null},{"line":114,"address":[7028624],"length":1,"stats":{"Line":30},"fn_name":"http_method"},{"line":115,"address":[],"length":0,"stats":{"Line":30},"fn_name":null},{"line":117,"address":[7028656],"length":1,"stats":{"Line":30},"fn_name":"uri"},{"line":118,"address":[],"length":0,"stats":{"Line":30},"fn_name":null},{"line":123,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":124,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":128,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":129,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":133,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":134,"address":[],"length":0,"stats":{"Line":21},"fn_name":null},{"line":138,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":139,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":142,"address":[7028688],"length":1,"stats":{"Line":21},"fn_name":"handle"},{"line":148,"address":[],"length":0,"stats":{"Line":21},"fn_name":null}],"covered":14,"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, 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":[16206189],"length":1,"stats":{"Line":0},"fn_name":null},{"line":464,"address":[10679521,10679920,10679311,10679980],"length":1,"stats":{"Line":0},"fn_name":null},{"line":466,"address":[14578997],"length":1,"stats":{"Line":2},"fn_name":null},{"line":467,"address":[19133984],"length":1,"stats":{"Line":2},"fn_name":"eq"},{"line":508,"address":[5589451],"length":1,"stats":{"Line":14},"fn_name":null},{"line":512,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":518,"address":[1888299,1888324,1888086,1888289,1887978,1888060,1888070],"length":1,"stats":{"Line":0},"fn_name":null},{"line":523,"address":[16209120],"length":1,"stats":{"Line":8},"fn_name":null},{"line":528,"address":[5589744],"length":1,"stats":{"Line":9},"fn_name":null},{"line":529,"address":[5589800],"length":1,"stats":{"Line":9},"fn_name":null},{"line":536,"address":[10474864],"length":1,"stats":{"Line":0},"fn_name":null},{"line":541,"address":[15625216],"length":1,"stats":{"Line":0},"fn_name":"next"},{"line":546,"address":[4949133],"length":1,"stats":{"Line":1},"fn_name":null},{"line":547,"address":[4949166],"length":1,"stats":{"Line":1},"fn_name":null},{"line":595,"address":[7875472],"length":1,"stats":{"Line":0},"fn_name":"blink"}],"covered":8,"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":[6288150,6288345,6286512],"length":1,"stats":{"Line":4},"fn_name":null},{"line":28,"address":[7795387,7796217,7796712],"length":1,"stats":{"Line":12},"fn_name":null},{"line":50,"address":[7797632,7798270,7798313],"length":1,"stats":{"Line":2},"fn_name":null},{"line":51,"address":[7797755],"length":1,"stats":{"Line":2},"fn_name":null},{"line":52,"address":[7797865,7797789],"length":1,"stats":{"Line":4},"fn_name":null},{"line":53,"address":[7797972],"length":1,"stats":{"Line":1},"fn_name":null},{"line":54,"address":[6289065,6289172],"length":1,"stats":{"Line":4},"fn_name":null},{"line":58,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":60,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":63,"address":[6289472,6290555,6290718],"length":1,"stats":{"Line":2},"fn_name":null},{"line":64,"address":[7798457],"length":1,"stats":{"Line":2},"fn_name":null},{"line":66,"address":[7798568],"length":1,"stats":{"Line":2},"fn_name":null},{"line":67,"address":[7798697,7798624],"length":1,"stats":{"Line":4},"fn_name":null},{"line":68,"address":[7798784],"length":1,"stats":{"Line":2},"fn_name":null},{"line":69,"address":[7799530,7798792],"length":1,"stats":{"Line":4},"fn_name":null},{"line":72,"address":[7798753],"length":1,"stats":{"Line":2},"fn_name":null},{"line":73,"address":[7798954,7799149],"length":1,"stats":{"Line":4},"fn_name":null},{"line":74,"address":[7799168,7799477],"length":1,"stats":{"Line":2},"fn_name":null},{"line":79,"address":[6290752,6291381],"length":1,"stats":{"Line":3},"fn_name":null},{"line":80,"address":[6290798,6291443,6291408],"length":1,"stats":{"Line":7},"fn_name":"{closure#0}"},{"line":81,"address":[6291007,6291335,6290843],"length":1,"stats":{"Line":7},"fn_name":null},{"line":82,"address":[6291108,6291060],"length":1,"stats":{"Line":4},"fn_name":null},{"line":83,"address":[6291116],"length":1,"stats":{"Line":2},"fn_name":null},{"line":84,"address":[6291276,6291193],"length":1,"stats":{"Line":4},"fn_name":null},{"line":89,"address":[6291472,6292235,6292291],"length":1,"stats":{"Line":3},"fn_name":null},{"line":90,"address":[6291515,6291619],"length":1,"stats":{"Line":6},"fn_name":null},{"line":91,"address":[6291675],"length":1,"stats":{"Line":1},"fn_name":null},{"line":93,"address":[6291899],"length":1,"stats":{"Line":1},"fn_name":null},{"line":95,"address":[6292020],"length":1,"stats":{"Line":1},"fn_name":null},{"line":96,"address":[6292166],"length":1,"stats":{"Line":1},"fn_name":null},{"line":99,"address":[6291661],"length":1,"stats":{"Line":3},"fn_name":null},{"line":100,"address":[6291780],"length":1,"stats":{"Line":3},"fn_name":null}],"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":[6318384,6318991,6319019],"length":1,"stats":{"Line":2},"fn_name":"get_security"},{"line":33,"address":[6318437,6318558],"length":1,"stats":{"Line":2},"fn_name":null},{"line":34,"address":[6318494],"length":1,"stats":{"Line":1},"fn_name":null},{"line":35,"address":[6318486],"length":1,"stats":{"Line":1},"fn_name":null},{"line":38,"address":[6318512],"length":1,"stats":{"Line":1},"fn_name":null},{"line":41,"address":[6318593],"length":1,"stats":{"Line":0},"fn_name":null},{"line":43,"address":[6318616],"length":1,"stats":{"Line":0},"fn_name":null},{"line":47,"address":[6318671],"length":1,"stats":{"Line":0},"fn_name":null},{"line":49,"address":[6318694],"length":1,"stats":{"Line":0},"fn_name":null},{"line":52,"address":[6318716],"length":1,"stats":{"Line":1},"fn_name":null},{"line":53,"address":[6319301,6318741],"length":1,"stats":{"Line":2},"fn_name":null},{"line":55,"address":[6319351],"length":1,"stats":{"Line":1},"fn_name":null},{"line":59,"address":[6318969],"length":1,"stats":{"Line":1},"fn_name":null},{"line":60,"address":[6319716,6319648],"length":1,"stats":{"Line":2},"fn_name":null},{"line":62,"address":[6319852],"length":1,"stats":{"Line":1},"fn_name":null},{"line":70,"address":[6321321,6319936,6320855],"length":1,"stats":{"Line":2},"fn_name":"openapi_string"},{"line":74,"address":[6320002],"length":1,"stats":{"Line":2},"fn_name":null},{"line":76,"address":[6320116,6320057],"length":1,"stats":{"Line":4},"fn_name":null},{"line":77,"address":[6320144,6320192],"length":1,"stats":{"Line":4},"fn_name":null},{"line":78,"address":[6320301,6320200],"length":1,"stats":{"Line":4},"fn_name":null},{"line":79,"address":[6320317],"length":1,"stats":{"Line":2},"fn_name":null},{"line":80,"address":[6320580],"length":1,"stats":{"Line":2},"fn_name":null},{"line":82,"address":[6320712],"length":1,"stats":{"Line":2},"fn_name":null},{"line":85,"address":[6321969,6321344],"length":1,"stats":{"Line":2},"fn_name":"create_openapi_response"},{"line":86,"address":[6321382],"length":1,"stats":{"Line":2},"fn_name":null},{"line":87,"address":[6321441],"length":1,"stats":{"Line":2},"fn_name":null},{"line":88,"address":[6321464],"length":1,"stats":{"Line":2},"fn_name":null},{"line":89,"address":[6321599,6321722],"length":1,"stats":{"Line":4},"fn_name":null},{"line":90,"address":[6321730,6321947],"length":1,"stats":{"Line":2},"fn_name":null},{"line":91,"address":[6321916],"length":1,"stats":{"Line":2},"fn_name":null},{"line":93,"address":[6321615],"length":1,"stats":{"Line":0},"fn_name":null},{"line":94,"address":[6322036,6322362,6321628,6322227],"length":1,"stats":{"Line":0},"fn_name":null},{"line":98,"address":[6322056],"length":1,"stats":{"Line":0},"fn_name":null},{"line":115,"address":[6322480],"length":1,"stats":{"Line":2},"fn_name":null},{"line":123,"address":[6322496],"length":1,"stats":{"Line":2},"fn_name":"new_handler"},{"line":124,"address":[6322505],"length":1,"stats":{"Line":2},"fn_name":null},{"line":129,"address":[6322544,6322840],"length":1,"stats":{"Line":2},"fn_name":"handle"},{"line":130,"address":[6322587],"length":1,"stats":{"Line":2},"fn_name":null},{"line":131,"address":[6322561,6322640],"length":1,"stats":{"Line":4},"fn_name":null},{"line":145,"address":[6322864],"length":1,"stats":{"Line":0},"fn_name":null},{"line":153,"address":[6322880],"length":1,"stats":{"Line":0},"fn_name":"new_handler"},{"line":154,"address":[6322889],"length":1,"stats":{"Line":0},"fn_name":null},{"line":158,"address":[6326254,6326481,6322928],"length":1,"stats":{"Line":0},"fn_name":"redoc_handler"},{"line":162,"address":[6322975,6323252],"length":1,"stats":{"Line":0},"fn_name":null},{"line":163,"address":[6323326,6323177],"length":1,"stats":{"Line":0},"fn_name":null},{"line":165,"address":[6323398],"length":1,"stats":{"Line":0},"fn_name":null},{"line":166,"address":[6323477],"length":1,"stats":{"Line":0},"fn_name":null},{"line":167,"address":[6323484,6323738],"length":1,"stats":{"Line":0},"fn_name":null},{"line":169,"address":[6323981,6324082,6324127],"length":1,"stats":{"Line":0},"fn_name":null},{"line":171,"address":[6324052],"length":1,"stats":{"Line":0},"fn_name":null},{"line":172,"address":[6326528,6326553,6324119],"length":1,"stats":{"Line":0},"fn_name":"{closure#0}"},{"line":174,"address":[6324348],"length":1,"stats":{"Line":0},"fn_name":null},{"line":175,"address":[6326379],"length":1,"stats":{"Line":0},"fn_name":null},{"line":178,"address":[6324170],"length":1,"stats":{"Line":0},"fn_name":null},{"line":179,"address":[6324448,6324375],"length":1,"stats":{"Line":0},"fn_name":null},{"line":180,"address":[6324567],"length":1,"stats":{"Line":0},"fn_name":null},{"line":181,"address":[6324456],"length":1,"stats":{"Line":0},"fn_name":null},{"line":182,"address":[6324494],"length":1,"stats":{"Line":0},"fn_name":null},{"line":184,"address":[6325183],"length":1,"stats":{"Line":0},"fn_name":null},{"line":185,"address":[6324637],"length":1,"stats":{"Line":0},"fn_name":null},{"line":186,"address":[6325012,6324804],"length":1,"stats":{"Line":0},"fn_name":null},{"line":190,"address":[6326285,6325621,6325316],"length":1,"stats":{"Line":0},"fn_name":null},{"line":191,"address":[6326263,6325691],"length":1,"stats":{"Line":0},"fn_name":null},{"line":192,"address":[6326232,6325872],"length":1,"stats":{"Line":0},"fn_name":null},{"line":193,"address":[6326095],"length":1,"stats":{"Line":0},"fn_name":null},{"line":197,"address":[6327219,6326624],"length":1,"stats":{"Line":0},"fn_name":"handle"},{"line":198,"address":[6326715,6327092,6326641],"length":1,"stats":{"Line":0},"fn_name":null},{"line":199,"address":[6327043,6326782],"length":1,"stats":{"Line":0},"fn_name":null},{"line":200,"address":[6326912,6327140],"length":1,"stats":{"Line":0},"fn_name":null}],"covered":32,"coverable":69},{"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::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\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 operation_id = E::operation_id().or_else(|| {\n\t\t\tE::operation_verb()\n\t\t\t\t.map(|verb| format!(\"{verb}_{}\", path.replace(\"/\", \"_\").trim_start_matches('_')))\n\t\t});\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":[6182771,6182208,6182815],"length":1,"stats":{"Line":1},"fn_name":"new_parameter_data"},{"line":25,"address":[6182297,6182370],"length":1,"stats":{"Line":2},"fn_name":null},{"line":27,"address":[6182380],"length":1,"stats":{"Line":1},"fn_name":null},{"line":29,"address":[6182451],"length":1,"stats":{"Line":1},"fn_name":null},{"line":42,"address":[6183951,6182832,6183980],"length":1,"stats":{"Line":2},"fn_name":null},{"line":46,"address":[6182872],"length":1,"stats":{"Line":2},"fn_name":null},{"line":47,"address":[6182932],"length":1,"stats":{"Line":1},"fn_name":null},{"line":50,"address":[6183002,6183040],"length":1,"stats":{"Line":2},"fn_name":null},{"line":51,"address":[6183098],"length":1,"stats":{"Line":1},"fn_name":null},{"line":54,"address":[6183173,6183320,6184371],"length":1,"stats":{"Line":3},"fn_name":null},{"line":55,"address":[6183577,6184048],"length":1,"stats":{"Line":2},"fn_name":null},{"line":56,"address":[6184251],"length":1,"stats":{"Line":1},"fn_name":null},{"line":57,"address":[6184075],"length":1,"stats":{"Line":1},"fn_name":null},{"line":58,"address":[6184192],"length":1,"stats":{"Line":1},"fn_name":null},{"line":65,"address":[6185743,6185772,6184624],"length":1,"stats":{"Line":2},"fn_name":null},{"line":69,"address":[6184664],"length":1,"stats":{"Line":2},"fn_name":null},{"line":70,"address":[6184724],"length":1,"stats":{"Line":1},"fn_name":null},{"line":73,"address":[6184794,6184832],"length":1,"stats":{"Line":2},"fn_name":null},{"line":74,"address":[6184890],"length":1,"stats":{"Line":1},"fn_name":null},{"line":77,"address":[6184965,6185112,6186193],"length":1,"stats":{"Line":3},"fn_name":null},{"line":78,"address":[6185369,6185840],"length":1,"stats":{"Line":2},"fn_name":null},{"line":79,"address":[6186051],"length":1,"stats":{"Line":1},"fn_name":null},{"line":80,"address":[6185867],"length":1,"stats":{"Line":1},"fn_name":null},{"line":82,"address":[6185984],"length":1,"stats":{"Line":1},"fn_name":null},{"line":83,"address":[6186043],"length":1,"stats":{"Line":1},"fn_name":null},{"line":88,"address":[6186448,6186753,6186790],"length":1,"stats":{"Line":2},"fn_name":null},{"line":89,"address":[6186470],"length":1,"stats":{"Line":2},"fn_name":null},{"line":90,"address":[6186560],"length":1,"stats":{"Line":2},"fn_name":null},{"line":91,"address":[6186652],"length":1,"stats":{"Line":2},"fn_name":null},{"line":92,"address":[6186719],"length":1,"stats":{"Line":2},"fn_name":null},{"line":111,"address":[7275635,7274784,7275563],"length":1,"stats":{"Line":8},"fn_name":null},{"line":115,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":116,"address":[],"length":0,"stats":{"Line":12},"fn_name":null},{"line":117,"address":[],"length":0,"stats":{"Line":22},"fn_name":null},{"line":121,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":123,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":125,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":128,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":132,"address":[6186832,6186908],"length":1,"stats":{"Line":1},"fn_name":null},{"line":133,"address":[6186868,6186947],"length":1,"stats":{"Line":2},"fn_name":null},{"line":136,"address":[6187066,6186976],"length":1,"stats":{"Line":1},"fn_name":null},{"line":137,"address":[6187012,6187105],"length":1,"stats":{"Line":2},"fn_name":null},{"line":140,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":141,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":142,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":145,"address":[6187136,6188377],"length":1,"stats":{"Line":3},"fn_name":null},{"line":149,"address":[6187178],"length":1,"stats":{"Line":3},"fn_name":null},{"line":150,"address":[6187341,6187252],"length":1,"stats":{"Line":6},"fn_name":null},{"line":151,"address":[6187971,6187615],"length":1,"stats":{"Line":6},"fn_name":null},{"line":152,"address":[6187881,6187793],"length":1,"stats":{"Line":6},"fn_name":null},{"line":153,"address":[6187891],"length":1,"stats":{"Line":3},"fn_name":null},{"line":156,"address":[6187635],"length":1,"stats":{"Line":3},"fn_name":null},{"line":159,"address":[6192442,6188416,6192184],"length":1,"stats":{"Line":2},"fn_name":null},{"line":161,"address":[6188483,6188837],"length":1,"stats":{"Line":4},"fn_name":null},{"line":162,"address":[6189084],"length":1,"stats":{"Line":2},"fn_name":null},{"line":163,"address":[6189124],"length":1,"stats":{"Line":2},"fn_name":null},{"line":164,"address":[6189164],"length":1,"stats":{"Line":2},"fn_name":null},{"line":165,"address":[6189196],"length":1,"stats":{"Line":2},"fn_name":null},{"line":166,"address":[6189252],"length":1,"stats":{"Line":2},"fn_name":null},{"line":167,"address":[6189285],"length":1,"stats":{"Line":2},"fn_name":null},{"line":168,"address":[6189303],"length":1,"stats":{"Line":2},"fn_name":null},{"line":169,"address":[6189343],"length":1,"stats":{"Line":2},"fn_name":null},{"line":171,"address":[6188563],"length":1,"stats":{"Line":2},"fn_name":null},{"line":172,"address":[6188593],"length":1,"stats":{"Line":2},"fn_name":null},{"line":173,"address":[6188623],"length":1,"stats":{"Line":2},"fn_name":null},{"line":174,"address":[6188653],"length":1,"stats":{"Line":2},"fn_name":null},{"line":175,"address":[6188733],"length":1,"stats":{"Line":2},"fn_name":null},{"line":176,"address":[6188745],"length":1,"stats":{"Line":2},"fn_name":null},{"line":177,"address":[6188797],"length":1,"stats":{"Line":2},"fn_name":null},{"line":178,"address":[6188827],"length":1,"stats":{"Line":2},"fn_name":null},{"line":181,"address":[6189361,6189497],"length":1,"stats":{"Line":4},"fn_name":null},{"line":183,"address":[6192832,6192875,6189489,6194007,6193954],"length":1,"stats":{"Line":6},"fn_name":"{closure#0}"},{"line":185,"address":[6192935,6193010],"length":1,"stats":{"Line":4},"fn_name":null},{"line":187,"address":[6193098,6193156],"length":1,"stats":{"Line":4},"fn_name":null},{"line":188,"address":[6193395],"length":1,"stats":{"Line":2},"fn_name":null},{"line":189,"address":[6193174],"length":1,"stats":{"Line":2},"fn_name":null},{"line":191,"address":[6194070,6194048],"length":1,"stats":{"Line":4},"fn_name":"{closure#0}"},{"line":193,"address":[6193248],"length":1,"stats":{"Line":2},"fn_name":null},{"line":194,"address":[6193344],"length":1,"stats":{"Line":2},"fn_name":null},{"line":200,"address":[6194581,6194096,6189559],"length":1,"stats":{"Line":3},"fn_name":"{closure#1}"},{"line":201,"address":[6194118,6194351],"length":1,"stats":{"Line":2},"fn_name":null},{"line":202,"address":[6194134,6194226],"length":1,"stats":{"Line":2},"fn_name":null},{"line":204,"address":[6194295],"length":1,"stats":{"Line":1},"fn_name":null},{"line":208,"address":[6189723],"length":1,"stats":{"Line":2},"fn_name":null},{"line":209,"address":[6189741,6190408],"length":1,"stats":{"Line":3},"fn_name":null},{"line":210,"address":[6189775],"length":1,"stats":{"Line":1},"fn_name":null},{"line":211,"address":[6189899,6189826,6192537],"length":1,"stats":{"Line":2},"fn_name":null},{"line":212,"address":[6190048],"length":1,"stats":{"Line":1},"fn_name":null},{"line":216,"address":[6189745],"length":1,"stats":{"Line":2},"fn_name":null},{"line":219,"address":[6190493],"length":1,"stats":{"Line":2},"fn_name":null},{"line":221,"address":[6190785],"length":1,"stats":{"Line":2},"fn_name":null}],"covered":91,"coverable":91},{"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":[7172706,7171650,7172384,7171328],"length":1,"stats":{"Line":2},"fn_name":null},{"line":44,"address":[7171429,7172411,7172485,7171355],"length":1,"stats":{"Line":4},"fn_name":null},{"line":45,"address":[7171434,7172490,7172508,7171452],"length":1,"stats":{"Line":4},"fn_name":null},{"line":47,"address":[7171825,7172503,7173100,7173008,7173153,7171680,7171447,7171772],"length":1,"stats":{"Line":5},"fn_name":"{closure#0}<(), (), openapi_supports_scope::openapi_supports_scope::{closure#0}::{closure#0}::{closure#0}::{closure_env#0}>"},{"line":48,"address":[7172112,7172369,7172568,7171512,7172736,7172993],"length":1,"stats":{"Line":4},"fn_name":"{closure#1}<(), (), openapi_supports_scope::openapi_supports_scope::{closure#0}::{closure#0}::{closure#0}::{closure_env#0}>"},{"line":49,"address":[7172137,7172304,7172761,7172928],"length":1,"stats":{"Line":4},"fn_name":null},{"line":51,"address":[7172771,7172209,7172833,7172147],"length":1,"stats":{"Line":4},"fn_name":null},{"line":52,"address":[7172300,7172924],"length":1,"stats":{"Line":2},"fn_name":null},{"line":54,"address":[7172334,7172958],"length":1,"stats":{"Line":2},"fn_name":null},{"line":64,"address":[7174338,7174160,7174316],"length":1,"stats":{"Line":2},"fn_name":"openapi_spec<(), ()>"},{"line":65,"address":[7174190,7174289],"length":1,"stats":{"Line":4},"fn_name":null},{"line":67,"address":[7174268],"length":1,"stats":{"Line":2},"fn_name":null},{"line":68,"address":[7174218],"length":1,"stats":{"Line":2},"fn_name":null},{"line":84,"address":[7174352,7174496],"length":1,"stats":{"Line":6},"fn_name":"resource<(), (), openapi_supports_scope::FooResource>"},{"line":85,"address":[7174637,7174493,7174520,7174376],"length":1,"stats":{"Line":7},"fn_name":null},{"line":86,"address":[7174590,7174446],"length":1,"stats":{"Line":1},"fn_name":null},{"line":88,"address":[7174550,7174406],"length":1,"stats":{"Line":6},"fn_name":null},{"line":98,"address":[7174640,7180097,7187137,7181680,7187761,7180721],"length":1,"stats":{"Line":9},"fn_name":"endpoint<(), (), openapi_supports_scope::read_all___gotham_restful_endpoint>"},{"line":99,"address":[7181733,7174693],"length":1,"stats":{"Line":9},"fn_name":null},{"line":100,"address":[7174842,7181882,7174906,7181946],"length":1,"stats":{"Line":18},"fn_name":null},{"line":101,"address":[7187705,7180665],"length":1,"stats":{"Line":9},"fn_name":null},{"line":103,"address":[7182169,7175129,7180630,7187670],"length":1,"stats":{"Line":18},"fn_name":null},{"line":106,"address":[7175173,7175358,7182213,7182505,7175465,7182398],"length":1,"stats":{"Line":27},"fn_name":null},{"line":107,"address":[7180581,7175599,7187621,7182639],"length":1,"stats":{"Line":9},"fn_name":null},{"line":108,"address":[7175798,7175860,7182900,7182838],"length":1,"stats":{"Line":18},"fn_name":null},{"line":109,"address":[7182920,7175880],"length":1,"stats":{"Line":4},"fn_name":null},{"line":111,"address":[7175866,7175935,7182975,7182906],"length":1,"stats":{"Line":18},"fn_name":null},{"line":112,"address":[7182995,7175955],"length":1,"stats":{"Line":1},"fn_name":null},{"line":114,"address":[7183050,7176010,7175941,7182981],"length":1,"stats":{"Line":18},"fn_name":null},{"line":115,"address":[7183079,7176039],"length":1,"stats":{"Line":2},"fn_name":null},{"line":116,"address":[7183164,7176124],"length":1,"stats":{"Line":2},"fn_name":null},{"line":119,"address":[7176133,7183268,7176024,7183173,7176228,7183064],"length":1,"stats":{"Line":27},"fn_name":null},{"line":121,"address":[6264233,6264736,6265231,6264761,6264208,6264353,6264703,6264881],"length":1,"stats":{"Line":62},"fn_name":"{closure#0}"},{"line":122,"address":[7188610,7181506],"length":1,"stats":{"Line":4},"fn_name":null},{"line":124,"address":[7176332,7183372,7184046,7176427,7177006,7183467],"length":1,"stats":{"Line":23},"fn_name":null},{"line":125,"address":[7176754,7183794],"length":1,"stats":{"Line":5},"fn_name":null},{"line":128,"address":[7177019,7184059,7183575,7176535],"length":1,"stats":{"Line":18},"fn_name":null},{"line":129,"address":[7184178,7184067,7177027,7177138],"length":1,"stats":{"Line":18},"fn_name":null},{"line":130,"address":[7177285,7184277,7177237,7184325],"length":1,"stats":{"Line":18},"fn_name":null},{"line":131,"address":[7177545,7178461,7184585,7185501],"length":1,"stats":{"Line":7},"fn_name":null},{"line":132,"address":[7177781,7184821,7178686,7185726],"length":1,"stats":{"Line":1},"fn_name":null},{"line":133,"address":[7184703,7185612,7178572,7177663],"length":1,"stats":{"Line":0},"fn_name":null},{"line":134,"address":[7177899,7178800,7185840,7184939],"length":1,"stats":{"Line":0},"fn_name":null},{"line":135,"address":[7185357,7184467,7177427,7178317],"length":1,"stats":{"Line":0},"fn_name":null},{"line":136,"address":[7178017,7178914,7185954,7185057],"length":1,"stats":{"Line":0},"fn_name":null},{"line":137,"address":[7186182,7178217,7179142,7185257],"length":1,"stats":{"Line":1},"fn_name":null},{"line":138,"address":[7179028,7185157,7178117,7186068],"length":1,"stats":{"Line":0},"fn_name":null},{"line":139,"address":[7177339,7184379],"length":1,"stats":{"Line":0},"fn_name":null},{"line":140,"address":[7177379,7179307,7186417,7186549,7179509,7186347,7184419,7179377],"length":1,"stats":{"Line":0},"fn_name":null},{"line":143,"address":[7186691,7179651],"length":1,"stats":{"Line":9},"fn_name":null},{"line":145,"address":[7186905,7179865],"length":1,"stats":{"Line":9},"fn_name":null}],"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":[6883520,6883424],"length":1,"stats":{"Line":0},"fn_name":null},{"line":14,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":22,"address":[6486752],"length":1,"stats":{"Line":0},"fn_name":"into_response_error"},{"line":23,"address":[8010518],"length":1,"stats":{"Line":0},"fn_name":null},{"line":25,"address":[8255952],"length":1,"stats":{"Line":0},"fn_name":null},{"line":26,"address":[8255987],"length":1,"stats":{"Line":0},"fn_name":null},{"line":31,"address":[6146960],"length":1,"stats":{"Line":1},"fn_name":"status_codes"},{"line":32,"address":[8256200,8256125],"length":1,"stats":{"Line":1},"fn_name":null},{"line":36,"address":[7801584],"length":1,"stats":{"Line":1},"fn_name":"schema"},{"line":38,"address":[6147363],"length":1,"stats":{"Line":1},"fn_name":null},{"line":96,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":97,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"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":[6115648],"length":1,"stats":{"Line":3},"fn_name":"or_all_types"},{"line":47,"address":[6292336,6292349],"length":1,"stats":{"Line":9},"fn_name":"{closure#0}"},{"line":87,"address":[6292928,6292496,6293360,6293779,6292856,6293751,6292884,6293320,6293292],"length":1,"stats":{"Line":8},"fn_name":null},{"line":90,"address":[6292991,6293451,6292559],"length":1,"stats":{"Line":9},"fn_name":null},{"line":92,"address":[6292697,6293133,6293592],"length":1,"stats":{"Line":9},"fn_name":null},{"line":98,"address":[6293808,6294205,6294230],"length":1,"stats":{"Line":2},"fn_name":null},{"line":101,"address":[6293848],"length":1,"stats":{"Line":2},"fn_name":null},{"line":102,"address":[6293869],"length":1,"stats":{"Line":2},"fn_name":null},{"line":103,"address":[6294046],"length":1,"stats":{"Line":3},"fn_name":null},{"line":109,"address":[6294466,6294256,6294441],"length":1,"stats":{"Line":3},"fn_name":null},{"line":112,"address":[6294277],"length":1,"stats":{"Line":4},"fn_name":null},{"line":114,"address":[6294291],"length":1,"stats":{"Line":4},"fn_name":null},{"line":120,"address":[6294665,6294690,6294480],"length":1,"stats":{"Line":0},"fn_name":null},{"line":123,"address":[6294501],"length":1,"stats":{"Line":0},"fn_name":null},{"line":125,"address":[6294515],"length":1,"stats":{"Line":0},"fn_name":null},{"line":130,"address":[6294704],"length":1,"stats":{"Line":1},"fn_name":null},{"line":131,"address":[6294709],"length":1,"stats":{"Line":1},"fn_name":null},{"line":135,"address":[6294720],"length":1,"stats":{"Line":1},"fn_name":null},{"line":136,"address":[6294725],"length":1,"stats":{"Line":1},"fn_name":null},{"line":140,"address":[6294736],"length":1,"stats":{"Line":2},"fn_name":null},{"line":141,"address":[6294754],"length":1,"stats":{"Line":2},"fn_name":null},{"line":144,"address":[6294800,6295005],"length":1,"stats":{"Line":4},"fn_name":null},{"line":145,"address":[6294958,6294832],"length":1,"stats":{"Line":8},"fn_name":null},{"line":146,"address":[6294985],"length":1,"stats":{"Line":4},"fn_name":null},{"line":150,"address":[6295024,6295423,6295444],"length":1,"stats":{"Line":1},"fn_name":null},{"line":156,"address":[6295321,6295054,6295111],"length":1,"stats":{"Line":6},"fn_name":null},{"line":157,"address":[6295337],"length":1,"stats":{"Line":2},"fn_name":null},{"line":164,"address":[6295472],"length":1,"stats":{"Line":0},"fn_name":"into_response"},{"line":165,"address":[6295491],"length":1,"stats":{"Line":0},"fn_name":null},{"line":179,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":180,"address":[],"length":0,"stats":{"Line":7},"fn_name":null},{"line":188,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":189,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":227,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":230,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":236,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":237,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":243,"address":[6300028,6298044,6298064,6299056,6298016,6297072,6300000,6296080,6299008,6297024,6299036,6297052],"length":1,"stats":{"Line":1},"fn_name":"handle_error"},{"line":247,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":248,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":249,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":250,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":251,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":252,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":253,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":255,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":257,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":266,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":267,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":270,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":271,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":280,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":281,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":284,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":285,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"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":[6147392],"length":1,"stats":{"Line":0},"fn_name":"from"},{"line":43,"address":[6147404],"length":1,"stats":{"Line":0},"fn_name":null},{"line":49,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":50,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":54,"address":[7801792],"length":1,"stats":{"Line":0},"fn_name":null},{"line":64,"address":[7802083,7801808,7802112],"length":1,"stats":{"Line":2},"fn_name":"into_response"},{"line":65,"address":[6147500,6147577],"length":1,"stats":{"Line":6},"fn_name":null},{"line":68,"address":[6147792],"length":1,"stats":{"Line":3},"fn_name":"accepted_types"},{"line":69,"address":[6147805],"length":1,"stats":{"Line":5},"fn_name":null},{"line":75,"address":[6147856],"length":1,"stats":{"Line":2},"fn_name":"status_codes"},{"line":76,"address":[6147869,6147943],"length":1,"stats":{"Line":2},"fn_name":null},{"line":80,"address":[7802304],"length":1,"stats":{"Line":2},"fn_name":"schema"},{"line":82,"address":[6148259],"length":1,"stats":{"Line":2},"fn_name":null},{"line":92,"address":[6148288],"length":1,"stats":{"Line":1},"fn_name":"into_response"},{"line":95,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":96,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":97,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":101,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":102,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":111,"address":[6148515,6148400],"length":1,"stats":{"Line":1},"fn_name":"status_codes"},{"line":112,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":113,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":114,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":117,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":118,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":119,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":120,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"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":[6903904],"length":1,"stats":{"Line":7},"fn_name":null},{"line":58,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":59,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":67,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":68,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":73,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":75,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":76,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":85,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":93,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":94,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":104,"address":[6647872],"length":1,"stats":{"Line":5},"fn_name":"into_response<&str>"},{"line":107,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":116,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":118,"address":[6648419],"length":1,"stats":{"Line":3},"fn_name":null},{"line":129,"address":[6148544],"length":1,"stats":{"Line":1},"fn_name":"into_response<&str, gotham_restful::response::raw::test::MsgError>"},{"line":130,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":131,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":132,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":143,"address":[6148771,6148656],"length":1,"stats":{"Line":1},"fn_name":"status_codes"},{"line":144,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":145,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":146,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":149,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":150,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":151,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":152,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"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":[7746992],"length":1,"stats":{"Line":1},"fn_name":"into_response"},{"line":46,"address":[7956993,7956190,7956116,7956247,7956791,7957031,7956160],"length":1,"stats":{"Line":3},"fn_name":"{async_block#0}"},{"line":47,"address":[6648560,6648646],"length":1,"stats":{"Line":4},"fn_name":null},{"line":48,"address":[6649290,6649200,6648558,6649333,6648691],"length":1,"stats":{"Line":2},"fn_name":null},{"line":49,"address":[8202269],"length":1,"stats":{"Line":2},"fn_name":null},{"line":57,"address":[7747968],"length":1,"stats":{"Line":1},"fn_name":"status_codes"},{"line":58,"address":[8202621,8202696],"length":1,"stats":{"Line":1},"fn_name":null},{"line":61,"address":[6649520],"length":1,"stats":{"Line":0},"fn_name":"schema"},{"line":63,"address":[8202864],"length":1,"stats":{"Line":0},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":85,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":87,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":98,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":99,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":100,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":101,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":104,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":105,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":106,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":107,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"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!(\n\t\t\t>::accepted_types()\n\t\t\t\t.or_all_types()\n\t\t\t\t.contains(&APPLICATION_JSON)\n\t\t)\n\t}\n}\n","traces":[{"line":33,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":34,"address":[6649857],"length":1,"stats":{"Line":2},"fn_name":null},{"line":35,"address":[6649880],"length":1,"stats":{"Line":2},"fn_name":null},{"line":36,"address":[6505719,6505800],"length":1,"stats":{"Line":4},"fn_name":null},{"line":37,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":38,"address":[],"length":0,"stats":{"Line":4},"fn_name":null},{"line":43,"address":[6505872],"length":1,"stats":{"Line":3},"fn_name":"status_codes"},{"line":44,"address":[6505959,6505885],"length":1,"stats":{"Line":3},"fn_name":null},{"line":48,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":50,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":61,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":62,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":63,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":64,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":68,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":69,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":79,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":80,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":81,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":82,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":85,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":86,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":87,"address":[],"length":0,"stats":{"Line":2},"fn_name":null},{"line":88,"address":[],"length":0,"stats":{"Line":2},"fn_name":null}],"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!(\n\t\t\t>::accepted_types()\n\t\t\t\t.or_all_types()\n\t\t\t\t.contains(&APPLICATION_JSON)\n\t\t)\n\t}\n}\n","traces":[{"line":54,"address":[6712704,6712974,6712752,6712832],"length":1,"stats":{"Line":3},"fn_name":"from"},{"line":57,"address":[],"length":0,"stats":{"Line":3},"fn_name":null},{"line":64,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":65,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":69,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":70,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":77,"address":[6713328,6713610,6713629,6713276,6713305,6713008],"length":1,"stats":{"Line":2},"fn_name":"into_response"},{"line":78,"address":[],"length":0,"stats":{"Line":6},"fn_name":null},{"line":79,"address":[],"length":0,"stats":{"Line":9},"fn_name":null},{"line":80,"address":[],"length":0,"stats":{"Line":5},"fn_name":null},{"line":83,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":92,"address":[],"length":0,"stats":{"Line":0},"fn_name":null}],"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":[6331168,6329312,6330309],"length":1,"stats":{"Line":5},"fn_name":"response_from"},{"line":63,"address":[6329355,6329506],"length":1,"stats":{"Line":10},"fn_name":null},{"line":64,"address":[6329583,6329514],"length":1,"stats":{"Line":10},"fn_name":null},{"line":65,"address":[6329591],"length":1,"stats":{"Line":5},"fn_name":null},{"line":66,"address":[6329912,6329705,6330154,6330290],"length":1,"stats":{"Line":8},"fn_name":null},{"line":68,"address":[6329776],"length":1,"stats":{"Line":5},"fn_name":null},{"line":69,"address":[6330362,6331695,6329788],"length":1,"stats":{"Line":10},"fn_name":null},{"line":70,"address":[6331454,6331268,6330612],"length":1,"stats":{"Line":0},"fn_name":null},{"line":71,"address":[6331300],"length":1,"stats":{"Line":0},"fn_name":null},{"line":74,"address":[6331290],"length":1,"stats":{"Line":0},"fn_name":null},{"line":75,"address":[6331559],"length":1,"stats":{"Line":0},"fn_name":null},{"line":78,"address":[6330631],"length":1,"stats":{"Line":5},"fn_name":null},{"line":79,"address":[6330948,6330660],"length":1,"stats":{"Line":10},"fn_name":null},{"line":80,"address":[6330725,6331201],"length":1,"stats":{"Line":5},"fn_name":null},{"line":84,"address":[6330710],"length":1,"stats":{"Line":5},"fn_name":null},{"line":86,"address":[6330958],"length":1,"stats":{"Line":5},"fn_name":null},{"line":89,"address":[7023376],"length":1,"stats":{"Line":21},"fn_name":"endpoint_handler"},{"line":96,"address":[],"length":0,"stats":{"Line":63},"fn_name":null},{"line":97,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":100,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":101,"address":[],"length":0,"stats":{"Line":13},"fn_name":null},{"line":103,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":105,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":106,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":107,"address":[],"length":0,"stats":{"Line":16},"fn_name":null},{"line":109,"address":[],"length":0,"stats":{"Line":16},"fn_name":null},{"line":110,"address":[],"length":0,"stats":{"Line":16},"fn_name":null},{"line":111,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":112,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":113,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":114,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":118,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":119,"address":[],"length":0,"stats":{"Line":8},"fn_name":null},{"line":120,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":121,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":122,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":123,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":124,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":125,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":126,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":130,"address":[],"length":0,"stats":{"Line":13},"fn_name":null},{"line":133,"address":[],"length":0,"stats":{"Line":43},"fn_name":null},{"line":134,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":135,"address":[],"length":0,"stats":{"Line":80},"fn_name":null},{"line":136,"address":[],"length":0,"stats":{"Line":42},"fn_name":null},{"line":145,"address":[6331920],"length":1,"stats":{"Line":5},"fn_name":"is_match"},{"line":146,"address":[6331953],"length":1,"stats":{"Line":7},"fn_name":null},{"line":147,"address":[6332008],"length":1,"stats":{"Line":0},"fn_name":null},{"line":148,"address":[6331988],"length":1,"stats":{"Line":5},"fn_name":null},{"line":154,"address":[6332032,6332421,6332495],"length":1,"stats":{"Line":6},"fn_name":null},{"line":155,"address":[6332054],"length":1,"stats":{"Line":8},"fn_name":null},{"line":156,"address":[6332121,6332264],"length":1,"stats":{"Line":6},"fn_name":null},{"line":157,"address":[6332145],"length":1,"stats":{"Line":5},"fn_name":null},{"line":160,"address":[6332334],"length":1,"stats":{"Line":6},"fn_name":null},{"line":166,"address":[6332512],"length":1,"stats":{"Line":0},"fn_name":"from"},{"line":167,"address":[6332520],"length":1,"stats":{"Line":0},"fn_name":null},{"line":177,"address":[6332544],"length":1,"stats":{"Line":0},"fn_name":"is_match"},{"line":178,"address":[6332577],"length":1,"stats":{"Line":0},"fn_name":null},{"line":179,"address":[6332632],"length":1,"stats":{"Line":0},"fn_name":null},{"line":180,"address":[6332612],"length":1,"stats":{"Line":0},"fn_name":null},{"line":186,"address":[6332656],"length":1,"stats":{"Line":0},"fn_name":null},{"line":188,"address":[6332670,6332720,6332733],"length":1,"stats":{"Line":0},"fn_name":"{closure#0}"},{"line":194,"address":[6332784],"length":1,"stats":{"Line":0},"fn_name":"from"},{"line":195,"address":[6332792],"length":1,"stats":{"Line":0},"fn_name":null},{"line":207,"address":[7174125,7173872],"length":1,"stats":{"Line":2},"fn_name":"with_openapi<(), (), openapi_supports_scope::openapi_supports_scope::{closure#0}::{closure_env#0}>"},{"line":211,"address":[7173900,7173988],"length":1,"stats":{"Line":4},"fn_name":null},{"line":213,"address":[7173910],"length":1,"stats":{"Line":2},"fn_name":null},{"line":214,"address":[7173983,7173919],"length":1,"stats":{"Line":4},"fn_name":null},{"line":216,"address":[7174023],"length":1,"stats":{"Line":2},"fn_name":null},{"line":225,"address":[6661040],"length":1,"stats":{"Line":6},"fn_name":"resource<(), (), custom_request_body::FooResource>"},{"line":226,"address":[6661181,6661064],"length":1,"stats":{"Line":5},"fn_name":null},{"line":227,"address":[6661134],"length":1,"stats":{"Line":3},"fn_name":null},{"line":229,"address":[6661094],"length":1,"stats":{"Line":6},"fn_name":null},{"line":238,"address":[6668864,6669736],"length":1,"stats":{"Line":31},"fn_name":"endpoint<(), (), custom_request_body::create___gotham_restful_endpoint>"},{"line":239,"address":[6668884,6668966,6669043],"length":1,"stats":{"Line":62},"fn_name":null},{"line":240,"address":[6669380,6669293,6669466,6669596],"length":1,"stats":{"Line":110},"fn_name":null},{"line":241,"address":[6669710,6669391],"length":1,"stats":{"Line":93},"fn_name":null},{"line":242,"address":[7022333,7022522,7022455,7022132],"length":1,"stats":{"Line":128},"fn_name":null},{"line":243,"address":[7022158,7022338,7022919,7022366],"length":1,"stats":{"Line":62},"fn_name":null},{"line":244,"address":[7022354,7022387,7022494,7022912],"length":1,"stats":{"Line":60},"fn_name":null},{"line":250,"address":[7022569],"length":1,"stats":{"Line":29},"fn_name":null},{"line":251,"address":[7022802,7022849,7022693],"length":1,"stats":{"Line":46},"fn_name":null},{"line":253,"address":[7022775],"length":1,"stats":{"Line":16},"fn_name":null},{"line":254,"address":[7022722],"length":1,"stats":{"Line":14},"fn_name":null}],"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":[7036808,7036656],"length":1,"stats":{"Line":2},"fn_name":"from_body<()>"},{"line":61,"address":[7771552,7771616],"length":1,"stats":{"Line":4},"fn_name":null},{"line":84,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":85,"address":[],"length":0,"stats":{"Line":0},"fn_name":null},{"line":90,"address":[],"length":0,"stats":{"Line":1},"fn_name":null},{"line":91,"address":[],"length":0,"stats":{"Line":1},"fn_name":null}],"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.38028169014085,"covered":521,"coverable":710} \ No newline at end of file