use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::task::{Context, Poll};
use ::axum::extract::FromRequestParts;
use ::axum::response::IntoResponse;
use ::http::request::Parts;
use ::http::StatusCode;
use ::tower::{Layer, Service};
use crate::compress::{compress, CompressionLevel};
use crate::discovery::{find_and_parse, FindError};
use crate::parser::FafFile;
use crate::types::FafData;
use crate::validator::{validate, ValidationResult};
#[derive(Clone, Debug)]
pub struct FafContext(Arc<FafContextInner>);
#[derive(Debug)]
struct FafContextInner {
file: FafFile,
validation: ValidationResult,
compressed: Option<FafData>,
}
impl FafContext {
#[inline]
pub fn project_name(&self) -> &str {
self.0.file.project_name()
}
pub fn score(&self) -> Option<u8> {
self.0.file.score()
}
#[inline]
pub fn version(&self) -> &str {
self.0.file.version()
}
pub fn tech_stack(&self) -> Option<&str> {
self.0.file.tech_stack()
}
pub fn goal(&self) -> Option<&str> {
self.0.file.goal()
}
pub fn data(&self) -> &FafData {
&self.0.file.data
}
pub fn validation(&self) -> &ValidationResult {
&self.0.validation
}
pub fn compressed(&self) -> Option<&FafData> {
self.0.compressed.as_ref()
}
pub fn file(&self) -> &FafFile {
&self.0.file
}
pub fn ptr_eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl<S> FromRequestParts<S> for FafContext
where
S: Send + Sync,
{
type Rejection = FafContextRejection;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
parts
.extensions
.get::<FafContext>()
.cloned()
.ok_or(FafContextRejection)
}
}
#[derive(Debug)]
pub struct FafContextRejection;
impl IntoResponse for FafContextRejection {
fn into_response(self) -> ::axum::response::Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
"FafLayer not installed — add .layer(FafLayer::new()) to your Router",
)
.into_response()
}
}
impl std::fmt::Display for FafContextRejection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "FafLayer not installed")
}
}
#[derive(Clone, Debug)]
pub struct FafLayer {
context: FafContext,
}
impl Default for FafLayer {
fn default() -> Self {
Self::new()
}
}
impl FafLayer {
pub fn new() -> Self {
Self::builder()
.try_build()
.expect("FafLayer::new() — no .faf file found. Use FafLayer::builder().dir(...).try_build() for graceful handling.")
}
pub fn builder() -> FafLayerBuilder {
FafLayerBuilder {
dir: None,
compression: None,
validate: true,
}
}
pub fn from_file(file: FafFile) -> Self {
let validation = validate(&file);
Self {
context: FafContext(Arc::new(FafContextInner {
file,
validation,
compressed: None,
})),
}
}
pub fn context(&self) -> &FafContext {
&self.context
}
}
impl<S> Layer<S> for FafLayer {
type Service = FafService<S>;
fn layer(&self, inner: S) -> Self::Service {
FafService {
inner,
context: self.context.clone(),
}
}
}
pub struct FafLayerBuilder {
dir: Option<PathBuf>,
compression: Option<CompressionLevel>,
validate: bool,
}
impl FafLayerBuilder {
pub fn dir<P: AsRef<Path>>(mut self, dir: P) -> Self {
self.dir = Some(dir.as_ref().to_path_buf());
self
}
pub fn compression(mut self, level: CompressionLevel) -> Self {
self.compression = Some(level);
self
}
pub fn validate(mut self, yes: bool) -> Self {
self.validate = yes;
self
}
pub fn try_build(self) -> Result<FafLayer, FindError> {
let file = match &self.dir {
Some(d) => find_and_parse(Some(d))?,
None => find_and_parse::<PathBuf>(None)?,
};
let validation = if self.validate {
validate(&file)
} else {
ValidationResult {
valid: true,
errors: Vec::new(),
warnings: Vec::new(),
score: 0,
}
};
let compressed = self.compression.map(|level| compress(&file, level));
Ok(FafLayer {
context: FafContext(Arc::new(FafContextInner {
file,
validation,
compressed,
})),
})
}
pub fn build(self) -> FafLayer {
self.try_build().expect("FafLayerBuilder::build() failed")
}
}
#[derive(Clone, Debug)]
pub struct FafService<S> {
inner: S,
context: FafContext,
}
impl<S, B> Service<::http::Request<B>> for FafService<S>
where
S: Service<::http::Request<B>>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, mut req: ::http::Request<B>) -> Self::Future {
req.extensions_mut().insert(self.context.clone());
self.inner.call(req)
}
}