use std::fmt;
use std::ops::{Deref, DerefMut};
use std::convert::TryInto;
use yansi::Paint;
use either::Either;
use figment::{Figment, Provider};
use crate::{Catcher, Config, Route, Shutdown, sentinel, shield::Shield};
use crate::router::Router;
use crate::trip_wire::TripWire;
use crate::fairing::{Fairing, Fairings};
use crate::phase::{Phase, Build, Building, Ignite, Igniting, Orbit, Orbiting};
use crate::phase::{Stateful, StateRef, State};
use crate::http::uri::{self, Origin};
use crate::http::ext::IntoOwned;
use crate::error::{Error, ErrorKind};
use crate::log::PaintExt;
pub struct Rocket<P: Phase>(pub(crate) P::State);
impl Rocket<Build> {
#[inline(always)]
pub fn build() -> Self {
Rocket::custom(Config::figment())
}
pub fn custom<T: Provider>(provider: T) -> Self {
crate::log::init_default();
let rocket: Rocket<Build> = Rocket(Building {
figment: Figment::from(provider),
..Default::default()
});
rocket.attach(Shield::default())
}
pub fn configure<T: Provider>(mut self, provider: T) -> Self {
self.figment = Figment::from(provider);
self
}
fn load<'a, B, T, F, M>(mut self, kind: &str, base: B, items: Vec<T>, m: M, f: F) -> Self
where B: TryInto<Origin<'a>> + Clone + fmt::Display,
B::Error: fmt::Display,
M: Fn(&Origin<'a>, T) -> Result<T, uri::Error<'static>>,
F: Fn(&mut Self, T),
T: Clone + fmt::Display,
{
let mut base = base.clone().try_into()
.map(|origin| origin.into_owned())
.unwrap_or_else(|e| {
error!("invalid {} base: {}", kind, Paint::white(&base));
error_!("{}", e);
panic!("aborting due to {} base error", kind);
});
if base.query().is_some() {
warn!("query in {} base '{}' is ignored", kind, Paint::white(&base));
base.clear_query();
}
for unmounted_item in items {
let item = m(&base, unmounted_item.clone())
.unwrap_or_else(|e| {
error!("malformed URI in {} {}", kind, unmounted_item);
error_!("{}", e);
panic!("aborting due to invalid {} URI", kind);
});
f(&mut self, item)
}
self
}
pub fn mount<'a, B, R>(self, base: B, routes: R) -> Self
where B: TryInto<Origin<'a>> + Clone + fmt::Display,
B::Error: fmt::Display,
R: Into<Vec<Route>>
{
self.load("route", base, routes.into(),
|base, route| route.map_base(|old| format!("{}{}", base, old)),
|r, route| r.0.routes.push(route))
}
pub fn register<'a, B, C>(self, base: B, catchers: C) -> Self
where B: TryInto<Origin<'a>> + Clone + fmt::Display,
B::Error: fmt::Display,
C: Into<Vec<Catcher>>
{
self.load("catcher", base, catchers.into(),
|base, catcher| catcher.map_base(|old| format!("{}{}", base, old)),
|r, catcher| r.0.catchers.push(catcher))
}
pub fn manage<T>(self, state: T) -> Self
where T: Send + Sync + 'static
{
let type_name = std::any::type_name::<T>();
if !self.state.set(state) {
error!("state for type '{}' is already being managed", type_name);
panic!("aborting due to duplicately managed state");
}
self
}
pub fn attach<F: Fairing>(mut self, fairing: F) -> Self {
self.fairings.add(Box::new(fairing));
self
}
pub async fn ignite(mut self) -> Result<Rocket<Ignite>, Error> {
self = Fairings::handle_ignite(self).await;
self.fairings.audit().map_err(|f| ErrorKind::FailedFairings(f.to_vec()))?;
#[allow(unused_mut)]
let mut config = self.figment.extract::<Config>().map_err(ErrorKind::Config)?;
crate::log::init(&config);
#[cfg(feature = "secrets")]
if !config.secret_key.is_provided() {
let profile = self.figment.profile();
if profile != Config::DEBUG_PROFILE {
return Err(Error::new(ErrorKind::InsecureSecretKey(profile.clone())));
}
if config.secret_key.is_zero() {
config.secret_key = crate::config::SecretKey::generate()
.unwrap_or(crate::config::SecretKey::zero());
}
};
let mut router = Router::new();
self.routes.clone().into_iter().for_each(|r| router.add_route(r));
self.catchers.clone().into_iter().for_each(|c| router.add_catcher(c));
router.finalize().map_err(ErrorKind::Collisions)?;
self.state.freeze();
config.pretty_print(self.figment());
log_items("🛰 ", "Routes", self.routes(), |r| &r.uri.base, |r| &r.uri);
log_items("👾 ", "Catchers", self.catchers(), |c| &c.base, |c| &c.base);
self.fairings.pretty_print();
let rocket: Rocket<Ignite> = Rocket(Igniting {
router, config,
shutdown: Shutdown(TripWire::new()),
figment: self.0.figment,
fairings: self.0.fairings,
state: self.0.state,
});
let sentinels = rocket.routes().flat_map(|r| r.sentinels.iter());
sentinel::query(sentinels, &rocket).map_err(ErrorKind::SentinelAborts)?;
Ok(rocket)
}
}
fn log_items<T, I, B, O>(e: &str, t: &str, items: I, base: B, origin: O)
where T: fmt::Display + Copy, I: Iterator<Item = T>,
B: Fn(&T) -> &Origin<'_>, O: Fn(&T) -> &Origin<'_>
{
let mut items: Vec<_> = items.collect();
if !items.is_empty() {
launch_info!("{}{}:", Paint::emoji(e), Paint::magenta(t));
}
items.sort_by_key(|i| origin(i).path().as_str().chars().count());
items.sort_by_key(|i| origin(i).path().segments().len());
items.sort_by_key(|i| base(i).path().as_str().chars().count());
items.sort_by_key(|i| base(i).path().segments().len());
items.iter().for_each(|i| launch_info_!("{}", i));
}
impl Rocket<Ignite> {
pub fn config(&self) -> &Config {
&self.config
}
pub fn shutdown(&self) -> Shutdown {
self.shutdown.clone()
}
fn into_orbit(self) -> Rocket<Orbit> {
Rocket(Orbiting {
router: self.0.router,
fairings: self.0.fairings,
figment: self.0.figment,
config: self.0.config,
state: self.0.state,
shutdown: self.0.shutdown,
})
}
async fn _local_launch(self) -> Rocket<Orbit> {
let rocket = self.into_orbit();
rocket.fairings.handle_liftoff(&rocket).await;
launch_info!("{}{}", Paint::emoji("🚀 "),
Paint::default("Rocket has launched into local orbit").bold());
rocket
}
async fn _launch(self) -> Result<(), Error> {
self.into_orbit().default_tcp_http_server(|rkt| Box::pin(async move {
rkt.fairings.handle_liftoff(&rkt).await;
let proto = rkt.config.tls_enabled().then(|| "https").unwrap_or("http");
let addr = format!("{}://{}:{}", proto, rkt.config.address, rkt.config.port);
launch_info!("{}{} {}",
Paint::emoji("🚀 "),
Paint::default("Rocket has launched from").bold(),
Paint::default(addr).bold().underline());
})).await
}
}
impl Rocket<Orbit> {
pub fn config(&self) -> &Config {
&self.config
}
pub fn shutdown(&self) -> Shutdown {
self.shutdown.clone()
}
}
impl<P: Phase> Rocket<P> {
pub fn routes(&self) -> impl Iterator<Item = &Route> {
match self.0.as_state_ref() {
StateRef::Build(p) => Either::Left(p.routes.iter()),
StateRef::Ignite(p) => Either::Right(p.router.routes()),
StateRef::Orbit(p) => Either::Right(p.router.routes()),
}
}
pub fn catchers(&self) -> impl Iterator<Item = &Catcher> {
match self.0.as_state_ref() {
StateRef::Build(p) => Either::Left(p.catchers.iter()),
StateRef::Ignite(p) => Either::Right(p.router.catchers()),
StateRef::Orbit(p) => Either::Right(p.router.catchers()),
}
}
pub fn state<T: Send + Sync + 'static>(&self) -> Option<&T> {
match self.0.as_state_ref() {
StateRef::Build(p) => p.state.try_get(),
StateRef::Ignite(p) => p.state.try_get(),
StateRef::Orbit(p) => p.state.try_get(),
}
}
pub fn figment(&self) -> &Figment {
match self.0.as_state_ref() {
StateRef::Build(p) => &p.figment,
StateRef::Ignite(p) => &p.figment,
StateRef::Orbit(p) => &p.figment,
}
}
pub(crate) async fn local_launch(self) -> Result<Rocket<Orbit>, Error> {
let rocket = match self.0.into_state() {
State::Build(s) => Rocket::from(s).ignite().await?._local_launch().await,
State::Ignite(s) => Rocket::from(s)._local_launch().await,
State::Orbit(s) => Rocket::from(s)
};
Ok(rocket)
}
pub async fn launch(self) -> Result<(), Error> {
match self.0.into_state() {
State::Build(s) => Rocket::from(s).ignite().await?._launch().await,
State::Ignite(s) => Rocket::from(s)._launch().await,
State::Orbit(_) => Ok(())
}
}
}
#[doc(hidden)]
impl<P: Phase> Deref for Rocket<P> {
type Target = P::State;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[doc(hidden)]
impl<P: Phase> DerefMut for Rocket<P> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<P: Phase> fmt::Debug for Rocket<P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}