-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: initial prototype from the wip branch
o basic rocket setup w/ error handling via failure o initial db setup via diesel+mysql o skeleton of needed auth calls o tests + travisci Issue #4
- Loading branch information
Showing
13 changed files
with
520 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
language: rust | ||
sudo: false | ||
cache: cargo | ||
|
||
rust: | ||
- nightly | ||
env: | ||
global: | ||
# XXX: begin_test_transaction doesn't play nice over threaded tests | ||
- RUST_TEST_THREADS=1 | ||
- ROCKET_DATABASE_URL="mysql://[email protected]/megaphone" | ||
|
||
services: | ||
- mysql | ||
|
||
# XXX: kill the diesel_cli requirement: | ||
# https://docs.rs/diesel/0.16.0/diesel/macro.embed_migrations.html | ||
before_script: | ||
- mysql -e 'CREATE DATABASE IF NOT EXISTS megaphone;' | ||
- | | ||
cargo install diesel_cli --no-default-features --features mysql || \ | ||
echo "diesel_cli already installed" | ||
- diesel setup --database-url $ROCKET_DATABASE_URL |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,9 +4,11 @@ version = "0.1.0" | |
authors = ["jrconlin <[email protected]>"] | ||
|
||
[dependencies] | ||
diesel = { version = "1.0", features = ["mysql"] } | ||
dotenv = "0.9" | ||
diesel = { version = "1.0", features = ["mysql", "r2d2"] } | ||
failure = "0.1" | ||
rocket = "0.3" | ||
rocket_codegen = "0.3" | ||
rocket_contrib = "0.3" | ||
serde = "1.0" | ||
serde_derive = "1.0" | ||
serde_json = "1.0" | ||
websocket = "0.20" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[development] | ||
#database_url = "mysql://" | ||
json_logging = false | ||
|
||
[staging] | ||
#database_url = "mysql://" | ||
json_logging = true | ||
|
||
[production] | ||
#database_url = "mysql://" | ||
json_logging = true |
Empty file.
1 change: 1 addition & 0 deletions
1
migrations/2018-02-20-220249_create_broadcastsv1_table/down.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
DROP TABLE broadcastsv1; |
7 changes: 7 additions & 0 deletions
7
migrations/2018-02-20-220249_create_broadcastsv1_table/up.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
CREATE TABLE broadcastsv1 ( | ||
broadcaster_id VARCHAR(64) NOT NULL, | ||
bchannel_id VARCHAR(128) NOT NULL, | ||
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL, | ||
version VARCHAR(200) NOT NULL, | ||
PRIMARY KEY(broadcaster_id, bchannel_id) | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
pub mod schema; | ||
pub mod models; | ||
|
||
use std::ops::Deref; | ||
|
||
use diesel::mysql::MysqlConnection; | ||
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; | ||
use failure::err_msg; | ||
|
||
use rocket::http::Status; | ||
use rocket::request::{self, FromRequest}; | ||
use rocket::{Config, Outcome, Request, State}; | ||
|
||
use error::Result; | ||
|
||
pub type MysqlPool = Pool<ConnectionManager<MysqlConnection>>; | ||
|
||
pub fn pool_from_config(config: &Config) -> Result<MysqlPool> { | ||
let database_url = config | ||
.get_str("database_url") | ||
.map_err(|_| err_msg("ROCKET_DATABASE_URL undefined"))? | ||
.to_string(); | ||
let max_size = config.get_int("database_pool_max_size").unwrap_or(10) as u32; | ||
let manager = ConnectionManager::<MysqlConnection>::new(database_url); | ||
Ok(Pool::builder().max_size(max_size).build(manager)?) | ||
} | ||
|
||
pub struct Conn(pub PooledConnection<ConnectionManager<MysqlConnection>>); | ||
|
||
impl Deref for Conn { | ||
type Target = MysqlConnection; | ||
|
||
#[inline(always)] | ||
fn deref(&self) -> &Self::Target { | ||
&self.0 | ||
} | ||
} | ||
|
||
impl<'a, 'r> FromRequest<'a, 'r> for Conn { | ||
type Error = (); | ||
|
||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, ()> { | ||
let pool = request.guard::<State<MysqlPool>>()?; | ||
match pool.get() { | ||
Ok(conn) => Outcome::Success(Conn(conn)), | ||
Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
use failure::ResultExt; | ||
use diesel::{replace_into, RunQueryDsl}; | ||
use diesel::mysql::MysqlConnection; | ||
|
||
use super::schema::broadcastsv1; | ||
use error::{HandlerErrorKind, HandlerResult}; | ||
|
||
#[derive(Debug, Queryable, Insertable)] | ||
#[table_name = "broadcastsv1"] | ||
pub struct Broadcast { | ||
pub broadcaster_id: String, | ||
pub bchannel_id: String, | ||
pub version: String, | ||
} | ||
|
||
impl Broadcast { | ||
pub fn id(&self) -> String { | ||
format!("{}/{}", self.broadcaster_id, self.bchannel_id) | ||
} | ||
} | ||
|
||
/// An authorized broadcaster | ||
pub struct Broadcaster { | ||
pub id: String, | ||
} | ||
|
||
impl Broadcaster { | ||
pub fn new_broadcast( | ||
self, | ||
conn: &MysqlConnection, | ||
bchannel_id: String, | ||
version: String, | ||
) -> HandlerResult<usize> { | ||
let broadcast = Broadcast { | ||
broadcaster_id: self.id, | ||
bchannel_id: bchannel_id, | ||
version: version, | ||
}; | ||
Ok(replace_into(broadcastsv1::table) | ||
.values(&broadcast) | ||
.execute(conn) | ||
.context(HandlerErrorKind::DBError)?) | ||
} | ||
} | ||
|
||
// An authorized reader of current broadcasts | ||
//struct BroadcastAdmin; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
table! { | ||
broadcastsv1 (broadcaster_id, bchannel_id) { | ||
broadcaster_id -> Varchar, | ||
bchannel_id -> Varchar, | ||
last_updated -> Timestamp, | ||
version -> Varchar, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/// Error handling based on the failure crate | ||
/// | ||
/// Only rocket's Handlers can render error responses w/ a contextual JSON | ||
/// payload. So request guards should generally return VALIDATION_FAILED, | ||
/// leaving error handling to the Handler (which in turn must take a Result of | ||
/// request guards' fields). | ||
/// | ||
/// HandlerErrors are rocket Responders (render their own error responses). | ||
use std::fmt; | ||
use std::result; | ||
|
||
use failure::{Backtrace, Context, Error, Fail}; | ||
use rocket::{self, response, Request}; | ||
use rocket::http::Status; | ||
use rocket::response::{Responder, Response}; | ||
use rocket_contrib::Json; | ||
|
||
pub type Result<T> = result::Result<T, Error>; | ||
|
||
pub type HandlerResult<T> = result::Result<T, HandlerError>; | ||
|
||
/// Signal a request guard failure, propagated up to the Handler to render an | ||
/// error response | ||
pub const VALIDATION_FAILED: Status = Status::InternalServerError; | ||
|
||
#[derive(Debug)] | ||
pub struct HandlerError { | ||
inner: Context<HandlerErrorKind>, | ||
} | ||
|
||
#[derive(Clone, Eq, PartialEq, Debug, Fail)] | ||
pub enum HandlerErrorKind { | ||
/// 401 Unauthorized | ||
#[fail(display = "Unauthorized: {}", _0)] | ||
Unauthorized(String), | ||
/// 404 Not Found | ||
#[fail(display = "Not Found")] | ||
NotFound, | ||
#[fail(display = "A database error occurred")] | ||
DBError, | ||
#[fail(display = "Version information not included in body of update")] | ||
MissingVersionDataError, | ||
#[fail(display = "Invalid Version info (must be URL safe Base 64)")] | ||
InvalidVersionDataError, | ||
#[fail(display = "Unexpected rocket error: {:?}", _0)] | ||
RocketError(rocket::Error), // rocket::Error isn't a std Error (so no #[cause]) | ||
} | ||
|
||
impl HandlerErrorKind { | ||
/// Return a rocket response Status to be rendered for an error | ||
pub fn http_status(&self) -> Status { | ||
match *self { | ||
HandlerErrorKind::DBError => Status::ServiceUnavailable, | ||
HandlerErrorKind::NotFound => Status::NotFound, | ||
HandlerErrorKind::Unauthorized(..) => Status::Unauthorized, | ||
_ => Status::BadRequest, | ||
} | ||
} | ||
} | ||
|
||
impl Fail for HandlerError { | ||
fn cause(&self) -> Option<&Fail> { | ||
self.inner.cause() | ||
} | ||
|
||
fn backtrace(&self) -> Option<&Backtrace> { | ||
self.inner.backtrace() | ||
} | ||
} | ||
|
||
impl fmt::Display for HandlerError { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
fmt::Display::fmt(&self.inner, f) | ||
} | ||
} | ||
|
||
impl HandlerError { | ||
pub fn kind(&self) -> &HandlerErrorKind { | ||
self.inner.get_context() | ||
} | ||
} | ||
|
||
impl From<HandlerErrorKind> for HandlerError { | ||
fn from(kind: HandlerErrorKind) -> HandlerError { | ||
HandlerError { | ||
inner: Context::new(kind), | ||
} | ||
} | ||
} | ||
|
||
impl From<Context<HandlerErrorKind>> for HandlerError { | ||
fn from(inner: Context<HandlerErrorKind>) -> HandlerError { | ||
HandlerError { inner: inner } | ||
} | ||
} | ||
|
||
/// Generate HTTP error responses for HandlerErrors | ||
impl<'r> Responder<'r> for HandlerError { | ||
fn respond_to(self, request: &Request) -> response::Result<'r> { | ||
let status = self.kind().http_status(); | ||
let json = Json(json!({ | ||
"status": status.code, | ||
"error": format!("{}", self) | ||
})); | ||
// XXX: logging | ||
Response::build_from(json.respond_to(request)?) | ||
.status(status) | ||
.ok() | ||
} | ||
} |
Oops, something went wrong.