diff --git a/Cargo.lock b/Cargo.lock index ded4ba85..a99f1f24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2091,17 +2091,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.53", -] - [[package]] name = "num-integer" version = "0.1.45" @@ -3473,7 +3462,7 @@ dependencies = [ [[package]] name = "tide-disco" -version = "0.7.0" +version = "0.8.0" dependencies = [ "anyhow", "ark-serialize", @@ -3499,8 +3488,6 @@ dependencies = [ "libc", "markdown", "maud", - "num-derive", - "num-traits", "parking_lot", "portpicker", "prometheus", diff --git a/Cargo.toml b/Cargo.toml index ed3a0464..048d3e16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tide-disco" -version = "0.7.0" +version = "0.8.0" edition = "2021" authors = ["Espresso Systems "] description = "Discoverability for Tide" @@ -41,8 +41,6 @@ lazy_static = "1.4" libc = "0.2" markdown = "0.3" maud = { version = "0.26", features = ["tide"] } -num-derive = "0.4" -num-traits = "0.2" parking_lot = "0.12" prometheus = "0.13" reqwest = { version = "0.12", features = ["json"] } diff --git a/examples/hello-world/main.rs b/examples/hello-world/main.rs index 2cf2cdef..f0109944 100644 --- a/examples/hello-world/main.rs +++ b/examples/hello-world/main.rs @@ -37,7 +37,7 @@ impl tide_disco::Error for HelloError { impl From for HelloError { fn from(err: RequestError) -> Self { - Self::catch_all(StatusCode::BadRequest, err.to_string()) + Self::catch_all(StatusCode::BAD_REQUEST, err.to_string()) } } @@ -116,14 +116,14 @@ mod test { let client = Client::new(url).await; let res = client.get("greeting/tester").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.json::().await.unwrap(), "Hello, tester"); let res = client.post("greeting/Sup").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); let res = client.get("greeting/tester").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.json::().await.unwrap(), "Sup, tester"); } @@ -138,7 +138,7 @@ mod test { // Check the API version. let res = client.get("hello/version").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); let api_version = ApiVersion { api_version: Some(env!("CARGO_PKG_VERSION").parse().unwrap()), spec_version: "0.1.0".parse().unwrap(), @@ -147,7 +147,7 @@ mod test { // Check the overall version. let res = client.get("version").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.json::().await.unwrap(), AppVersion { @@ -169,7 +169,7 @@ mod test { // Check the API health. let res = client.get("hello/healthcheck").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); // The example API does not have a custom healthcheck, so we just get the default response. assert_eq!( res.json::().await.unwrap(), @@ -178,12 +178,12 @@ mod test { // Check the overall health. let res = client.get("healthcheck").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.json::().await.unwrap(), AppHealth { status: HealthStatus::Available, - modules: [("hello".to_string(), [(0, StatusCode::Ok)].into())].into(), + modules: [("hello".to_string(), [(0, StatusCode::OK)].into())].into(), } ) } diff --git a/examples/versions/main.rs b/examples/versions/main.rs index bcbcfeb9..12ede17e 100644 --- a/examples/versions/main.rs +++ b/examples/versions/main.rs @@ -79,7 +79,7 @@ mod test { .unwrap() ); assert_eq!( - StatusCode::NotFound, + StatusCode::NOT_FOUND, client.get("v1/api/added").send().await.unwrap().status() ); @@ -95,7 +95,7 @@ mod test { .unwrap() ); assert_eq!( - StatusCode::NotFound, + StatusCode::NOT_FOUND, client.get("v2/api/deleted").send().await.unwrap().status() ); @@ -111,7 +111,7 @@ mod test { .unwrap() ); assert_eq!( - StatusCode::NotFound, + StatusCode::NOT_FOUND, client.get("api/deleted").send().await.unwrap().status() ); } diff --git a/src/api.rs b/src/api.rs index e9eb9141..ab499554 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1545,7 +1545,7 @@ mod test { |_req, _conn: Connection<(), (), _, StaticVer01>, _state| { async move { Err(ServerError::catch_all( - StatusCode::InternalServerError, + StatusCode::INTERNAL_SERVER_ERROR, "an error message".to_string(), )) } @@ -1669,7 +1669,7 @@ mod test { // We intentionally return a stream that never terminates, to check that simply // yielding an error causes the connection to terminate. repeat(Err(ServerError::catch_all( - StatusCode::InternalServerError, + StatusCode::INTERNAL_SERVER_ERROR, "an error message".to_string(), ))) .boxed() @@ -1737,7 +1737,7 @@ mod test { let client = Client::new(url).await; let res = client.get("/mod/healthcheck").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.json::().await.unwrap(), HealthStatus::Available @@ -1793,7 +1793,7 @@ mod test { tracing::info!("making metrics request {i}"); let expected = format!("# HELP counter count of how many times metrics have been exported\n# TYPE counter counter\ncounter {i}\n"); let res = client.get("mod/metrics").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.text().await.unwrap(), expected); } } diff --git a/src/app.rs b/src/app.rs index 3a17e94f..6443f91c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -216,7 +216,7 @@ impl App { /// Check the health of each registered module in response to a request. /// - /// The response includes a status code for each module, which will be [StatusCode::Ok] if the + /// The response includes a status code for each module, which will be [StatusCode::OK] if the /// module is healthy. Detailed health status from each module is not included in the response /// (due to type erasure) but can be queried using [module_health](Self::module_health) or by /// hitting the endpoint `GET /:module/healthcheck`. @@ -227,7 +227,7 @@ impl App { let versions_health = modules_health.entry(module.path()).or_default(); for (version, api) in &module.versions { let health = StatusCode::from(api.health(req.clone(), state).await.status()); - if health != StatusCode::Ok { + if health != StatusCode::OK { status = HealthStatus::Unhealthy; } versions_health.insert(*version, health); @@ -241,7 +241,7 @@ impl App { /// Check the health of the named module. /// - /// The resulting [Response](tide::Response) has a status code which is [StatusCode::Ok] if the + /// The resulting [Response](tide::Response) has a status code which is [StatusCode::OK] if the /// module is healthy. The response body is constructed from the results of the module's /// registered healthcheck handler. If the module does not have an explicit healthcheck /// handler, the response will be a [HealthStatus]. @@ -471,7 +471,7 @@ where br{} (api.documentation()) }; - Ok(tide::Response::builder(StatusCode::NotFound) + Ok(tide::Response::builder(StatusCode::NOT_FOUND) .body(docs.into_string()) .build()) } @@ -665,7 +665,7 @@ where // serve documentation listing the available versions. let Some(module) = req.state().modules.search(&path[1..]) else { let message = format!("No API matches /{}", path[1..].join("/")); - return Ok(Self::top_level_error(req, StatusCode::NotFound, message)); + return Ok(Self::top_level_error(req, StatusCode::NOT_FOUND, message)); }; if !module.versions.contains_key(&version) { // This version is not supported, list suported versions. @@ -695,7 +695,7 @@ where } let Some(module) = req.state().modules.search(&path) else { let message = format!("No API matches /{}", path.join("/")); - return Ok(Self::top_level_error(req, StatusCode::NotFound, message)); + return Ok(Self::top_level_error(req, StatusCode::NOT_FOUND, message)); }; let latest_version = *module.versions.last_key_value().unwrap().0; @@ -983,7 +983,7 @@ mod test { .send() .await .unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.json::().await.unwrap(), method.to_string()); } @@ -994,12 +994,12 @@ mod test { .send() .await .unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.text().await.unwrap(), "METRICS"); // Metrics without Accept header. let res = client.get("mod/test").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.text().await.unwrap(), "METRICS"); // Socket. @@ -1050,14 +1050,14 @@ mod test { let client = Client::new(url.clone()).await; let res = client.get("mod/test/a/42").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.json::<(String, String)>().await.unwrap(), ("a".to_string(), "42".to_string()) ); let res = client.get("mod/test/b/true").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.json::<(String, String)>().await.unwrap(), ("b".to_string(), "true".to_string()) @@ -1340,12 +1340,12 @@ mod test { // Test the application health. let res = client.get("healthcheck").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::ServiceUnavailable); + assert_eq!(res.status(), StatusCode::SERVICE_UNAVAILABLE); let health: AppHealth = res.json().await.unwrap(); assert_eq!(health.status, HealthStatus::Unhealthy); assert_eq!( health.modules["mod"], - [(3, StatusCode::Ok), (1, StatusCode::ServiceUnavailable)].into() + [(3, StatusCode::OK), (1, StatusCode::SERVICE_UNAVAILABLE)].into() ); } @@ -1484,7 +1484,7 @@ mod test { .get("err", |_req, _state| { async move { Err::(ServerError::catch_all( - StatusCode::InternalServerError, + StatusCode::INTERNAL_SERVER_ERROR, "err".into(), )) } @@ -1541,7 +1541,7 @@ mod test { tracing::info!("checking successful deserialization"); assert_eq!( expected, - get::(client, endpoint, StatusCode::Ok).await.unwrap() + get::(client, endpoint, StatusCode::OK).await.unwrap() ); } @@ -1556,8 +1556,8 @@ mod test { AppHealth { status: HealthStatus::Available, modules: [ - ("mod02".into(), [(0, StatusCode::Ok)].into()), - ("mod03".into(), [(0, StatusCode::Ok)].into()), + ("mod02".into(), [(0, StatusCode::OK)].into()), + ("mod03".into(), [(0, StatusCode::OK)].into()), ] .into(), }, @@ -1590,7 +1590,7 @@ mod test { endpoint: &str, ) { tracing::info!("checking deserialization fails with wrong version"); - get::(client, endpoint, StatusCode::Ok) + get::(client, endpoint, StatusCode::OK) .await .unwrap_err(); } @@ -1609,10 +1609,10 @@ mod test { tracing::info!("checking error deserialization"); tracing::info!("checking successful deserialization"); assert_eq!( - get::(client, endpoint, StatusCode::InternalServerError) + get::(client, endpoint, StatusCode::INTERNAL_SERVER_ERROR) .await .unwrap(), - ServerError::catch_all(StatusCode::InternalServerError, "err".into()) + ServerError::catch_all(StatusCode::INTERNAL_SERVER_ERROR, "err".into()) ); } @@ -1681,7 +1681,7 @@ mod test { let res = client.get("/test").send().await.unwrap(); assert_eq!( res.status(), - StatusCode::Ok, + StatusCode::OK, "{}", res.text().await.unwrap() ); @@ -1692,14 +1692,14 @@ mod test { // API-level endpoints, so that a singleton API behaves like a normal API, while app-level // stuff is reserved for non-trivial applications with more than one API. let res = client.get("/healthcheck").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.json::().await.unwrap(), HealthStatus::Available ); let res = client.get("/version").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.json::().await.unwrap(), ApiVersion { @@ -1739,7 +1739,7 @@ mod test { // Test an endpoint. let res = client.get(&format!("api/{api}/test")).send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.json::().await.unwrap(), api); // Test healthcheck. @@ -1748,7 +1748,7 @@ mod test { .send() .await .unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.json::().await.unwrap(), HealthStatus::Available @@ -1760,7 +1760,7 @@ mod test { .send() .await .unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.json::().await.unwrap().api_version.unwrap(), "0.1.0".parse().unwrap() @@ -1769,14 +1769,14 @@ mod test { // Test app-level healthcheck. let res = client.get("healthcheck").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.json::().await.unwrap(), AppHealth { status: HealthStatus::Available, modules: [ - ("api/a".into(), [(0, StatusCode::Ok)].into()), - ("api/b".into(), [(0, StatusCode::Ok)].into()), + ("api/a".into(), [(0, StatusCode::OK)].into()), + ("api/b".into(), [(0, StatusCode::OK)].into()), ] .into() } @@ -1784,7 +1784,7 @@ mod test { // Test app-level version. let res = client.get("version").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::Ok); + assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.json::().await.unwrap().modules, [ diff --git a/src/error.rs b/src/error.rs index f822ffc2..8578df92 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,11 +27,11 @@ pub trait Error: std::error::Error + Serialize + DeserializeOwned + Send + Sync fn status(&self) -> StatusCode; fn from_io_error(source: IoError) -> Self { - Self::catch_all(StatusCode::InternalServerError, source.to_string()) + Self::catch_all(StatusCode::INTERNAL_SERVER_ERROR, source.to_string()) } fn from_config_error(source: ConfigError) -> Self { - Self::catch_all(StatusCode::InternalServerError, source.to_string()) + Self::catch_all(StatusCode::INTERNAL_SERVER_ERROR, source.to_string()) } fn from_route_error(source: RouteError) -> Self { @@ -39,7 +39,7 @@ pub trait Error: std::error::Error + Serialize + DeserializeOwned + Send + Sync } fn from_request_error(source: RequestError) -> Self { - Self::catch_all(StatusCode::BadRequest, source.to_string()) + Self::catch_all(StatusCode::BAD_REQUEST, source.to_string()) } fn from_socket_error(source: SocketError) -> Self { diff --git a/src/healthcheck.rs b/src/healthcheck.rs index b5f9099f..638f378a 100644 --- a/src/healthcheck.rs +++ b/src/healthcheck.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; /// A type implementing [HealthCheck] may be returned from a healthcheck endpoint itself (via its /// [Serialize] implementation) as well as incorporated automatically into the global healthcheck /// endpoint for an app. The global healthcheck will fail if any of the module healthchecks return -/// an implementation `h` of [HealthCheck] where `h.status() != StatusCode::Ok`. +/// an implementation `h` of [HealthCheck] where `h.status() != StatusCode::OK`. /// /// We provide a standard implementation [HealthStatus] which has variants for common states an /// application might encounter. We recommend using this implementation as a standard, although it @@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize}; pub trait HealthCheck: Serialize { /// The status of this health check. /// - /// Should return [StatusCode::Ok] if the status is considered healthy, and some other status + /// Should return [StatusCode::OK] if the status is considered healthy, and some other status /// code if it is not. fn status(&self) -> StatusCode; } @@ -50,8 +50,8 @@ impl HealthCheck for HealthStatus { // Return healthy in normal states even if the state is not `Available`, so that load // balances and health monitors don't kill the service while it is starting up or // gracefully shutting down. - Self::Available | Self::Initializing | Self::ShuttingDown => StatusCode::Ok, - _ => StatusCode::ServiceUnavailable, + Self::Available | Self::Initializing | Self::ShuttingDown => StatusCode::OK, + _ => StatusCode::SERVICE_UNAVAILABLE, } } } diff --git a/src/lib.rs b/src/lib.rs index 2ab09ab9..ad5c3040 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -477,7 +477,7 @@ pub async fn healthcheck( req: tide::Request, ) -> Result { let status = req.state().health_status.read().await; - Ok(tide::Response::builder(StatusCode::Ok) + Ok(tide::Response::builder(StatusCode::OK) .content_type(mime::JSON) .body(tide::prelude::json!({"status": status.as_ref() })) .build()) diff --git a/src/listener.rs b/src/listener.rs index f3c01032..da63e6b6 100644 --- a/src/listener.rs +++ b/src/listener.rs @@ -28,7 +28,7 @@ use tide::{ /// TCP listener which accepts only a limited number of connections at a time. /// -/// This listener is based on [`tide::listener::TcpListener`] and should match the semantics of that +/// This listener is based on `tide::listener::TcpListener` and should match the semantics of that /// listener in every way, accept that when there are more simultaneous outstanding requests than /// the configured limit, excess requests will fail immediately with error code 429 (Too Many /// Requests). @@ -117,7 +117,7 @@ where } else { // Otherwise, we are rate limited. Respond immediately with an // error. - Ok(http::Response::new(StatusCode::TooManyRequests)) + Ok(http::Response::new(StatusCode::TOO_MANY_REQUESTS)) } }); @@ -227,11 +227,11 @@ mod test { // The next request gets rate limited. let res = client.get("mod/test").send().await.unwrap(); - assert_eq!(StatusCode::TooManyRequests, res.status()); + assert_eq!(StatusCode::TOO_MANY_REQUESTS, res.status()); // The other requests eventually complete successfully. for res in try_join_all(reqs).await.unwrap() { - assert_eq!(StatusCode::Ok, res.status()); + assert_eq!(StatusCode::OK, res.status()); } } } diff --git a/src/request.rs b/src/request.rs index 10965c55..8678160a 100644 --- a/src/request.rs +++ b/src/request.rs @@ -229,7 +229,7 @@ impl RequestParams { /// /// impl From for ApiError { /// fn from(err: RequestError) -> Self { - /// Self::catch_all(StatusCode::BadRequest, err.to_string()) + /// Self::catch_all(StatusCode::BAD_REQUEST, err.to_string()) /// } /// } /// diff --git a/src/route.rs b/src/route.rs index 0e24cc9c..19d64b6e 100644 --- a/src/route.rs +++ b/src/route.rs @@ -77,9 +77,9 @@ impl RouteError { pub fn status(&self) -> StatusCode { match self { Self::Request(_) | Self::UnsupportedContentType | Self::IncorrectMethod { .. } => { - StatusCode::BadRequest + StatusCode::BAD_REQUEST } - _ => StatusCode::InternalServerError, + _ => StatusCode::INTERNAL_SERVER_ERROR, } } @@ -503,7 +503,7 @@ impl Route { /// Print documentation about the route, to aid the developer when the route is not yet /// implemented. pub(crate) fn default_handler(&self) -> Result> { - Ok(tide::Response::builder(StatusCode::NotImplemented) + Ok(tide::Response::builder(StatusCode::NOT_IMPLEMENTED) .body(self.documentation().into_string()) .build()) } @@ -671,7 +671,7 @@ pub(crate) fn respond_with( _: VER, ) -> Result> { let (body, content_type) = response_body::<_, _, VER>(accept, body)?; - Ok(tide::Response::builder(StatusCode::Ok) + Ok(tide::Response::builder(StatusCode::OK) .body(body) .content_type(content_type) .build()) diff --git a/src/socket.rs b/src/socket.rs index 2a75e174..dfd42690 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -52,9 +52,9 @@ impl SocketError { pub fn status(&self) -> StatusCode { match self { Self::Request(_) | Self::UnsupportedMessageType | Self::IncorrectMethod { .. } => { - StatusCode::BadRequest + StatusCode::BAD_REQUEST } - _ => StatusCode::InternalServerError, + _ => StatusCode::INTERNAL_SERVER_ERROR, } } diff --git a/src/status.rs b/src/status.rs index d9fbb73d..4bd3b919 100644 --- a/src/status.rs +++ b/src/status.rs @@ -4,462 +4,43 @@ // You should have received a copy of the MIT License // along with the tide-disco library. If not, see . -use num_derive::FromPrimitive; -use num_traits::FromPrimitive; use serde::{Deserialize, Serialize}; -use snafu::{OptionExt, Snafu}; use std::fmt::{self, Display, Formatter}; /// Serializable HTTP status code. -/// -/// The deserialization implementation for [StatusCode] uses `deserialize_any` unnecessarily, -/// which prevents it from working with [bincode]. We define our own version without this problem. -#[repr(u16)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, FromPrimitive)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] #[serde(try_from = "u16", into = "u16")] -pub enum StatusCode { - /// 100 Continue - /// - /// This interim response indicates that everything so far is OK and that - /// the client should continue the request, or ignore the response if - /// the request is already finished. - Continue = 100, - - /// 101 Switching Protocols - /// - /// This code is sent in response to an Upgrade request header from the - /// client, and indicates the protocol the server is switching to. - SwitchingProtocols = 101, - - /// 103 Early Hints - /// - /// This status code is primarily intended to be used with the Link header, - /// letting the user agent start preloading resources while the server - /// prepares a response. - EarlyHints = 103, - - /// 200 Ok - /// - /// The request has succeeded - Ok = 200, - - /// 201 Created - /// - /// The request has succeeded and a new resource has been created as a - /// result. This is typically the response sent after POST requests, or - /// some PUT requests. - Created = 201, - - /// 202 Accepted - /// - /// The request has been received but not yet acted upon. It is - /// noncommittal, since there is no way in HTTP to later send an - /// asynchronous response indicating the outcome of the request. It is - /// intended for cases where another process or server handles the request, - /// or for batch processing. - Accepted = 202, - - /// 203 Non Authoritative Information - /// - /// This response code means the returned meta-information is not exactly - /// the same as is available from the origin server, but is collected - /// from a local or a third-party copy. This is mostly used for mirrors - /// or backups of another resource. Except for that specific case, the - /// "200 OK" response is preferred to this status. - NonAuthoritativeInformation = 203, - - /// 204 No Content - /// - /// There is no content to send for this request, but the headers may be - /// useful. The user-agent may update its cached headers for this - /// resource with the new ones. - NoContent = 204, - - /// 205 Reset Content - /// - /// Tells the user-agent to reset the document which sent this request. - ResetContent = 205, - - /// 206 Partial Content - /// - /// This response code is used when the Range header is sent from the client - /// to request only part of a resource. - PartialContent = 206, - - /// 207 Multi-Status - /// - /// A Multi-Status response conveys information about - /// multiple resources in situations where multiple - /// status codes might be appropriate. - MultiStatus = 207, - - /// 226 Im Used - /// - /// The server has fulfilled a GET request for the resource, and the - /// response is a representation of the result of one or more - /// instance-manipulations applied to the current instance. - ImUsed = 226, - - /// 300 Multiple Choice - /// - /// The request has more than one possible response. The user-agent or user - /// should choose one of them. (There is no standardized way of choosing - /// one of the responses, but HTML links to the possibilities are - /// recommended so the user can pick.) - MultipleChoice = 300, - - /// 301 Moved Permanently - /// - /// The URL of the requested resource has been changed permanently. The new - /// URL is given in the response. - MovedPermanently = 301, - - /// 302 Found - /// - /// This response code means that the URI of requested resource has been - /// changed temporarily. Further changes in the URI might be made in the - /// future. Therefore, this same URI should be used by the client in - /// future requests. - Found = 302, - - /// 303 See Other - /// - /// The server sent this response to direct the client to get the requested - /// resource at another URI with a GET request. - SeeOther = 303, - - /// 304 Not Modified - /// - /// This is used for caching purposes. It tells the client that the response - /// has not been modified, so the client can continue to use the same - /// cached version of the response. - NotModified = 304, - - /// 307 Temporary Redirect - /// - /// The server sends this response to direct the client to get the requested - /// resource at another URI with same method that was used in the prior - /// request. This has the same semantics as the 302 Found HTTP response - /// code, with the exception that the user agent must not change the - /// HTTP method used: If a POST was used in the first request, a POST must - /// be used in the second request. - TemporaryRedirect = 307, - - /// 308 Permanent Redirect - /// - /// This means that the resource is now permanently located at another URI, - /// specified by the Location: HTTP Response header. This has the same - /// semantics as the 301 Moved Permanently HTTP response code, with the - /// exception that the user agent must not change the HTTP method - /// used: If a POST was used in the first request, a POST must be used in - /// the second request. - PermanentRedirect = 308, - - /// 400 Bad Request - /// - /// The server could not understand the request due to invalid syntax. - BadRequest = 400, - - /// 401 Unauthorized - /// - /// Although the HTTP standard specifies "unauthorized", semantically this - /// response means "unauthenticated". That is, the client must - /// authenticate itself to get the requested response. - Unauthorized = 401, - - /// 402 Payment Required - /// - /// This response code is reserved for future use. The initial aim for - /// creating this code was using it for digital payment systems, however - /// this status code is used very rarely and no standard convention - /// exists. - PaymentRequired = 402, - - /// 403 Forbidden - /// - /// The client does not have access rights to the content; that is, it is - /// unauthorized, so the server is refusing to give the requested - /// resource. Unlike 401, the client's identity is known to the server. - Forbidden = 403, - - /// 404 Not Found - /// - /// The server can not find requested resource. In the browser, this means - /// the URL is not recognized. In an API, this can also mean that the - /// endpoint is valid but the resource itself does not exist. Servers - /// may also send this response instead of 403 to hide the existence of - /// a resource from an unauthorized client. This response code is probably - /// the most famous one due to its frequent occurrence on the web. - NotFound = 404, - - /// 405 Method Not Allowed - /// - /// The request method is known by the server but has been disabled and - /// cannot be used. For example, an API may forbid DELETE-ing a - /// resource. The two mandatory methods, GET and HEAD, must never be - /// disabled and should not return this error code. - MethodNotAllowed = 405, - - /// 406 Not Acceptable - /// - /// This response is sent when the web server, after performing - /// server-driven content negotiation, doesn't find any content that - /// conforms to the criteria given by the user agent. - NotAcceptable = 406, - - /// 407 Proxy Authentication Required - /// - /// This is similar to 401 but authentication is needed to be done by a - /// proxy. - ProxyAuthenticationRequired = 407, - - /// 408 Request Timeout - /// - /// This response is sent on an idle connection by some servers, even - /// without any previous request by the client. It means that the server - /// would like to shut down this unused connection. This response is - /// used much more since some browsers, like Chrome, Firefox 27+, - /// or IE9, use HTTP pre-connection mechanisms to speed up surfing. Also - /// note that some servers merely shut down the connection without - /// sending this message. - RequestTimeout = 408, - - /// 409 Conflict - /// - /// This response is sent when a request conflicts with the current state of - /// the server. - Conflict = 409, - - /// 410 Gone - /// - /// This response is sent when the requested content has been permanently - /// deleted from server, with no forwarding address. Clients are - /// expected to remove their caches and links to the resource. The HTTP - /// specification intends this status code to be used for "limited-time, - /// promotional services". APIs should not feel compelled to indicate - /// resources that have been deleted with this status code. - Gone = 410, - - /// 411 Length Required - /// - /// Server rejected the request because the Content-Length header field is - /// not defined and the server requires it. - LengthRequired = 411, - - /// 412 Precondition Failed - /// - /// The client has indicated preconditions in its headers which the server - /// does not meet. - PreconditionFailed = 412, - - /// 413 Payload Too Large - /// - /// Request entity is larger than limits defined by server; the server might - /// close the connection or return an Retry-After header field. - PayloadTooLarge = 413, - - /// 414 URI Too Long - /// - /// The URI requested by the client is longer than the server is willing to - /// interpret. - UriTooLong = 414, - - /// 415 Unsupported Media Type - /// - /// The media format of the requested data is not supported by the server, - /// so the server is rejecting the request. - UnsupportedMediaType = 415, - - /// 416 Requested Range Not Satisfiable - /// - /// The range specified by the Range header field in the request can't be - /// fulfilled; it's possible that the range is outside the size of the - /// target URI's data. - RequestedRangeNotSatisfiable = 416, - - /// 417 Expectation Failed - /// - /// This response code means the expectation indicated by the Expect request - /// header field can't be met by the server. - ExpectationFailed = 417, - /// - /// 418 I'm a teapot - /// - /// The server refuses the attempt to brew coffee with a teapot. - ImATeapot = 418, - - /// 421 Misdirected Request - /// - /// The request was directed at a server that is not able to produce a - /// response. This can be sent by a server that is not configured to - /// produce responses for the combination of scheme and authority that - /// are included in the request URI. - MisdirectedRequest = 421, - - /// 422 Unprocessable Entity - /// - /// The request was well-formed but was unable to be followed due to - /// semantic errors. - UnprocessableEntity = 422, - - /// 423 Locked - /// - /// The resource that is being accessed is locked. - Locked = 423, - - /// 424 Failed Dependency - /// - /// The request failed because it depended on another request and that - /// request failed (e.g., a PROPPATCH). - FailedDependency = 424, - - /// 425 Too Early - /// - /// Indicates that the server is unwilling to risk processing a request that - /// might be replayed. - TooEarly = 425, - - /// 426 Upgrade Required - /// - /// The server refuses to perform the request using the current protocol but - /// might be willing to do so after the client upgrades to a different - /// protocol. The server sends an Upgrade header in a 426 response to - /// indicate the required protocol(s). - UpgradeRequired = 426, - - /// 428 Precondition Required - /// - /// The origin server requires the request to be conditional. This response - /// is intended to prevent the 'lost update' problem, where a client - /// GETs a resource's state, modifies it, and PUTs it back to the - /// server, when meanwhile a third party has modified the state on the - /// server, leading to a conflict. - PreconditionRequired = 428, - - /// 429 Too Many Requests - /// - /// The user has sent too many requests in a given amount of time ("rate - /// limiting"). - TooManyRequests = 429, - - /// 431 Request Header Fields Too Large - /// - /// The server is unwilling to process the request because its header fields - /// are too large. The request may be resubmitted after reducing the - /// size of the request header fields. - RequestHeaderFieldsTooLarge = 431, - - /// 451 Unavailable For Legal Reasons - /// - /// The user-agent requested a resource that cannot legally be provided, - /// such as a web page censored by a government. - UnavailableForLegalReasons = 451, - - /// 500 Internal Server Error - /// - /// The server has encountered a situation it doesn't know how to handle. - InternalServerError = 500, - - /// 501 Not Implemented - /// - /// The request method is not supported by the server and cannot be handled. - /// The only methods that servers are required to support (and therefore - /// that must not return this code) are GET and HEAD. - NotImplemented = 501, - - /// 502 Bad Gateway - /// - /// This error response means that the server, while working as a gateway to - /// get a response needed to handle the request, got an invalid - /// response. - BadGateway = 502, - - /// 503 Service Unavailable - /// - /// The server is not ready to handle the request. Common causes are a - /// server that is down for maintenance or that is overloaded. Note that - /// together with this response, a user-friendly page explaining the - /// problem should be sent. This responses should be used for temporary - /// conditions and the Retry-After: HTTP header should, if possible, contain - /// the estimated time before the recovery of the service. The webmaster - /// must also take care about the caching-related headers that are sent - /// along with this response, as these temporary condition responses - /// should usually not be cached. - ServiceUnavailable = 503, - - /// 504 Gateway Timeout - /// - /// This error response is given when the server is acting as a gateway and - /// cannot get a response in time. - GatewayTimeout = 504, - - /// 505 HTTP Version Not Supported - /// - /// The HTTP version used in the request is not supported by the server. - HttpVersionNotSupported = 505, - - /// 506 Variant Also Negotiates - /// - /// The server has an internal configuration error: the chosen variant - /// resource is configured to engage in transparent content negotiation - /// itself, and is therefore not a proper end point in the negotiation - /// process. - VariantAlsoNegotiates = 506, - - /// 507 Insufficient Storage - /// - /// The server is unable to store the representation needed to complete the - /// request. - InsufficientStorage = 507, - - /// 508 Loop Detected - /// - /// The server detected an infinite loop while processing the request. - LoopDetected = 508, - - /// 510 Not Extended - /// - /// Further extensions to the request are required for the server to fulfil - /// it. - NotExtended = 510, - - /// 511 Network Authentication Required - /// - /// The 511 status code indicates that the client needs to authenticate to - /// gain network access. - NetworkAuthenticationRequired = 511, -} - -#[derive(Clone, Copy, Debug, Snafu)] -#[snafu(display("status code out of range"))] -pub struct OutOfRangeError; +pub struct StatusCode(reqwest::StatusCode); impl TryFrom for StatusCode { - type Error = OutOfRangeError; + type Error = >::Error; fn try_from(code: u16) -> Result { - Self::from_u16(code).context(OutOfRangeSnafu) + Ok(reqwest::StatusCode::try_from(code)?.into()) } } impl From for u16 { fn from(code: StatusCode) -> Self { - code as u16 + code.0.as_u16() } } -impl From for tide::StatusCode { - fn from(code: StatusCode) -> Self { - // `StatusCode` and `tide::StatusCode` have the same variants, so converting from one to - // the other through `u16` cannot fail. - u16::from(code).try_into().unwrap() +impl TryFrom for tide::StatusCode { + type Error = >::Error; + + fn try_from(code: StatusCode) -> Result { + // Tide's status code enum does not represent all possible HTTP status codes, while the + // source type (`reqwest::StatusCode`) does, so this conversion may fail. + u16::from(code).try_into() } } impl From for StatusCode { fn from(code: tide::StatusCode) -> Self { - // `StatusCode` and `tide::StatusCode` have the same variants, so converting from one to - // the other through `u16` cannot fail. + // The source type, `tide::StatusCode`, only represents valid HTTP status codes, and the + // destination type, `reqwest::StatusCode`, can represent all valid HTTP status codes, so + // this conversion will never panic. u16::from(code).try_into().unwrap() } } @@ -472,19 +53,19 @@ impl PartialEq for StatusCode { impl PartialEq for tide::StatusCode { fn eq(&self, other: &StatusCode) -> bool { - *self == Self::from(*other) + StatusCode::from(*self) == *other } } impl From for reqwest::StatusCode { fn from(code: StatusCode) -> Self { - reqwest::StatusCode::from_u16(code.into()).unwrap() + code.0 } } impl From for StatusCode { fn from(code: reqwest::StatusCode) -> Self { - code.as_u16().try_into().unwrap() + Self(code) } } @@ -502,7 +83,7 @@ impl PartialEq for reqwest::StatusCode { impl Display for StatusCode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", *self as u16) + write!(f, "{}", u16::from(*self)) } } @@ -512,7 +93,7 @@ impl StatusCode { /// If this returns `true` it indicates that the request was received, /// continuing process. pub fn is_informational(self) -> bool { - tide::StatusCode::from(self).is_informational() + self.0.is_informational() } /// Returns `true` if the status code is the `2xx` range. @@ -520,7 +101,7 @@ impl StatusCode { /// If this returns `true` it indicates that the request was successfully /// received, understood, and accepted. pub fn is_success(self) -> bool { - tide::StatusCode::from(self).is_success() + self.0.is_success() } /// Returns `true` if the status code is the `3xx` range. @@ -528,7 +109,7 @@ impl StatusCode { /// If this returns `true` it indicates that further action needs to be /// taken in order to complete the request. pub fn is_redirection(self) -> bool { - tide::StatusCode::from(self).is_redirection() + self.0.is_redirection() } /// Returns `true` if the status code is the `4xx` range. @@ -536,7 +117,7 @@ impl StatusCode { /// If this returns `true` it indicates that the request contains bad syntax /// or cannot be fulfilled. pub fn is_client_error(self) -> bool { - tide::StatusCode::from(self).is_client_error() + self.0.is_client_error() } /// Returns `true` if the status code is the `5xx` range. @@ -544,13 +125,80 @@ impl StatusCode { /// If this returns `true` it indicates that the server failed to fulfill an /// apparently valid request. pub fn is_server_error(self) -> bool { - tide::StatusCode::from(self).is_server_error() + self.0.is_server_error() } /// The canonical reason for a given status code - pub fn canonical_reason(self) -> &'static str { - tide::StatusCode::from(self).canonical_reason() + pub fn canonical_reason(self) -> Option<&'static str> { + self.0.canonical_reason() } + + pub const CONTINUE: Self = Self(reqwest::StatusCode::CONTINUE); + pub const SWITCHING_PROTOCOLS: Self = Self(reqwest::StatusCode::SWITCHING_PROTOCOLS); + pub const PROCESSING: Self = Self(reqwest::StatusCode::PROCESSING); + pub const OK: Self = Self(reqwest::StatusCode::OK); + pub const CREATED: Self = Self(reqwest::StatusCode::CREATED); + pub const ACCEPTED: Self = Self(reqwest::StatusCode::ACCEPTED); + pub const NON_AUTHORITATIVE_INFORMATION: Self = + Self(reqwest::StatusCode::NON_AUTHORITATIVE_INFORMATION); + pub const NO_CONTENT: Self = Self(reqwest::StatusCode::NO_CONTENT); + pub const RESET_CONTENT: Self = Self(reqwest::StatusCode::RESET_CONTENT); + pub const PARTIAL_CONTENT: Self = Self(reqwest::StatusCode::PARTIAL_CONTENT); + pub const MULTI_STATUS: Self = Self(reqwest::StatusCode::MULTI_STATUS); + pub const ALREADY_REPORTED: Self = Self(reqwest::StatusCode::ALREADY_REPORTED); + pub const IM_USED: Self = Self(reqwest::StatusCode::IM_USED); + pub const MULTIPLE_CHOICES: Self = Self(reqwest::StatusCode::MULTIPLE_CHOICES); + pub const MOVED_PERMANENTLY: Self = Self(reqwest::StatusCode::MOVED_PERMANENTLY); + pub const FOUND: Self = Self(reqwest::StatusCode::FOUND); + pub const SEE_OTHER: Self = Self(reqwest::StatusCode::SEE_OTHER); + pub const NOT_MODIFIED: Self = Self(reqwest::StatusCode::NOT_MODIFIED); + pub const USE_PROXY: Self = Self(reqwest::StatusCode::USE_PROXY); + pub const TEMPORARY_REDIRECT: Self = Self(reqwest::StatusCode::TEMPORARY_REDIRECT); + pub const PERMANENT_REDIRECT: Self = Self(reqwest::StatusCode::PERMANENT_REDIRECT); + pub const BAD_REQUEST: Self = Self(reqwest::StatusCode::BAD_REQUEST); + pub const UNAUTHORIZED: Self = Self(reqwest::StatusCode::UNAUTHORIZED); + pub const PAYMENT_REQUIRED: Self = Self(reqwest::StatusCode::PAYMENT_REQUIRED); + pub const FORBIDDEN: Self = Self(reqwest::StatusCode::FORBIDDEN); + pub const NOT_FOUND: Self = Self(reqwest::StatusCode::NOT_FOUND); + pub const METHOD_NOT_ALLOWED: Self = Self(reqwest::StatusCode::METHOD_NOT_ALLOWED); + pub const NOT_ACCEPTABLE: Self = Self(reqwest::StatusCode::NOT_ACCEPTABLE); + pub const PROXY_AUTHENTICATION_REQUIRED: Self = + Self(reqwest::StatusCode::PROXY_AUTHENTICATION_REQUIRED); + pub const REQUEST_TIMEOUT: Self = Self(reqwest::StatusCode::REQUEST_TIMEOUT); + pub const CONFLICT: Self = Self(reqwest::StatusCode::CONFLICT); + pub const GONE: Self = Self(reqwest::StatusCode::GONE); + pub const LENGTH_REQUIRED: Self = Self(reqwest::StatusCode::LENGTH_REQUIRED); + pub const PRECONDITION_FAILED: Self = Self(reqwest::StatusCode::PRECONDITION_FAILED); + pub const PAYLOAD_TOO_LARGE: Self = Self(reqwest::StatusCode::PAYLOAD_TOO_LARGE); + pub const URI_TOO_LONG: Self = Self(reqwest::StatusCode::URI_TOO_LONG); + pub const UNSUPPORTED_MEDIA_TYPE: Self = Self(reqwest::StatusCode::UNSUPPORTED_MEDIA_TYPE); + pub const RANGE_NOT_SATISFIABLE: Self = Self(reqwest::StatusCode::RANGE_NOT_SATISFIABLE); + pub const EXPECTATION_FAILED: Self = Self(reqwest::StatusCode::EXPECTATION_FAILED); + pub const IM_A_TEAPOT: Self = Self(reqwest::StatusCode::IM_A_TEAPOT); + pub const MISDIRECTED_REQUEST: Self = Self(reqwest::StatusCode::MISDIRECTED_REQUEST); + pub const UNPROCESSABLE_ENTITY: Self = Self(reqwest::StatusCode::UNPROCESSABLE_ENTITY); + pub const LOCKED: Self = Self(reqwest::StatusCode::LOCKED); + pub const FAILED_DEPENDENCY: Self = Self(reqwest::StatusCode::FAILED_DEPENDENCY); + pub const UPGRADE_REQUIRED: Self = Self(reqwest::StatusCode::UPGRADE_REQUIRED); + pub const PRECONDITION_REQUIRED: Self = Self(reqwest::StatusCode::PRECONDITION_REQUIRED); + pub const TOO_MANY_REQUESTS: Self = Self(reqwest::StatusCode::TOO_MANY_REQUESTS); + pub const REQUEST_HEADER_FIELDS_TOO_LARGE: Self = + Self(reqwest::StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE); + pub const UNAVAILABLE_FOR_LEGAL_REASONS: Self = + Self(reqwest::StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS); + pub const INTERNAL_SERVER_ERROR: Self = Self(reqwest::StatusCode::INTERNAL_SERVER_ERROR); + pub const NOT_IMPLEMENTED: Self = Self(reqwest::StatusCode::NOT_IMPLEMENTED); + pub const BAD_GATEWAY: Self = Self(reqwest::StatusCode::BAD_GATEWAY); + pub const SERVICE_UNAVAILABLE: Self = Self(reqwest::StatusCode::SERVICE_UNAVAILABLE); + pub const GATEWAY_TIMEOUT: Self = Self(reqwest::StatusCode::GATEWAY_TIMEOUT); + pub const HTTP_VERSION_NOT_SUPPORTED: Self = + Self(reqwest::StatusCode::HTTP_VERSION_NOT_SUPPORTED); + pub const VARIANT_ALSO_NEGOTIATES: Self = Self(reqwest::StatusCode::VARIANT_ALSO_NEGOTIATES); + pub const INSUFFICIENT_STORAGE: Self = Self(reqwest::StatusCode::INSUFFICIENT_STORAGE); + pub const LOOP_DETECTED: Self = Self(reqwest::StatusCode::LOOP_DETECTED); + pub const NOT_EXTENDED: Self = Self(reqwest::StatusCode::NOT_EXTENDED); + pub const NETWORK_AUTHENTICATION_REQUIRED: Self = + Self(reqwest::StatusCode::NETWORK_AUTHENTICATION_REQUIRED); } #[cfg(test)] @@ -561,16 +209,15 @@ mod test { type SerializerV01 = Serializer>; #[test] fn test_status_code() { - for code in 0u16.. { + for code in 100u16.. { // Iterate over all valid status codes, then break. let Ok(status) = StatusCode::try_from(code) else { break; }; // Test type conversions. - assert_eq!( - tide::StatusCode::try_from(code).unwrap(), - tide::StatusCode::from(status) - ); + if let Ok(tide_status) = tide::StatusCode::try_from(code) { + assert_eq!(tide_status, tide::StatusCode::try_from(status).unwrap()); + } assert_eq!( reqwest::StatusCode::from_u16(code).unwrap(), reqwest::StatusCode::from(status) @@ -590,26 +237,26 @@ mod test { let json = serde_json::to_string(&status).unwrap(); assert_eq!(status, serde_json::from_str::(&json).unwrap()); assert_eq!(json, code.to_string()); - assert_eq!( - json, - serde_json::to_string(&tide::StatusCode::from(status)).unwrap() - ); + if let Ok(tide_status) = tide::StatusCode::try_from(status) { + assert_eq!(json, serde_json::to_string(&tide_status).unwrap()); + } // Test display. assert_eq!(status.to_string(), code.to_string()); - assert_eq!( - status.to_string(), - tide::StatusCode::from(status).to_string() - ); + if let Ok(tide_status) = tide::StatusCode::try_from(status) { + assert_eq!(status.to_string(), tide_status.to_string()); + } // Test equality. - assert_eq!(status, tide::StatusCode::from(status)); + if let Ok(tide_status) = tide::StatusCode::try_from(status) { + assert_eq!(status, tide_status); + } assert_eq!(status, reqwest::StatusCode::from(status)); } // Now iterate over all valid _Tide_ status codes, and ensure the ycan be converted to our // `StatusCode`. - for code in 0u16.. { + for code in 100u16.. { let Ok(status) = tide::StatusCode::try_from(code) else { break; }; @@ -621,7 +268,7 @@ mod test { // Now iterate over all valid _reqwest_ status codes, and ensure the ycan be converted to // our `StatusCode`. - for code in 0u16.. { + for code in 100u16.. { let Ok(status) = reqwest::StatusCode::from_u16(code) else { break; };