diff --git a/README.md b/README.md index d444c76..b8c371f 100644 --- a/README.md +++ b/README.md @@ -412,6 +412,20 @@ match result { Err(err) => // ... } ``` + +### Listing a Specific Event Types + +To list a specific event type, call the `read_event_type` function. The function returns the detailed event type, which includes the schema: + +```rust +let event_type_name = "io.eventsourcingdb.library.book-acquired"; +let result := client.read_event_type(event_type_name).await; +match result { + Ok(event_type) => // ... + Err(err) => // ... +} +``` + ### Using Testcontainers Call the `Container::start_default()` function, get a client, and run your test code: diff --git a/examples/event_types.rs b/examples/event_types.rs index cbd3efc..b73ebd6 100644 --- a/examples/event_types.rs +++ b/examples/event_types.rs @@ -3,7 +3,7 @@ use futures::StreamExt; #[tokio::main] async fn main() { - let db = Container::start_default().await.unwrap(); + let db = Container::start_preview().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); @@ -14,7 +14,7 @@ async fn main() { Err(err) => panic!("{}", err), Ok(mut event_types) => { while let Some(Ok(event_type)) = event_types.next().await { - println!("{:?}", event_type) + println!("{event_type:?}") } } } diff --git a/examples/listing_subjects.rs b/examples/listing_subjects.rs index 88ac2c0..841dc3c 100644 --- a/examples/listing_subjects.rs +++ b/examples/listing_subjects.rs @@ -3,7 +3,7 @@ use futures::StreamExt; #[tokio::main] async fn main() { - let db = Container::start_default().await.unwrap(); + let db = Container::start_preview().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); @@ -14,7 +14,7 @@ async fn main() { Err(err) => panic!("{}", err), Ok(mut subjects) => { while let Some(Ok(subject)) = subjects.next().await { - println!("{:?}", subject) + println!("{subject:?}") } } } diff --git a/examples/observing_events.rs b/examples/observing_events.rs index 23e143c..27a7587 100644 --- a/examples/observing_events.rs +++ b/examples/observing_events.rs @@ -6,7 +6,7 @@ use futures::StreamExt; #[tokio::main] async fn main() { - let db = Container::start_default().await.unwrap(); + let db = Container::start_preview().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); @@ -26,7 +26,7 @@ async fn main() { Err(err) => panic!("{}", err), Ok(mut stream) => { while let Some(Ok(event)) = stream.next().await { - println!("{:?}", event) + println!("{event:?}") } } } diff --git a/examples/ping.rs b/examples/ping.rs index abc4f6f..4fb99b7 100644 --- a/examples/ping.rs +++ b/examples/ping.rs @@ -2,7 +2,7 @@ use eventsourcingdb::{client::Client, container::Container}; #[tokio::main] async fn main() { - let db = Container::start_default().await.unwrap(); + let db = Container::start_preview().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); diff --git a/examples/reading_events.rs b/examples/reading_events.rs index b77bdf6..cd79b0a 100644 --- a/examples/reading_events.rs +++ b/examples/reading_events.rs @@ -6,7 +6,7 @@ use futures::StreamExt; #[tokio::main] async fn main() { - let db = Container::start_default().await.unwrap(); + let db = Container::start_preview().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); @@ -28,7 +28,7 @@ async fn main() { Err(err) => panic!("{}", err), Ok(mut stream) => { while let Some(Ok(event)) = stream.next().await { - println!("{:?}", event) + println!("{event:?}") } } } diff --git a/examples/registering_event_schema.rs b/examples/registering_event_schema.rs index 67a7c10..c5dc5a1 100644 --- a/examples/registering_event_schema.rs +++ b/examples/registering_event_schema.rs @@ -3,7 +3,7 @@ use serde_json::json; #[tokio::main] async fn main() { - let db = Container::start_default().await.unwrap(); + let db = Container::start_preview().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); diff --git a/examples/running_eventql.rs b/examples/running_eventql.rs index e4237e3..46ef97b 100644 --- a/examples/running_eventql.rs +++ b/examples/running_eventql.rs @@ -3,7 +3,7 @@ use futures::StreamExt; #[tokio::main] async fn main() { - let db = Container::start_default().await.unwrap(); + let db = Container::start_preview().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); @@ -16,7 +16,7 @@ async fn main() { Err(err) => panic!("{}", err), Ok(mut stream) => { while let Some(Ok(row)) = stream.next().await { - println!("{:?}", row) + println!("{row:?}") } } } diff --git a/examples/verify_api_token.rs b/examples/verify_api_token.rs index 46e8654..8ae80b1 100644 --- a/examples/verify_api_token.rs +++ b/examples/verify_api_token.rs @@ -2,7 +2,7 @@ use eventsourcingdb::{client::Client, container::Container}; #[tokio::main] async fn main() { - let db = Container::start_default().await.unwrap(); + let db = Container::start_preview().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); diff --git a/examples/write_events.rs b/examples/write_events.rs index 48ef6d6..9243c9a 100644 --- a/examples/write_events.rs +++ b/examples/write_events.rs @@ -3,7 +3,7 @@ use serde_json::json; #[tokio::main] async fn main() { - let db = Container::start_default().await.unwrap(); + let db = Container::start_preview().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); @@ -21,7 +21,7 @@ async fn main() { let result = client.write_events(vec![event.clone()], vec![]).await; match result { - Ok(written_events) => println!("{:?}", written_events), + Ok(written_events) => println!("{written_events:?}"), Err(err) => panic!("{}", err), } } diff --git a/src/client.rs b/src/client.rs index 1871c7d..bcadab1 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,7 +3,7 @@ //! To use the client, create it with the base URL and API token of your [EventsourcingDB](https://www.eventsourcingdb.io/) instance. //! ``` //! # tokio_test::block_on(async { -//! # let container = eventsourcingdb::container::Container::start_default().await.unwrap(); +//! # let container = eventsourcingdb::container::Container::start_preview().await.unwrap(); //! let db_url = "http://localhost:3000/"; //! let api_token = "secrettoken"; //! # let db_url = container.get_base_url().await.unwrap(); @@ -22,14 +22,15 @@ mod precondition; pub mod request_options; use crate::{ + client::client_request::ReadEventTypeRequest, error::ClientError, event::{Event, EventCandidate, ManagementEvent}, + request_options::EventType, }; use client_request::{ ClientRequest, ListEventTypesRequest, ListSubjectsRequest, ObserveEventsRequest, OneShotRequest, PingRequest, ReadEventsRequest, RegisterEventSchemaRequest, RunEventqlQueryRequest, StreamingRequest, VerifyApiTokenRequest, WriteEventsRequest, - list_event_types::EventType, }; use futures::Stream; pub use precondition::Precondition; @@ -161,7 +162,7 @@ impl Client { /// /// ``` /// # tokio_test::block_on(async { - /// # let container = eventsourcingdb::container::Container::start_default().await.unwrap(); + /// # let container = eventsourcingdb::container::Container::start_preview().await.unwrap(); /// let db_url = "http://localhost:3000/"; /// let api_token = "secrettoken"; /// # let db_url = container.get_base_url().await.unwrap(); @@ -185,7 +186,7 @@ impl Client { /// use futures::StreamExt; /// # use serde_json::json; /// # tokio_test::block_on(async { - /// # let container = eventsourcingdb::container::Container::start_default().await.unwrap(); + /// # let container = eventsourcingdb::container::Container::start_preview().await.unwrap(); /// let db_url = "http://localhost:3000/"; /// let api_token = "secrettoken"; /// # let db_url = container.get_base_url().await.unwrap(); @@ -211,6 +212,49 @@ impl Client { Ok(response) } + /// Reads a specific event type from the DB instance. + /// + /// ``` + /// use eventsourcingdb::event::EventCandidate; + /// use futures::StreamExt; + /// # use serde_json::json; + /// # tokio_test::block_on(async { + /// # let container = eventsourcingdb::container::Container::start_preview().await.unwrap(); + /// let db_url = "http://localhost:3000/"; + /// let api_token = "secrettoken"; + /// # let db_url = container.get_base_url().await.unwrap(); + /// # let api_token = container.get_api_token(); + /// let client = eventsourcingdb::client::Client::new(db_url, api_token); + /// let event_type = "io.eventsourcingdb.test"; + /// let schema = json!({ + /// "type": "object", + /// "properties": { + /// "id": { + /// "type": "string" + /// }, + /// "name": { + /// "type": "string" + /// } + /// }, + /// "required": ["id", "name"] + /// }); + /// client.register_event_schema(event_type, &schema).await.expect("Failed to register event types"); + /// let type_info = client.read_event_type(event_type).await.expect("Failed to read event type"); + /// println!("Found Type {} with schema {:?}", type_info.name, type_info.schema); + /// # }) + /// ``` + /// + /// # Errors + /// This function will return an error if the request fails or if the URL is invalid. + pub async fn read_event_type(&self, event_type: &str) -> Result { + let response = self + .request_oneshot(ReadEventTypeRequest { + event_type: event_type.to_string(), + }) + .await?; + Ok(response) + } + /// Observe events from the DB instance. /// /// ``` @@ -218,7 +262,7 @@ impl Client { /// use futures::StreamExt; /// # use serde_json::json; /// # tokio_test::block_on(async { - /// # let container = eventsourcingdb::container::Container::start_default().await.unwrap(); + /// # let container = eventsourcingdb::container::Container::start_preview().await.unwrap(); /// let db_url = "http://localhost:3000/"; /// let api_token = "secrettoken"; /// # let db_url = container.get_base_url().await.unwrap(); @@ -261,7 +305,7 @@ impl Client { /// /// ``` /// # tokio_test::block_on(async { - /// # let container = eventsourcingdb::container::Container::start_default().await.unwrap(); + /// # let container = eventsourcingdb::container::Container::start_preview().await.unwrap(); /// let db_url = "http://localhost:3000/"; /// let api_token = "secrettoken"; /// # let db_url = container.get_base_url().await.unwrap(); @@ -285,7 +329,7 @@ impl Client { /// use futures::StreamExt; /// # use serde_json::json; /// # tokio_test::block_on(async { - /// # let container = eventsourcingdb::container::Container::start_default().await.unwrap(); + /// # let container = eventsourcingdb::container::Container::start_preview().await.unwrap(); /// let db_url = "http://localhost:3000/"; /// let api_token = "secrettoken"; /// # let db_url = container.get_base_url().await.unwrap(); @@ -327,7 +371,7 @@ impl Client { /// use futures::StreamExt; /// # use serde_json::json; /// # tokio_test::block_on(async { - /// # let container = eventsourcingdb::container::Container::start_default().await.unwrap(); + /// # let container = eventsourcingdb::container::Container::start_preview().await.unwrap(); /// let db_url = "http://localhost:3000/"; /// let api_token = "secrettoken"; /// # let db_url = container.get_base_url().await.unwrap(); @@ -346,7 +390,7 @@ impl Client { /// use futures::StreamExt; /// # use serde_json::json; /// # tokio_test::block_on(async { - /// # let container = eventsourcingdb::container::Container::start_default().await.unwrap(); + /// # let container = eventsourcingdb::container::Container::start_preview().await.unwrap(); /// let db_url = "http://localhost:3000/"; /// let api_token = "secrettoken"; /// # let db_url = container.get_base_url().await.unwrap(); @@ -380,7 +424,7 @@ impl Client { /// use futures::StreamExt; /// # use serde_json::json; /// # tokio_test::block_on(async { - /// # let container = eventsourcingdb::container::Container::start_default().await.unwrap(); + /// # let container = eventsourcingdb::container::Container::start_preview().await.unwrap(); /// let db_url = "http://localhost:3000/"; /// let api_token = "secrettoken"; /// # let db_url = container.get_base_url().await.unwrap(); @@ -408,7 +452,7 @@ impl Client { /// use eventsourcingdb::event::EventCandidate; /// # use serde_json::json; /// # tokio_test::block_on(async { - /// # let container = eventsourcingdb::container::Container::start_default().await.unwrap(); + /// # let container = eventsourcingdb::container::Container::start_preview().await.unwrap(); /// let db_url = "http://localhost:3000/"; /// let api_token = "secrettoken"; /// # let db_url = container.get_base_url().await.unwrap(); @@ -447,7 +491,7 @@ impl Client { /// use futures::StreamExt; /// # use serde_json::json; /// # tokio_test::block_on(async { - /// # let container = eventsourcingdb::container::Container::start_default().await.unwrap(); + /// # let container = eventsourcingdb::container::Container::start_preview().await.unwrap(); /// let db_url = "http://localhost:3000/"; /// let api_token = "secrettoken"; /// # let db_url = container.get_base_url().await.unwrap(); diff --git a/src/client/client_request.rs b/src/client/client_request.rs index cf5a713..a0fbd6f 100644 --- a/src/client/client_request.rs +++ b/src/client/client_request.rs @@ -4,6 +4,7 @@ pub mod list_event_types; mod list_subjects; mod observe_events; mod ping; +mod read_event_type; mod read_events; mod register_event_schema; mod run_eventql_query; @@ -14,6 +15,7 @@ pub use list_event_types::ListEventTypesRequest; pub use list_subjects::ListSubjectsRequest; pub use observe_events::ObserveEventsRequest; pub use ping::PingRequest; +pub use read_event_type::ReadEventTypeRequest; pub use read_events::ReadEventsRequest; pub use register_event_schema::RegisterEventSchemaRequest; pub use run_eventql_query::RunEventqlQueryRequest; diff --git a/src/client/client_request/list_event_types.rs b/src/client/client_request/list_event_types.rs index aeed53b..6998836 100644 --- a/src/client/client_request/list_event_types.rs +++ b/src/client/client_request/list_event_types.rs @@ -1,18 +1,8 @@ +use super::{ClientRequest, StreamingRequest}; use reqwest::Method; -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -use crate::error::ClientError; +use serde::Serialize; -use super::{ClientRequest, StreamingRequest}; -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct EventType { - #[serde(rename = "eventType")] - pub name: String, - pub is_phantom: bool, - pub schema: Option, -} +use crate::{client::request_options::EventType, error::ClientError}; #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/src/client/client_request/read_event_type.rs b/src/client/client_request/read_event_type.rs new file mode 100644 index 0000000..d1e40ea --- /dev/null +++ b/src/client/client_request/read_event_type.rs @@ -0,0 +1,28 @@ +use reqwest::Method; +use serde::Serialize; + +use crate::{ + client::client_request::{ClientRequest, OneShotRequest}, + error::ClientError, + request_options::EventType, +}; + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ReadEventTypeRequest { + #[serde(rename = "eventType")] + /// The name of the event type to read + pub event_type: String, +} + +impl ClientRequest for ReadEventTypeRequest { + const URL_PATH: &'static str = "/api/v1/read-event-type"; + const METHOD: Method = Method::POST; + + fn body(&self) -> Option> { + Some(Ok(self)) + } +} +impl OneShotRequest for ReadEventTypeRequest { + type Response = EventType; +} diff --git a/src/client/request_options.rs b/src/client/request_options.rs index 4a6e453..34fbc31 100644 --- a/src/client/request_options.rs +++ b/src/client/request_options.rs @@ -1,6 +1,7 @@ //! This module contains supporting options for the client requests. -use serde::Serialize; +use serde::{Deserialize, Serialize}; +use serde_json::Value; /// Options for reading events from the database #[derive(Debug, Default, Clone, Serialize)] @@ -112,3 +113,16 @@ pub struct ObserveFromLatestEventOptions<'a> { #[serde(rename = "type")] pub ty: &'a str, } + +/// Represents an event type in the database +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct EventType { + /// The name of the event type + #[serde(rename = "eventType")] + pub name: String, + /// Whether the event type is a phantom type + pub is_phantom: bool, + /// The schema of the event type, if available + pub schema: Option, +} diff --git a/src/container.rs b/src/container.rs index 01d1312..f23d573 100644 --- a/src/container.rs +++ b/src/container.rs @@ -12,6 +12,7 @@ //! # use eventsourcingdb::container::Container; //! # tokio_test::block_on(async { //! let container = Container::start_default().await; +//! # let container = Container::start_preview().await; //! // let client = container.get_client().await; //! # }); //! ``` @@ -147,6 +148,7 @@ impl ContainerBuilder { /// # use eventsourcingdb::container::Container; /// # tokio_test::block_on(async { /// let container = Container::start_default().await; +/// # let container = Container::start_preview().await; /// // let client = container.get_client().await; /// # }); /// ``` @@ -169,7 +171,7 @@ impl Container { /// Shortcut method to start the container with default settings. /// /// This is the same as calling [`Container::builder`] and then [`ContainerBuilder::start`]. - /// In most cases this will create a contaienr with the latest image tag and a working configuration. + /// In most cases this will create a container with the latest image tag and a working configuration. /// /// # Errors /// This functions returns the errors of [`ContainerBuilder::start()`] @@ -177,6 +179,17 @@ impl Container { Self::builder().start().await } + /// Shortcut method to start the container with the preview tag and default settings. + /// + /// This is the same as calling [`Container::builder`], [`Container::with_image_tag("preview")`] and then [`ContainerBuilder::start`]. + /// In most cases this will create a container with the latest image tag and a working configuration. + /// + /// # Errors + /// This functions returns the errors of [`ContainerBuilder::start()`] + pub async fn start_preview() -> Result { + Self::builder().with_image_tag("preview").start().await + } + /// Get the host of the container. /// /// This is the host that you can use to connect to the database. In most cases this will be `localhost`. diff --git a/tests/metadata_and_discovery.rs b/tests/metadata_and_discovery.rs index 0abba51..2a3e4d2 100644 --- a/tests/metadata_and_discovery.rs +++ b/tests/metadata_and_discovery.rs @@ -116,4 +116,50 @@ async fn list_all_event_types() { } } +#[tokio::test] +async fn read_single_event_type() { + let container = create_test_container().await; + let client = container.get_client().await.unwrap(); + let test_event_type = "io.eventsourcingdb.test"; + let schema = json!({ + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": ["id", "name"] + }); + client + .register_event_schema(test_event_type, &schema) + .await + .expect("Failed to register event schema"); + let res = client.read_event_type(test_event_type).await; + match res { + Ok(event_type) => { + assert_eq!( + event_type.name, test_event_type, + "Expected event type to be 'io.eventsourcingdb.test', but got: {:?}", + event_type.name + ); + assert_eq!( + event_type.schema.as_ref(), + Some(&schema), + "Expected event type schema to be {:?}, but got: {:?}", + schema, + event_type.schema + ); + assert!( + event_type.is_phantom, + "Expected event type is_phantom to be true, but got: {:?}", + event_type.is_phantom + ); + } + Err(err) => panic!("Failed to read event type: {err:?}"), + } +} + // TODO!: add list event types test after writing to db diff --git a/tests/testcontainer.rs b/tests/testcontainer.rs index 0de7530..030f8fb 100644 --- a/tests/testcontainer.rs +++ b/tests/testcontainer.rs @@ -2,13 +2,13 @@ use eventsourcingdb::container::Container; #[tokio::test] async fn start_stop_testcontainer() { - let c = Container::start_default().await.unwrap(); + let c = Container::start_preview().await.unwrap(); c.stop().await.unwrap(); } #[tokio::test] async fn get_base_url() { - let c = Container::start_default().await.unwrap(); + let c = Container::start_preview().await.unwrap(); let base_url = c.get_base_url().await.unwrap(); let host = c.get_host().await.unwrap(); let port = c.get_mapped_port().await.unwrap(); @@ -17,7 +17,7 @@ async fn get_base_url() { #[tokio::test] async fn db_is_reachable() { - let c = Container::start_default().await.unwrap(); + let c = Container::start_preview().await.unwrap(); let base_url = c.get_base_url().await.unwrap(); let ping_url = base_url .join("/api/v1/ping") @@ -27,7 +27,7 @@ async fn db_is_reachable() { #[tokio::test] async fn generate_client() { - let c = Container::start_default().await.unwrap(); + let c = Container::start_preview().await.unwrap(); let generated_client = c.get_client().await.unwrap(); let base_url = c.get_base_url().await.unwrap(); let api_token = c.get_api_token();