Skip to content

Commit 79829db

Browse files
committed
Add VSS Http thin client implementation for get/put/listKeyVersions api's
1 parent 143d762 commit 79829db

File tree

3 files changed

+173
-0
lines changed

3 files changed

+173
-0
lines changed

vss-accessor/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ edition = "2021"
55
build = "build.rs"
66

77
[dependencies]
8+
prost = "0.11.9"
9+
#bytes = "1.4.0"
10+
reqwest = { version = "0.11.13", features = ["rustls-tls", "blocking"] }
811

912
[dev-dependencies]
1013

vss-accessor/src/lib.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,94 @@
11
#![deny(rustdoc::broken_intra_doc_links)]
22
#![deny(rustdoc::private_intra_doc_links)]
3+
4+
use std::error::Error;
5+
6+
use ::prost::Message;
7+
use reqwest;
8+
use reqwest::Client;
9+
10+
use crate::vss::{
11+
GetObjectRequest, GetObjectResponse, ListKeyVersionsRequest, ListKeyVersionsResponse, PutObjectRequest,
12+
PutObjectResponse,
13+
};
14+
use crate::vss_error::VssError;
15+
16+
mod vss_error;
17+
18+
/// Import auto-generated code
19+
pub mod vss {
20+
include!("generated-src/org.vss.rs");
21+
}
22+
23+
/// Thin-client to access hosted instance of `Versioned Storage Service (VSS)`.
24+
/// Api for VssClient is minimalistic and directly mimics vss-server-side api's as it is.
25+
pub struct VssClient {
26+
base_url: String,
27+
client: Client,
28+
}
29+
30+
impl VssClient {
31+
/// Constructs new instance of VssClient.
32+
/// `base_url` is the vss-server endpoint to be used.
33+
pub fn new(base_url: &str) -> Result<Self, Box<dyn Error>> {
34+
let client = Client::new();
35+
Ok(Self { base_url: String::from(base_url), client })
36+
}
37+
38+
/// Fetches a value against a given `key` in `request`.
39+
/// Makes a service call to GetObject endpoint of vss-server.
40+
/// For api-contract/usage, refer docs for: [`GetObjectRequest`] and [`GetObjectResponse`]
41+
pub async fn get_object(&self, request: GetObjectRequest) -> Result<GetObjectResponse, VssError> {
42+
let url = format!("{}/getObject", self.base_url);
43+
44+
let response_raw = self.client.post(url).body(request.encode_to_vec()).send().await?;
45+
let status = response_raw.status();
46+
let payload = response_raw.bytes().await?;
47+
48+
if status.is_success() {
49+
let response = GetObjectResponse::decode(&payload[..])?;
50+
Ok(response)
51+
} else {
52+
Err(VssError::new(status, payload))
53+
}
54+
}
55+
56+
/// Writes multiple `transaction_items` of `request` in single transaction.
57+
/// Makes a service call to PutObject endpoint of vss-server, with multiple items.
58+
/// Items in the `request` are written in a single all-or-nothing transaction.
59+
/// For api-contract/usage, refer docs for: [`PutObjectRequest`] and [`PutObjectResponse`]
60+
pub async fn put_object(&self, request: PutObjectRequest) -> Result<PutObjectResponse, VssError> {
61+
let url = format!("{}/putObjects", self.base_url);
62+
63+
let response_raw = self.client.post(url).body(request.encode_to_vec()).send().await?;
64+
let status = response_raw.status();
65+
let payload = response_raw.bytes().await?;
66+
67+
if status.is_success() {
68+
let response = PutObjectResponse::decode(&payload[..])?;
69+
Ok(response)
70+
} else {
71+
Err(VssError::new(status, payload))
72+
}
73+
}
74+
75+
/// Lists keys and their corresponding version for a given `store_id` in `request`.
76+
/// Makes a service call to ListKeyVersions endpoint of vss-server.
77+
/// For api-contract/usage, refer docs for: [`ListKeyVersionsRequest`] and [`ListKeyVersionsResponse`]
78+
pub async fn list_key_versions(
79+
&self, request: ListKeyVersionsRequest,
80+
) -> Result<ListKeyVersionsResponse, VssError> {
81+
let url = format!("{}/listKeyVersions", self.base_url);
82+
83+
let response_raw = self.client.post(url).body(request.encode_to_vec()).send().await?;
84+
let status = response_raw.status();
85+
let payload = response_raw.bytes().await?;
86+
87+
if status.is_success() {
88+
let response = ListKeyVersionsResponse::decode(&payload[..])?;
89+
Ok(response)
90+
} else {
91+
Err(VssError::new(status, payload))
92+
}
93+
}
94+
}

vss-accessor/src/vss_error.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
/// When there is an error while writing to vss-storage, the response contains a relevant error code.
9+
/// This enum is direct mapping of vss-server error codes. Refer docs for [`ErrorResponse`] for more
10+
/// information regarding each error code and corresponding use-cases.
11+
#[derive(Debug)]
12+
pub enum VssError {
13+
InvalidRequestError(String),
14+
ConflictError(String),
15+
InternalServerError(String),
16+
InternalError(String),
17+
}
18+
19+
impl VssError {
20+
/// Create new instance of VssError
21+
pub fn new(status: StatusCode, payload: Bytes) -> VssError {
22+
match ErrorResponse::decode(&payload[..]) {
23+
Ok(error_response) => VssError::from(error_response),
24+
Err(e) => {
25+
let message =
26+
format!("Unable to decode ErrorResponse from server, HttpStatusCode: {}, DecodeErr: {}", status, e);
27+
VssError::InternalError(message)
28+
}
29+
}
30+
}
31+
}
32+
33+
impl Display for VssError {
34+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
35+
match self {
36+
VssError::InvalidRequestError(message) => {
37+
write!(f, "Request sent to VSS Storage was invalid : {}", message)
38+
}
39+
VssError::ConflictError(message) => {
40+
write!(f, "Potential version conflict in write operation : {}", message)
41+
}
42+
VssError::InternalServerError(message) => {
43+
write!(f, "InternalServerError : {}", message)
44+
}
45+
VssError::InternalError(message) => {
46+
write!(f, "InternalError : {}", message)
47+
}
48+
}
49+
}
50+
}
51+
52+
impl Error for VssError {}
53+
54+
impl From<ErrorResponse> for VssError {
55+
fn from(error_response: ErrorResponse) -> Self {
56+
return match error_response.error_code() {
57+
ErrorCode::InvalidRequestException => VssError::InvalidRequestError(error_response.message),
58+
ErrorCode::ConflictException => VssError::ConflictError(error_response.message),
59+
ErrorCode::InternalServerException => VssError::InternalServerError(error_response.message),
60+
_ => VssError::InternalError(format!(
61+
"VSS responded with an unknown error code: {}, message: {}",
62+
error_response.error_code, error_response.message
63+
)),
64+
};
65+
}
66+
}
67+
68+
impl From<DecodeError> for VssError {
69+
fn from(err: DecodeError) -> Self {
70+
VssError::InternalError(err.to_string())
71+
}
72+
}
73+
74+
impl From<reqwest::Error> for VssError {
75+
fn from(err: reqwest::Error) -> Self {
76+
VssError::InternalError(err.to_string())
77+
}
78+
}

0 commit comments

Comments
 (0)