Skip to content

Split tide into smaller crates #220

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 19, 2019
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
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
members = [
"tide",
"tide-compression",
"tide-cookies",
"tide-core",
"examples",
]

Expand Down
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,15 @@ fn main() -> Result<(), std::io::Error> {

**More Examples**

- [Hello World](https://github.com/rustasync/tide/tree/master/examples/src/hello.rs)
- [Messages](https://github.com/rustasync/tide/tree/master/examples/src/messages.rs)
- [Body Types](https://github.com/rustasync/tide/tree/master/examples/src/body_types.rs)
- [Multipart Form](https://github.com/rustasync/tide/tree/master/examples/src/multipart_form/mod.rs)
- [Catch All](https://github.com/rustasync/tide/tree/master/examples/src/catch_all.rs)
- [Cookies](https://github.com/rustasync/tide/tree/master/examples/src/cookies.rs)
- [Default Headers](https://github.com/rustasync/tide/tree/master/examples/src/default_headers.rs)
- [GraphQL](https://github.com/rustasync/tide/tree/master/examples/src/graphql.rs)
- [Hello World](https://github.com/rustasync/tide/blob/master/examples/src/hello.rs)
- [Messages](https://github.com/rustasync/tide/blob/master/examples/src/messages.rs)
- [Body Types](https://github.com/rustasync/tide/blob/master/examples/src/body_types.rs)
- [Multipart Form](https://github.com/rustasync/tide/blob/master/examples/src/multipart-form/main.rs)
- [Catch All](https://github.com/rustasync/tide/blob/master/examples/src/catch_all.rs)
- [Cookies](https://github.com/rustasync/tide/blob/master/examples/src/cookies.rs)
- [Default Headers](https://github.com/rustasync/tide/blob/master/examples/src/default_headers.rs)
- [GraphQL](https://github.com/rustasync/tide/blob/master/examples/src/graphql.rs)
- [Staticfile](https://github.com/rustasync/tide/blob/master/examples/src/staticfile.rs)

## Resources

Expand Down
16 changes: 1 addition & 15 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
authors = [
"Tide Developers",
"Tide Developers",
]
description = "Tide web server examples"
documentation = "https://docs.rs/tide"
Expand All @@ -15,32 +15,18 @@ publish = false
[dependencies]
tide = { path = "../tide" }
cookie = { version = "0.12", features = ["percent-encode"] }
futures-preview = "0.3.0-alpha.16"
fnv = "1.0.6"
http = "0.1"
http-service = "0.2.0"
pin-utils = "0.1.0-alpha.4"
route-recognizer = "0.1.12"
serde_json = "1.0.39"
slog = "2.4.1"
slog-async = "2.3.0"
slog-term = "2.4.0"
typemap = "0.3.3"
serde_urlencoded = "0.5.5"
basic-cookies = "0.1.3"
bytes = "0.4.12"
futures-fs = "0.0.5"
futures-util-preview = { version = "0.3.0-alpha.16", features = ["compat"] }
http-service-mock = "0.2.0"
juniper = "0.11.1"
mime = "0.3.13"
mime_guess = "2.0.0-alpha.6"
percent-encoding = "1.0.1"
serde = { version = "1.0.91", features = ["derive"] }
structopt = "0.2.15"

[dependencies.multipart]
default-features = false
features = ["server"]
version = "0.16.1"

21 changes: 21 additions & 0 deletions tide-cookies/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "tide-cookies"
version = "0.2.0"
edition = "2018"
authors = [
"Tide Developers",
]
description = "Cookie management for Tide web framework"
documentation = "https://docs.rs/tide-core"
license = "MIT OR Apache-2.0"
repository = "https://github.com/rustasync/tide"

[dependencies]
cookie = { version = "0.12", features = ["percent-encode"] }
futures-preview = "0.3.0-alpha.16"
http = "0.1"
http-service = "0.2.0"
tide-core = { path = "../tide-core" }

[dev-dependencies]
http-service-mock = "0.2.0"
3 changes: 1 addition & 2 deletions tide/src/cookies.rs → tide-cookies/src/data.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use cookie::{Cookie, CookieJar, ParseError};

use crate::error::StringError;
use crate::Context;
use http::HeaderMap;
use std::sync::{Arc, RwLock};
use tide_core::{error::StringError, Context};

const MIDDLEWARE_MISSING_MSG: &str =
"CookiesMiddleware must be used to populate request and response cookies";
Expand Down
18 changes: 18 additions & 0 deletions tide-cookies/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#![cfg_attr(feature = "nightly", deny(missing_docs))]
#![cfg_attr(test, deny(warnings))]
#![feature(async_await)]
#![deny(
nonstandard_style,
rust_2018_idioms,
future_incompatible,
missing_debug_implementations
)]

#[macro_use]
extern crate tide_core;

mod data;
mod middleware;

pub use self::data::ContextExt;
pub use self::middleware::CookiesMiddleware;
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::cookies::CookieData;
use crate::data::CookieData;
use futures::future::BoxFuture;
use http::header::HeaderValue;

use crate::{
use tide_core::{
middleware::{Middleware, Next},
Context, Response,
};
Expand Down Expand Up @@ -63,11 +63,12 @@ impl<Data: Send + Sync + 'static> Middleware<Data> for CookiesMiddleware {
#[cfg(test)]
mod tests {
use super::*;
use crate::{cookies::ContextExt, Context};
use crate::data::ContextExt;
use cookie::Cookie;
use futures::executor::block_on;
use http_service::Body;
use http_service_mock::make_server;
use tide_core::Context;

static COOKIE_NAME: &str = "testCookie";

Expand All @@ -90,8 +91,8 @@ mod tests {
cx.set_cookie(Cookie::new("C2", "V2")).unwrap();
}

fn app() -> crate::App<()> {
let mut app = crate::App::new();
fn app() -> tide_core::App<()> {
let mut app = tide_core::App::new();
app.middleware(CookiesMiddleware::new());

app.at("/get").get(retrieve_cookie);
Expand Down Expand Up @@ -168,5 +169,4 @@ mod tests {

assert!(iter.next().is_none());
}

}
32 changes: 32 additions & 0 deletions tide-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "tide-core"
version = "0.2.0"
edition = "2018"
authors = [
"Tide Developers",
]
description = "Core types and traits for Tide web framework"
documentation = "https://docs.rs/tide-core"
license = "MIT OR Apache-2.0"
repository = "https://github.com/rustasync/tide"

[dependencies]
fnv = "1.0.6"
futures-preview = "0.3.0-alpha.16"
http = "0.1"
http-service = "0.2.0"
route-recognizer = "0.1.12"
serde = "1.0.91"
serde_json = "1.0.39"

[dependencies.http-service-hyper]
optional = true
version = "0.2.0"

[dev-dependencies]
tide = { path = "../tide" }
serde_derive = "1.0.91"

[features]
default = ["hyper"]
hyper = ["http-service-hyper"]
1 change: 1 addition & 0 deletions tide/src/app.rs → tide-core/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ use crate::{
///
/// ```rust, no_run
/// #![feature(async_await)]
/// #[macro_use] extern crate serde_derive;
///
/// use http::status::StatusCode;
/// use serde::{Deserialize, Serialize};
Expand Down
File renamed without changes.
File renamed without changes.
104 changes: 104 additions & 0 deletions tide-core/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// use core::pin::Pin;
// use futures::future::Future;
use http::{HttpTryFrom, Response, StatusCode};
use http_service::Body;

use crate::response::IntoResponse;

#[derive(Debug)]
pub struct StringError(pub String);
impl std::error::Error for StringError {}

impl std::fmt::Display for StringError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
self.0.fmt(f)
}
}

#[macro_export]
macro_rules! err_fmt {
{$($t:tt)*} => {
crate::error::StringError(format!($($t)*))
}
}

/// A convenient `Result` instantiation appropriate for most endpoints.
pub type EndpointResult<T = Response<Body>> = Result<T, Error>;

/// A generic endpoint error, which can be converted into a response.
#[derive(Debug)]
pub struct Error {
resp: Response<Body>,
}

impl IntoResponse for Error {
fn into_response(self) -> Response<Body> {
self.resp
}
}

struct Cause(Box<dyn std::error::Error + Send + Sync>);

impl From<Response<Body>> for Error {
fn from(resp: Response<Body>) -> Error {
Error { resp }
}
}

impl From<StatusCode> for Error {
fn from(status: StatusCode) -> Error {
let resp = Response::builder()
.status(status)
.body(Body::empty())
.unwrap();
Error { resp }
}
}

/// Extends the `Result` type with convenient methods for constructing Tide errors.
pub trait ResultExt<T>: Sized {
/// Convert to an `EndpointResult`, treating the `Err` case as a client
/// error (response code 400).
fn client_err(self) -> EndpointResult<T> {
self.with_err_status(400)
}

/// Convert to an `EndpointResult`, treating the `Err` case as a server
/// error (response code 500).
fn server_err(self) -> EndpointResult<T> {
self.with_err_status(500)
}

/// Convert to an `EndpointResult`, wrapping the `Err` case with a custom
/// response status.
fn with_err_status<S>(self, status: S) -> EndpointResult<T>
where
StatusCode: HttpTryFrom<S>;
}

/// Extends the `Response` type with a method to extract error causes when applicable.
pub trait ResponseExt {
/// Extract the cause of the unsuccessful response, if any
fn err_cause(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)>;
}

impl<T> ResponseExt for Response<T> {
fn err_cause(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> {
self.extensions().get().map(|Cause(c)| &**c)
}
}

impl<T, E: std::error::Error + Send + Sync + 'static> ResultExt<T> for std::result::Result<T, E> {
fn with_err_status<S>(self, status: S) -> EndpointResult<T>
where
StatusCode: HttpTryFrom<S>,
{
self.map_err(|e| Error {
resp: Response::builder()
.status(status)
.extension(Cause(Box::new(e)))
.body(Body::empty())
.unwrap(),
})
}
}
37 changes: 37 additions & 0 deletions tide-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#![cfg_attr(feature = "nightly", deny(missing_docs))]
#![cfg_attr(test, deny(warnings))]
#![feature(async_await, existential_type)]
#![deny(
nonstandard_style,
rust_2018_idioms,
future_incompatible,
missing_debug_implementations
)]
// TODO: Remove this after clippy bug due to async await is resolved.
// ISSUE: https://github.com/rust-lang/rust-clippy/issues/3988
#![allow(clippy::needless_lifetimes)]

#[macro_export]
macro_rules! box_async {
{$($t:tt)*} => {
::futures::future::FutureExt::boxed(async move { $($t)* })
};
}

mod app;
mod context;
mod endpoint;
pub mod error;
pub mod middleware;
pub mod response;
mod route;
mod router;

pub use crate::{
app::{App, Server},
context::Context,
endpoint::Endpoint,
error::{EndpointResult, Error},
response::Response,
route::Route,
};
38 changes: 38 additions & 0 deletions tide-core/src/middleware.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use crate::{endpoint::DynEndpoint, Context, Response};
use futures::future::BoxFuture;

use std::sync::Arc;

/// Middleware that wraps around remaining middleware chain.
pub trait Middleware<State>: 'static + Send + Sync {
/// Asynchronously handle the request, and return a response.
fn handle<'a>(&'a self, cx: Context<State>, next: Next<'a, State>) -> BoxFuture<'a, Response>;
}

impl<Data, F> Middleware<Data> for F
where
F: Send + Sync + 'static + for<'a> Fn(Context<Data>, Next<'a, Data>) -> BoxFuture<'a, Response>,
{
fn handle<'a>(&'a self, cx: Context<Data>, next: Next<'a, Data>) -> BoxFuture<'a, Response> {
(self)(cx, next)
}
}

/// The remainder of a middleware chain, including the endpoint.
#[allow(missing_debug_implementations)]
pub struct Next<'a, State> {
pub(crate) endpoint: &'a DynEndpoint<State>,
pub(crate) next_middleware: &'a [Arc<dyn Middleware<State>>],
}

impl<'a, State: 'static> Next<'a, State> {
/// Asynchronously execute the remaining middleware chain.
pub fn run(mut self, cx: Context<State>) -> BoxFuture<'a, Response> {
if let Some((current, next)) = self.next_middleware.split_first() {
self.next_middleware = next;
current.handle(cx, self)
} else {
(self.endpoint)(cx)
}
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading