From 8d017e1f714c9d31170df22e6262522643973135 Mon Sep 17 00:00:00 2001 From: Alexander Kampf Date: Thu, 12 Jun 2025 07:44:03 +0200 Subject: [PATCH 1/6] feat: Update readme --- README.md | 441 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 430 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 1cb81a6..04eb15a 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,435 @@ # eventsourcingdb -The official Rust client SDK for [EventSourcingDB](https://www.eventsourcingdb.io/). +The official Rust client SDK for [EventSourcingDB](https://www.eventsourcingdb.io) – a purpose-built database for event sourcing. -# Project status +EventSourcingDB enables you to build and operate event-driven applications with native support for writing, reading, and observing events. This client SDK provides convenient access to its capabilities in Rust. -This is a work in progress and not yet ready for production use. -Based on the [compliance criteria](https://docs.eventsourcingdb.io/client-sdks/compliance-criteria/) the SDK covers these criteria: +For more information on EventSourcingDB, see its [official documentation](https://docs.eventsourcingdb.io/). -- 🚀 [Essentials](https://docs.eventsourcingdb.io/client-sdks/compliance-criteria/#essentials) -- 🚀 [Writing Events](https://docs.eventsourcingdb.io/client-sdks/compliance-criteria/#writing-events) -- 🚀 [Reading Events](https://docs.eventsourcingdb.io/client-sdks/compliance-criteria/#reading-events) -- 🚀 [Using EventQL](https://docs.eventsourcingdb.io/client-sdks/compliance-criteria/#using-eventql) -- 🚀 [Observing Events](https://docs.eventsourcingdb.io/client-sdks/compliance-criteria/#observing-events) -- 🚀 [Metadata and Discovery](https://docs.eventsourcingdb.io/client-sdks/compliance-criteria/#metadata-and-discovery) -- 🚀 [Testcontainers Support](https://docs.eventsourcingdb.io/client-sdks/compliance-criteria/#testcontainers-support) +This client SDK includes support for [Testcontainers](https://testcontainers.com/) to spin up EventSourcingDB instances in integration tests. For details, see [Using Testcontainers](#using-testcontainers). + +## Getting Started + +Install the client SDK: + +```shell +cargo add eventsourcingdb +``` + +Import the package and create an instance by providing the URL of your EventSourcingDB instance and the API token to use: + +```rust +use eventsourcingdb::client::Client; + +// ... + +let base_url: Url = "localhost:3000".parse().unwrap(); +let api_token = "secret"; +let client = Client::new(base_url, api_token); +``` + +Then call the `ping` function to check whether the instance is reachable. If it is not, the function will return an error: + +```rust +let result = client.ping(); +if result.is_err() { + // ... +} +``` + +*Note that `Ping` does not require authentication, so the call may succeed even if the API token is invalid.* + +If you want to verify the API token, call `verify_api_token`. If the token is invalid, the function will return an error: + +```rust +let result = client.verify_api_token(); +if result.is_err() { + // ... +} +``` + +### Writing Events + +Call the `write_events` function and hand over a vector with one or more events. You do not have to provide all event fields – some are automatically added by the server. + +Specify `source`, `subject`, `type` (using `ty`), and `data` according to the [CloudEvents](https://docs.eventsourcingdb.io/fundamentals/cloud-events/) format. + +For `data` provide a json object using a `serde_json:Value`. + +The function returns the written events, including the fields added by the server: + +```rust +let event = EventCandidate::builder() + .source("https://library.eventsourcingdb.io".to_string()) + .subject("/books/42".to_string()) + .ty("io.eventsourcingdb.library.book-acquired") + .data(json!({ + "title": "2001 - A Space Odyssey", + "author": "Arthur C. Clarke", + "isbn": "978-0756906788", + })) + .build() + +let result = client.write_events(vec![event.clone()], vec![]).await; +match result { + Ok(written_events) => // ... + Err(err) => // ... +} +``` + +#### Using the `IsSubjectPristine` precondition + +If you only want to write events in case a subject (such as `/books/42`) does not yet have any events, use the `IsSubjectPristine` Precondition to create a precondition and pass it in a vector as the second argument: + +```rust +let result = client.write_events( + vec![event.clone()], + vec![Precondition::IsSubjectPristine { + subject: "/books/42".to_string(), + }], +).await; +match result { + Ok(written_events) => // ... + Err(err) => // ... +} +``` + +#### Using the `IsSubjectOnEventId` precondition + +If you only want to write events in case the last event of a subject (such as `/books/42`) has a specific ID (e.g., `0`), use the `IsSubjectOnEventID` Precondition to create a precondition and pass it in a vector as the second argument: + +```rust +let result = client.write_events( + vec![event.clone()], + vec![Precondition::IsSubjectPristine { + subject: "/books/42".to_string(), + event_id: "0".to_string(), + }], +).await; +match result { + Ok(written_events) => // ... + Err(err) => // ... +} +``` + +*Note that according to the CloudEvents standard, event IDs must be of type string.* + +### Reading Events + +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. + +The function returns a stream from which you can retrieve one event at a time: + +```rust +let result = client + .read_events("/books/42", Some( + ReadEventsRequestOptions { + recursive: false, + ..Default::default(), + } + )) + .await + +match result { + Err(err) => // ... + Some(stream) => { + while let Some(event) = stream.next().await { + // ... + } + } +} +``` + +#### Reading From Subjects Recursively + +If you want to read not only all the events of a subject, but also the events of all nested subjects, set the `recursive` option to `true`: + +```rust +let result = client + .read_events("/books/42", Some( + ReadEventsRequestOptions { + recursive: true, + ..Default::default(), + } + )) + .await +``` + +This also allows you to read *all* events ever written. To do so, provide `/` as the subject and set `recursive` to `true`, since all subjects are nested under the root subject. + +#### Reading in Anti-Chronological Order + +By default, events are read in chronological order. To read in anti-chronological order, provide the `order` option and set it using the `Antichronological` Ordering: + +```rust +let result = client + .read_events("/books/42", Some( + ReadEventsRequestOptions { + recursive: false, + order: Some(Ordering::Antichronological) + ..Default::default(), + } + )) + .await +``` + +*Note that you can also use the `Chronological` Ordering to explicitly enforce the default order.* + +#### Specifying Bounds + +Sometimes you do not want to read all events, but only a range of events. For that, you can specify the `lower_bound` and `upper_bound` options – either one of them or even both at the same time. + +Specify the ID and whether to include or exclude it, for both the lower and upper bound: + +```rust +let result = client + .read_events("/books/42", Some( + ReadEventsRequestOptions { + recursive: false, + lower_bound: Some(Bound { + bound_type: BoundType::Inclusive, + id: "100", + }), + upper_bound: Some(Bound { + bound_type: BoundType::Exclusive, + id: "200", + }), + ..Default::default(), + } + )) + .await +``` + +#### Starting From the Latest Event of a Given Type + +To read starting from the latest event of a given type, provide the `from_latest_event` option and specify the subject, the type, and how to proceed if no such event exists. + +Possible options are `ReadNothing`, which skips reading entirely, or `ReadyEverything`, which effectively behaves as if `from_latest_event` was not specified: + +```rust +let result = client + .read_events("/books/42", Some( + ReadEventsRequestOptions { + recursive: false, + from_latest_event: Some( + FromLatestEventOptions { + subject: "/books/42", + ty: "io.eventsourcingdb.library.book-borrowed", + if_event_is_missing: EventMissingStrategy::ReadEverything, + } + ) + ..Default::default(), + } + )) + .await +``` + +*Note that `from_latest_event` and `lower_bound` can not be provided at the same time.* + +### Running EventQL Queries + +To run an EventQL query, call the `run_eventql_query` function and provide the query as argument. The function returns a stream. + +```rust +let result = client + .run_eventql_query("FROM e IN events PROJECT INTO e") + .await + +match result { + Err(err) => // ... + Some(stream) => { + while let Some(row) = stream.next().await { + // ... + } + } +} +``` + +*Note that each row returned by the stream is of type `serde_json::Value` and matches the projection specified in your query.* + +### Observing Events + +To observe all events of a subject, call the `observe_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. + +The function returns a stream from which you can retrieve one event at a time: + + +```rust +let result = client + .observe_events("/books/42", Some( + ObserveEventsRequestOptions { + recursive: false, + ..Default::default(), + } + )) + .await + +match result { + Err(err) => // ... + Some(stream) => { + while let Some(event) = stream.next().await { + // ... + } + } +} +``` + +#### Observing From Subjects Recursively + +If you want to observe not only all the events of a subject, but also the events of all nested subjects, set the `recursive` option to `true`: + +```rust +let result = client + .observe_events("/books/42", Some( + ObserveEventsRequestOptions { + recursive: true, + ..Default::default(), + } + )) + .await +``` + +This also allows you to observe *all* events ever written. To do so, provide `/` as the subject and set `recursive` to `true`, since all subjects are nested under the root subject. + +#### Specifying Bounds + +Sometimes you do not want to observe all events, but only a range of events. For that, you can specify the `lower_bound` option. + +Specify the ID and whether to include or exclude it: + +```rust +let result = client + .read_events("/books/42", Some( + ReadEventsRequestOptions { + recursive: false, + lower_bound: Some(Bound { + bound_type: BoundType::Inclusive, + id: "100", + }), + ..Default::default(), + } + )) + .await +``` + +#### Starting From the Latest Event of a Given Type + +To observe starting from the latest event of a given type, provide the `from_latest_event` option and specify the subject, the type, and how to proceed if no such event exists. + +Possible options are `WaitForEvent`, which waits for an event of the given type to happen, or `ObserveEverything`, which effectively behaves as if `from_latest_event` was not specified: + +```rust +let result = client + .read_events("/books/42", Some( + ReadEventsRequestOptions { + recursive: false, + from_latest_event: Some( + ObserveFromLatestEventOptions { + subject: "/books/42", + ty: "io.eventsourcingdb.library.book-borrowed", + if_event_is_missing: EventMissingStrategy::ObserveEverything, + } + ) + ..Default::default(), + } + )) + .await +``` + +*Note that `from_latest_event` and `lower_bound` can not be provided at the same time.* + +#### Aborting Observing + +The observe will automatically be canceled if the stream is dropped from scope. + +### Registering an Event Schema + +To register an event schema, call the `register_event_schema` function and hand over an event type and the desired schema: + +```rust +client.register_event_schema( + "io.eventsourcingdb.library.book-acquired", + json!({ + "type": "object", + "properties": { + "title": { "type": "string" }, + "author": { "type": "string" }, + "isbn": { "type": "string" }, + }, + "required": [ + "title", + "author", + "isbn", + ], + "additionalProperties": false, + }), +) +``` + +### Listing Subjects + +To list all subjects, call the `list_subjects` function with `/` as the base subject. The function returns a stream from which you can retrieve one subject at a time: + +```rust +let result := client.list_subjects("/"); +match result { + Ok(subjects) => // ... + Err(err) => // ... +} +``` + +If you only want to list subjects within a specific branch, provide the desired base subject instead: + +```rust +let result := client.list_subjects("/books"); +``` + +### Listing Event Types + +To list all event types, call the `list_event_types` function. The function returns a stream from which you can retrieve one event type at a time: + +```rust +let result := client.list_event_types(); +match result { + Ok(event_types) => // ... + Err(err) => // ... +} +``` +### Using Testcontainers + +Call the `Container::start_default()` function, get a client, and run your test code: + +```rust +let container = Container::start_default().await.unwrap(); +let client = container.get_client().await.unwrap(); +``` + +#### Configuring the Container Instance + +By default, `Container` uses the `latest` tag of the official EventSourcingDB Docker image. To change that use the provided builder and call the `with_image_tag` function. + +```rust +let container = Container::builder() + .with_image_tag("1.0.0") + .build() + .await.unwrap() +``` + +Similarly, you can configure the port to use and the API token. Call the `with_port` or the `with_api_token` function respectively: + +```rust +let container = Container::builder() + .with_port(4000) + .with_api_token("secret") + .build() + .await.unwrap() +``` + +#### Configuring the Client Manually + +In case you need to set up the client yourself, use the following functions to get details on the container: + +- `get_host()` returns the host name +- `get_mapped_port()` returns the port +- `get_base_url()` returns the full URL of the container +- `get_api_token()` returns the API token \ No newline at end of file From 4ad04637ef418486f64874dc324e2e3de8f871b6 Mon Sep 17 00:00:00 2001 From: Alexander Kampf Date: Thu, 26 Jun 2025 11:37:43 +0200 Subject: [PATCH 2/6] chore: Add examples --- README.md | 50 +++++++++++++++------------- clippy.toml | 1 + examples/event_types.rs | 21 ++++++++++++ examples/listing_subjects.rs | 21 ++++++++++++ examples/observing_events.rs | 30 +++++++++++++++++ examples/ping.rs | 15 +++++++++ examples/reading_events.rs | 32 ++++++++++++++++++ examples/registering_event_schema.rs | 34 +++++++++++++++++++ examples/running_eventql.rs | 23 +++++++++++++ examples/verify_api_token.rs | 15 +++++++++ examples/write_events.rs | 27 +++++++++++++++ src/lib.rs | 32 +++++++++++++++++- 12 files changed, 277 insertions(+), 24 deletions(-) create mode 100644 clippy.toml create mode 100644 examples/event_types.rs create mode 100644 examples/listing_subjects.rs create mode 100644 examples/observing_events.rs create mode 100644 examples/ping.rs create mode 100644 examples/reading_events.rs create mode 100644 examples/registering_event_schema.rs create mode 100644 examples/running_eventql.rs create mode 100644 examples/verify_api_token.rs create mode 100644 examples/write_events.rs diff --git a/README.md b/README.md index 04eb15a..94ad85b 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ let client = Client::new(base_url, api_token); Then call the `ping` function to check whether the instance is reachable. If it is not, the function will return an error: ```rust -let result = client.ping(); -if result.is_err() { - // ... +let result = client.ping().await; +if let Err(err) = result { + // handle error... } ``` @@ -42,9 +42,9 @@ if result.is_err() { If you want to verify the API token, call `verify_api_token`. If the token is invalid, the function will return an error: ```rust -let result = client.verify_api_token(); -if result.is_err() { - // ... +let result = client.verify_api_token().await; +if let Err(err) = result { + // handle error... } ``` @@ -68,7 +68,7 @@ let event = EventCandidate::builder() "author": "Arthur C. Clarke", "isbn": "978-0756906788", })) - .build() + .build(); let result = client.write_events(vec![event.clone()], vec![]).await; match result { @@ -125,14 +125,17 @@ let result = client .read_events("/books/42", Some( ReadEventsRequestOptions { recursive: false, - ..Default::default(), + from_latest_event: None, + order: None, + lower_bound: None, + upper_bound: None, } )) - .await + .await; match result { Err(err) => // ... - Some(stream) => { + Ok(mut stream) => { while let Some(event) = stream.next().await { // ... } @@ -152,7 +155,7 @@ let result = client ..Default::default(), } )) - .await + .await; ``` This also allows you to read *all* events ever written. To do so, provide `/` as the subject and set `recursive` to `true`, since all subjects are nested under the root subject. @@ -170,7 +173,7 @@ let result = client ..Default::default(), } )) - .await + .await; ``` *Note that you can also use the `Chronological` Ordering to explicitly enforce the default order.* @@ -197,7 +200,7 @@ let result = client ..Default::default(), } )) - .await + .await; ``` #### Starting From the Latest Event of a Given Type @@ -221,7 +224,7 @@ let result = client ..Default::default(), } )) - .await + .await; ``` *Note that `from_latest_event` and `lower_bound` can not be provided at the same time.* @@ -233,11 +236,11 @@ To run an EventQL query, call the `run_eventql_query` function and provide the q ```rust let result = client .run_eventql_query("FROM e IN events PROJECT INTO e") - .await + .await; match result { Err(err) => // ... - Some(stream) => { + Ok(mut stream) => { while let Some(row) = stream.next().await { // ... } @@ -259,14 +262,15 @@ let result = client .observe_events("/books/42", Some( ObserveEventsRequestOptions { recursive: false, - ..Default::default(), + from_latest_event: None, + lower_bound: None, } )) - .await + .await; match result { Err(err) => // ... - Some(stream) => { + Ok(mut stream) => { while let Some(event) = stream.next().await { // ... } @@ -349,7 +353,7 @@ To register an event schema, call the `register_event_schema` function and hand ```rust client.register_event_schema( "io.eventsourcingdb.library.book-acquired", - json!({ + &json!({ "type": "object", "properties": { "title": { "type": "string" }, @@ -362,7 +366,7 @@ client.register_event_schema( "isbn", ], "additionalProperties": false, - }), + }).await;, ) ``` @@ -371,7 +375,7 @@ client.register_event_schema( To list all subjects, call the `list_subjects` function with `/` as the base subject. The function returns a stream from which you can retrieve one subject at a time: ```rust -let result := client.list_subjects("/"); +let result = client.list_subjects(Some("/")).await; match result { Ok(subjects) => // ... Err(err) => // ... @@ -389,7 +393,7 @@ let result := client.list_subjects("/books"); To list all event types, call the `list_event_types` function. The function returns a stream from which you can retrieve one event type at a time: ```rust -let result := client.list_event_types(); +let result := client.list_event_types().await; match result { Ok(event_types) => // ... Err(err) => // ... diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..63e849f --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +doc-valid-idents = ["EventSourcingDB", "EventQL", "CloudEvents", ".."] \ No newline at end of file diff --git a/examples/event_types.rs b/examples/event_types.rs new file mode 100644 index 0000000..f960138 --- /dev/null +++ b/examples/event_types.rs @@ -0,0 +1,21 @@ +use eventsourcingdb::client::Client; +use futures::StreamExt; +use url::Url; + +#[tokio::main] +async fn main() { + let base_url: Url = "localhost:3000".parse().unwrap(); + let api_token = "secret"; + let client = Client::new(base_url, api_token); + + let result = client.list_event_types().await; + + match result { + Err(err) => panic!("{}", err), + Ok(mut event_types) => { + while let Some(Ok(event_type)) = event_types.next().await { + println!("{:?}", event_type) + } + } + } +} diff --git a/examples/listing_subjects.rs b/examples/listing_subjects.rs new file mode 100644 index 0000000..249b286 --- /dev/null +++ b/examples/listing_subjects.rs @@ -0,0 +1,21 @@ +use eventsourcingdb::client::Client; +use futures::StreamExt; +use url::Url; + +#[tokio::main] +async fn main() { + let base_url: Url = "localhost:3000".parse().unwrap(); + let api_token = "secret"; + let client = Client::new(base_url, api_token); + + let result = client.list_subjects(Some("/")).await; + + match result { + Err(err) => panic!("{}", err), + Ok(mut subjects) => { + while let Some(Ok(subject)) = subjects.next().await { + println!("{:?}", subject) + } + } + } +} diff --git a/examples/observing_events.rs b/examples/observing_events.rs new file mode 100644 index 0000000..bf1f2a8 --- /dev/null +++ b/examples/observing_events.rs @@ -0,0 +1,30 @@ +use eventsourcingdb::client::{Client, request_options::ObserveEventsRequestOptions}; +use futures::StreamExt; +use url::Url; + +#[tokio::main] +async fn main() { + let base_url: Url = "localhost:3000".parse().unwrap(); + let api_token = "secret"; + let client = Client::new(base_url, api_token); + + let result = client + .observe_events( + "/books/42", + Some(ObserveEventsRequestOptions { + recursive: false, + from_latest_event: None, + lower_bound: None, + }), + ) + .await; + + match result { + Err(err) => panic!("{}", err), + Ok(mut stream) => { + while let Some(Ok(event)) = stream.next().await { + println!("{:?}", event) + } + } + } +} diff --git a/examples/ping.rs b/examples/ping.rs new file mode 100644 index 0000000..9b6c78e --- /dev/null +++ b/examples/ping.rs @@ -0,0 +1,15 @@ +use eventsourcingdb::client::Client; +use url::Url; + +#[tokio::main] +async fn main() { + let base_url: Url = "localhost:3000".parse().unwrap(); + let api_token = "secret"; + let client = Client::new(base_url, api_token); + + let result = client.ping().await; + if let Err(err) = result { + // handle error + panic!("{}", err) + } +} diff --git a/examples/reading_events.rs b/examples/reading_events.rs new file mode 100644 index 0000000..ca6e736 --- /dev/null +++ b/examples/reading_events.rs @@ -0,0 +1,32 @@ +use eventsourcingdb::client::{Client, request_options::ReadEventsRequestOptions}; +use futures::StreamExt; +use url::Url; + +#[tokio::main] +async fn main() { + let base_url: Url = "localhost:3000".parse().unwrap(); + let api_token = "secret"; + let client = Client::new(base_url, api_token); + + let result = client + .read_events( + "/books/42", + Some(ReadEventsRequestOptions { + recursive: false, + from_latest_event: None, + order: None, + lower_bound: None, + upper_bound: None, + }), + ) + .await; + + match result { + Err(err) => panic!("{}", err), + Ok(mut stream) => { + while let Some(Ok(event)) = stream.next().await { + println!("{:?}", event) + } + } + } +} diff --git a/examples/registering_event_schema.rs b/examples/registering_event_schema.rs new file mode 100644 index 0000000..10178d2 --- /dev/null +++ b/examples/registering_event_schema.rs @@ -0,0 +1,34 @@ +use eventsourcingdb::client::Client; +use serde_json::json; +use url::Url; + +#[tokio::main] +async fn main() { + let base_url: Url = "localhost:3000".parse().unwrap(); + let api_token = "secret"; + let client = Client::new(base_url, api_token); + + let result = client + .register_event_schema( + "io.eventsourcingdb.library.book-acquired", + &json!({ + "type": "object", + "properties": { + "title": { "type": "string" }, + "author": { "type": "string" }, + "isbn": { "type": "string" }, + }, + "required": [ + "title", + "author", + "isbn", + ], + "additionalProperties": false, + }), + ) + .await; + + if let Err(err) = result { + panic!("{}", err) + } +} diff --git a/examples/running_eventql.rs b/examples/running_eventql.rs new file mode 100644 index 0000000..520b4ff --- /dev/null +++ b/examples/running_eventql.rs @@ -0,0 +1,23 @@ +use eventsourcingdb::client::Client; +use futures::StreamExt; +use url::Url; + +#[tokio::main] +async fn main() { + let base_url: Url = "localhost:3000".parse().unwrap(); + let api_token = "secret"; + let client = Client::new(base_url, api_token); + + let result = client + .run_eventql_query("FROM e IN events PROJECT INTO e") + .await; + + match result { + Err(err) => panic!("{}", err), + Ok(mut stream) => { + while let Some(Ok(row)) = stream.next().await { + println!("{:?}", row) + } + } + } +} diff --git a/examples/verify_api_token.rs b/examples/verify_api_token.rs new file mode 100644 index 0000000..65eab78 --- /dev/null +++ b/examples/verify_api_token.rs @@ -0,0 +1,15 @@ +use eventsourcingdb::client::Client; +use url::Url; + +#[tokio::main] +async fn main() { + let base_url: Url = "localhost:3000".parse().unwrap(); + let api_token = "secret"; + let client = Client::new(base_url, api_token); + + let result = client.verify_api_token().await; + if let Err(err) = result { + // handle error + panic!("{}", err) + } +} diff --git a/examples/write_events.rs b/examples/write_events.rs new file mode 100644 index 0000000..52b92b1 --- /dev/null +++ b/examples/write_events.rs @@ -0,0 +1,27 @@ +use eventsourcingdb::{client::Client, event::EventCandidate}; +use serde_json::json; +use url::Url; + +#[tokio::main] +async fn main() { + let base_url: Url = "localhost:3000".parse().unwrap(); + let api_token = "secret"; + let client = Client::new(base_url, api_token); + + let event = EventCandidate::builder() + .source("https://library.eventsourcingdb.io".to_string()) + .subject("/books/42".to_string()) + .ty("io.eventsourcingdb.library.book-acquired") + .data(json!({ + "title": "2001 - A Space Odyssey", + "author": "Arthur C. Clarke", + "isbn": "978-0756906788", + })) + .build(); + + let result = client.write_events(vec![event.clone()], vec![]).await; + match result { + Ok(written_events) => println!("{:?}", written_events), + Err(err) => panic!("{}", err), + } +} diff --git a/src/lib.rs b/src/lib.rs index 3843944..02a985c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,34 @@ -#![doc = include_str!("../README.md")] +//! # eventsourcingdb +//! +//! The official Rust client SDK for [EventSourcingDB](https://www.eventsourcingdb.io) – a purpose-built database for event sourcing. +//! EventSourcingDB enables you to build and operate event-driven applications with native support for writing, reading, and observing events. This client SDK provides convenient access to its capabilities in Rust. +//! For more information on EventSourcingDB, see its [official documentation](https://docs.eventsourcingdb.io/). +//! This client SDK includes support for [Testcontainers](https://testcontainers.com/) to spin up EventSourcingDB instances in integration tests. For details, see [Using Testcontainers](#using-testcontainers). +//! +//! ## Getting Started +//! +//! Install the client SDK: +//! +//! ```shell +//! cargo add eventsourcingdb +//! ``` +//! +//! Import the package and create an instance by providing the URL of your EventSourcingDB instance and the API token to use: +//! +//! ```rust +//! use eventsourcingdb::client::Client; +//! # use url::Url; +//! // ... +//! +//! let base_url: Url = "localhost:3000".parse().unwrap(); +//! let api_token = "secret"; +//! let client = Client::new(base_url, api_token); +//! ``` +//! +//! ## Examples +//! +//! Examples can be found in the [examples](https://github.com/thenativeweb/eventsourcingdb-client-rust/tree/main/examples) directory. +//! // There is a known bug in clippy: // https://github.com/rust-lang/rust-clippy/issues/12908 #![allow(clippy::needless_lifetimes)] From 3808c501c0994871c1a72c576cbd6ee0a53fb73d Mon Sep 17 00:00:00 2001 From: Alexander Kampf Date: Thu, 26 Jun 2025 12:11:31 +0200 Subject: [PATCH 3/6] chore: add missing new line --- clippy.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy.toml b/clippy.toml index 63e849f..6865770 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -doc-valid-idents = ["EventSourcingDB", "EventQL", "CloudEvents", ".."] \ No newline at end of file +doc-valid-idents = ["EventSourcingDB", "EventQL", "CloudEvents", ".."] From 526b3674f30fc6031c8244a3d50b3f9498442274 Mon Sep 17 00:00:00 2001 From: Alexander Kampf Date: Mon, 14 Jul 2025 08:15:44 +0200 Subject: [PATCH 4/6] chore: add testcontainer to examples --- examples/event_types.rs | 8 ++++---- examples/listing_subjects.rs | 8 ++++---- examples/observing_events.rs | 11 +++++++---- examples/ping.rs | 8 ++++---- examples/reading_events.rs | 11 +++++++---- examples/registering_event_schema.rs | 8 ++++---- examples/running_eventql.rs | 8 ++++---- examples/verify_api_token.rs | 8 ++++---- examples/write_events.rs | 8 ++++---- 9 files changed, 42 insertions(+), 36 deletions(-) diff --git a/examples/event_types.rs b/examples/event_types.rs index f960138..cbd3efc 100644 --- a/examples/event_types.rs +++ b/examples/event_types.rs @@ -1,11 +1,11 @@ -use eventsourcingdb::client::Client; +use eventsourcingdb::{client::Client, container::Container}; use futures::StreamExt; -use url::Url; #[tokio::main] async fn main() { - let base_url: Url = "localhost:3000".parse().unwrap(); - let api_token = "secret"; + let db = Container::start_default().await.unwrap(); + let base_url = db.get_base_url().await.unwrap(); + let api_token = db.get_api_token(); let client = Client::new(base_url, api_token); let result = client.list_event_types().await; diff --git a/examples/listing_subjects.rs b/examples/listing_subjects.rs index 249b286..88ac2c0 100644 --- a/examples/listing_subjects.rs +++ b/examples/listing_subjects.rs @@ -1,11 +1,11 @@ -use eventsourcingdb::client::Client; +use eventsourcingdb::{client::Client, container::Container}; use futures::StreamExt; -use url::Url; #[tokio::main] async fn main() { - let base_url: Url = "localhost:3000".parse().unwrap(); - let api_token = "secret"; + let db = Container::start_default().await.unwrap(); + let base_url = db.get_base_url().await.unwrap(); + let api_token = db.get_api_token(); let client = Client::new(base_url, api_token); let result = client.list_subjects(Some("/")).await; diff --git a/examples/observing_events.rs b/examples/observing_events.rs index bf1f2a8..1b1c1c1 100644 --- a/examples/observing_events.rs +++ b/examples/observing_events.rs @@ -1,11 +1,14 @@ -use eventsourcingdb::client::{Client, request_options::ObserveEventsRequestOptions}; +use eventsourcingdb::{ + client::{Client, request_options::ObserveEventsRequestOptions}, + container::Container, +}; use futures::StreamExt; -use url::Url; #[tokio::main] async fn main() { - let base_url: Url = "localhost:3000".parse().unwrap(); - let api_token = "secret"; + let db = Container::start_default().await.unwrap(); + let base_url = db.get_base_url().await.unwrap(); + let api_token = db.get_api_token(); let client = Client::new(base_url, api_token); let result = client diff --git a/examples/ping.rs b/examples/ping.rs index 9b6c78e..abc4f6f 100644 --- a/examples/ping.rs +++ b/examples/ping.rs @@ -1,10 +1,10 @@ -use eventsourcingdb::client::Client; -use url::Url; +use eventsourcingdb::{client::Client, container::Container}; #[tokio::main] async fn main() { - let base_url: Url = "localhost:3000".parse().unwrap(); - let api_token = "secret"; + let db = Container::start_default().await.unwrap(); + let base_url = db.get_base_url().await.unwrap(); + let api_token = db.get_api_token(); let client = Client::new(base_url, api_token); let result = client.ping().await; diff --git a/examples/reading_events.rs b/examples/reading_events.rs index ca6e736..f57f0be 100644 --- a/examples/reading_events.rs +++ b/examples/reading_events.rs @@ -1,11 +1,14 @@ -use eventsourcingdb::client::{Client, request_options::ReadEventsRequestOptions}; +use eventsourcingdb::{ + client::{Client, request_options::ReadEventsRequestOptions}, + container::Container, +}; use futures::StreamExt; -use url::Url; #[tokio::main] async fn main() { - let base_url: Url = "localhost:3000".parse().unwrap(); - let api_token = "secret"; + let db = Container::start_default().await.unwrap(); + let base_url = db.get_base_url().await.unwrap(); + let api_token = db.get_api_token(); let client = Client::new(base_url, api_token); let result = client diff --git a/examples/registering_event_schema.rs b/examples/registering_event_schema.rs index 10178d2..67a7c10 100644 --- a/examples/registering_event_schema.rs +++ b/examples/registering_event_schema.rs @@ -1,11 +1,11 @@ -use eventsourcingdb::client::Client; +use eventsourcingdb::{client::Client, container::Container}; use serde_json::json; -use url::Url; #[tokio::main] async fn main() { - let base_url: Url = "localhost:3000".parse().unwrap(); - let api_token = "secret"; + let db = Container::start_default().await.unwrap(); + let base_url = db.get_base_url().await.unwrap(); + let api_token = db.get_api_token(); let client = Client::new(base_url, api_token); let result = client diff --git a/examples/running_eventql.rs b/examples/running_eventql.rs index 520b4ff..e4237e3 100644 --- a/examples/running_eventql.rs +++ b/examples/running_eventql.rs @@ -1,11 +1,11 @@ -use eventsourcingdb::client::Client; +use eventsourcingdb::{client::Client, container::Container}; use futures::StreamExt; -use url::Url; #[tokio::main] async fn main() { - let base_url: Url = "localhost:3000".parse().unwrap(); - let api_token = "secret"; + let db = Container::start_default().await.unwrap(); + let base_url = db.get_base_url().await.unwrap(); + let api_token = db.get_api_token(); let client = Client::new(base_url, api_token); let result = client diff --git a/examples/verify_api_token.rs b/examples/verify_api_token.rs index 65eab78..46e8654 100644 --- a/examples/verify_api_token.rs +++ b/examples/verify_api_token.rs @@ -1,10 +1,10 @@ -use eventsourcingdb::client::Client; -use url::Url; +use eventsourcingdb::{client::Client, container::Container}; #[tokio::main] async fn main() { - let base_url: Url = "localhost:3000".parse().unwrap(); - let api_token = "secret"; + let db = Container::start_default().await.unwrap(); + let base_url = db.get_base_url().await.unwrap(); + let api_token = db.get_api_token(); let client = Client::new(base_url, api_token); let result = client.verify_api_token().await; diff --git a/examples/write_events.rs b/examples/write_events.rs index 52b92b1..48ef6d6 100644 --- a/examples/write_events.rs +++ b/examples/write_events.rs @@ -1,11 +1,11 @@ -use eventsourcingdb::{client::Client, event::EventCandidate}; +use eventsourcingdb::{client::Client, container::Container, event::EventCandidate}; use serde_json::json; -use url::Url; #[tokio::main] async fn main() { - let base_url: Url = "localhost:3000".parse().unwrap(); - let api_token = "secret"; + let db = Container::start_default().await.unwrap(); + let base_url = db.get_base_url().await.unwrap(); + let api_token = db.get_api_token(); let client = Client::new(base_url, api_token); let event = EventCandidate::builder() From f3bd46e734bbcf010c9f95a11bdc960465210f9f Mon Sep 17 00:00:00 2001 From: Alexander Kampf Date: Mon, 14 Jul 2025 08:53:35 +0200 Subject: [PATCH 5/6] feat: Add seperate EventMissingStrategy for observing events --- README.md | 37 +++++++++---------- examples/observing_events.rs | 4 +- examples/reading_events.rs | 4 +- src/client.rs | 4 +- src/client/client_request/observe_events.rs | 6 +-- src/client/client_request/read_events.rs | 4 +- src/client/request_options.rs | 41 ++++++++++++++++----- tests/read_events.rs | 28 +++++++------- 8 files changed, 74 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 94ad85b..ce02235 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ if let Err(err) = result { } ``` -*Note that `Ping` does not require authentication, so the call may succeed even if the API token is invalid.* +*Note that `ping` does not require authentication, so the call may succeed even if the API token is invalid.* If you want to verify the API token, call `verify_api_token`. If the token is invalid, the function will return an error: @@ -54,7 +54,7 @@ Call the `write_events` function and hand over a vector with one or more events. Specify `source`, `subject`, `type` (using `ty`), and `data` according to the [CloudEvents](https://docs.eventsourcingdb.io/fundamentals/cloud-events/) format. -For `data` provide a json object using a `serde_json:Value`. +For `data` provide a JSON object using a `serde_json:Value`. The function returns the written events, including the fields added by the server: @@ -79,7 +79,7 @@ match result { #### Using the `IsSubjectPristine` precondition -If you only want to write events in case a subject (such as `/books/42`) does not yet have any events, use the `IsSubjectPristine` Precondition to create a precondition and pass it in a vector as the second argument: +If you only want to write events in case a subject (such as `/books/42`) does not yet have any events, use the `IsSubjectPristine` precondition to create a precondition and pass it in a vector as the second argument: ```rust let result = client.write_events( @@ -96,12 +96,12 @@ match result { #### Using the `IsSubjectOnEventId` precondition -If you only want to write events in case the last event of a subject (such as `/books/42`) has a specific ID (e.g., `0`), use the `IsSubjectOnEventID` Precondition to create a precondition and pass it in a vector as the second argument: +If you only want to write events in case the last event of a subject (such as `/books/42`) has a specific ID (e.g., `0`), use the `IsSubjectOnEventId` precondition to create a precondition and pass it in a vector as the second argument: ```rust let result = client.write_events( vec![event.clone()], - vec![Precondition::IsSubjectPristine { + vec![Precondition::IsSubjectOnEventId { subject: "/books/42".to_string(), event_id: "0".to_string(), }], @@ -123,7 +123,7 @@ The function returns a stream from which you can retrieve one event at a time: ```rust let result = client .read_events("/books/42", Some( - ReadEventsRequestOptions { + ReadEventsOptions { recursive: false, from_latest_event: None, order: None, @@ -150,7 +150,7 @@ If you want to read not only all the events of a subject, but also the events of ```rust let result = client .read_events("/books/42", Some( - ReadEventsRequestOptions { + ReadEventsOptions { recursive: true, ..Default::default(), } @@ -167,7 +167,7 @@ By default, events are read in chronological order. To read in anti-chronologica ```rust let result = client .read_events("/books/42", Some( - ReadEventsRequestOptions { + ReadEventsOptions { recursive: false, order: Some(Ordering::Antichronological) ..Default::default(), @@ -187,7 +187,7 @@ Specify the ID and whether to include or exclude it, for both the lower and uppe ```rust let result = client .read_events("/books/42", Some( - ReadEventsRequestOptions { + ReadEventsOptions { recursive: false, lower_bound: Some(Bound { bound_type: BoundType::Inclusive, @@ -212,13 +212,13 @@ Possible options are `ReadNothing`, which skips reading entirely, or `ReadyEvery ```rust let result = client .read_events("/books/42", Some( - ReadEventsRequestOptions { + ReadEventsOptions { recursive: false, from_latest_event: Some( FromLatestEventOptions { subject: "/books/42", ty: "io.eventsourcingdb.library.book-borrowed", - if_event_is_missing: EventMissingStrategy::ReadEverything, + if_event_is_missing: ReadEventMissingStrategy::ReadEverything, } ) ..Default::default(), @@ -256,11 +256,10 @@ To observe all events of a subject, call the `observe_events` function with the The function returns a stream from which you can retrieve one event at a time: - ```rust let result = client .observe_events("/books/42", Some( - ObserveEventsRequestOptions { + ObserveEventsOptions { recursive: false, from_latest_event: None, lower_bound: None, @@ -285,7 +284,7 @@ If you want to observe not only all the events of a subject, but also the events ```rust let result = client .observe_events("/books/42", Some( - ObserveEventsRequestOptions { + ObserveEventsOptions { recursive: true, ..Default::default(), } @@ -303,8 +302,8 @@ Specify the ID and whether to include or exclude it: ```rust let result = client - .read_events("/books/42", Some( - ReadEventsRequestOptions { + .observe_events("/books/42", Some( + ObserveEventsOptions { recursive: false, lower_bound: Some(Bound { bound_type: BoundType::Inclusive, @@ -324,14 +323,14 @@ Possible options are `WaitForEvent`, which waits for an event of the given type ```rust let result = client - .read_events("/books/42", Some( - ReadEventsRequestOptions { + .observe_events("/books/42", Some( + ObserveEventsOptions { recursive: false, from_latest_event: Some( ObserveFromLatestEventOptions { subject: "/books/42", ty: "io.eventsourcingdb.library.book-borrowed", - if_event_is_missing: EventMissingStrategy::ObserveEverything, + if_event_is_missing: ObserveEventMissingStrategy::ObserveEverything, } ) ..Default::default(), diff --git a/examples/observing_events.rs b/examples/observing_events.rs index 1b1c1c1..23e143c 100644 --- a/examples/observing_events.rs +++ b/examples/observing_events.rs @@ -1,5 +1,5 @@ use eventsourcingdb::{ - client::{Client, request_options::ObserveEventsRequestOptions}, + client::{Client, request_options::ObserveEventsOptions}, container::Container, }; use futures::StreamExt; @@ -14,7 +14,7 @@ async fn main() { let result = client .observe_events( "/books/42", - Some(ObserveEventsRequestOptions { + Some(ObserveEventsOptions { recursive: false, from_latest_event: None, lower_bound: None, diff --git a/examples/reading_events.rs b/examples/reading_events.rs index f57f0be..b77bdf6 100644 --- a/examples/reading_events.rs +++ b/examples/reading_events.rs @@ -1,5 +1,5 @@ use eventsourcingdb::{ - client::{Client, request_options::ReadEventsRequestOptions}, + client::{Client, request_options::ReadEventsOptions}, container::Container, }; use futures::StreamExt; @@ -14,7 +14,7 @@ async fn main() { let result = client .read_events( "/books/42", - Some(ReadEventsRequestOptions { + Some(ReadEventsOptions { recursive: false, from_latest_event: None, order: None, diff --git a/src/client.rs b/src/client.rs index b739487..1871c7d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -203,7 +203,7 @@ impl Client { pub async fn read_events<'a>( &self, subject: &'a str, - options: Option>, + options: Option>, ) -> Result>, ClientError> { let response = self .request_streaming(ReadEventsRequest { subject, options }) @@ -249,7 +249,7 @@ impl Client { pub async fn observe_events<'a>( &self, subject: &'a str, - options: Option>, + options: Option>, ) -> Result>, ClientError> { let response = self .request_streaming(ObserveEventsRequest { subject, options }) diff --git a/src/client/client_request/observe_events.rs b/src/client/client_request/observe_events.rs index 489f1b1..0afac56 100644 --- a/src/client/client_request/observe_events.rs +++ b/src/client/client_request/observe_events.rs @@ -1,9 +1,7 @@ use reqwest::Method; use serde::Serialize; -use crate::{ - client::request_options::ObserveEventsRequestOptions, error::ClientError, event::Event, -}; +use crate::{client::request_options::ObserveEventsOptions, error::ClientError, event::Event}; use super::{ClientRequest, StreamingRequest}; @@ -11,7 +9,7 @@ use super::{ClientRequest, StreamingRequest}; pub struct ObserveEventsRequest<'a> { pub subject: &'a str, #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option>, + pub options: Option>, } impl ClientRequest for ObserveEventsRequest<'_> { diff --git a/src/client/client_request/read_events.rs b/src/client/client_request/read_events.rs index c0cba02..b377dfc 100644 --- a/src/client/client_request/read_events.rs +++ b/src/client/client_request/read_events.rs @@ -1,7 +1,7 @@ use reqwest::Method; use serde::Serialize; -use crate::{client::request_options::ReadEventsRequestOptions, error::ClientError, event::Event}; +use crate::{client::request_options::ReadEventsOptions, error::ClientError, event::Event}; use super::{ClientRequest, StreamingRequest}; @@ -9,7 +9,7 @@ use super::{ClientRequest, StreamingRequest}; pub struct ReadEventsRequest<'a> { pub subject: &'a str, #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option>, + pub options: Option>, } impl ClientRequest for ReadEventsRequest<'_> { diff --git a/src/client/request_options.rs b/src/client/request_options.rs index f5f17af..4a6e453 100644 --- a/src/client/request_options.rs +++ b/src/client/request_options.rs @@ -5,10 +5,10 @@ use serde::Serialize; /// Options for reading events from the database #[derive(Debug, Default, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ReadEventsRequestOptions<'a> { +pub struct ReadEventsOptions<'a> { /// Start reading events from this start event #[serde(skip_serializing_if = "Option::is_none")] - pub from_latest_event: Option>, + pub from_latest_event: Option>, /// Lower bound of events to read #[serde(skip_serializing_if = "Option::is_none")] pub lower_bound: Option>, @@ -25,10 +25,10 @@ pub struct ReadEventsRequestOptions<'a> { /// Options for observing events from the database #[derive(Debug, Default, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ObserveEventsRequestOptions<'a> { +pub struct ObserveEventsOptions<'a> { /// Start reading events from this start event #[serde(skip_serializing_if = "Option::is_none")] - pub from_latest_event: Option>, + pub from_latest_event: Option>, /// Lower bound of events to read #[serde(skip_serializing_if = "Option::is_none")] pub lower_bound: Option>, @@ -67,25 +67,48 @@ pub struct Bound<'a> { pub id: &'a str, } -/// The strategy for handling missing events +/// The strategy for handling missing events while reading #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "kebab-case")] -pub enum EventMissingStrategy { +pub enum ReadEventMissingStrategy { /// Read all events if the required one is missing ReadEverything, /// Read no events if the required one is missing ReadNothing, } -/// Options for reading events from the start reading at +/// The strategy for handling missing events while observing +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum ObserveEventMissingStrategy { + /// Observe all events if the required one is missing + ObserveEverything, + /// Wait for the event until observing + WaitForEvent, +} + +/// Options for reading events from the latest event of certain type or subject #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct FromLatestEventOptions<'a> { +pub struct ReadFromLatestEventOptions<'a> { /// The strategy for handling missing events - pub if_event_is_missing: EventMissingStrategy, + pub if_event_is_missing: ReadEventMissingStrategy, /// The subject the event should be on pub subject: &'a str, /// The type of the event to read from #[serde(rename = "type")] pub ty: &'a str, } + +/// Options for observe events from the latest event of certain type or subject +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ObserveFromLatestEventOptions<'a> { + /// The strategy for handling missing events + pub if_event_is_missing: ObserveEventMissingStrategy, + /// The subject the event should be on + pub subject: &'a str, + /// The type of the event to observe from + #[serde(rename = "type")] + pub ty: &'a str, +} diff --git a/tests/read_events.rs b/tests/read_events.rs index fbd7f67..0f773ba 100644 --- a/tests/read_events.rs +++ b/tests/read_events.rs @@ -3,7 +3,7 @@ mod utils; use eventsourcingdb::{ container::Container, request_options::{ - EventMissingStrategy, FromLatestEventOptions, Ordering, ReadEventsRequestOptions, + Ordering, ReadEventMissingStrategy, ReadEventsOptions, ReadFromLatestEventOptions, }, }; use futures::TryStreamExt; @@ -120,7 +120,7 @@ async fn read_recursive() { let events_stream = client .read_events( "/test", - Some(ReadEventsRequestOptions { + Some(ReadEventsOptions { recursive: true, ..Default::default() }), @@ -155,7 +155,7 @@ async fn read_not_recursive() { let events_stream = client .read_events( "/test", - Some(ReadEventsRequestOptions { + Some(ReadEventsOptions { recursive: false, ..Default::default() }), @@ -183,7 +183,7 @@ async fn read_chronological() { let events_stream = client .read_events( "/test", - Some(ReadEventsRequestOptions { + Some(ReadEventsOptions { order: Some(Ordering::Chronological), ..Default::default() }), @@ -211,7 +211,7 @@ async fn read_antichronological() { let events_stream = client .read_events( "/test", - Some(ReadEventsRequestOptions { + Some(ReadEventsOptions { order: Some(Ordering::Antichronological), ..Default::default() }), @@ -241,11 +241,11 @@ async fn read_everything_from_missing_latest_event() { let events_stream = client .read_events( "/test", - Some(ReadEventsRequestOptions { - from_latest_event: Some(FromLatestEventOptions { + Some(ReadEventsOptions { + from_latest_event: Some(ReadFromLatestEventOptions { subject: "/", ty: "io.eventsourcingdb.test.does-not-exist", - if_event_is_missing: EventMissingStrategy::ReadEverything, + if_event_is_missing: ReadEventMissingStrategy::ReadEverything, }), ..Default::default() }), @@ -273,11 +273,11 @@ async fn read_nothing_from_missing_latest_event() { let events_stream = client .read_events( "/test", - Some(ReadEventsRequestOptions { - from_latest_event: Some(FromLatestEventOptions { + Some(ReadEventsOptions { + from_latest_event: Some(ReadFromLatestEventOptions { subject: "/", ty: "io.eventsourcingdb.test.does-not-exist", - if_event_is_missing: EventMissingStrategy::ReadNothing, + if_event_is_missing: ReadEventMissingStrategy::ReadNothing, }), ..Default::default() }), @@ -316,11 +316,11 @@ async fn read_from_latest_event() { let events_stream = client .read_events( "/test", - Some(ReadEventsRequestOptions { - from_latest_event: Some(FromLatestEventOptions { + Some(ReadEventsOptions { + from_latest_event: Some(ReadFromLatestEventOptions { subject: "/marker", ty: "io.eventsourcingdb.test", - if_event_is_missing: EventMissingStrategy::ReadNothing, + if_event_is_missing: ReadEventMissingStrategy::ReadNothing, }), ..Default::default() }), From 4b2a6e0860cf8dc7a4a9d4c4be4ff7c514853a06 Mon Sep 17 00:00:00 2001 From: Alexander Kampf Date: Mon, 14 Jul 2025 12:18:21 +0200 Subject: [PATCH 6/6] fix: Fix typos --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ce02235..05c7207 100644 --- a/README.md +++ b/README.md @@ -125,10 +125,7 @@ let result = client .read_events("/books/42", Some( ReadEventsOptions { recursive: false, - from_latest_event: None, - order: None, - lower_bound: None, - upper_bound: None, + ...Default::default(), } )) .await; @@ -162,7 +159,7 @@ This also allows you to read *all* events ever written. To do so, provide `/` as #### Reading in Anti-Chronological Order -By default, events are read in chronological order. To read in anti-chronological order, provide the `order` option and set it using the `Antichronological` Ordering: +By default, events are read in chronological order. To read in anti-chronological order, provide the `order` option and set it using the `Antichronological` ordering: ```rust let result = client @@ -176,7 +173,7 @@ let result = client .await; ``` -*Note that you can also use the `Chronological` Ordering to explicitly enforce the default order.* +*Note that you can also use the `Chronological` ordering to explicitly enforce the default order.* #### Specifying Bounds @@ -207,7 +204,7 @@ let result = client To read starting from the latest event of a given type, provide the `from_latest_event` option and specify the subject, the type, and how to proceed if no such event exists. -Possible options are `ReadNothing`, which skips reading entirely, or `ReadyEverything`, which effectively behaves as if `from_latest_event` was not specified: +Possible options are `ReadNothing`, which skips reading entirely, or `ReadEverything`, which effectively behaves as if `from_latest_event` was not specified: ```rust let result = client