nidus-http 1.0.2

Axum and Tower HTTP integration, controllers, middleware, health, metrics, and server defaults for Nidus.
Documentation
use crate::error::RoutePathError;

pub(crate) fn join_paths(prefix: &str, path: &str) -> Result<String, RoutePathError> {
    let prefix = normalize_path(prefix)?;
    let path = normalize_path(path)?;
    let prefix = trim_mount_suffix(&prefix);
    let full_path = if prefix == "/" {
        path
    } else if path == "/" {
        prefix.to_owned()
    } else {
        format!("{prefix}{path}")
    };
    Ok(full_path)
}

pub(crate) fn normalize_path(path: impl AsRef<str>) -> Result<String, RoutePathError> {
    let path = path.as_ref().trim();
    validate_path(path)?;
    let with_slash = if path.starts_with('/') {
        path.to_owned()
    } else {
        format!("/{path}")
    };
    Ok(convert_nest_params(&with_slash))
}

fn validate_path(path: &str) -> Result<(), RoutePathError> {
    for segment in path.split('/') {
        if segment == ":" {
            return Err(RoutePathError::empty_parameter(path));
        }
    }
    Ok(())
}

fn convert_nest_params(path: &str) -> String {
    path.split('/')
        .map(|segment| {
            if let Some(name) = segment.strip_prefix(':') {
                format!("{{{name}}}")
            } else {
                segment.to_owned()
            }
        })
        .collect::<Vec<_>>()
        .join("/")
}

fn trim_mount_suffix(prefix: &str) -> &str {
    let trimmed = prefix.trim_end_matches('/');
    if trimmed.is_empty() { "/" } else { trimmed }
}