Skip to content

Commit 0031a6b

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

File tree

9 files changed

+438
-278
lines changed

9 files changed

+438
-278
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
/target
2-
/vss-accessor/src/proto/
2+
/src/proto/

Cargo.toml

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
1-
[workspace]
2-
members = [
3-
"vss-accessor",
4-
]
1+
[package]
2+
name = "vss-client"
3+
version = "0.1.0"
4+
edition = "2021"
5+
build = "build.rs"
6+
7+
[dependencies]
8+
prost = "0.11.9"
9+
reqwest = { version = "0.11.13", features = ["rustls-tls"] }
10+
11+
[dev-dependencies]
12+
13+
[build-dependencies]
14+
prost-build = { version = "0.11.3" }
15+
reqwest = { version = "0.11.13", features = ["blocking"] }
16+
17+
[features]
18+
genproto = []

vss-accessor/build.rs renamed to build.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1+
#[cfg(feature = "genproto")]
12
extern crate prost_build;
2-
3-
use std::fs::File;
4-
use std::path::Path;
5-
use std::{env, fs};
3+
#[cfg(feature = "genproto")]
4+
use std::{env, fs, fs::File, path::Path};
65

76
/// To generate updated proto objects:
87
/// 1. Place `vss.proto` file in `src/proto/`
@@ -20,8 +19,8 @@ fn generate_protos() {
2019
).unwrap();
2120

2221
prost_build::compile_protos(&["src/proto/vss.proto"], &["src/"]).unwrap();
23-
let from_path = Path::new(&env::var("OUT_DIR").unwrap()).join("org.vss.rs");
24-
fs::copy(from_path, "src/generated-src/org.vss.rs").unwrap();
22+
let from_path = Path::new(&env::var("OUT_DIR").unwrap()).join("vss.rs");
23+
fs::copy(from_path, "src/types.rs").unwrap();
2524
}
2625

2726
#[cfg(feature = "genproto")]

src/client.rs

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

src/error.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use crate::types::{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+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
#![deny(rustdoc::broken_intra_doc_links)]
22
#![deny(rustdoc::private_intra_doc_links)]
3+
4+
pub mod client;
5+
pub mod error;
6+
pub mod types;

0 commit comments

Comments
 (0)