Skip to content

Commit f2614f4

Browse files
committed
feat(agent): allow to use an existing project for impact / explain / unit ids / test examples
1 parent f39425c commit f2614f4

File tree

13 files changed

+362
-51
lines changed

13 files changed

+362
-51
lines changed

src/api/explain_request.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ use crate::{
33
http::{Header, Request},
44
router::{Router, RouterConfig, Trace},
55
};
6+
use std::sync::Arc;
67

78
use super::{Example, Rule};
9+
use crate::api::rules_message::RuleChangeSet;
810
use serde::{Deserialize, Serialize};
911

1012
// Input
@@ -23,6 +25,12 @@ pub struct TmpRules {
2325
pub rules: Vec<Rule>,
2426
}
2527

28+
#[derive(Deserialize, Debug, Clone)]
29+
pub struct ExplainRequestProjectInput {
30+
pub example: Example,
31+
pub change_set: RuleChangeSet,
32+
}
33+
2634
// Output
2735

2836
#[derive(Serialize, Debug, Clone)]
@@ -50,17 +58,30 @@ pub struct ExplainRequestOutputError {
5058
// Implementation
5159

5260
impl ExplainRequestOutput {
53-
pub fn create_result(explain_request_input: ExplainRequestInput) -> Result<ExplainRequestOutput, ExplainRequestOutputError> {
61+
pub fn create_result_from_project(
62+
explain_request_input: ExplainRequestProjectInput,
63+
existing_router: Arc<Router<Rule>>,
64+
) -> Result<ExplainRequestOutput, ExplainRequestOutputError> {
65+
let explain_request_router = explain_request_input.change_set.update_existing_router(existing_router.clone());
66+
67+
Self::create_result(&explain_request_router, &explain_request_input.example)
68+
}
69+
70+
pub fn create_result_without_project(
71+
explain_request_input: ExplainRequestInput,
72+
) -> Result<ExplainRequestOutput, ExplainRequestOutputError> {
5473
let router_config = explain_request_input.router_config;
5574
let mut router = Router::<Rule>::from_config(router_config.clone());
5675

5776
for rule in explain_request_input.rules.rules.iter() {
58-
router.insert(rule.clone().into_route(&router_config));
77+
router.insert(rule.clone());
5978
}
6079

61-
let example = &explain_request_input.example;
80+
Self::create_result(&router, &explain_request_input.example)
81+
}
6282

63-
let request = match Request::from_example(&router_config, example) {
83+
fn create_result(router: &Router<Rule>, example: &Example) -> Result<ExplainRequestOutput, ExplainRequestOutputError> {
84+
let request = match Request::from_example(&router.config, example) {
6485
Ok(request) => request,
6586
Err(e) => {
6687
return Err(ExplainRequestOutputError {

src/api/impact.rs

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use crate::action::{Action, UnitTrace};
2+
use crate::api::rules_message::RuleChangeSet;
23
use crate::api::{Example, Rule};
34
use crate::http::Header;
45
use crate::http::Request;
56
use crate::router::{Router, RouterConfig, Trace};
67
use serde::{Deserialize, Serialize};
8+
use std::sync::Arc;
79
use url::Url;
810

911
const REDIRECTION_CODES: [u16; 4] = [301, 302, 307, 308];
@@ -20,6 +22,15 @@ pub struct ImpactInput {
2022
pub rules: TmpRules,
2123
}
2224

25+
#[derive(Deserialize, Debug, Clone)]
26+
pub struct ImpactProjectInput {
27+
pub max_hops: u8,
28+
pub with_redirection_loop: bool,
29+
pub rule: Rule,
30+
pub action: String,
31+
pub change_set: RuleChangeSet,
32+
}
33+
2334
// FIXME: find a way to avoid creating this structure.
2435
// It would be more convenient to inline the structure
2536
#[derive(Deserialize, Debug, Clone)]
@@ -92,6 +103,28 @@ impl Impact {
92103
}
93104

94105
impl ImpactOutput {
106+
pub fn from_impact_project(impact_input: ImpactProjectInput, existing_router: Arc<Router<Rule>>) -> ImpactOutput {
107+
let mut impact_router = impact_input.change_set.update_existing_router(existing_router.clone());
108+
let mut trace_unique_router = Router::<Rule>::from_arc_config(existing_router.config.clone());
109+
110+
impact_router.remove(impact_input.rule.id.as_str());
111+
112+
if impact_input.action == "add" || impact_input.action == "update" {
113+
impact_router.insert(impact_input.rule.clone());
114+
trace_unique_router.insert(impact_input.rule.clone());
115+
}
116+
117+
let impacts = ImpactOutput::compute_impacts(
118+
&impact_router,
119+
&trace_unique_router,
120+
impact_input.rule.examples,
121+
impact_input.with_redirection_loop,
122+
impact_input.max_hops,
123+
);
124+
125+
ImpactOutput { impacts }
126+
}
127+
95128
pub fn create_result(impact_input: ImpactInput) -> ImpactOutput {
96129
let mut router = Router::<Rule>::from_config(impact_input.router_config.clone());
97130
let mut trace_unique_router = Router::<Rule>::from_config(impact_input.router_config.clone());
@@ -104,23 +137,40 @@ impl ImpactOutput {
104137
if rule.id == impact_input.rule.id {
105138
continue;
106139
}
107-
router.insert(rule.clone().into_route(&impact_input.router_config));
140+
router.insert(rule.clone());
108141
}
109142

110143
if impact_input.action == "add" || impact_input.action == "update" {
111-
router.insert(impact_input.rule.clone().into_route(&impact_input.router_config));
112-
trace_unique_router.insert(impact_input.rule.clone().into_route(&impact_input.router_config));
144+
router.insert(impact_input.rule.clone());
145+
trace_unique_router.insert(impact_input.rule.clone());
113146
}
114147

115-
let impacts = ImpactOutput::compute_impacts(&router, &trace_unique_router, &impact_input);
148+
let impacts = ImpactOutput::compute_impacts(
149+
&router,
150+
&trace_unique_router,
151+
impact_input.rule.examples,
152+
impact_input.with_redirection_loop,
153+
impact_input.max_hops,
154+
);
116155

117156
ImpactOutput { impacts }
118157
}
119158

120-
fn compute_impacts(router: &Router<Rule>, trace_unique_router: &Router<Rule>, impact_input: &ImpactInput) -> Vec<Impact> {
159+
fn compute_impacts(
160+
router: &Router<Rule>,
161+
trace_unique_router: &Router<Rule>,
162+
examples: Option<Vec<Example>>,
163+
with_redirection_loop: bool,
164+
max_hops: u8,
165+
) -> Vec<Impact> {
121166
let mut impacts = Vec::new();
122-
for example in impact_input.rule.examples.as_ref().unwrap().iter() {
123-
let request = match Request::from_example(&router.config, example) {
167+
168+
if examples.is_none() {
169+
return impacts;
170+
}
171+
172+
for example in examples.unwrap() {
173+
let request = match Request::from_example(&router.config, &example) {
124174
Ok(request) => request,
125175
Err(e) => {
126176
impacts.push(Impact::new_with_error(
@@ -163,8 +213,8 @@ impl ImpactOutput {
163213

164214
unit_trace.squash_with_target_unit_traces();
165215

166-
let redirection_loop = if impact_input.with_redirection_loop {
167-
Some(ImpactOutput::compute_redirection_loop(router, impact_input, example))
216+
let redirection_loop = if with_redirection_loop {
217+
Some(ImpactOutput::compute_redirection_loop(router, max_hops, &example))
168218
} else {
169219
None
170220
};
@@ -187,7 +237,7 @@ impl ImpactOutput {
187237
impacts
188238
}
189239

190-
fn compute_redirection_loop(router: &Router<Rule>, impact_input: &ImpactInput, example: &Example) -> RedirectionLoop {
240+
fn compute_redirection_loop(router: &Router<Rule>, max_hops: u8, example: &Example) -> RedirectionLoop {
191241
let mut current_url = example.url.clone();
192242
let mut current_method = example.method.clone().unwrap_or(String::from("GET"));
193243
let mut error = None;
@@ -198,7 +248,7 @@ impl ImpactOutput {
198248
method: current_method.clone(),
199249
}];
200250

201-
'outer: for i in 1..=impact_input.max_hops {
251+
'outer: for i in 1..=max_hops {
202252
let new_example = example.with_url(current_url.clone()).with_method(Some(current_method.clone()));
203253

204254
let request = Request::from_example(&router.config, &new_example).unwrap();
@@ -260,7 +310,7 @@ impl ImpactOutput {
260310
method: current_method.clone(),
261311
});
262312

263-
if i >= impact_input.max_hops {
313+
if i >= max_hops {
264314
error = Some(RedirectionError::TooManyHops);
265315
break;
266316
}

src/api/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ pub use body_filter::{BodyFilter, HTMLBodyFilter, TextAction, TextBodyFilter};
2929
pub use date_time::DateTimeConstraint;
3030
pub use examples::Example;
3131
#[cfg(feature = "router")]
32-
pub use explain_request::{ExplainRequestInput, ExplainRequestOutput, ExplainRequestOutputError};
32+
pub use explain_request::{ExplainRequestInput, ExplainRequestOutput, ExplainRequestOutputError, ExplainRequestProjectInput};
3333
pub use header::Header;
3434
pub use header_filter::HeaderFilter;
3535
#[cfg(feature = "router")]
36-
pub use impact::{ImpactInput, ImpactOutput};
36+
pub use impact::{ImpactInput, ImpactOutput, ImpactProjectInput};
3737
pub use ip::IpConstraint;
3838
pub use marker::Marker;
3939
#[cfg(feature = "router")]
@@ -42,8 +42,8 @@ pub use rule::Rule;
4242
pub use rules_message::RulesMessage;
4343
pub use source::Source;
4444
#[cfg(feature = "router")]
45-
pub use test_examples::{TestExamplesInput, TestExamplesOutput};
45+
pub use test_examples::{TestExamplesInput, TestExamplesOutput, TestExamplesProjectInput};
4646
pub use transformer::Transformer;
4747
#[cfg(feature = "router")]
48-
pub use unit_ids::{UnitIdsInput, UnitIdsOutput};
48+
pub use unit_ids::{UnitIdsInput, UnitIdsOutput, UnitIdsProjectInput};
4949
pub use variable::{Variable, VariableKind};

src/api/rule.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::api::{BodyFilter, DateTimeConstraint, Example, HeaderFilter, IpConstraint, Marker, Source, Variable};
22
use crate::http::Request;
33
use crate::marker::{Marker as RouteMarker, MarkerString, StaticOrDynamic, Transform};
4-
use crate::router::{Route, RouteDateTime, RouteHeader, RouteHeaderKind, RouteIp, RouteTime, RouteWeekday, RouterConfig};
4+
use crate::router::{IntoRoute, Route, RouteDateTime, RouteHeader, RouteHeaderKind, RouteIp, RouteTime, RouteWeekday, RouterConfig};
55
use cidr::AnyIpCidr;
66
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
77
use serde::{Deserialize, Serialize};
@@ -277,8 +277,10 @@ impl Rule {
277277

278278
headers
279279
}
280+
}
280281

281-
pub fn into_route(self, config: &RouterConfig) -> Route<Rule> {
282+
impl IntoRoute<Rule> for Rule {
283+
fn into_route(self, config: &RouterConfig) -> Route<Rule> {
282284
Route::new(
283285
self.source.methods.clone(),
284286
self.source.exclude_methods,

src/api/rules_message.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,27 @@
11
use crate::api::Rule;
2+
use crate::router::Router;
23
use serde::{Deserialize, Serialize};
4+
use std::sync::Arc;
35

46
#[derive(Serialize, Deserialize, Debug, Clone)]
57
pub struct RulesMessage {
68
#[serde(rename = "hydra:member")]
79
pub rules: Vec<Rule>,
810
}
11+
12+
#[derive(Deserialize, Debug, Clone)]
13+
pub struct RuleChangeSet {
14+
pub deleted: Vec<String>,
15+
pub added: Vec<Rule>,
16+
pub updated: Vec<Rule>,
17+
}
18+
19+
impl RuleChangeSet {
20+
pub fn update_existing_router(self, existing_router: Arc<Router<Rule>>) -> Router<Rule> {
21+
let mut new_router = existing_router.as_ref().clone();
22+
23+
new_router.apply_change_set(self.added, self.updated, self.deleted);
24+
25+
new_router
26+
}
27+
}

src/api/test_examples.rs

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::collections::HashMap;
2+
use std::sync::Arc;
23

34
use crate::{
45
action::{Action, UnitTrace},
@@ -7,6 +8,7 @@ use crate::{
78
};
89

910
use super::{Example, Rule};
11+
use crate::api::rules_message::RuleChangeSet;
1012
use linked_hash_set::LinkedHashSet;
1113
use serde::{Deserialize, Serialize};
1214

@@ -18,6 +20,11 @@ pub struct TestExamplesInput {
1820
pub rules: TmpRules,
1921
}
2022

23+
#[derive(Deserialize, Debug, Clone)]
24+
pub struct TestExamplesProjectInput {
25+
pub change_set: RuleChangeSet,
26+
}
27+
2128
// FIXME: find a way to avoid creating this structure.
2229
// It would be more convenient to inline the structure
2330
#[derive(Deserialize, Debug, Clone)]
@@ -65,11 +72,92 @@ pub struct ErroredExample {
6572
// Implementation
6673

6774
impl TestExamplesOutput {
75+
pub fn from_project(test_examples_input: TestExamplesProjectInput, existing_router: Arc<Router<Rule>>) -> TestExamplesOutput {
76+
let test_example_router = test_examples_input.change_set.update_existing_router(existing_router.clone());
77+
let mut results = TestExamplesOutput::default();
78+
79+
for (id, route) in test_example_router.routes() {
80+
let examples = &route.handler().examples;
81+
82+
if examples.is_none() {
83+
continue;
84+
}
85+
86+
for example in examples.as_ref().unwrap().iter() {
87+
if example.unit_ids_applied.is_none() {
88+
continue;
89+
}
90+
91+
let request = match Request::from_example(&test_example_router.config, example) {
92+
Ok(request) => request,
93+
Err(e) => {
94+
results.add_errored_example(route.handler(), example.clone(), e.to_string());
95+
continue;
96+
}
97+
};
98+
let mut unit_trace = UnitTrace::default();
99+
100+
let routes = test_example_router.match_request(&request);
101+
let mut action = Action::from_routes_rule(routes, &request, Some(&mut unit_trace));
102+
103+
let action_status_code = action.get_status_code(0, Some(&mut unit_trace));
104+
let (final_status_code, backend_status_code) = if action_status_code != 0 {
105+
(action_status_code, action_status_code)
106+
} else {
107+
// We call the backend and get a response code
108+
let backend_status_code = example.response_status_code.unwrap_or(200);
109+
let final_status_code = action.get_status_code(backend_status_code, Some(&mut unit_trace));
110+
(final_status_code, backend_status_code)
111+
};
112+
113+
action.filter_headers(Vec::new(), backend_status_code, false, Some(&mut unit_trace));
114+
115+
if let Some(mut body_filter) = action.create_filter_body(backend_status_code, &[]) {
116+
let body = "<!DOCTYPE html>
117+
<html>
118+
<head>
119+
</head>
120+
<body>
121+
</body>
122+
</html>";
123+
124+
body_filter.filter(body.into(), Some(&mut unit_trace));
125+
body_filter.end(Some(&mut unit_trace));
126+
}
127+
128+
action.should_log_request(true, final_status_code, Some(&mut unit_trace));
129+
130+
unit_trace.squash_with_target_unit_traces();
131+
132+
let unit_ids_not_applied_anymore = unit_trace.diff(example.unit_ids_applied.clone().unwrap());
133+
134+
// If it should match but not unit are applied anymore
135+
// If it should match but the rule is not applied
136+
// If it should not matche but the rule is applied
137+
if example.must_match && (!unit_ids_not_applied_anymore.is_empty() || !unit_trace.rule_ids_contains(id.as_str()))
138+
|| !example.must_match && unit_trace.rule_ids_contains(id.as_str())
139+
{
140+
results.add_failed_example(
141+
route.handler(),
142+
example.clone(),
143+
unit_trace.get_rule_ids_applied(),
144+
unit_trace.get_unit_ids_applied(),
145+
unit_ids_not_applied_anymore,
146+
);
147+
}
148+
149+
results.increment_example_count();
150+
}
151+
}
152+
153+
results
154+
}
155+
68156
pub fn create_result(test_examples_input: TestExamplesInput) -> TestExamplesOutput {
69157
let mut router = Router::<Rule>::from_config(test_examples_input.router_config.clone());
70158

71159
for rule in test_examples_input.rules.rules.iter() {
72-
router.insert(rule.clone().into_route(&test_examples_input.router_config));
160+
router.insert(rule.clone());
73161
}
74162

75163
router.cache(10000);

0 commit comments

Comments
 (0)