Skip to content

Commit e3b94dc

Browse files
feat(preconditions): add support for isEventQLTrue preconditions (#34)
Signed-off-by: Raphael Höser <[email protected]>
1 parent 5c55acf commit e3b94dc

File tree

9 files changed

+125
-75
lines changed

9 files changed

+125
-75
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,23 @@ match result {
114114

115115
*Note that according to the CloudEvents standard, event IDs must be of type string.*
116116

117+
#### Using the `IsEventQLTrue` precondition
118+
119+
If you want to write events depending on an EventQL query, use the `IsEventQLTrue` precondition to create a precondition and pass it in a vector as the second argument:
120+
121+
```rust
122+
let result = client.write_events(
123+
vec![event.clone()],
124+
vec![Precondition::IsEventQLTrue {
125+
query: "FROM e IN events WHERE e.type == 'io.eventsourcingdb.library.book-borrowed' PROJECT INTO COUNT() < 10".to_string(),
126+
}],
127+
).await;
128+
match result {
129+
Ok(written_events) => // ...
130+
Err(err) => // ...
131+
}
132+
```
133+
117134
### Reading Events
118135

119136
To read all events of a subject, call the `read_events` function with the subject and an options object. Set the `recursive` option to `false`. This ensures that only events of the given subject are returned, not events of nested subjects.

src/client/precondition.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,10 @@ pub enum Precondition {
1919
#[serde(rename = "eventId")]
2020
event_id: String,
2121
},
22+
/// Check if an EventQL query returns true
23+
#[serde(rename = "isEventQlTrue")]
24+
IsEventQLTrue {
25+
/// The EventQL query to check
26+
query: String,
27+
},
2228
}

tests/essentials.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
use eventsourcingdb::{Client, container::Container};
1+
mod utils;
2+
use eventsourcingdb::Client;
3+
use utils::create_test_container;
24

35
#[tokio::test]
46
async fn ping() {
5-
let container = Container::start_default().await.unwrap();
7+
let container = create_test_container().await;
68
let client = container.get_client().await.unwrap();
79
client.ping().await.expect("Failed to ping");
810
}
@@ -11,12 +13,12 @@ async fn ping() {
1113
async fn ping_unavailable_server_errors() {
1214
let client = Client::new("http://localhost:12345".parse().unwrap(), "secrettoken");
1315
let result = client.ping().await;
14-
assert!(result.is_err(), "Expected an error, but got: {:?}", result);
16+
assert!(result.is_err(), "Expected an error, but got: {result:?}");
1517
}
1618

1719
#[tokio::test]
1820
async fn verify_api_token() {
19-
let container = Container::start_default().await.unwrap();
21+
let container = create_test_container().await;
2022
let client = container.get_client().await.unwrap();
2123
client
2224
.verify_api_token()
@@ -28,14 +30,14 @@ async fn verify_api_token() {
2830
async fn verify_api_token_unavailable_server_errors() {
2931
let client = Client::new("http://localhost:12345".parse().unwrap(), "secrettoken");
3032
let result = client.verify_api_token().await;
31-
assert!(result.is_err(), "Expected an error, but got: {:?}", result);
33+
assert!(result.is_err(), "Expected an error, but got: {result:?}");
3234
}
3335

3436
#[tokio::test]
3537
async fn verify_api_token_invalid_token_errors() {
36-
let container = Container::start_default().await.unwrap();
38+
let container = create_test_container().await;
3739
let client = container.get_client().await.unwrap();
3840
let invalid_client = Client::new(client.get_base_url().clone(), "invalid_token");
3941
let result = invalid_client.verify_api_token().await;
40-
assert!(result.is_err(), "Expected an error, but got: {:?}", result);
42+
assert!(result.is_err(), "Expected an error, but got: {result:?}");
4143
}

tests/metadata_and_discovery.rs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
use eventsourcingdb::container::Container;
1+
mod utils;
22
use futures::StreamExt;
33
use serde_json::json;
4+
use utils::create_test_container;
45

56
#[tokio::test]
67
async fn register_event_schema() {
7-
let container = Container::start_default().await.unwrap();
8+
let container = create_test_container().await;
89
let client = container.get_client().await.unwrap();
910
client
1011
.register_event_schema(
@@ -28,7 +29,7 @@ async fn register_event_schema() {
2829

2930
#[tokio::test]
3031
async fn register_invalid_event_schema() {
31-
let container = Container::start_default().await.unwrap();
32+
let container = create_test_container().await;
3233
let client = container.get_client().await.unwrap();
3334
let res = client
3435
.register_event_schema(
@@ -38,24 +39,23 @@ async fn register_invalid_event_schema() {
3839
}),
3940
)
4041
.await;
41-
assert!(res.is_err(), "Expected an error, but got: {:?}", res);
42+
assert!(res.is_err(), "Expected an error, but got: {res:?}");
4243
}
4344

4445
#[tokio::test]
4546
async fn list_all_subjects() {
46-
let container = Container::start_default().await.unwrap();
47+
let container = create_test_container().await;
4748
let client = container.get_client().await.unwrap();
4849
let res = client.list_subjects(None).await;
4950
match res {
5051
Ok(subjects) => {
5152
let subjects = subjects.collect::<Vec<_>>().await;
5253
assert!(
5354
subjects.is_empty(),
54-
"Expected no subjects, but got: {:?}",
55-
subjects
55+
"Expected no subjects, but got: {subjects:?}"
5656
);
5757
}
58-
Err(err) => panic!("Failed to list subjects: {:?}", err),
58+
Err(err) => panic!("Failed to list subjects: {err:?}"),
5959
}
6060
}
6161

@@ -65,7 +65,7 @@ async fn list_all_subjects() {
6565

6666
#[tokio::test]
6767
async fn list_all_event_types() {
68-
let container = Container::start_default().await.unwrap();
68+
let container = create_test_container().await;
6969
let client = container.get_client().await.unwrap();
7070
let test_event_type = "io.eventsourcingdb.test";
7171
let schema = json!({
@@ -90,8 +90,7 @@ async fn list_all_event_types() {
9090
let mut event_types = event_types.collect::<Vec<_>>().await;
9191
assert!(
9292
event_types.len() == 1,
93-
"Expected one event types, but got: {:?}",
94-
event_types
93+
"Expected one event types, but got: {event_types:?}"
9594
);
9695
assert!(event_types[0].is_ok(), "Expected event type to be ok");
9796
let response_event_type = event_types.pop().unwrap().unwrap();
@@ -113,7 +112,7 @@ async fn list_all_event_types() {
113112
response_event_type.is_phantom
114113
);
115114
}
116-
Err(err) => panic!("Failed to list event types: {:?}", err),
115+
Err(err) => panic!("Failed to list event types: {err:?}"),
117116
}
118117
}
119118

tests/observe_events.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
mod utils;
2-
3-
use eventsourcingdb::container::Container;
42
use futures::stream::StreamExt;
53
use serde_json::json;
4+
use utils::create_test_container;
65
use utils::create_test_eventcandidate;
76

87
#[tokio::test]
98
async fn observe_existing_events() {
10-
let container = Container::start_default().await.unwrap();
9+
let container = create_test_container().await;
1110
let client = container.get_client().await.unwrap();
1211
let event_candidate = create_test_eventcandidate("/test", json!({"value": 1}));
1312
let written = client
@@ -30,7 +29,7 @@ async fn observe_existing_events() {
3029

3130
#[tokio::test]
3231
async fn keep_observing_events() {
33-
let container = Container::start_default().await.unwrap();
32+
let container = create_test_container().await;
3433
let client = container.get_client().await.unwrap();
3534

3635
let mut events_stream = client

tests/read_events.rs

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,32 @@
11
mod utils;
22

3-
use eventsourcingdb::{
4-
container::Container,
5-
request_options::{
6-
Ordering, ReadEventMissingStrategy, ReadEventsOptions, ReadFromLatestEventOptions,
7-
},
3+
use eventsourcingdb::request_options::{
4+
Ordering, ReadEventMissingStrategy, ReadEventsOptions, ReadFromLatestEventOptions,
85
};
96
use futures::TryStreamExt;
107
use serde_json::json;
8+
use utils::create_test_container;
119
use utils::{
1210
assert_event_match_eventcandidate, create_numbered_eventcandidates, create_test_eventcandidate,
1311
};
1412

1513
#[tokio::test]
1614
async fn make_read_call() {
17-
let container = Container::start_default().await.unwrap();
15+
let container = create_test_container().await;
1816
let client = container.get_client().await.unwrap();
1917
let events_stream = client
2018
.read_events("/", None)
2119
.await
2220
.expect("Failed to read events");
2321
let events: Result<Vec<_>, _> = events_stream.try_collect().await;
24-
assert!(events.is_ok(), "Failed to write events: {:?}", events);
22+
assert!(events.is_ok(), "Failed to write events: {events:?}");
2523
let events = events.expect("Failed to read events");
2624
assert_eq!(events.len(), 0);
2725
}
2826

2927
#[tokio::test]
3028
async fn make_read_call_with_event() {
31-
let container = Container::start_default().await.unwrap();
29+
let container = create_test_container().await;
3230
let client = container.get_client().await.unwrap();
3331
let event_candidate = create_test_eventcandidate("/test", json!({"value": 1}));
3432
let written = client
@@ -50,7 +48,7 @@ async fn make_read_call_with_event() {
5048

5149
#[tokio::test]
5250
async fn make_read_call_with_multiple_events() {
53-
let container = Container::start_default().await.unwrap();
51+
let container = create_test_container().await;
5452
let client = container.get_client().await.unwrap();
5553
let event_candidates = create_numbered_eventcandidates(10);
5654
let written = client
@@ -72,7 +70,7 @@ async fn make_read_call_with_multiple_events() {
7270

7371
#[tokio::test]
7472
async fn read_from_exact_topic() {
75-
let container = Container::start_default().await.unwrap();
73+
let container = create_test_container().await;
7674
let client = container.get_client().await.unwrap();
7775
let event_candidate = create_test_eventcandidate("/test", json!({"value": 1}));
7876
client
@@ -102,7 +100,7 @@ async fn read_from_exact_topic() {
102100

103101
#[tokio::test]
104102
async fn read_recursive() {
105-
let container = Container::start_default().await.unwrap();
103+
let container = create_test_container().await;
106104
let client = container.get_client().await.unwrap();
107105
let event_candidate_parent = create_test_eventcandidate("/test", json!({"value": 1}));
108106
let event_candidate_child = create_test_eventcandidate("/test/sub", json!({"value": 2}));
@@ -137,7 +135,7 @@ async fn read_recursive() {
137135

138136
#[tokio::test]
139137
async fn read_not_recursive() {
140-
let container = Container::start_default().await.unwrap();
138+
let container = create_test_container().await;
141139
let client = container.get_client().await.unwrap();
142140
let event_candidate_parent = create_test_eventcandidate("/test", json!({"value": 1}));
143141
let event_candidate_child = create_test_eventcandidate("/test/sub", json!({"value": 2}));
@@ -172,7 +170,7 @@ async fn read_not_recursive() {
172170

173171
#[tokio::test]
174172
async fn read_chronological() {
175-
let container = Container::start_default().await.unwrap();
173+
let container = create_test_container().await;
176174
let client = container.get_client().await.unwrap();
177175
let event_candidates = create_numbered_eventcandidates(10);
178176
let written = client
@@ -200,7 +198,7 @@ async fn read_chronological() {
200198

201199
#[tokio::test]
202200
async fn read_antichronological() {
203-
let container = Container::start_default().await.unwrap();
201+
let container = create_test_container().await;
204202
let client = container.get_client().await.unwrap();
205203
let event_candidates = create_numbered_eventcandidates(10);
206204
let written = client
@@ -230,7 +228,7 @@ async fn read_antichronological() {
230228

231229
#[tokio::test]
232230
async fn read_everything_from_missing_latest_event() {
233-
let container = Container::start_default().await.unwrap();
231+
let container = create_test_container().await;
234232
let client = container.get_client().await.unwrap();
235233
let event_candidates = create_numbered_eventcandidates(10);
236234
let written = client
@@ -262,7 +260,7 @@ async fn read_everything_from_missing_latest_event() {
262260

263261
#[tokio::test]
264262
async fn read_nothing_from_missing_latest_event() {
265-
let container = Container::start_default().await.unwrap();
263+
let container = create_test_container().await;
266264
let client = container.get_client().await.unwrap();
267265
let event_candidates = create_numbered_eventcandidates(10);
268266
client
@@ -294,7 +292,7 @@ async fn read_nothing_from_missing_latest_event() {
294292

295293
#[tokio::test]
296294
async fn read_from_latest_event() {
297-
let container = Container::start_default().await.unwrap();
295+
let container = create_test_container().await;
298296
let client = container.get_client().await.unwrap();
299297
let event_candidates = create_numbered_eventcandidates(10);
300298
client

tests/run_eventql_query.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
use eventsourcingdb::container::Container;
1+
mod utils;
22
use futures::stream::TryStreamExt;
3+
use utils::create_test_container;
34

45
#[tokio::test]
56
async fn run_empty_query() {
6-
let container = Container::start_default().await.unwrap();
7+
let container = create_test_container().await;
78
let client = container.get_client().await.unwrap();
89
let rows = client
910
.run_eventql_query("FROM e IN events ORDER BY e.time DESC TOP 100 PROJECT INTO e")
1011
.await
1112
.expect("Unable to run query");
1213
let rows: Result<Vec<_>, _> = rows.try_collect().await;
13-
assert!(rows.is_ok(), "Failed to run query: {:?}", rows);
14+
assert!(rows.is_ok(), "Failed to run query: {rows:?}");
1415
let rows = rows.expect("Failed to read rows");
1516
assert_eq!(rows.len(), 0);
1617
}

tests/utils/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
use chrono::{TimeDelta, Utc};
2-
use eventsourcingdb::{Event, EventCandidate};
2+
use eventsourcingdb::{Event, EventCandidate, container::Container};
33
use serde_json::{Value, json};
44

5+
pub async fn create_test_container() -> Container {
6+
Container::builder()
7+
.with_image_tag("preview")
8+
.start()
9+
.await
10+
.expect("Failed to start test container")
11+
}
12+
513
pub fn create_test_eventcandidate(
614
subject: impl ToString,
715
data: impl Into<Value>,

0 commit comments

Comments
 (0)