use std::collections::HashSet;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("Invalid pattern: {0}")]
InvalidPattern(String),
#[error("Unknown route path: {0}. Valid paths are: /v1/mint/quote/{{method}}, /v1/mint/{{method}}, /v1/melt/quote/{{method}}, /v1/melt/{{method}}, /v1/swap, /v1/checkstate, /v1/restore, /v1/auth/blind/mint, /v1/ws")]
UnknownRoute(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
pub struct Settings {
pub openid_discovery: String,
pub client_id: String,
pub protected_endpoints: Vec<ProtectedEndpoint>,
}
impl Settings {
pub fn new(
openid_discovery: String,
client_id: String,
protected_endpoints: Vec<ProtectedEndpoint>,
) -> Self {
Self {
openid_discovery,
client_id,
protected_endpoints,
}
}
}
impl<'de> Deserialize<'de> for Settings {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct RawSettings {
openid_discovery: String,
client_id: String,
protected_endpoints: Vec<RawProtectedEndpoint>,
}
#[derive(Deserialize)]
struct RawProtectedEndpoint {
method: Method,
path: String,
}
let raw = RawSettings::deserialize(deserializer)?;
let mut protected_endpoints = HashSet::new();
for raw_endpoint in raw.protected_endpoints {
let expanded_paths = matching_route_paths(&raw_endpoint.path).map_err(|e| {
serde::de::Error::custom(format!("Invalid pattern '{}': {}", raw_endpoint.path, e))
})?;
for path in expanded_paths {
protected_endpoints.insert(ProtectedEndpoint::new(raw_endpoint.method, path));
}
}
Ok(Settings {
openid_discovery: raw.openid_discovery,
client_id: raw.client_id,
protected_endpoints: protected_endpoints.into_iter().collect(),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
pub struct ProtectedEndpoint {
pub method: Method,
pub path: RoutePath,
}
impl ProtectedEndpoint {
pub fn new(method: Method, path: RoutePath) -> Self {
Self { method, path }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
pub enum Method {
Get,
Post,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
pub enum RoutePath {
MintQuote(String),
Mint(String),
MeltQuote(String),
Melt(String),
Swap,
Checkstate,
Restore,
MintBlindAuth,
Ws,
}
impl Serialize for RoutePath {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl std::str::FromStr for RoutePath {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"/v1/swap" => Ok(RoutePath::Swap),
"/v1/checkstate" => Ok(RoutePath::Checkstate),
"/v1/restore" => Ok(RoutePath::Restore),
"/v1/auth/blind/mint" => Ok(RoutePath::MintBlindAuth),
"/v1/ws" => Ok(RoutePath::Ws),
_ => {
if let Some(method) = s.strip_prefix("/v1/mint/quote/") {
Ok(RoutePath::MintQuote(method.to_string()))
} else if let Some(method) = s.strip_prefix("/v1/mint/") {
Ok(RoutePath::Mint(method.to_string()))
} else if let Some(method) = s.strip_prefix("/v1/melt/quote/") {
Ok(RoutePath::MeltQuote(method.to_string()))
} else if let Some(method) = s.strip_prefix("/v1/melt/") {
Ok(RoutePath::Melt(method.to_string()))
} else {
Err(Error::UnknownRoute(s.to_string()))
}
}
}
}
}
impl<'de> Deserialize<'de> for RoutePath {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
RoutePath::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl RoutePath {
pub fn static_paths() -> Vec<RoutePath> {
vec![
RoutePath::Swap,
RoutePath::Checkstate,
RoutePath::Restore,
RoutePath::MintBlindAuth,
RoutePath::Ws,
]
}
pub fn common_payment_method_paths() -> Vec<RoutePath> {
let methods = vec!["bolt11", "bolt12"];
let mut paths = Vec::new();
for method in methods {
paths.push(RoutePath::MintQuote(method.to_string()));
paths.push(RoutePath::Mint(method.to_string()));
paths.push(RoutePath::MeltQuote(method.to_string()));
paths.push(RoutePath::Melt(method.to_string()));
}
paths
}
pub fn all_known_paths() -> Vec<RoutePath> {
let mut paths = Self::static_paths();
paths.extend(Self::common_payment_method_paths());
paths
}
}
pub fn matching_route_paths(pattern: &str) -> Result<Vec<RoutePath>, Error> {
if let Some(prefix) = pattern.strip_suffix('*') {
if prefix.contains('*') {
return Err(Error::InvalidPattern(
"Wildcard '*' must be the last character".to_string(),
));
}
Ok(RoutePath::all_known_paths()
.into_iter()
.filter(|path| path.to_string().starts_with(prefix))
.collect())
} else {
if pattern.contains('*') {
return Err(Error::InvalidPattern(
"Wildcard '*' must be the last character".to_string(),
));
}
match RoutePath::from_str(pattern) {
Ok(path) => Ok(vec![path]),
Err(_) => Ok(vec![]), }
}
}
impl std::fmt::Display for RoutePath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RoutePath::MintQuote(method) => write!(f, "/v1/mint/quote/{}", method),
RoutePath::Mint(method) => write!(f, "/v1/mint/{}", method),
RoutePath::MeltQuote(method) => write!(f, "/v1/melt/quote/{}", method),
RoutePath::Melt(method) => write!(f, "/v1/melt/{}", method),
RoutePath::Swap => write!(f, "/v1/swap"),
RoutePath::Checkstate => write!(f, "/v1/checkstate"),
RoutePath::Restore => write!(f, "/v1/restore"),
RoutePath::MintBlindAuth => write!(f, "/v1/auth/blind/mint"),
RoutePath::Ws => write!(f, "/v1/ws"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nut00::KnownMethod;
use crate::PaymentMethod;
#[test]
fn test_matching_route_paths_root_wildcard() {
let paths = matching_route_paths("*").unwrap();
assert_eq!(paths.len(), RoutePath::all_known_paths().len());
}
#[test]
fn test_matching_route_paths_middle_wildcard() {
let result = matching_route_paths("/v1/*/mint");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::InvalidPattern(_)));
}
#[test]
fn test_matching_route_paths_prefix_without_slash() {
let paths = matching_route_paths("/v1/mint*").unwrap();
assert_eq!(paths.len(), 4);
assert!(!paths.contains(&RoutePath::MeltQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
}
#[test]
fn test_matching_route_paths_exact_match_unknown() {
let paths = matching_route_paths("/v1/invalid/path").unwrap();
assert!(paths.is_empty());
}
#[test]
fn test_matching_route_paths_dynamic_method() {
let paths = matching_route_paths("/v1/mint/custom_method").unwrap();
assert_eq!(paths.len(), 1);
assert_eq!(paths[0], RoutePath::Mint("custom_method".to_string()));
}
#[test]
fn test_matching_route_paths_all() {
let paths = matching_route_paths("/v1/*").unwrap();
assert_eq!(paths.len(), RoutePath::all_known_paths().len());
assert!(paths.contains(&RoutePath::MintQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(paths.contains(&RoutePath::Mint(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(paths.contains(&RoutePath::MeltQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(paths.contains(&RoutePath::Melt(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(paths.contains(&RoutePath::MintQuote(
PaymentMethod::Known(KnownMethod::Bolt12).to_string()
)));
assert!(paths.contains(&RoutePath::Mint(
PaymentMethod::Known(KnownMethod::Bolt12).to_string()
)));
assert!(paths.contains(&RoutePath::Swap));
assert!(paths.contains(&RoutePath::Checkstate));
assert!(paths.contains(&RoutePath::Restore));
assert!(paths.contains(&RoutePath::MintBlindAuth));
}
#[test]
fn test_matching_route_paths_mint_only() {
let paths = matching_route_paths("/v1/mint/*").unwrap();
assert_eq!(paths.len(), 4);
assert!(paths.contains(&RoutePath::MintQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(paths.contains(&RoutePath::Mint(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(paths.contains(&RoutePath::MintQuote(
PaymentMethod::Known(KnownMethod::Bolt12).to_string()
)));
assert!(paths.contains(&RoutePath::Mint(
PaymentMethod::Known(KnownMethod::Bolt12).to_string()
)));
assert!(!paths.contains(&RoutePath::MeltQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(!paths.contains(&RoutePath::Melt(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(!paths.contains(&RoutePath::MeltQuote(
PaymentMethod::Known(KnownMethod::Bolt12).to_string()
)));
assert!(!paths.contains(&RoutePath::Melt(
PaymentMethod::Known(KnownMethod::Bolt12).to_string()
)));
assert!(!paths.contains(&RoutePath::Swap));
}
#[test]
fn test_matching_route_paths_quote_only() {
let paths = matching_route_paths("/v1/mint/quote/*").unwrap();
assert_eq!(paths.len(), 2);
assert!(paths.contains(&RoutePath::MintQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(paths.contains(&RoutePath::MintQuote(
PaymentMethod::Known(KnownMethod::Bolt12).to_string()
)));
assert!(!paths.contains(&RoutePath::Mint(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(!paths.contains(&RoutePath::Melt(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
}
#[test]
fn test_matching_route_paths_no_match() {
let paths = matching_route_paths("/nonexistent/path").unwrap();
assert!(paths.is_empty());
}
#[test]
fn test_matching_route_paths_quote_bolt11_only() {
let paths = matching_route_paths("/v1/mint/quote/bolt11").unwrap();
assert_eq!(paths.len(), 1);
assert!(paths.contains(&RoutePath::MintQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
}
#[test]
fn test_matching_route_paths_invalid_regex() {
let result = matching_route_paths("/*unclosed parenthesis");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::InvalidPattern(_)));
}
#[test]
fn test_route_path_to_string() {
assert_eq!(
RoutePath::MintQuote(PaymentMethod::Known(KnownMethod::Bolt11).to_string()).to_string(),
"/v1/mint/quote/bolt11"
);
assert_eq!(
RoutePath::Mint(PaymentMethod::Known(KnownMethod::Bolt11).to_string()).to_string(),
"/v1/mint/bolt11"
);
assert_eq!(
RoutePath::MeltQuote(PaymentMethod::Known(KnownMethod::Bolt11).to_string()).to_string(),
"/v1/melt/quote/bolt11"
);
assert_eq!(
RoutePath::Melt(PaymentMethod::Known(KnownMethod::Bolt11).to_string()).to_string(),
"/v1/melt/bolt11"
);
assert_eq!(
RoutePath::MintQuote("paypal".to_string()).to_string(),
"/v1/mint/quote/paypal"
);
assert_eq!(RoutePath::Swap.to_string(), "/v1/swap");
assert_eq!(RoutePath::Checkstate.to_string(), "/v1/checkstate");
assert_eq!(RoutePath::Restore.to_string(), "/v1/restore");
assert_eq!(RoutePath::MintBlindAuth.to_string(), "/v1/auth/blind/mint");
}
#[test]
fn test_route_path_serialization() {
let json = serde_json::to_string(&RoutePath::Mint(
PaymentMethod::Known(KnownMethod::Bolt11).to_string(),
))
.unwrap();
assert_eq!(json, "\"/v1/mint/bolt11\"");
let json = serde_json::to_string(&RoutePath::MintQuote("paypal".to_string())).unwrap();
assert_eq!(json, "\"/v1/mint/quote/paypal\"");
let path: RoutePath = serde_json::from_str("\"/v1/mint/bolt11\"").unwrap();
assert_eq!(
path,
RoutePath::Mint(PaymentMethod::Known(KnownMethod::Bolt11).to_string())
);
let path: RoutePath = serde_json::from_str("\"/v1/melt/quote/venmo\"").unwrap();
assert_eq!(path, RoutePath::MeltQuote("venmo".to_string()));
let original = RoutePath::Melt(PaymentMethod::Known(KnownMethod::Bolt12).to_string());
let json = serde_json::to_string(&original).unwrap();
let deserialized: RoutePath = serde_json::from_str(&json).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn test_settings_deserialize_direct_paths() {
let json = r#"{
"openid_discovery": "https://example.com/.well-known/openid-configuration",
"client_id": "client123",
"protected_endpoints": [
{
"method": "GET",
"path": "/v1/mint/bolt11"
},
{
"method": "POST",
"path": "/v1/swap"
}
]
}"#;
let settings: Settings = serde_json::from_str(json).unwrap();
assert_eq!(
settings.openid_discovery,
"https://example.com/.well-known/openid-configuration"
);
assert_eq!(settings.client_id, "client123");
assert_eq!(settings.protected_endpoints.len(), 2);
let paths = settings
.protected_endpoints
.iter()
.map(|ep| (ep.method, ep.path.clone()))
.collect::<Vec<_>>();
assert!(paths.contains(&(
Method::Get,
RoutePath::Mint(PaymentMethod::Known(KnownMethod::Bolt11).to_string())
)));
assert!(paths.contains(&(Method::Post, RoutePath::Swap)));
}
#[test]
fn test_settings_deserialize_with_regex() {
let json = r#"{
"openid_discovery": "https://example.com/.well-known/openid-configuration",
"client_id": "client123",
"protected_endpoints": [
{
"method": "GET",
"path": "/v1/mint/*"
},
{
"method": "POST",
"path": "/v1/swap"
}
]
}"#;
let settings: Settings = serde_json::from_str(json).unwrap();
assert_eq!(
settings.openid_discovery,
"https://example.com/.well-known/openid-configuration"
);
assert_eq!(settings.client_id, "client123");
assert_eq!(settings.protected_endpoints.len(), 5);
let expected_protected: HashSet<ProtectedEndpoint> = HashSet::from_iter(vec![
ProtectedEndpoint::new(Method::Post, RoutePath::Swap),
ProtectedEndpoint::new(
Method::Get,
RoutePath::Mint(PaymentMethod::Known(KnownMethod::Bolt11).to_string()),
),
ProtectedEndpoint::new(
Method::Get,
RoutePath::MintQuote(PaymentMethod::Known(KnownMethod::Bolt11).to_string()),
),
ProtectedEndpoint::new(
Method::Get,
RoutePath::MintQuote(PaymentMethod::Known(KnownMethod::Bolt12).to_string()),
),
ProtectedEndpoint::new(
Method::Get,
RoutePath::Mint(PaymentMethod::Known(KnownMethod::Bolt12).to_string()),
),
]);
let deserlized_protected = settings.protected_endpoints.into_iter().collect();
assert_eq!(expected_protected, deserlized_protected);
}
#[test]
fn test_settings_deserialize_invalid_regex() {
let json = r#"{
"openid_discovery": "https://example.com/.well-known/openid-configuration",
"client_id": "client123",
"protected_endpoints": [
{
"method": "GET",
"path": "/*wildcard_start"
}
]
}"#;
let result = serde_json::from_str::<Settings>(json);
assert!(result.is_err());
}
#[test]
fn test_settings_deserialize_exact_path_match() {
let json = r#"{
"openid_discovery": "https://example.com/.well-known/openid-configuration",
"client_id": "client123",
"protected_endpoints": [
{
"method": "GET",
"path": "/v1/mint/quote/bolt11"
}
]
}"#;
let settings: Settings = serde_json::from_str(json).unwrap();
assert_eq!(settings.protected_endpoints.len(), 1);
assert_eq!(settings.protected_endpoints[0].method, Method::Get);
assert_eq!(
settings.protected_endpoints[0].path,
RoutePath::MintQuote(PaymentMethod::Known(KnownMethod::Bolt11).to_string())
);
}
#[test]
fn test_settings_deserialize_all_paths() {
let json = r#"{
"openid_discovery": "https://example.com/.well-known/openid-configuration",
"client_id": "client123",
"protected_endpoints": [
{
"method": "GET",
"path": "/v1/*"
}
]
}"#;
let settings: Settings = serde_json::from_str(json).unwrap();
assert_eq!(
settings.protected_endpoints.len(),
RoutePath::all_known_paths().len()
);
}
#[test]
fn test_matching_route_paths_empty_pattern() {
let paths = matching_route_paths("").unwrap();
assert!(paths.is_empty());
}
#[test]
fn test_matching_route_paths_just_slash() {
let paths = matching_route_paths("/").unwrap();
assert!(paths.is_empty());
}
#[test]
fn test_matching_route_paths_trailing_slash() {
let result = matching_route_paths("/v1/mint/*/");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::InvalidPattern(_)));
}
#[test]
fn test_matching_route_paths_consecutive_wildcards() {
let result = matching_route_paths("**");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::InvalidPattern(_)));
}
#[test]
fn test_matching_route_paths_method_specific() {
let paths = matching_route_paths("/v1/swap").unwrap();
assert_eq!(paths.len(), 1);
assert!(paths.contains(&RoutePath::Swap));
}
#[test]
fn test_settings_mixed_methods() {
let json = r#"{
"openid_discovery": "https://example.com/.well-known/openid-configuration",
"client_id": "client123",
"protected_endpoints": [
{
"method": "GET",
"path": "/v1/swap"
},
{
"method": "POST",
"path": "/v1/swap"
}
]
}"#;
let settings: Settings = serde_json::from_str(json).unwrap();
assert_eq!(settings.protected_endpoints.len(), 2);
let methods: Vec<_> = settings
.protected_endpoints
.iter()
.map(|ep| ep.method)
.collect();
assert!(methods.contains(&Method::Get));
assert!(methods.contains(&Method::Post));
for ep in &settings.protected_endpoints {
assert_eq!(ep.path, RoutePath::Swap);
}
}
#[test]
fn test_matching_route_paths_melt_prefix() {
let paths = matching_route_paths("/v1/melt/*").unwrap();
assert_eq!(paths.len(), 4);
assert!(paths.contains(&RoutePath::Melt(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(paths.contains(&RoutePath::MeltQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(paths.contains(&RoutePath::Melt(
PaymentMethod::Known(KnownMethod::Bolt12).to_string()
)));
assert!(paths.contains(&RoutePath::MeltQuote(
PaymentMethod::Known(KnownMethod::Bolt12).to_string()
)));
assert!(!paths.contains(&RoutePath::Mint(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
}
#[test]
fn test_matching_route_paths_static_exact() {
let swap_paths = matching_route_paths("/v1/swap").unwrap();
assert_eq!(swap_paths.len(), 1);
assert_eq!(swap_paths[0], RoutePath::Swap);
let checkstate_paths = matching_route_paths("/v1/checkstate").unwrap();
assert_eq!(checkstate_paths.len(), 1);
assert_eq!(checkstate_paths[0], RoutePath::Checkstate);
let restore_paths = matching_route_paths("/v1/restore").unwrap();
assert_eq!(restore_paths.len(), 1);
assert_eq!(restore_paths[0], RoutePath::Restore);
let ws_paths = matching_route_paths("/v1/ws").unwrap();
assert_eq!(ws_paths.len(), 1);
assert_eq!(ws_paths[0], RoutePath::Ws);
}
#[test]
fn test_matching_route_paths_auth_blind_mint() {
let paths = matching_route_paths("/v1/auth/blind/mint").unwrap();
assert_eq!(paths.len(), 1);
assert_eq!(paths[0], RoutePath::MintBlindAuth);
}
#[test]
fn test_settings_empty_endpoints() {
let json = r#"{
"openid_discovery": "https://example.com/.well-known/openid-configuration",
"client_id": "client123",
"protected_endpoints": []
}"#;
let settings: Settings = serde_json::from_str(json).unwrap();
assert!(settings.protected_endpoints.is_empty());
}
#[test]
fn test_settings_duplicate_paths() {
let json = r#"{
"openid_discovery": "https://example.com/.well-known/openid-configuration",
"client_id": "client123",
"protected_endpoints": [
{
"method": "POST",
"path": "/v1/swap"
},
{
"method": "POST",
"path": "/v1/swap"
}
]
}"#;
let settings: Settings = serde_json::from_str(json).unwrap();
assert_eq!(settings.protected_endpoints.len(), 1);
assert_eq!(settings.protected_endpoints[0].method, Method::Post);
assert_eq!(settings.protected_endpoints[0].path, RoutePath::Swap);
}
#[test]
fn test_matching_route_paths_only_wildcard() {
let paths = matching_route_paths("*").unwrap();
assert_eq!(paths.len(), RoutePath::all_known_paths().len());
}
#[test]
fn test_matching_route_paths_wildcard_in_middle() {
let result = matching_route_paths("/v1/*/bolt11");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::InvalidPattern(_)));
}
#[test]
fn test_exact_match_no_child_paths() {
let paths = matching_route_paths("/v1/mint").unwrap();
assert!(paths.is_empty());
assert!(!paths.contains(&RoutePath::Mint(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(!paths.contains(&RoutePath::MintQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
}
#[test]
fn test_exact_match_no_extra_path() {
let paths = matching_route_paths("/v1/swap").unwrap();
assert_eq!(paths.len(), 1);
assert_eq!(paths[0], RoutePath::Swap);
assert!(!paths.contains(&RoutePath::Checkstate));
assert!(!paths.contains(&RoutePath::Restore));
}
#[test]
fn test_partial_prefix_matching() {
let paths = matching_route_paths("/v1/mi*").unwrap();
assert!(paths.contains(&RoutePath::Mint(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(paths.contains(&RoutePath::MintQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(!paths.contains(&RoutePath::Melt(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(!paths.contains(&RoutePath::MeltQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
}
#[test]
fn test_exact_match_wrong_payment_method() {
let paths = matching_route_paths("/v1/mint/quote/bolt11").unwrap();
assert_eq!(paths.len(), 1);
assert!(paths.contains(&RoutePath::MintQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(!paths.contains(&RoutePath::MintQuote(
PaymentMethod::Known(KnownMethod::Bolt12).to_string()
)));
assert!(!paths.contains(&RoutePath::Mint(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
}
#[test]
fn test_prefix_match_wrong_category() {
let paths = matching_route_paths("/v1/mint/*").unwrap();
assert!(paths.contains(&RoutePath::Mint(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(paths.contains(&RoutePath::MintQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(!paths.contains(&RoutePath::Melt(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(!paths.contains(&RoutePath::MeltQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(!paths.contains(&RoutePath::Swap));
assert!(!paths.contains(&RoutePath::Checkstate));
}
#[test]
fn test_case_sensitivity() {
let paths_upper = matching_route_paths("/v1/MINT/*").unwrap();
let paths_lower = matching_route_paths("/v1/mint/*").unwrap();
assert!(paths_upper.is_empty());
assert_eq!(paths_lower.len(), 4);
}
#[test]
fn test_negative_assertions_comprehensive() {
let bolt11_paths = matching_route_paths("/v1/mint/quote/bolt11").unwrap();
assert!(!bolt11_paths.contains(&RoutePath::MintQuote(
PaymentMethod::Known(KnownMethod::Bolt12).to_string()
)));
let mint_paths = matching_route_paths("/v1/mint/*").unwrap();
assert!(!mint_paths.contains(&RoutePath::Melt(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(!mint_paths.contains(&RoutePath::MeltQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
let swap_paths = matching_route_paths("/v1/swap").unwrap();
assert!(!swap_paths.contains(&RoutePath::Checkstate));
assert!(!swap_paths.contains(&RoutePath::Restore));
assert!(!swap_paths.contains(&RoutePath::MintBlindAuth));
assert!(matching_route_paths("/V1/SWAP").unwrap().is_empty());
assert!(matching_route_paths("/V1/MINT/*").unwrap().is_empty());
assert!(matching_route_paths("/unknown/path").unwrap().is_empty());
assert!(matching_route_paths("/invalid").unwrap().is_empty());
}
#[test]
fn test_prefix_vs_exact_boundary() {
let paths = matching_route_paths("/v1/mint/quote/*").unwrap();
assert!(!paths.is_empty());
assert!(paths.contains(&RoutePath::MintQuote(
PaymentMethod::Known(KnownMethod::Bolt11).to_string()
)));
assert!(paths.contains(&RoutePath::MintQuote(
PaymentMethod::Known(KnownMethod::Bolt12).to_string()
)));
assert_eq!(paths.len(), 2);
}
}