Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 161 additions & 51 deletions adapter/rest/src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,75 +11,185 @@ pub struct RequestRoute {
// Only if both matched, it will return true
impl IdentifiableFlow for RequestRoute {
fn identify(&self, flow: &ValidationFlow) -> bool {
// Get Method of the FlowSetting
let method_str = flow
.settings
.iter()
.find(|s| s.flow_setting_id == "httpMethod")
.and_then(|s| s.value.as_ref())
.and_then(|v| v.kind.as_ref())
.and_then(|k| match k {
Kind::StringValue(s) => Some(s.as_str()),
_ => None,
});
log::debug!(
"route identify start: flow_id={} project_slug={} request_method={} request_path={:?}",
flow.flow_id,
flow.project_slug,
self.method.as_str(),
self.url
);

let Some(flow_method) = extract_flow_setting_as_string(flow, "httpMethod") else {
log::debug!(
"route identify reject: flow_id={} reason=missing_or_invalid_httpMethod",
flow.flow_id
);
return false;
};
Comment thread
raphael-goetz marked this conversation as resolved.

log::debug!(
"Comparing flows method: {:?} with request route: {}",
method_str,
"route identify method check: flow_id={} flow_method={} request_method={}",
flow.flow_id,
flow_method,
self.method.as_str()
);

if let Some(method) = method_str {
if method != self.method.as_str() {
log::debug!("Method didn't eq");
return false;
}
} else {
log::debug!("Method didn't eq");
if flow_method != self.method.as_str() {
log::debug!(
"route identify reject: flow_id={} reason=method_mismatch flow_method={} request_method={}",
flow.flow_id,
flow_method,
self.method.as_str()
);
return false;
}
// Get URL of the FlowSetting
let regex_str_v = flow
.settings
.iter()
.find(|s| s.flow_setting_id == "httpURL");

log::debug!("Extracted: {:?} as httpURL", &regex_str_v);

let regex_str = regex_str_v
.and_then(|s| s.value.as_ref())
.and_then(|v| v.kind.as_ref())
.and_then(|k| match k {
Kind::StringValue(s) => Some(s.as_str()),
_ => None,
});

let Some(regex_str) = regex_str else {
log::debug!("Regex was empty");

let Some(flow_http_url) = extract_flow_setting_as_string(flow, "httpURL") else {
log::debug!(
"route identify reject: flow_id={} reason=missing_or_invalid_httpURL",
flow.flow_id
);
return false;
};

let route_pattern = format!("/{}{}", flow.project_slug, flow_http_url);
log::debug!(
"Comparing regex {} with literal route: {}",
regex_str,
"route identify route check: flow_id={} httpURL={:?} resolved_pattern={:?} request_path={:?}",
flow.flow_id,
flow_http_url,
route_pattern,
self.url
);

// Check if the request is matching
match regex::Regex::new(regex_str) {
Ok(regex) => {
log::debug!("Successfully compiled regex: {}", regex_str);
regex.is_match(&self.url)
}
Err(err) => {
log::error!("Failed to compile regex: {}", err);
false
}
}
let is_match = matches_route_pattern(&route_pattern, &self.url);
log::debug!(
"route identify result: flow_id={} matched={}",
flow.flow_id,
is_match
);
is_match
Comment on lines +55 to +70
}
}

pub fn extract_slug_from_path(path: &str) -> Option<&str> {
let trimmed = path.trim_start_matches('/');
trimmed.split('/').next().filter(|s| !s.is_empty())
}

fn matches_route_pattern(pattern: &str, route: &str) -> bool {
let anchored_pattern = format!("^{}$", pattern);
log::debug!(
"route pattern eval: raw_pattern={:?} anchored_pattern={:?} route={:?}",
pattern,
anchored_pattern,
route
);

let regex = match regex::Regex::new(&anchored_pattern) {
Ok(regex) => regex,
Err(err) => {
log::error!(
"route pattern invalid regex: anchored_pattern={:?} error={}",
anchored_pattern,
err
);
return false;
}
};

let is_match = regex.is_match(route);
log::debug!(
"route pattern result: anchored_pattern={:?} route={:?} matched={}",
anchored_pattern,
route,
is_match
);
is_match
}

fn extract_flow_setting_as_string<'a>(
flow: &'a ValidationFlow,
flow_setting_id: &str,
) -> Option<&'a str> {
let setting = match flow
.settings
.iter()
.find(|setting| setting.flow_setting_id == flow_setting_id)
{
Some(setting) => setting,
None => {
log::debug!(
"flow setting is missing: flow_id={} flow_setting_id={}",
flow.flow_id,
flow_setting_id
);
return None;
}
};

let value = match setting.value.as_ref() {
Some(value) => value,
None => {
log::debug!(
"flow setting has no value: flow_id={} flow_setting_id={}",
flow.flow_id,
flow_setting_id
);
return None;
}
};

let kind = match value.kind.as_ref() {
Some(kind) => kind,
None => {
log::debug!(
"flow setting has no kind: flow_id={} flow_setting_id={}",
flow.flow_id,
flow_setting_id
);
return None;
}
};

match kind {
Kind::StringValue(value) => Some(value.as_str()),
_ => {
log::debug!(
"flow setting has non-string kind: flow_id={} flow_setting_id={} kind={:?}",
flow.flow_id,
flow_setting_id,
kind
);
None
}
}
}

#[cfg(test)]
mod tests {
use super::matches_route_pattern;

#[test]
fn exact_literal_match_works() {
Comment thread
raphael-goetz marked this conversation as resolved.
assert!(matches_route_pattern("test2", "test2"));
}

#[test]
fn substring_match_is_rejected() {
assert!(!matches_route_pattern("test", "test2"));
}

#[test]
fn nested_path_match_is_anchored() {
assert!(matches_route_pattern("/api/v1/test2", "/api/v1/test2"));
assert!(!matches_route_pattern(
"/api/v1/test2",
"/api/v1/test2/extra"
));
}

#[test]
fn invalid_regex_returns_false() {
assert!(!matches_route_pattern("(", "/test"));
}
}