Skip to content

Commit 193bffc

Browse files
committed
Add VSS Http thin client implementation for get/put/listKeyVersions api's
1 parent fe4f654 commit 193bffc

File tree

3 files changed

+171
-0
lines changed

3 files changed

+171
-0
lines changed

vss-accessor/Cargo.toml

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

77
[dependencies]
8+
prost = "0.11.9"
9+
reqwest = { version = "0.11.13", features = ["rustls-tls"] }
810

911
[dev-dependencies]
1012

vss-accessor/src/lib.rs

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

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+
/// A mapping from a VSS server error codes. Refer to [`ErrorResponse`] docs 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+
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)