path.rs
1 // Copyright (c) 2025-2026 ACDC Network 2 // This file is part of the alphaos library. 3 // 4 // Alpha Chain | Delta Chain Protocol 5 // International Monetary Graphite. 6 // 7 // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com). 8 // They built world-class ZK infrastructure. We installed the EASY button. 9 // Their cryptography: elegant. Our modifications: bureaucracy-compatible. 10 // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours. 11 // 12 // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0 13 // All modifications and new work: CC0 1.0 Universal Public Domain Dedication. 14 // No rights reserved. No permission required. No warranty. No refunds. 15 // 16 // https://creativecommons.org/publicdomain/zero/1.0/ 17 // SPDX-License-Identifier: CC0-1.0 18 19 use super::RestError; 20 21 use axum::{ 22 extract::{path::ErrorKind, rejection::PathRejection, FromRequestParts}, 23 http::request::Parts, 24 }; 25 use serde::de::DeserializeOwned; 26 27 struct PathError { 28 message: String, 29 path: String, 30 cause: anyhow::Error, 31 location: Option<String>, 32 } 33 34 /// Convert Path errors into the unified REST error type. 35 impl From<PathError> for RestError { 36 fn from(val: PathError) -> Self { 37 let err = if let Some(loc) = val.location { 38 val.cause.context(format!("Invalid argument \"{loc}\" in path \"{}\": {}", val.path, val.message)) 39 } else { 40 val.cause.context(format!("Invalid path \"{}\": {}", val.path, val.message)) 41 }; 42 43 RestError::bad_request(err) 44 } 45 } 46 47 /// Custom Path extractor to improve errors in invalid URLs. 48 /// Adapted from axum's [customize-path-rejection](https://github.com/tokio-rs/axum/blob/main/examples/customize-path-rejection/src/main.rs) 49 pub struct Path<T>(pub T); 50 51 impl<S, T> FromRequestParts<S> for Path<T> 52 where 53 T: DeserializeOwned + Send, 54 S: Send + Sync, 55 { 56 type Rejection = RestError; 57 58 async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { 59 match axum::extract::Path::<T>::from_request_parts(parts, state).await { 60 Ok(value) => Ok(Self(value.0)), 61 Err(rejection) => { 62 let err = match rejection { 63 PathRejection::FailedToDeserializePathParams(inner) => { 64 let kind = inner.kind(); 65 66 let (message, location) = match &kind { 67 ErrorKind::WrongNumberOfParameters { .. } => { 68 ("wrong number of parameters".to_string(), None) 69 } 70 ErrorKind::ParseErrorAtKey { key, value, expected_type } => ( 71 format!("value `{value}` is not of expected type `{expected_type}`"), 72 Some(key.clone()), 73 ), 74 ErrorKind::ParseErrorAtIndex { index, value, expected_type } => ( 75 format!("value `{value}` at index {index} is not of expected type `{expected_type}`"), 76 None, 77 ), 78 ErrorKind::ParseError { value, expected_type } => { 79 (format!("value `{value}` is not of expected type `{expected_type}`"), None) 80 } 81 ErrorKind::InvalidUtf8InPathParam { key } => { 82 ("invalid UTF-8 in parameter".to_string(), Some(key.clone())) 83 } 84 ErrorKind::Message(msg) => (format!("unknown error: {msg}"), None), 85 _ => ("unknown error".to_string(), None), 86 }; 87 88 PathError { message, path: parts.uri.path().to_string(), location, cause: inner.into() } 89 } 90 PathRejection::MissingPathParams(error) => PathError { 91 message: "missing path parameter".to_string(), 92 path: parts.uri.path().to_string(), 93 location: None, 94 cause: error.into(), 95 }, 96 _ => PathError { 97 message: "unknown path error".to_string(), 98 path: parts.uri.path().to_string(), 99 location: None, 100 cause: rejection.into(), 101 }, 102 }; 103 104 Err(err.into()) 105 } 106 } 107 } 108 }