Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion mocktail/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ serde_json = "1"
thiserror = "2"
tokio = "1"
tokio-stream = "0"
tonic = "0.12"
tracing = "0"
url = "2"
uuid = { version = "1.16.0", features = ["fast-rng", "v7"] }
34 changes: 0 additions & 34 deletions mocktail/src/ext.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,7 @@
//! Extension traits
use bytes::{BufMut, Bytes, BytesMut};
use http::status::InvalidStatusCode;
use prost::Message;

pub trait CodeExt {
/// Creates a gRPC status code from an equivalent HTTP status code.
fn from_u16(code: u16) -> Result<tonic::Code, InvalidStatusCode>;
/// Creates a gRPC status code from an equivalent [`http::StatusCode`].
fn from_http(status_code: http::StatusCode) -> tonic::Code;
}

impl CodeExt for tonic::Code {
fn from_u16(code: u16) -> Result<tonic::Code, InvalidStatusCode> {
let status_code = http::StatusCode::from_u16(code)?;
Ok(tonic::Code::from_http(status_code))
}

fn from_http(status_code: http::StatusCode) -> tonic::Code {
match status_code {
http::StatusCode::INTERNAL_SERVER_ERROR => tonic::Code::Internal,
http::StatusCode::UNPROCESSABLE_ENTITY | http::StatusCode::BAD_REQUEST => {
tonic::Code::InvalidArgument
}
http::StatusCode::UNAUTHORIZED => tonic::Code::Unauthenticated,
http::StatusCode::FORBIDDEN => tonic::Code::PermissionDenied,
http::StatusCode::NOT_FOUND => tonic::Code::NotFound,
http::StatusCode::NOT_IMPLEMENTED => tonic::Code::Unimplemented,
http::StatusCode::TOO_MANY_REQUESTS
| http::StatusCode::BAD_GATEWAY
| http::StatusCode::SERVICE_UNAVAILABLE
| http::StatusCode::GATEWAY_TIMEOUT => tonic::Code::Unavailable,
http::StatusCode::OK => tonic::Code::Ok,
_ => tonic::Code::Unknown,
}
}
}

pub trait MessageExt {
/// Encodes the messages to bytes for a HTTP body.
fn to_bytes(&self) -> Bytes;
Expand Down
6 changes: 0 additions & 6 deletions mocktail/src/headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,6 @@ impl From<&http::HeaderMap> for Headers {
}
}

impl From<Headers> for tonic::metadata::MetadataMap {
fn from(value: Headers) -> Self {
tonic::metadata::MetadataMap::from_headers(value.into())
}
}

/// Represents a HTTP header name.
#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct HeaderName(String);
Expand Down
7 changes: 5 additions & 2 deletions mocktail/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ pub use mock_set::MockSet;
mod request;
pub use request::{Method, Request};
mod response;
pub use response::{Response, StatusCode};
pub use response::Response;
pub mod server;
mod status;
pub use status::{Code, StatusCode};
pub mod prelude {
pub use crate::{
body::Body,
Expand All @@ -21,8 +23,9 @@ pub mod prelude {
mock::Mock,
mock_set::MockSet,
request::{Method, Request},
response::{Response, StatusCode},
response::Response,
server::MockServer,
status::{Code, StatusCode},
};
}
mod ext;
Expand Down
3 changes: 2 additions & 1 deletion mocktail/src/mock_builder/then.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use bytes::Bytes;
use crate::{
body::Body,
headers::{HeaderName, HeaderValue, Headers},
response::{Response, StatusCode},
response::Response,
status::StatusCode,
};

/// A response builder.
Expand Down
181 changes: 1 addition & 180 deletions mocktail/src/response.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
//! Mock response
use std::num::NonZeroU16;

use super::{body::Body, headers::Headers};
use crate::{ext::CodeExt, Error};
use super::{body::Body, headers::Headers, status::StatusCode};

/// Represents a HTTP response.
#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -73,179 +70,3 @@ impl Default for Response {
}
}
}

/// Represents a HTTP status code.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StatusCode(NonZeroU16);

impl StatusCode {
pub fn from_u16(code: u16) -> Result<Self, Error> {
if !(100..1000).contains(&code) {
return Err(Error::Invalid("invalid status code".into()));
}
Ok(Self(NonZeroU16::new(code).unwrap()))
}

pub fn as_u16(&self) -> u16 {
self.0.get()
}

pub fn is_informational(&self) -> bool {
(100..200).contains(&self.as_u16())
}

pub fn is_success(&self) -> bool {
(200..300).contains(&self.as_u16())
}

pub fn is_redirection(&self) -> bool {
(300..400).contains(&self.as_u16())
}

pub fn is_error(&self) -> bool {
(400..600).contains(&self.as_u16())
}

pub fn is_ok(&self) -> bool {
self.is_success()
}

pub fn as_http(&self) -> http::StatusCode {
http::StatusCode::from_u16(self.as_u16()).unwrap()
}

pub fn as_grpc(&self) -> tonic::Code {
tonic::Code::from_u16(self.as_u16()).unwrap()
}

pub fn as_grpc_i32(&self) -> i32 {
self.as_grpc() as i32
}
}

impl StatusCode {
pub const CONTINUE: StatusCode = StatusCode(NonZeroU16::new(100).unwrap());
pub const SWITCHING_PROTOCOLS: StatusCode = StatusCode(NonZeroU16::new(101).unwrap());
pub const PROCESSING: StatusCode = StatusCode(NonZeroU16::new(102).unwrap());
pub const EARLY_HINTS: StatusCode = StatusCode(NonZeroU16::new(103).unwrap());

pub const OK: StatusCode = StatusCode(NonZeroU16::new(200).unwrap());
pub const CREATED: StatusCode = StatusCode(NonZeroU16::new(201).unwrap());
pub const ACCEPTED: StatusCode = StatusCode(NonZeroU16::new(202).unwrap());
pub const NON_AUTHORITATIVE_INFORMATION: StatusCode = StatusCode(NonZeroU16::new(203).unwrap());
pub const NO_CONTENT: StatusCode = StatusCode(NonZeroU16::new(204).unwrap());
pub const RESET_CONTENT: StatusCode = StatusCode(NonZeroU16::new(205).unwrap());
pub const PARTIAL_CONTENT: StatusCode = StatusCode(NonZeroU16::new(206).unwrap());
pub const MULTI_STATUS: StatusCode = StatusCode(NonZeroU16::new(207).unwrap());
pub const ALREADY_REPORTED: StatusCode = StatusCode(NonZeroU16::new(208).unwrap());
pub const IM_USED: StatusCode = StatusCode(NonZeroU16::new(226).unwrap());

pub const MULTIPLE_CHOICES: StatusCode = StatusCode(NonZeroU16::new(300).unwrap());
pub const MOVED_PERMANENTLY: StatusCode = StatusCode(NonZeroU16::new(301).unwrap());
pub const FOUND: StatusCode = StatusCode(NonZeroU16::new(302).unwrap());
pub const SEE_OTHER: StatusCode = StatusCode(NonZeroU16::new(303).unwrap());
pub const NOT_MODIFIED: StatusCode = StatusCode(NonZeroU16::new(304).unwrap());
pub const USE_PROXY: StatusCode = StatusCode(NonZeroU16::new(305).unwrap());
pub const TEMPORARY_REDIRECT: StatusCode = StatusCode(NonZeroU16::new(307).unwrap());
pub const PERMANENT_REDIRECT: StatusCode = StatusCode(NonZeroU16::new(308).unwrap());

pub const BAD_REQUEST: StatusCode = StatusCode(NonZeroU16::new(400).unwrap());
pub const UNAUTHORIZED: StatusCode = StatusCode(NonZeroU16::new(401).unwrap());
pub const PAYMENT_REQUIRED: StatusCode = StatusCode(NonZeroU16::new(402).unwrap());
pub const FORBIDDEN: StatusCode = StatusCode(NonZeroU16::new(403).unwrap());
pub const NOT_FOUND: StatusCode = StatusCode(NonZeroU16::new(404).unwrap());
pub const METHOD_NOT_ALLOWED: StatusCode = StatusCode(NonZeroU16::new(405).unwrap());
pub const NOT_ACCEPTABLE: StatusCode = StatusCode(NonZeroU16::new(406).unwrap());
pub const PROXY_AUTHENTICATION_REQUIRED: StatusCode = StatusCode(NonZeroU16::new(407).unwrap());
pub const REQUEST_TIMEOUT: StatusCode = StatusCode(NonZeroU16::new(408).unwrap());
pub const CONFLICT: StatusCode = StatusCode(NonZeroU16::new(409).unwrap());
pub const GONE: StatusCode = StatusCode(NonZeroU16::new(410).unwrap());
pub const LENGTH_REQUIRED: StatusCode = StatusCode(NonZeroU16::new(411).unwrap());
pub const PRECONDITION_FAILED: StatusCode = StatusCode(NonZeroU16::new(412).unwrap());
pub const PAYLOAD_TOO_LARGE: StatusCode = StatusCode(NonZeroU16::new(413).unwrap());
pub const URI_TOO_LONG: StatusCode = StatusCode(NonZeroU16::new(414).unwrap());
pub const UNSUPPORTED_MEDIA_TYPE: StatusCode = StatusCode(NonZeroU16::new(415).unwrap());
pub const RANGE_NOT_SATISFIABLE: StatusCode = StatusCode(NonZeroU16::new(416).unwrap());
pub const EXPECTATION_FAILED: StatusCode = StatusCode(NonZeroU16::new(417).unwrap());
pub const IM_A_TEAPOT: StatusCode = StatusCode(NonZeroU16::new(418).unwrap());
pub const MISDIRECTED_REQUEST: StatusCode = StatusCode(NonZeroU16::new(421).unwrap());
pub const UNPROCESSABLE_ENTITY: StatusCode = StatusCode(NonZeroU16::new(422).unwrap());
pub const LOCKED: StatusCode = StatusCode(NonZeroU16::new(423).unwrap());
pub const FAILED_DEPENDENCY: StatusCode = StatusCode(NonZeroU16::new(424).unwrap());
pub const TOO_EARLY: StatusCode = StatusCode(NonZeroU16::new(425).unwrap());
pub const UPGRADE_REQUIRED: StatusCode = StatusCode(NonZeroU16::new(426).unwrap());
pub const PRECONDITION_REQUIRED: StatusCode = StatusCode(NonZeroU16::new(428).unwrap());
pub const TOO_MANY_REQUESTS: StatusCode = StatusCode(NonZeroU16::new(429).unwrap());
pub const REQUEST_HEADER_FIELDS_TOO_LARGE: StatusCode =
StatusCode(NonZeroU16::new(431).unwrap());
pub const UNAVAILABLE_FOR_LEGAL_REASONS: StatusCode = StatusCode(NonZeroU16::new(451).unwrap());

pub const INTERNAL_SERVER_ERROR: StatusCode = StatusCode(NonZeroU16::new(500).unwrap());
pub const NOT_IMPLEMENTED: StatusCode = StatusCode(NonZeroU16::new(501).unwrap());
pub const BAD_GATEWAY: StatusCode = StatusCode(NonZeroU16::new(502).unwrap());
pub const SERVICE_UNAVAILABLE: StatusCode = StatusCode(NonZeroU16::new(503).unwrap());
pub const GATEWAY_TIMEOUT: StatusCode = StatusCode(NonZeroU16::new(504).unwrap());
pub const HTTP_VERSION_NOT_SUPPORTED: StatusCode = StatusCode(NonZeroU16::new(505).unwrap());
pub const VARIANT_ALSO_NEGOTIATES: StatusCode = StatusCode(NonZeroU16::new(506).unwrap());
pub const INSUFFICIENT_STORAGE: StatusCode = StatusCode(NonZeroU16::new(507).unwrap());
pub const LOOP_DETECTED: StatusCode = StatusCode(NonZeroU16::new(508).unwrap());
pub const NOT_EXTENDED: StatusCode = StatusCode(NonZeroU16::new(510).unwrap());
pub const NETWORK_AUTHENTICATION_REQUIRED: StatusCode =
StatusCode(NonZeroU16::new(511).unwrap());
}

impl Default for StatusCode {
fn default() -> Self {
Self::OK
}
}

impl PartialEq<u16> for StatusCode {
fn eq(&self, other: &u16) -> bool {
self.as_u16() == *other
}
}

impl PartialEq<StatusCode> for u16 {
fn eq(&self, other: &StatusCode) -> bool {
*self == other.as_u16()
}
}

impl PartialEq<StatusCode> for http::StatusCode {
fn eq(&self, other: &StatusCode) -> bool {
*self == other.as_u16()
}
}

impl TryFrom<u16> for StatusCode {
type Error = Error;

fn try_from(value: u16) -> Result<Self, Self::Error> {
Self::from_u16(value)
}
}

impl From<StatusCode> for u16 {
fn from(status: StatusCode) -> u16 {
status.0.get()
}
}

impl From<http::StatusCode> for StatusCode {
fn from(value: http::StatusCode) -> Self {
Self::from_u16(value.as_u16()).unwrap()
}
}

impl From<StatusCode> for http::StatusCode {
fn from(value: StatusCode) -> Self {
Self::from_u16(value.0.into()).unwrap()
}
}

impl From<StatusCode> for tonic::Code {
fn from(value: StatusCode) -> Self {
tonic::Code::from_u16(value.as_u16()).unwrap()
}
}
16 changes: 9 additions & 7 deletions mocktail/src/service/grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ use http_body_util::{BodyExt, StreamBody};
use hyper::{body::Incoming, service::Service};
use tokio::sync::mpsc;
use tokio_stream::wrappers::ReceiverStream;
use tonic::body::BoxBody;
use tracing::debug;

use crate::{request::Request, server::MockServerState};
use crate::{request::Request, server::MockServerState, service::http::empty, Code};

use super::http::BoxBody;

/// Mock gRPC service.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -40,7 +41,7 @@ impl Service<http::Request<Incoming>> for GrpcMockService {
return Ok(http::Response::builder()
.status(http::StatusCode::METHOD_NOT_ALLOWED)
.header("Allow", "POST")
.body(tonic::body::empty_body())
.body(empty())
.unwrap());
}
let content_type = req.headers().get("content-type");
Expand All @@ -52,7 +53,7 @@ impl Service<http::Request<Incoming>> for GrpcMockService {
return Ok(http::Response::builder()
.status(http::StatusCode::UNSUPPORTED_MEDIA_TYPE)
.header("Accept-Post", "application/grpc")
.body(tonic::body::empty_body())
.body(empty())
.unwrap());
}

Expand All @@ -61,7 +62,7 @@ impl Service<http::Request<Incoming>> for GrpcMockService {

// Create response stream
let (response_tx, response_rx) =
mpsc::channel::<Result<Frame<Bytes>, tonic::Status>>(32);
mpsc::channel::<Result<Frame<Bytes>, hyper::Error>>(32);
let response_stream = ReceiverStream::new(response_rx);
let response_body = BoxBody::new(StreamBody::new(response_stream));
let response = http::Response::builder()
Expand Down Expand Up @@ -95,7 +96,8 @@ impl Service<http::Request<Incoming>> for GrpcMockService {
}
// Send trailers frame
let mut trailers = HeaderMap::from(response.headers().clone());
trailers.insert("grpc-status", response.status().as_grpc_i32().into());
trailers
.insert("grpc-status", response.status().as_grpc().to_header_value());
if let Some(message) = response.message() {
trailers
.insert("grpc-message", HeaderValue::from_str(message).unwrap());
Expand All @@ -122,7 +124,7 @@ impl Service<http::Request<Incoming>> for GrpcMockService {

fn mock_not_found_trailer() -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert("grpc-status", (tonic::Code::NotFound as i32).into());
headers.insert("grpc-status", Code::NotFound.to_header_value());
headers.insert("grpc-message", HeaderValue::from_static("mock not found"));
headers
}
7 changes: 4 additions & 3 deletions mocktail/src/service/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use tracing::debug;

use crate::{request::Request, server::MockServerState};

type BoxBody = http_body_util::combinators::BoxBody<Bytes, hyper::Error>;
/// A type-erased HTTP body.
pub type BoxBody = http_body_util::combinators::BoxBody<Bytes, hyper::Error>;

const ALLOWED_METHODS: [http::Method; 5] = [
http::Method::GET,
Expand Down Expand Up @@ -157,10 +158,10 @@ impl Service<http::Request<Incoming>> for HttpMockService {
}
}

fn full(data: Bytes) -> BoxBody {
pub fn full(data: Bytes) -> BoxBody {
Full::new(data).map_err(|err| match err {}).boxed()
}

fn empty() -> BoxBody {
pub fn empty() -> BoxBody {
Empty::new().map_err(|err| match err {}).boxed()
}
Loading