/* This file is part of gnix (https://codeberg.org/metamuffin/gnix) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin */ use super::{Node, NodeContext, NodeKind, NodeRequest, NodeResponse}; use crate::{config::DynNode, error::ServiceError}; use futures::Future; use http::Uri; use regex::{Regex, RegexSet}; use serde::{ de::{ value::{MapAccessDeserializer, SeqAccessDeserializer}, Visitor, }, Deserialize, }; use serde_yml::Value; use std::{collections::BTreeMap, marker::PhantomData, pin::Pin, sync::Arc}; pub struct PathsKind; struct Paths { matcher: RegexSet, extractors: Vec, handlers: Vec, } impl NodeKind for PathsKind { fn name(&self) -> &'static str { "paths" } fn instanciate(&self, config: Value) -> anyhow::Result> { let routes = serde_yml::from_value::>(config)?.0; let mut handlers = Vec::new(); let mut patterns = Vec::new(); let mut extractors = Vec::new(); for (k, v) in routes { let pattern = format!("^{k}$"); handlers.push(v); extractors.push(Regex::new(&pattern)?); patterns.push(pattern); } let matcher = RegexSet::new(&patterns)?; Ok(Arc::new(Paths { handlers, matcher, extractors, })) } } impl Node for Paths { fn handle<'a>( &'a self, context: &'a mut NodeContext, mut request: NodeRequest, ) -> Pin> + Send + Sync + 'a>> { Box::pin(async move { let path = request.uri().path(); let index = self .matcher .matches(path) .iter() .next() .ok_or(ServiceError::UnknownPath)?; let caps = self.extractors[index] .captures(path) .ok_or(ServiceError::Other)?; if let Some(rest) = caps.get(1) { let mut parts = http::uri::Parts::default(); parts.scheme = request.uri().scheme().cloned(); parts.authority = request.uri().authority().cloned(); if let Some(q) = request.uri().query() { parts.path_and_query = Some( format!("{}?{}", rest.as_str(), q) .parse() .map_err(|_| ServiceError::Other)?, ); } else { parts.path_and_query = Some(rest.as_str().parse().map_err(|_| ServiceError::Other)?); } *request.uri_mut() = Uri::from_parts(parts).map_err(|_| ServiceError::InvalidUri)? } self.handlers[index].handle(context, request).await }) } } struct SeqOrMap(Vec<(String, T)>); impl<'de, T: Deserialize<'de>> Deserialize<'de> for SeqOrMap { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { struct Vis(PhantomData); impl<'de, V: Deserialize<'de>> Visitor<'de> for Vis { type Value = SeqOrMap; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("map or sequence of maps") } fn visit_seq(self, seq: A) -> Result where A: serde::de::SeqAccess<'de>, { Vec::>::deserialize(SeqAccessDeserializer::new(seq)) .map(|e| SeqOrMap(e.into_iter().flat_map(|el| el.into_iter()).collect())) } fn visit_map(self, map: A) -> Result where A: serde::de::MapAccess<'de>, { BTreeMap::::deserialize(MapAccessDeserializer::new(map)) .map(|map| SeqOrMap(map.into_iter().collect())) } } deserializer.deserialize_any(Vis(PhantomData)) } }