coil-runtime 0.1.1

HTTP runtime and request handling for the Coil framework.
Documentation
use std::collections::BTreeMap;

use super::RouteUrlError;

pub(super) fn match_route_path(pattern: &str, actual: &str) -> Option<BTreeMap<String, String>> {
    let pattern_segments = path_segments(pattern);
    let actual_segments = path_segments(actual);
    if pattern_segments.len() != actual_segments.len() {
        return None;
    }

    let mut params = BTreeMap::new();
    for (pattern_segment, actual_segment) in pattern_segments.iter().zip(actual_segments.iter()) {
        if pattern_segment.starts_with('{')
            && pattern_segment.ends_with('}')
            && pattern_segment.len() > 2
        {
            params.insert(
                pattern_segment[1..pattern_segment.len() - 1].to_string(),
                (*actual_segment).to_string(),
            );
        } else if pattern_segment != actual_segment {
            return None;
        }
    }

    Some(params)
}

pub(super) fn render_route_path(
    pattern: &str,
    params: &BTreeMap<String, String>,
    route_name: &str,
) -> Result<String, RouteUrlError> {
    let rendered_segments = path_segments(pattern)
        .into_iter()
        .map(|segment| {
            if segment.starts_with('{') && segment.ends_with('}') && segment.len() > 2 {
                let parameter = &segment[1..segment.len() - 1];
                params
                    .get(parameter)
                    .cloned()
                    .ok_or_else(|| RouteUrlError::MissingRouteParameter {
                        route: route_name.to_string(),
                        parameter: parameter.to_string(),
                    })
            } else {
                Ok(segment.to_string())
            }
        })
        .collect::<Result<Vec<_>, _>>()?;

    if rendered_segments.is_empty() {
        Ok("/".to_string())
    } else {
        Ok(format!("/{}", rendered_segments.join("/")))
    }
}

fn path_segments(path: &str) -> Vec<&str> {
    path.trim_matches('/')
        .split('/')
        .filter(|segment| !segment.is_empty())
        .collect()
}