1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-27 17:52:56 +01:00

non exhaustive http errors (#2161)

This commit is contained in:
Rob Ede 2021-04-14 06:07:59 +01:00 committed by GitHub
parent a9f26286f9
commit ff65f1d006
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 108 additions and 402 deletions

View File

@ -10,6 +10,7 @@
* The type parameter of `Response` no longer has a default. [#2152] * The type parameter of `Response` no longer has a default. [#2152]
* The `Message` variant of `body::Body` is now `Pin<Box<dyn MessageBody>>`. [#2152] * The `Message` variant of `body::Body` is now `Pin<Box<dyn MessageBody>>`. [#2152]
* `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152] * `BodyStream` and `SizedStream` are no longer restricted to Unpin types. [#2152]
* Error enum types are marked `#[non_exhaustive]`. [#2161]
### Removed ### Removed
* `cookies` feature flag. [#2065] * `cookies` feature flag. [#2065]
@ -28,6 +29,7 @@
[#2152]: https://github.com/actix/actix-web/pull/2152 [#2152]: https://github.com/actix/actix-web/pull/2152
[#2159]: https://github.com/actix/actix-web/pull/2159 [#2159]: https://github.com/actix/actix-web/pull/2159
[#2158]: https://github.com/actix/actix-web/pull/2158 [#2158]: https://github.com/actix/actix-web/pull/2158
[#2161]: https://github.com/actix/actix-web/pull/2161
## 3.0.0-beta.5 - 2021-04-02 ## 3.0.0-beta.5 - 2021-04-02

View File

@ -62,6 +62,7 @@ local-channel = "0.1"
once_cell = "1.5" once_cell = "1.5"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
paste = "1"
percent-encoding = "2.1" percent-encoding = "2.1"
pin-project = "1.0.0" pin-project = "1.0.0"
pin-project-lite = "0.2" pin-project-lite = "0.2"

View File

@ -140,7 +140,7 @@ impl From<ResponseBuilder> for Error {
} }
} }
#[derive(Debug, Display)] #[derive(Debug, Display, Error)]
#[display(fmt = "Unknown Error")] #[display(fmt = "Unknown Error")]
struct UnitError; struct UnitError;
@ -190,38 +190,47 @@ impl ResponseError for header::InvalidHeaderValue {
} }
} }
/// A set of errors that can occur during parsing HTTP streams /// A set of errors that can occur during parsing HTTP streams.
#[derive(Debug, Display)] #[derive(Debug, Display, Error)]
#[non_exhaustive]
pub enum ParseError { pub enum ParseError {
/// An invalid `Method`, such as `GE.T`. /// An invalid `Method`, such as `GE.T`.
#[display(fmt = "Invalid Method specified")] #[display(fmt = "Invalid Method specified")]
Method, Method,
/// An invalid `Uri`, such as `exam ple.domain`. /// An invalid `Uri`, such as `exam ple.domain`.
#[display(fmt = "Uri error: {}", _0)] #[display(fmt = "Uri error: {}", _0)]
Uri(InvalidUri), Uri(InvalidUri),
/// An invalid `HttpVersion`, such as `HTP/1.1` /// An invalid `HttpVersion`, such as `HTP/1.1`
#[display(fmt = "Invalid HTTP version specified")] #[display(fmt = "Invalid HTTP version specified")]
Version, Version,
/// An invalid `Header`. /// An invalid `Header`.
#[display(fmt = "Invalid Header provided")] #[display(fmt = "Invalid Header provided")]
Header, Header,
/// A message head is too large to be reasonable. /// A message head is too large to be reasonable.
#[display(fmt = "Message head is too large")] #[display(fmt = "Message head is too large")]
TooLarge, TooLarge,
/// A message reached EOF, but is not complete. /// A message reached EOF, but is not complete.
#[display(fmt = "Message is incomplete")] #[display(fmt = "Message is incomplete")]
Incomplete, Incomplete,
/// An invalid `Status`, such as `1337 ELITE`. /// An invalid `Status`, such as `1337 ELITE`.
#[display(fmt = "Invalid Status provided")] #[display(fmt = "Invalid Status provided")]
Status, Status,
/// A timeout occurred waiting for an IO event. /// A timeout occurred waiting for an IO event.
#[allow(dead_code)] #[allow(dead_code)]
#[display(fmt = "Timeout")] #[display(fmt = "Timeout")]
Timeout, Timeout,
/// An `io::Error` that occurred while trying to read or write to a network
/// stream. /// An `io::Error` that occurred while trying to read or write to a network stream.
#[display(fmt = "IO error: {}", _0)] #[display(fmt = "IO error: {}", _0)]
Io(io::Error), Io(io::Error),
/// Parsing a field as string failed /// Parsing a field as string failed
#[display(fmt = "UTF8 error: {}", _0)] #[display(fmt = "UTF8 error: {}", _0)]
Utf8(Utf8Error), Utf8(Utf8Error),
@ -273,17 +282,16 @@ impl From<httparse::Error> for ParseError {
} }
/// A set of errors that can occur running blocking tasks in thread pool. /// A set of errors that can occur running blocking tasks in thread pool.
#[derive(Debug, Display)] #[derive(Debug, Display, Error)]
#[display(fmt = "Blocking thread pool is gone")] #[display(fmt = "Blocking thread pool is gone")]
pub struct BlockingError; pub struct BlockingError;
impl std::error::Error for BlockingError {}
/// `InternalServerError` for `BlockingError` /// `InternalServerError` for `BlockingError`
impl ResponseError for BlockingError {} impl ResponseError for BlockingError {}
#[derive(Display, Debug)] /// A set of errors that can occur during payload parsing.
/// A set of errors that can occur during payload parsing #[derive(Debug, Display)]
#[non_exhaustive]
pub enum PayloadError { pub enum PayloadError {
/// A payload reached EOF, but is not complete. /// A payload reached EOF, but is not complete.
#[display( #[display(
@ -367,8 +375,9 @@ impl ResponseError for PayloadError {
} }
} }
#[derive(Debug, Display, From)] /// A set of errors that can occur during dispatching HTTP requests.
/// A set of errors that can occur during dispatching HTTP requests #[derive(Debug, Display, Error, From)]
#[non_exhaustive]
pub enum DispatchError { pub enum DispatchError {
/// Service error /// Service error
Service(Error), Service(Error),
@ -414,8 +423,9 @@ pub enum DispatchError {
Unknown, Unknown,
} }
/// A set of error that can occur during parsing content type /// A set of error that can occur during parsing content type.
#[derive(Debug, PartialEq, Display, Error)] #[derive(Debug, Display, Error)]
#[non_exhaustive]
pub enum ContentTypeError { pub enum ContentTypeError {
/// Can not parse content type /// Can not parse content type
#[display(fmt = "Can not parse content type")] #[display(fmt = "Can not parse content type")]
@ -426,6 +436,22 @@ pub enum ContentTypeError {
UnknownEncoding, UnknownEncoding,
} }
#[cfg(test)]
mod content_type_test_impls {
use super::*;
impl std::cmp::PartialEq for ContentTypeError {
fn eq(&self, other: &Self) -> bool {
match self {
Self::ParseError => matches!(other, ContentTypeError::ParseError),
Self::UnknownEncoding => {
matches!(other, ContentTypeError::UnknownEncoding)
}
}
}
}
}
/// Return `BadRequest` for `ContentTypeError` /// Return `BadRequest` for `ContentTypeError`
impl ResponseError for ContentTypeError { impl ResponseError for ContentTypeError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
@ -533,395 +559,72 @@ where
} }
} }
/// Helper function that creates wrapper of any error and generate *BAD macro_rules! error_helper {
/// REQUEST* response. ($name:ident, $status:ident) => {
paste::paste! {
#[doc = "Helper function that wraps any error and generates a `" $status "` response."]
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn ErrorBadRequest<T>(err: T) -> Error pub fn $name<T>(err: T) -> Error
where where
T: fmt::Debug + fmt::Display + 'static, T: fmt::Debug + fmt::Display + 'static,
{ {
InternalError::new(err, StatusCode::BAD_REQUEST).into() InternalError::new(err, StatusCode::$status).into()
}
}
}
} }
/// Helper function that creates wrapper of any error and generate error_helper!(ErrorBadRequest, BAD_REQUEST);
/// *UNAUTHORIZED* response. error_helper!(ErrorUnauthorized, UNAUTHORIZED);
#[allow(non_snake_case)] error_helper!(ErrorPaymentRequired, PAYMENT_REQUIRED);
pub fn ErrorUnauthorized<T>(err: T) -> Error error_helper!(ErrorForbidden, FORBIDDEN);
where error_helper!(ErrorNotFound, NOT_FOUND);
T: fmt::Debug + fmt::Display + 'static, error_helper!(ErrorMethodNotAllowed, METHOD_NOT_ALLOWED);
{ error_helper!(ErrorNotAcceptable, NOT_ACCEPTABLE);
InternalError::new(err, StatusCode::UNAUTHORIZED).into() error_helper!(
} ErrorProxyAuthenticationRequired,
PROXY_AUTHENTICATION_REQUIRED
/// Helper function that creates wrapper of any error and generate );
/// *PAYMENT_REQUIRED* response. error_helper!(ErrorRequestTimeout, REQUEST_TIMEOUT);
#[allow(non_snake_case)] error_helper!(ErrorConflict, CONFLICT);
pub fn ErrorPaymentRequired<T>(err: T) -> Error error_helper!(ErrorGone, GONE);
where error_helper!(ErrorLengthRequired, LENGTH_REQUIRED);
T: fmt::Debug + fmt::Display + 'static, error_helper!(ErrorPayloadTooLarge, PAYLOAD_TOO_LARGE);
{ error_helper!(ErrorUriTooLong, URI_TOO_LONG);
InternalError::new(err, StatusCode::PAYMENT_REQUIRED).into() error_helper!(ErrorUnsupportedMediaType, UNSUPPORTED_MEDIA_TYPE);
} error_helper!(ErrorRangeNotSatisfiable, RANGE_NOT_SATISFIABLE);
error_helper!(ErrorImATeapot, IM_A_TEAPOT);
/// Helper function that creates wrapper of any error and generate *FORBIDDEN* error_helper!(ErrorMisdirectedRequest, MISDIRECTED_REQUEST);
/// response. error_helper!(ErrorUnprocessableEntity, UNPROCESSABLE_ENTITY);
#[allow(non_snake_case)] error_helper!(ErrorLocked, LOCKED);
pub fn ErrorForbidden<T>(err: T) -> Error error_helper!(ErrorFailedDependency, FAILED_DEPENDENCY);
where error_helper!(ErrorUpgradeRequired, UPGRADE_REQUIRED);
T: fmt::Debug + fmt::Display + 'static, error_helper!(ErrorPreconditionFailed, PRECONDITION_FAILED);
{ error_helper!(ErrorPreconditionRequired, PRECONDITION_REQUIRED);
InternalError::new(err, StatusCode::FORBIDDEN).into() error_helper!(ErrorTooManyRequests, TOO_MANY_REQUESTS);
} error_helper!(
ErrorRequestHeaderFieldsTooLarge,
/// Helper function that creates wrapper of any error and generate *NOT FOUND* REQUEST_HEADER_FIELDS_TOO_LARGE
/// response. );
#[allow(non_snake_case)] error_helper!(
pub fn ErrorNotFound<T>(err: T) -> Error ErrorUnavailableForLegalReasons,
where UNAVAILABLE_FOR_LEGAL_REASONS
T: fmt::Debug + fmt::Display + 'static, );
{ error_helper!(ErrorExpectationFailed, EXPECTATION_FAILED);
InternalError::new(err, StatusCode::NOT_FOUND).into() error_helper!(ErrorInternalServerError, INTERNAL_SERVER_ERROR);
} error_helper!(ErrorNotImplemented, NOT_IMPLEMENTED);
error_helper!(ErrorBadGateway, BAD_GATEWAY);
/// Helper function that creates wrapper of any error and generate *METHOD NOT error_helper!(ErrorServiceUnavailable, SERVICE_UNAVAILABLE);
/// ALLOWED* response. error_helper!(ErrorGatewayTimeout, GATEWAY_TIMEOUT);
#[allow(non_snake_case)] error_helper!(ErrorHttpVersionNotSupported, HTTP_VERSION_NOT_SUPPORTED);
pub fn ErrorMethodNotAllowed<T>(err: T) -> Error error_helper!(ErrorVariantAlsoNegotiates, VARIANT_ALSO_NEGOTIATES);
where error_helper!(ErrorInsufficientStorage, INSUFFICIENT_STORAGE);
T: fmt::Debug + fmt::Display + 'static, error_helper!(ErrorLoopDetected, LOOP_DETECTED);
{ error_helper!(ErrorNotExtended, NOT_EXTENDED);
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into() error_helper!(
} ErrorNetworkAuthenticationRequired,
NETWORK_AUTHENTICATION_REQUIRED
/// Helper function that creates wrapper of any error and generate *NOT );
/// ACCEPTABLE* response.
#[allow(non_snake_case)]
pub fn ErrorNotAcceptable<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::NOT_ACCEPTABLE).into()
}
/// Helper function that creates wrapper of any error and generate *PROXY
/// AUTHENTICATION REQUIRED* response.
#[allow(non_snake_case)]
pub fn ErrorProxyAuthenticationRequired<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::PROXY_AUTHENTICATION_REQUIRED).into()
}
/// Helper function that creates wrapper of any error and generate *REQUEST
/// TIMEOUT* response.
#[allow(non_snake_case)]
pub fn ErrorRequestTimeout<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into()
}
/// Helper function that creates wrapper of any error and generate *CONFLICT*
/// response.
#[allow(non_snake_case)]
pub fn ErrorConflict<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::CONFLICT).into()
}
/// Helper function that creates wrapper of any error and generate *GONE*
/// response.
#[allow(non_snake_case)]
pub fn ErrorGone<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::GONE).into()
}
/// Helper function that creates wrapper of any error and generate *LENGTH
/// REQUIRED* response.
#[allow(non_snake_case)]
pub fn ErrorLengthRequired<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::LENGTH_REQUIRED).into()
}
/// Helper function that creates wrapper of any error and generate
/// *PAYLOAD TOO LARGE* response.
#[allow(non_snake_case)]
pub fn ErrorPayloadTooLarge<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::PAYLOAD_TOO_LARGE).into()
}
/// Helper function that creates wrapper of any error and generate
/// *URI TOO LONG* response.
#[allow(non_snake_case)]
pub fn ErrorUriTooLong<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::URI_TOO_LONG).into()
}
/// Helper function that creates wrapper of any error and generate
/// *UNSUPPORTED MEDIA TYPE* response.
#[allow(non_snake_case)]
pub fn ErrorUnsupportedMediaType<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::UNSUPPORTED_MEDIA_TYPE).into()
}
/// Helper function that creates wrapper of any error and generate
/// *RANGE NOT SATISFIABLE* response.
#[allow(non_snake_case)]
pub fn ErrorRangeNotSatisfiable<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::RANGE_NOT_SATISFIABLE).into()
}
/// Helper function that creates wrapper of any error and generate
/// *IM A TEAPOT* response.
#[allow(non_snake_case)]
pub fn ErrorImATeapot<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::IM_A_TEAPOT).into()
}
/// Helper function that creates wrapper of any error and generate
/// *MISDIRECTED REQUEST* response.
#[allow(non_snake_case)]
pub fn ErrorMisdirectedRequest<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::MISDIRECTED_REQUEST).into()
}
/// Helper function that creates wrapper of any error and generate
/// *UNPROCESSABLE ENTITY* response.
#[allow(non_snake_case)]
pub fn ErrorUnprocessableEntity<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::UNPROCESSABLE_ENTITY).into()
}
/// Helper function that creates wrapper of any error and generate
/// *LOCKED* response.
#[allow(non_snake_case)]
pub fn ErrorLocked<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::LOCKED).into()
}
/// Helper function that creates wrapper of any error and generate
/// *FAILED DEPENDENCY* response.
#[allow(non_snake_case)]
pub fn ErrorFailedDependency<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::FAILED_DEPENDENCY).into()
}
/// Helper function that creates wrapper of any error and generate
/// *UPGRADE REQUIRED* response.
#[allow(non_snake_case)]
pub fn ErrorUpgradeRequired<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::UPGRADE_REQUIRED).into()
}
/// Helper function that creates wrapper of any error and generate
/// *PRECONDITION FAILED* response.
#[allow(non_snake_case)]
pub fn ErrorPreconditionFailed<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::PRECONDITION_FAILED).into()
}
/// Helper function that creates wrapper of any error and generate
/// *PRECONDITION REQUIRED* response.
#[allow(non_snake_case)]
pub fn ErrorPreconditionRequired<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::PRECONDITION_REQUIRED).into()
}
/// Helper function that creates wrapper of any error and generate
/// *TOO MANY REQUESTS* response.
#[allow(non_snake_case)]
pub fn ErrorTooManyRequests<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::TOO_MANY_REQUESTS).into()
}
/// Helper function that creates wrapper of any error and generate
/// *REQUEST HEADER FIELDS TOO LARGE* response.
#[allow(non_snake_case)]
pub fn ErrorRequestHeaderFieldsTooLarge<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE).into()
}
/// Helper function that creates wrapper of any error and generate
/// *UNAVAILABLE FOR LEGAL REASONS* response.
#[allow(non_snake_case)]
pub fn ErrorUnavailableForLegalReasons<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS).into()
}
/// Helper function that creates wrapper of any error and generate
/// *EXPECTATION FAILED* response.
#[allow(non_snake_case)]
pub fn ErrorExpectationFailed<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::EXPECTATION_FAILED).into()
}
/// Helper function that creates wrapper of any error and
/// generate *INTERNAL SERVER ERROR* response.
#[allow(non_snake_case)]
pub fn ErrorInternalServerError<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into()
}
/// Helper function that creates wrapper of any error and
/// generate *NOT IMPLEMENTED* response.
#[allow(non_snake_case)]
pub fn ErrorNotImplemented<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::NOT_IMPLEMENTED).into()
}
/// Helper function that creates wrapper of any error and
/// generate *BAD GATEWAY* response.
#[allow(non_snake_case)]
pub fn ErrorBadGateway<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::BAD_GATEWAY).into()
}
/// Helper function that creates wrapper of any error and
/// generate *SERVICE UNAVAILABLE* response.
#[allow(non_snake_case)]
pub fn ErrorServiceUnavailable<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::SERVICE_UNAVAILABLE).into()
}
/// Helper function that creates wrapper of any error and
/// generate *GATEWAY TIMEOUT* response.
#[allow(non_snake_case)]
pub fn ErrorGatewayTimeout<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into()
}
/// Helper function that creates wrapper of any error and
/// generate *HTTP VERSION NOT SUPPORTED* response.
#[allow(non_snake_case)]
pub fn ErrorHttpVersionNotSupported<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::HTTP_VERSION_NOT_SUPPORTED).into()
}
/// Helper function that creates wrapper of any error and
/// generate *VARIANT ALSO NEGOTIATES* response.
#[allow(non_snake_case)]
pub fn ErrorVariantAlsoNegotiates<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::VARIANT_ALSO_NEGOTIATES).into()
}
/// Helper function that creates wrapper of any error and
/// generate *INSUFFICIENT STORAGE* response.
#[allow(non_snake_case)]
pub fn ErrorInsufficientStorage<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::INSUFFICIENT_STORAGE).into()
}
/// Helper function that creates wrapper of any error and
/// generate *LOOP DETECTED* response.
#[allow(non_snake_case)]
pub fn ErrorLoopDetected<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::LOOP_DETECTED).into()
}
/// Helper function that creates wrapper of any error and
/// generate *NOT EXTENDED* response.
#[allow(non_snake_case)]
pub fn ErrorNotExtended<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::NOT_EXTENDED).into()
}
/// Helper function that creates wrapper of any error and
/// generate *NETWORK AUTHENTICATION REQUIRED* response.
#[allow(non_snake_case)]
pub fn ErrorNetworkAuthenticationRequired<T>(err: T) -> Error
where
T: fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {