use std::collections::BTreeMap;
use std::path::Path;
use std::time::Duration;
use serde::Deserialize;
mod application;
pub use application::{ApplicationPool, PhpApplication};
pub(crate) mod file;
mod pool;
pub use pool::RequestPool;
#[cfg(feature = "tasks")]
mod task;
#[cfg(feature = "tasks")]
pub(crate) use task::TaskProgram;
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
#[serde(default, deny_unknown_fields)]
pub struct PhpEventConfig {
#[serde(default = "default_event_capacity")]
pub capacity: usize,
}
fn default_event_capacity() -> usize {
64
}
impl Default for PhpEventConfig {
fn default() -> Self {
Self { capacity: default_event_capacity() }
}
}
impl PhpEventConfig {
pub(crate) fn validate(self) -> Result<(), String> {
if self.capacity == 0 || self.capacity > usize::MAX / 2 {
return Err("PHP events.capacity must be positive and at most usize::MAX / 2".into());
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
#[serde(default, deny_unknown_fields)]
pub struct PhpManagementConfig {
#[serde(deserialize_with = "claviron_helpers::de::deserialize_duration")]
pub publish_interval: Duration,
#[serde(deserialize_with = "claviron_helpers::de::deserialize_duration")]
pub max_age: Duration,
}
impl Default for PhpManagementConfig {
fn default() -> Self {
Self {
publish_interval: Duration::from_secs(1),
max_age: Duration::from_secs(5),
}
}
}
impl PhpManagementConfig {
pub(crate) fn validate(self) -> Result<(), String> {
if self.publish_interval.is_zero() {
return Err(
"PHP management publish_interval must be greater than zero"
.into(),
);
}
if self.max_age.is_zero() {
return Err(
"PHP management max_age must be greater than zero".into()
);
}
Ok(())
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
#[serde(default, deny_unknown_fields)]
pub struct PhpLaunchConfig {
pub max_in_flight: usize,
#[serde(
deserialize_with = "claviron_helpers::de::deserialize_optional_duration"
)]
pub execution_timeout: Option<Duration>,
#[serde(deserialize_with = "claviron_helpers::de::deserialize_duration")]
pub drain_timeout: Duration,
#[cfg(feature = "tasks")]
pub log: claviron_tasks::LogConfig,
}
impl Default for PhpLaunchConfig {
fn default() -> Self {
Self {
max_in_flight: 8,
execution_timeout: None,
drain_timeout: Duration::from_secs(10),
#[cfg(feature = "tasks")]
log: claviron_tasks::LogConfig::default(),
}
}
}
impl PhpLaunchConfig {
pub(crate) fn validate(&self) -> Result<(), String> {
if self.max_in_flight == 0 {
return Err(
"PHP launch max_in_flight must be greater than zero".into()
);
}
if self.execution_timeout.is_some_and(|budget| budget.is_zero()) {
return Err("PHP launch execution_timeout must be greater than zero when set".into());
}
if self.drain_timeout.is_zero() {
return Err(
"PHP launch drain_timeout must be greater than zero".into()
);
}
Ok(())
}
}
fn default_instances() -> u8 {
1
}
pub(crate) fn default_cache_invalidation() -> bool {
true
}
fn validate_program(
kind: &str,
name: &str,
script_path: &Path,
args: &[String],
environment: &BTreeMap<String, String>,
) -> Result<(), String> {
if name.trim().is_empty() || name.contains('\0') {
return Err(format!(
"{kind} name must be nonempty and contain no NUL bytes"
));
}
if args.len() >= i32::MAX as usize
|| args.iter().any(|arg| arg.contains('\0'))
{
return Err(format!(
"{kind} {name:?} arguments must fit argc and contain no NUL bytes"
));
}
if script_path.as_os_str().is_empty()
|| script_path.as_os_str().as_encoded_bytes().contains(&0)
{
return Err(format!(
"{kind} {name:?} script_path must be nonempty and contain no NUL bytes"
));
}
if !script_path.exists() {
return Err(format!(
"{kind} {name:?} script_path {} file not exist",
script_path.to_string_lossy()
));
}
for (key, value) in environment {
if key.is_empty() || key.contains(['\0', '=']) {
return Err(format!(
"{kind} {name:?} environment keys must be nonempty and contain neither NUL nor '='"
));
}
if value.contains('\0') {
return Err(format!(
"{kind} {name:?} environment values must contain no NUL bytes"
));
}
}
Ok(())
}