use crate::*;
use rocket::{
async_trait,
fairing::{Fairing, Info, Kind},
Build, Data, Request, Rocket, State,
};
use std::{
collections::HashMap,
ops::{Index, IndexMut},
sync::Arc,
};
use std::{future::Future, pin::Pin};
type Fun = fn(&Request) -> Result<LangCode, Error>;
type AsyncFn = Arc<
dyn Fn(
&Request,
)
-> Pin<Box<dyn Future<Output = Result<LangCode, Error>> + Send + Sync + 'static>>
+ Send
+ Sync
+ 'static,
>;
#[derive(Clone)]
pub struct Config {
pub wildcard: Option<LangCode>,
pub(crate) accept_language: HashMap<LangCode, f32>,
pub(crate) url: Option<i32>,
pub(crate) custom: Option<Result<Fun, AsyncFn>>,
}
impl Config {
pub fn wildcard(mut self, lang: LangCode) -> Self {
self.wildcard = Some(lang);
self
}
pub fn url(mut self, position: i32) -> Self {
self.url = Some(position);
self
}
pub fn new() -> Self {
Self::default()
}
pub fn custom(self, f: Fun) -> Self {
Self {
custom: Some(Ok(f)),
..self
}
}
pub fn custom_async<F>(self, f: fn(&Request) -> F) -> Self
where
F: Future<Output = Result<LangCode, Error>> + Send + Sync + 'static,
{
Self {
custom: Some(Err(Arc::new(move |req| Box::pin(f(req))))),
..self
}
}
pub(crate) async fn choose(&self, req: &Request<'_>) -> Result<LangCode, Error> {
self.with_custom(req)
.await
.or_else(|e1| {
self.with_url(req)
.map_err(|e2| e1.or(e2))
})
.or_else(|e1| {
self.with_lang_header(req)
.map_err(|e2| e1.or(Some(e2)))
})
.or_else(|err| {
if let Some(val) = self.wildcard {
return Ok(val);
}
Err(err)
})
.map_err(Option::unwrap)
}
async fn with_custom(&self, req: &Request<'_>) -> Result<LangCode, Option<Error>> {
match self.custom.as_ref() {
Some(Ok(custom)) => {
return custom(req).map_err(Some);
}
Some(Err(custom)) => {
return custom(req)
.await
.map_err(Some);
}
None => Err(None),
}
}
fn with_url(&self, req: &Request) -> Result<LangCode, Option<Error>> {
if let Some(pos) = self.url {
return crate::url::get(req, pos).map_err(Some);
}
Err(None)
}
fn with_lang_header(&self, req: &Request) -> Result<LangCode, Error> {
crate::accept_language::with_config(req, self)
}
}
pub(crate) struct PrivConfig(Config);
impl Default for Config {
fn default() -> Self {
let mut config = Config {
wildcard: None,
url: None,
accept_language: HashMap::with_capacity(LangCode::ALL_CODES.len()),
custom: None,
};
for lang in LangCode::ALL_CODES {
config
.accept_language
.insert(*lang, 0.0);
}
config
}
}
#[async_trait]
impl Fairing for Config {
fn info(&self) -> Info {
Info {
name: "Language configuration",
kind: Kind::Ignite | Kind::Request,
}
}
async fn on_ignite(&self, rocket: Rocket<Build>) -> rocket::fairing::Result {
Ok(rocket.manage(PrivConfig(self.clone())))
}
async fn on_request(&self, req: &mut Request<'_>, _data: &mut Data<'_>) {
if let Outcome::Success(config) = req
.guard::<&State<PrivConfig>>()
.await
{
let result = config.0.choose(req).await;
req.local_cache(|| result);
}
}
}
impl Index<LangCode> for Config {
type Output = f32;
fn index(&self, index: LangCode) -> &Self::Output {
&self.accept_language[&index]
}
}
impl IndexMut<LangCode> for Config {
fn index_mut(&mut self, index: LangCode) -> &mut Self::Output {
self.accept_language
.get_mut(&index)
.unwrap()
}
}