Skip to content

Commit ce372f7

Browse files
committed
feat(client): rework write events based on forward looking learnings in #8
Signed-off-by: Raphael Höser <[email protected]>
1 parent 11b4dc5 commit ce372f7

File tree

6 files changed

+116
-65
lines changed

6 files changed

+116
-65
lines changed

src/client.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
mod client_request;
2121
mod precondition;
2222

23-
use client_request::{ClientRequest, PingRequest, VerifyApiTokenRequest, WriteEvents};
23+
use client_request::{
24+
ClientRequest, OneShotRequest, PingRequest, VerifyApiTokenRequest, WriteEventsRequest,
25+
};
2426

2527
pub use precondition::Precondition;
2628
use reqwest;
@@ -77,9 +79,14 @@ impl Client {
7779

7880
/// Utility function to request an endpoint of the API.
7981
///
82+
/// This function will return a [`reqwest::RequestBuilder`] which can be used to send the request.
83+
///
8084
/// # Errors
8185
/// This function will return an error if the request fails or if the URL is invalid.
82-
async fn request<R: ClientRequest>(&self, endpoint: R) -> Result<R::Response, ClientError> {
86+
fn build_request<R: ClientRequest>(
87+
&self,
88+
endpoint: &R,
89+
) -> Result<reqwest::RequestBuilder, ClientError> {
8390
let url = self
8491
.base_url
8592
.join(endpoint.url_path())
@@ -98,15 +105,27 @@ impl Client {
98105
} else {
99106
request
100107
};
108+
Ok(request)
109+
}
101110

102-
let response = request.send().await?;
111+
/// Utility function to request an endpoint of the API as a oneshot.
112+
///
113+
/// This means, that the response is not streamed, but returned as a single value.
114+
///
115+
/// # Errors
116+
/// This function will return an error if the request fails or if the URL is invalid.
117+
async fn request_oneshot<R: OneShotRequest>(
118+
&self,
119+
endpoint: R,
120+
) -> Result<R::Response, ClientError> {
121+
let response = self.build_request(&endpoint)?.send().await?;
103122

104123
if response.status().is_success() {
105124
let result = response.json().await?;
106125
endpoint.validate_response(&result)?;
107126
Ok(result)
108127
} else {
109-
Err(ClientError::DBError(
128+
Err(ClientError::DBApiError(
110129
response.status(),
111130
response.text().await.unwrap_or_default(),
112131
))
@@ -130,7 +149,7 @@ impl Client {
130149
/// # Errors
131150
/// This function will return an error if the request fails or if the URL is invalid.
132151
pub async fn ping(&self) -> Result<(), ClientError> {
133-
let _ = self.request(PingRequest).await?;
152+
let _ = self.request_oneshot(PingRequest).await?;
134153
Ok(())
135154
}
136155

@@ -151,7 +170,7 @@ impl Client {
151170
/// # Errors
152171
/// This function will return an error if the request fails or if the URL is invalid.
153172
pub async fn verify_api_token(&self) -> Result<(), ClientError> {
154-
let _ = self.request(VerifyApiTokenRequest).await?;
173+
let _ = self.request_oneshot(VerifyApiTokenRequest).await?;
155174
Ok(())
156175
}
157176

@@ -186,7 +205,7 @@ impl Client {
186205
events: Vec<EventCandidate>,
187206
preconditions: Vec<Precondition>,
188207
) -> Result<Vec<Event>, ClientError> {
189-
self.request(WriteEvents {
208+
self.request_oneshot(WriteEventsRequest {
190209
events,
191210
preconditions,
192211
})

src/client/client_request.rs

Lines changed: 15 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
//! This is a purely internal module to represent client requests to the database.
22
3-
use reqwest::Method;
4-
use serde::Serialize;
3+
mod ping;
4+
mod verify_api_token;
5+
mod write_events;
56

6-
use crate::{
7-
error::ClientError,
8-
event::{Event, EventCandidate, ManagementEvent},
9-
};
7+
pub use ping::PingRequest;
8+
pub use verify_api_token::VerifyApiTokenRequest;
9+
pub use write_events::WriteEventsRequest;
1010

11-
use super::precondition::Precondition;
11+
use crate::error::ClientError;
12+
use reqwest::Method;
13+
use serde::{Serialize, de::DeserializeOwned};
1214

1315
/// Represents a request to the database client
1416
pub trait ClientRequest {
1517
const URL_PATH: &'static str;
1618
const METHOD: Method;
17-
type Response: serde::de::DeserializeOwned;
1819

1920
/// Returns the URL path for the request
2021
fn url_path(&self) -> &'static str {
@@ -28,59 +29,16 @@ pub trait ClientRequest {
2829

2930
/// Returns the body for the request
3031
fn body(&self) -> Option<Result<impl Serialize, ClientError>> {
31-
None::<Result<(), ClientError>>
32+
None::<Result<(), _>>
3233
}
34+
}
35+
36+
/// Represents a request to the database that expects a single response
37+
pub trait OneShotRequest: ClientRequest {
38+
type Response: DeserializeOwned;
3339

3440
/// Validate the response from the database
3541
fn validate_response(&self, _response: &Self::Response) -> Result<(), ClientError> {
3642
Ok(())
3743
}
3844
}
39-
40-
/// Ping the Database instance
41-
#[derive(Debug, Clone, Copy)]
42-
pub struct PingRequest;
43-
44-
impl ClientRequest for PingRequest {
45-
const URL_PATH: &'static str = "/api/v1/ping";
46-
const METHOD: Method = Method::GET;
47-
type Response = ManagementEvent;
48-
49-
fn validate_response(&self, response: &Self::Response) -> Result<(), ClientError> {
50-
(response.ty() == "io.eventsourcingdb.api.ping-received")
51-
.then_some(())
52-
.ok_or(ClientError::PingFailed)
53-
}
54-
}
55-
56-
/// Verify the API token
57-
#[derive(Debug, Clone, Copy)]
58-
pub struct VerifyApiTokenRequest;
59-
60-
impl ClientRequest for VerifyApiTokenRequest {
61-
const URL_PATH: &'static str = "/api/v1/verify-api-token";
62-
const METHOD: Method = Method::POST;
63-
type Response = ManagementEvent;
64-
65-
fn validate_response(&self, response: &Self::Response) -> Result<(), ClientError> {
66-
(response.ty() == "io.eventsourcingdb.api.api-token-verified")
67-
.then_some(())
68-
.ok_or(ClientError::APITokenInvalid)
69-
}
70-
}
71-
72-
#[derive(Debug, Serialize)]
73-
pub struct WriteEvents {
74-
pub events: Vec<EventCandidate>,
75-
pub preconditions: Vec<Precondition>,
76-
}
77-
78-
impl ClientRequest for WriteEvents {
79-
const URL_PATH: &'static str = "/api/v1/write-events";
80-
const METHOD: Method = Method::POST;
81-
type Response = Vec<Event>;
82-
83-
fn body(&self) -> Option<Result<impl Serialize, ClientError>> {
84-
Some(Ok(self))
85-
}
86-
}

src/client/client_request/ping.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use reqwest::Method;
2+
3+
use crate::{error::ClientError, event::ManagementEvent};
4+
5+
use super::{ClientRequest, OneShotRequest};
6+
7+
/// Ping the Database instance
8+
#[derive(Debug, Clone, Copy)]
9+
pub struct PingRequest;
10+
11+
impl ClientRequest for PingRequest {
12+
const URL_PATH: &'static str = "/api/v1/ping";
13+
const METHOD: Method = Method::GET;
14+
}
15+
16+
impl OneShotRequest for PingRequest {
17+
type Response = ManagementEvent;
18+
19+
fn validate_response(&self, response: &Self::Response) -> Result<(), ClientError> {
20+
(response.ty() == "io.eventsourcingdb.api.ping-received")
21+
.then_some(())
22+
.ok_or(ClientError::PingFailed)
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use reqwest::Method;
2+
3+
use crate::{error::ClientError, event::ManagementEvent};
4+
5+
use super::{ClientRequest, OneShotRequest};
6+
7+
/// Verify the API token
8+
#[derive(Debug, Clone, Copy)]
9+
pub struct VerifyApiTokenRequest;
10+
11+
impl ClientRequest for VerifyApiTokenRequest {
12+
const URL_PATH: &'static str = "/api/v1/verify-api-token";
13+
const METHOD: Method = Method::POST;
14+
}
15+
16+
impl OneShotRequest for VerifyApiTokenRequest {
17+
type Response = ManagementEvent;
18+
19+
fn validate_response(&self, response: &Self::Response) -> Result<(), ClientError> {
20+
(response.ty() == "io.eventsourcingdb.api.api-token-verified")
21+
.then_some(())
22+
.ok_or(ClientError::APITokenInvalid)
23+
}
24+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use super::{ClientRequest, OneShotRequest};
2+
use crate::{
3+
client::Precondition,
4+
error::ClientError,
5+
event::{Event, EventCandidate},
6+
};
7+
use reqwest::Method;
8+
use serde::Serialize;
9+
10+
#[derive(Debug, Serialize)]
11+
pub struct WriteEventsRequest {
12+
pub events: Vec<EventCandidate>,
13+
pub preconditions: Vec<Precondition>,
14+
}
15+
16+
impl ClientRequest for WriteEventsRequest {
17+
const URL_PATH: &'static str = "/api/v1/write-events";
18+
const METHOD: Method = Method::POST;
19+
20+
fn body(&self) -> Option<Result<impl Serialize, ClientError>> {
21+
Some(Ok(self))
22+
}
23+
}
24+
impl OneShotRequest for WriteEventsRequest {
25+
type Response = Vec<Event>;
26+
}

src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub enum ClientError {
2626
SerdeJsonError(#[from] serde_json::Error),
2727
/// The DB returned an error+
2828
#[error("The DB returned an error: {0}")]
29-
DBError(StatusCode, String),
29+
DBApiError(StatusCode, String),
3030
/// There was a problem with the `cloudevents` message
3131
#[cfg(feature = "cloudevents")]
3232
#[error("The CloudEvents message is invalid: {0}")]

0 commit comments

Comments
 (0)