@@ -11,75 +11,185 @@ pub struct RequestRoute {
1111// Only if both matched, it will return true
1212impl IdentifiableFlow for RequestRoute {
1313 fn identify ( & self , flow : & ValidationFlow ) -> bool {
14- // Get Method of the FlowSetting
15- let method_str = flow
16- . settings
17- . iter ( )
18- . find ( |s| s. flow_setting_id == "httpMethod" )
19- . and_then ( |s| s. value . as_ref ( ) )
20- . and_then ( |v| v. kind . as_ref ( ) )
21- . and_then ( |k| match k {
22- Kind :: StringValue ( s) => Some ( s. as_str ( ) ) ,
23- _ => None ,
24- } ) ;
14+ log:: debug!(
15+ "route identify start: flow_id={} project_slug={} request_method={} request_path={:?}" ,
16+ flow. flow_id,
17+ flow. project_slug,
18+ self . method. as_str( ) ,
19+ self . url
20+ ) ;
21+
22+ let Some ( flow_method) = extract_flow_setting_as_string ( flow, "httpMethod" ) else {
23+ log:: debug!(
24+ "route identify reject: flow_id={} reason=missing_or_invalid_httpMethod" ,
25+ flow. flow_id
26+ ) ;
27+ return false ;
28+ } ;
2529
2630 log:: debug!(
27- "Comparing flows method: {:?} with request route: {}" ,
28- method_str,
31+ "route identify method check: flow_id={} flow_method={} request_method={}" ,
32+ flow. flow_id,
33+ flow_method,
2934 self . method. as_str( )
3035 ) ;
3136
32- if let Some ( method ) = method_str {
33- if method != self . method . as_str ( ) {
34- log :: debug! ( "Method didn't eq" ) ;
35- return false ;
36- }
37- } else {
38- log :: debug! ( "Method didn't eq" ) ;
37+ if flow_method != self . method . as_str ( ) {
38+ log :: debug! (
39+ "route identify reject: flow_id={} reason=method_mismatch flow_method={} request_method={}" ,
40+ flow . flow_id ,
41+ flow_method ,
42+ self . method . as_str ( )
43+ ) ;
3944 return false ;
4045 }
41- // Get URL of the FlowSetting
42- let regex_str_v = flow
43- . settings
44- . iter ( )
45- . find ( |s| s. flow_setting_id == "httpURL" ) ;
46-
47- log:: debug!( "Extracted: {:?} as httpURL" , & regex_str_v) ;
48-
49- let regex_str = regex_str_v
50- . and_then ( |s| s. value . as_ref ( ) )
51- . and_then ( |v| v. kind . as_ref ( ) )
52- . and_then ( |k| match k {
53- Kind :: StringValue ( s) => Some ( s. as_str ( ) ) ,
54- _ => None ,
55- } ) ;
56-
57- let Some ( regex_str) = regex_str else {
58- log:: debug!( "Regex was empty" ) ;
46+
47+ let Some ( flow_http_url) = extract_flow_setting_as_string ( flow, "httpURL" ) else {
48+ log:: debug!(
49+ "route identify reject: flow_id={} reason=missing_or_invalid_httpURL" ,
50+ flow. flow_id
51+ ) ;
5952 return false ;
6053 } ;
6154
55+ let route_pattern = format ! ( "/{}{}" , flow. project_slug, flow_http_url) ;
6256 log:: debug!(
63- "Comparing regex {} with literal route: {}" ,
64- regex_str,
57+ "route identify route check: flow_id={} httpURL={:?} resolved_pattern={:?} request_path={:?}" ,
58+ flow. flow_id,
59+ flow_http_url,
60+ route_pattern,
6561 self . url
6662 ) ;
6763
68- // Check if the request is matching
69- match regex:: Regex :: new ( regex_str) {
70- Ok ( regex) => {
71- log:: debug!( "Successfully compiled regex: {}" , regex_str) ;
72- regex. is_match ( & self . url )
73- }
74- Err ( err) => {
75- log:: error!( "Failed to compile regex: {}" , err) ;
76- false
77- }
78- }
64+ let is_match = matches_route_pattern ( & route_pattern, & self . url ) ;
65+ log:: debug!(
66+ "route identify result: flow_id={} matched={}" ,
67+ flow. flow_id,
68+ is_match
69+ ) ;
70+ is_match
7971 }
8072}
8173
8274pub fn extract_slug_from_path ( path : & str ) -> Option < & str > {
8375 let trimmed = path. trim_start_matches ( '/' ) ;
8476 trimmed. split ( '/' ) . next ( ) . filter ( |s| !s. is_empty ( ) )
8577}
78+
79+ fn matches_route_pattern ( pattern : & str , route : & str ) -> bool {
80+ let anchored_pattern = format ! ( "^{}$" , pattern) ;
81+ log:: debug!(
82+ "route pattern eval: raw_pattern={:?} anchored_pattern={:?} route={:?}" ,
83+ pattern,
84+ anchored_pattern,
85+ route
86+ ) ;
87+
88+ let regex = match regex:: Regex :: new ( & anchored_pattern) {
89+ Ok ( regex) => regex,
90+ Err ( err) => {
91+ log:: error!(
92+ "route pattern invalid regex: anchored_pattern={:?} error={}" ,
93+ anchored_pattern,
94+ err
95+ ) ;
96+ return false ;
97+ }
98+ } ;
99+
100+ let is_match = regex. is_match ( route) ;
101+ log:: debug!(
102+ "route pattern result: anchored_pattern={:?} route={:?} matched={}" ,
103+ anchored_pattern,
104+ route,
105+ is_match
106+ ) ;
107+ is_match
108+ }
109+
110+ fn extract_flow_setting_as_string < ' a > (
111+ flow : & ' a ValidationFlow ,
112+ flow_setting_id : & str ,
113+ ) -> Option < & ' a str > {
114+ let setting = match flow
115+ . settings
116+ . iter ( )
117+ . find ( |setting| setting. flow_setting_id == flow_setting_id)
118+ {
119+ Some ( setting) => setting,
120+ None => {
121+ log:: debug!(
122+ "flow setting is missing: flow_id={} flow_setting_id={}" ,
123+ flow. flow_id,
124+ flow_setting_id
125+ ) ;
126+ return None ;
127+ }
128+ } ;
129+
130+ let value = match setting. value . as_ref ( ) {
131+ Some ( value) => value,
132+ None => {
133+ log:: debug!(
134+ "flow setting has no value: flow_id={} flow_setting_id={}" ,
135+ flow. flow_id,
136+ flow_setting_id
137+ ) ;
138+ return None ;
139+ }
140+ } ;
141+
142+ let kind = match value. kind . as_ref ( ) {
143+ Some ( kind) => kind,
144+ None => {
145+ log:: debug!(
146+ "flow setting has no kind: flow_id={} flow_setting_id={}" ,
147+ flow. flow_id,
148+ flow_setting_id
149+ ) ;
150+ return None ;
151+ }
152+ } ;
153+
154+ match kind {
155+ Kind :: StringValue ( value) => Some ( value. as_str ( ) ) ,
156+ _ => {
157+ log:: debug!(
158+ "flow setting has non-string kind: flow_id={} flow_setting_id={} kind={:?}" ,
159+ flow. flow_id,
160+ flow_setting_id,
161+ kind
162+ ) ;
163+ None
164+ }
165+ }
166+ }
167+
168+ #[ cfg( test) ]
169+ mod tests {
170+ use super :: matches_route_pattern;
171+
172+ #[ test]
173+ fn exact_literal_match_works ( ) {
174+ assert ! ( matches_route_pattern( "test2" , "test2" ) ) ;
175+ }
176+
177+ #[ test]
178+ fn substring_match_is_rejected ( ) {
179+ assert ! ( !matches_route_pattern( "test" , "test2" ) ) ;
180+ }
181+
182+ #[ test]
183+ fn nested_path_match_is_anchored ( ) {
184+ assert ! ( matches_route_pattern( "/api/v1/test2" , "/api/v1/test2" ) ) ;
185+ assert ! ( !matches_route_pattern(
186+ "/api/v1/test2" ,
187+ "/api/v1/test2/extra"
188+ ) ) ;
189+ }
190+
191+ #[ test]
192+ fn invalid_regex_returns_false ( ) {
193+ assert ! ( !matches_route_pattern( "(" , "/test" ) ) ;
194+ }
195+ }
0 commit comments