Skip to content

Commit 4e74eab

Browse files
committed
Add VSS Http thin client implementation for get/put/listKeyVersions api's
1 parent 73ce6a3 commit 4e74eab

File tree

3 files changed

+183
-0
lines changed

3 files changed

+183
-0
lines changed

vss-accessor/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ version = "0.1.0"
44
edition = "2021"
55

66
[dependencies]
7+
prost = "0.11.9"
8+
reqwest = { version = "0.11.13", features = ["rustls-tls"] }
79

810
[dev-dependencies]
911
prost-build = { version = "0.11.3" }

vss-accessor/src/lib.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,107 @@
1+
use ::prost::Message;
2+
use reqwest;
3+
use reqwest::Client;
4+
use std::error::Error;
15

6+
use crate::vss::{
7+
GetObjectRequest, GetObjectResponse, KeyValue, ListKeyVersionsRequest, ListKeyVersionsResponse, PutObjectRequest,
8+
PutObjectResponse,
9+
};
10+
use crate::vss_error::VssError;
11+
12+
mod vss_error;
13+
14+
pub mod vss {
15+
include!("generated-src/org.vss.rs");
16+
}
17+
18+
/// Thin-client to access hosted instance of Versioned Storage Service (VSS).
19+
/// Api for VssAccessor is minimalistic and directly mimics vss-server-side api's as it is.
20+
pub struct VssAccessor {
21+
base_url: String,
22+
client: Client,
23+
}
24+
25+
impl VssAccessor {
26+
/// Constructs new instance of VssAccessor.
27+
/// `base_url` is the vss-server endpoint to be used.
28+
pub fn new(base_url: &str) -> Result<Self, Box<dyn Error>> {
29+
let client = Client::new();
30+
Ok(Self { base_url: String::from(base_url), client })
31+
}
32+
33+
/// get_object fetches a value against a given key.
34+
/// Makes a service call to GetObject endpoint of vss-server.
35+
/// For api-contract/usage, refer docs for: [`GetObjectRequest`] and [`GetObjectResponse`]
36+
pub async fn get_object(&self, store: String, key: String) -> Result<GetObjectResponse, VssError> {
37+
let url = format!("{}/getObject", self.base_url);
38+
39+
let request = GetObjectRequest { store_id: store, key };
40+
41+
let response_raw = self.client.post(url).body(request.encode_to_vec()).send().await?;
42+
let status = response_raw.status();
43+
let payload = response_raw.bytes().await?;
44+
45+
if status.is_success() {
46+
let response = GetObjectResponse::decode(&payload[..])?;
47+
Ok(response)
48+
} else {
49+
Err(VssError::new(status, payload))
50+
}
51+
}
52+
53+
/// put_object writes a value against the key in request.
54+
/// Makes a service call to PutObject endpoint of vss-server, with single item.
55+
/// For api-contract/usage, refer docs for: [`PutObjectRequest`] and [`PutObjectResponse`]
56+
pub async fn put_object(
57+
&self, store: String, global_version: Option<i64>, key: String, version: i64, value: &[u8],
58+
) -> Result<PutObjectResponse, VssError> {
59+
let kv = KeyValue { key: String::from(key), version, value: value.to_vec() };
60+
return self.put_objects_tx(store, global_version, vec![kv]).await;
61+
}
62+
63+
/// put_objects_tx writes multiple transaction_items in single transaction.
64+
/// Makes a service call to PutObject endpoint of vss-server, with multiple items.
65+
/// Items in the request are written in a single all-or-nothing transaction.
66+
/// For api-contract/usage, refer docs for: [`PutObjectRequest`] and [`PutObjectResponse`]
67+
pub async fn put_objects_tx(
68+
&self, store: String, global_version: Option<i64>, transaction_items: Vec<KeyValue>,
69+
) -> Result<PutObjectResponse, VssError> {
70+
let url = format!("{}/putObjects", self.base_url);
71+
72+
let request = PutObjectRequest { store_id: store, global_version, transaction_items };
73+
74+
let response_raw = self.client.post(url).body(request.encode_to_vec()).send().await?;
75+
let status = response_raw.status();
76+
let payload = response_raw.bytes().await?;
77+
78+
if status.is_success() {
79+
let response = PutObjectResponse::decode(&payload[..])?;
80+
Ok(response)
81+
} else {
82+
Err(VssError::new(status, payload))
83+
}
84+
}
85+
86+
/// list_key_versions lists keys and their corresponding version.
87+
/// Makes a service call to ListKeyVersions endpoint of vss-server.
88+
/// For api-contract/usage, refer docs for: [`ListKeyVersionsRequest`] and [`ListKeyVersionsResponse`]
89+
pub async fn list_key_versions(
90+
&self, store: String, key_prefix: String, page_size: Option<i32>, page_token: Option<String>,
91+
) -> Result<ListKeyVersionsResponse, VssError> {
92+
let url = format!("{}/listKeyVersions", self.base_url);
93+
94+
let request = ListKeyVersionsRequest { store_id: store, key_prefix: Some(key_prefix), page_size, page_token };
95+
96+
let response_raw = self.client.post(url).body(request.encode_to_vec()).send().await?;
97+
let status = response_raw.status();
98+
let payload = response_raw.bytes().await?;
99+
100+
if status.is_success() {
101+
let response = ListKeyVersionsResponse::decode(&payload[..])?;
102+
Ok(response)
103+
} else {
104+
Err(VssError::new(status, payload))
105+
}
106+
}
107+
}

vss-accessor/src/vss_error.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use crate::vss::{ErrorCode, ErrorResponse};
2+
use prost::bytes::Bytes;
3+
use prost::{DecodeError, Message};
4+
use reqwest::StatusCode;
5+
use std::error::Error;
6+
use std::fmt::{Display, Formatter};
7+
8+
#[derive(Debug)]
9+
pub enum VssError {
10+
InvalidRequestError(ErrorResponse),
11+
ConflictError(ErrorResponse),
12+
InternalServerError(ErrorResponse),
13+
InternalError(String),
14+
}
15+
16+
impl VssError {
17+
pub fn new(status: StatusCode, payload: Bytes) -> VssError {
18+
match ErrorResponse::decode(&payload[..]) {
19+
Ok(error_response) => VssError::from(error_response),
20+
Err(e) => {
21+
let message =
22+
format!("Unable to decode ErrorResponse from server, HttpStatusCode: {}, DecodeErr: {}", status, e);
23+
VssError::InternalError(message)
24+
}
25+
}
26+
}
27+
}
28+
29+
impl Display for VssError {
30+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
31+
match self {
32+
VssError::InvalidRequestError(error_response) => {
33+
write!(f, "Request sent to VSS Server was invalid : {}", error_response.message)
34+
}
35+
VssError::ConflictError(error_response) => {
36+
write!(f, "Potential version conflict in write operation : {}", error_response.message)
37+
}
38+
VssError::InternalServerError(error_response) => {
39+
write!(f, "InternalServerError : {}", error_response.message)
40+
}
41+
VssError::InternalError(message) => {
42+
write!(f, "InternalError : {}", message)
43+
}
44+
}
45+
}
46+
}
47+
48+
impl Error for VssError {}
49+
50+
impl From<ErrorResponse> for VssError {
51+
fn from(error_response: ErrorResponse) -> Self {
52+
return match error_response.error_code() {
53+
ErrorCode::InvalidRequestException => VssError::InvalidRequestError(error_response),
54+
ErrorCode::ConflictException => VssError::ConflictError(error_response),
55+
ErrorCode::InternalServerException => VssError::InternalServerError(error_response),
56+
_ => VssError::InternalError(format!(
57+
"Server responded with an unknown error code: {}, \
58+
message: {}",
59+
error_response.error_code, error_response.message
60+
)),
61+
};
62+
}
63+
}
64+
65+
impl From<DecodeError> for VssError {
66+
fn from(err: DecodeError) -> Self {
67+
VssError::InternalError(err.to_string())
68+
}
69+
}
70+
71+
impl From<reqwest::Error> for VssError {
72+
fn from(err: reqwest::Error) -> Self {
73+
VssError::InternalError(err.to_string())
74+
}
75+
}

0 commit comments

Comments
 (0)