blob: c8b04229009f10de973df01aeddadda218d798a3 [file] [log] [blame]
// Copyright 2016 `multipart` Crate Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! Mocked types for client-side and server-side APIs.
use std::cell::{Cell, RefCell};
use std::io::{self, Read, Write};
use std::{fmt, thread};
use rand::{self, Rng, ThreadRng};
/// A mock implementation of `client::HttpRequest` which can spawn an `HttpBuffer`.
///
/// `client::HttpRequest` impl requires the `client` feature.
#[derive(Default, Debug)]
pub struct ClientRequest {
boundary: Option<String>,
content_len: Option<u64>,
}
#[cfg(feature = "client")]
impl ::client::HttpRequest for ClientRequest {
type Stream = HttpBuffer;
type Error = io::Error;
fn apply_headers(&mut self, boundary: &str, content_len: Option<u64>) -> bool {
self.boundary = Some(boundary.into());
self.content_len = content_len;
true
}
/// ## Panics
/// If `apply_headers()` was not called.
fn open_stream(self) -> Result<HttpBuffer, io::Error> {
debug!("ClientRequest::open_stream called! {:?}", self);
let boundary = self.boundary.expect("ClientRequest::set_headers() was not called!");
Ok(HttpBuffer::new_empty(boundary, self.content_len))
}
}
/// A writable buffer which stores the boundary and content-length, if provided.
///
/// Implements `client::HttpStream` if the `client` feature is enabled.
pub struct HttpBuffer {
/// The buffer containing the raw bytes.
pub buf: Vec<u8>,
/// The multipart boundary.
pub boundary: String,
/// The value of the content-length header, if set.
pub content_len: Option<u64>,
rng: ThreadRng,
}
impl HttpBuffer {
/// Create an empty buffer with the given boundary and optional content-length.
pub fn new_empty(boundary: String, content_len: Option<u64>) -> HttpBuffer {
Self::with_buf(Vec::new(), boundary, content_len)
}
/// Wrap the given buffer with the given boundary and optional content-length.
pub fn with_buf(buf: Vec<u8>, boundary: String, content_len: Option<u64>) -> Self {
HttpBuffer {
buf,
boundary,
content_len,
rng: rand::thread_rng()
}
}
/// Get a `ServerRequest` wrapping the data in this buffer.
pub fn for_server(&self) -> ServerRequest {
ServerRequest {
data: &self.buf,
boundary: &self.boundary,
content_len: self.content_len,
rng: rand::thread_rng(),
}
}
}
impl Write for HttpBuffer {
/// To simulate a network connection, this will copy a random number of bytes
/// from `buf` to the buffer.
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if buf.is_empty() {
debug!("HttpBuffer::write() was passed a zero-sized buffer.");
return Ok(0);
}
// Simulate the randomness of a network connection by not always reading everything
let len = self.rng.gen_range(1, buf.len() + 1);
self.buf.write(&buf[..len])
}
fn flush(&mut self) -> io::Result<()> {
self.buf.flush()
}
}
#[cfg(feature = "client")]
impl ::client::HttpStream for HttpBuffer {
type Request = ClientRequest;
type Response = HttpBuffer;
type Error = io::Error;
/// Returns `Ok(self)`.
fn finish(self) -> Result<Self, io::Error> { Ok(self) }
}
impl fmt::Debug for HttpBuffer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("multipart::mock::HttpBuffer")
.field("buf", &self.buf)
.field("boundary", &self.boundary)
.field("content_len", &self.content_len)
.finish()
}
}
/// A mock implementation of `server::HttpRequest` that can be read.
///
/// Implements `server::HttpRequest` if the `server` feature is enabled.
pub struct ServerRequest<'a> {
/// Slice of the source `HttpBuffer::buf`
pub data: &'a [u8],
/// The multipart boundary.
pub boundary: &'a str,
/// The value of the content-length header, if set.
pub content_len: Option<u64>,
rng: ThreadRng,
}
impl<'a> ServerRequest<'a> {
/// Create a new `ServerRequest` with the given data and boundary.
///
/// Assumes `content_len: None`
pub fn new(data: &'a [u8], boundary: &'a str) -> Self {
ServerRequest {
data,
boundary,
content_len: None,
rng: rand::thread_rng(),
}
}
}
impl<'a> Read for ServerRequest<'a> {
/// To simulate a network connection, this will copy a random number of bytes
/// from the buffer to `out`.
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
if out.is_empty() {
debug!("ServerRequest::read() was passed a zero-sized buffer.");
return Ok(0);
}
// Simulate the randomness of a network connection by not always reading everything
let len = self.rng.gen_range(1, out.len() + 1);
self.data.read(&mut out[..len])
}
}
#[cfg(feature = "server")]
impl<'a> ::server::HttpRequest for ServerRequest<'a> {
type Body = Self;
fn multipart_boundary(&self) -> Option<&str> { Some(self.boundary) }
fn body(self) -> Self::Body {
self
}
}
/// A `Write` adapter that duplicates all data written to the inner writer as well as stdout.
pub struct StdoutTee<'s, W> {
inner: W,
stdout: io::StdoutLock<'s>,
}
impl<'s, W> StdoutTee<'s, W> {
/// Constructor
pub fn new(inner: W, stdout: &'s io::Stdout) -> Self {
Self {
inner, stdout: stdout.lock(),
}
}
}
impl<'s, W: Write> Write for StdoutTee<'s, W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write_all(buf)?;
self.stdout.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()?;
self.stdout.flush()
}
}