use std::convert::Infallible;
use std::sync::Arc;
use claviron_http::policy::{HttpPolicy, HttpPolicyOverride};
use claviron_http::{RouteBody, RouteResponse, empty_body};
use claviron_php::PhpRuntime;
use claviron_runtime::{AppState, registry};
use http::{Request, Response, StatusCode};
use crate::PhpApplicationService;
use crate::engine::context::RequestEnvironment;
#[derive(Clone)]
pub struct PhpApplicationAdapter {
service: Arc<PhpApplicationService>,
application: String,
environment: Option<RequestEnvironment>,
route: HttpPolicyOverride,
}
impl PhpApplicationAdapter {
pub fn new(
service: Arc<PhpApplicationService>,
application: impl Into<String>,
) -> Self {
Self {
service,
application: application.into(),
environment: None,
route: HttpPolicyOverride::default(),
}
}
pub fn environment(
mut self,
state: &AppState,
runtime: &PhpRuntime,
) -> registry::Result<Self> {
self.environment =
if runtime.env_file.is_empty() && runtime.environment.is_empty() {
None
} else {
let recipe = state
.program_recipe(
runtime.env_file.iter(),
runtime
.environment
.iter()
.map(|(key, value)| (key.clone(), value.clone())),
)
.map_err(|error| registry::Error::msg(error.to_string()))?;
Some(RequestEnvironment(
state.config_reader().environments().read(recipe).map_err(
|error| registry::Error::msg(error.to_string()),
)?,
))
};
Ok(self)
}
pub fn route_policy(
mut self,
route: HttpPolicyOverride,
) -> Self {
self.route = route;
self
}
pub async fn handle(
&self,
mut request: Request<RouteBody>,
) -> Result<RouteResponse, Infallible> {
let Some(policy) = request.extensions().get::<HttpPolicy>().copied()
else {
tracing::error!(application = %self.application, "HttpPolicy is required for PHP application execution");
let mut response = Response::new(empty_body());
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
return Ok(response);
};
let policy = policy.narrow(&self.route);
request.extensions_mut().insert(policy);
if let Some(environment) = &self.environment {
request.extensions_mut().insert(environment.clone());
}
Ok(match self.service.execute(&self.application, request).await {
Ok(response) => response,
Err(error) => {
tracing::warn!(application = %self.application, %error, "PHP application request failed");
let mut response = Response::new(empty_body());
*response.status_mut() = error.status_code();
if response.status() == StatusCode::SERVICE_UNAVAILABLE {
response.headers_mut().insert(
http::header::RETRY_AFTER,
http::HeaderValue::from_static("1"),
);
}
response
}
})
}
}