Skip to content

Commit 8f177c7

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

File tree

8 files changed

+192
-26
lines changed

8 files changed

+192
-26
lines changed

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: 3 additions & 4 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/`

src/client.rs

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

src/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+
}

src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![deny(rustdoc::broken_intra_doc_links)]
2+
#![deny(rustdoc::private_intra_doc_links)]
3+
4+
mod client;
5+
mod error;
6+
7+
/// Import auto-generated objects from protobuf specification of VSS
8+
pub mod vss {
9+
include!("generated-src/org.vss.rs");
10+
}

vss-accessor/Cargo.toml

Lines changed: 0 additions & 16 deletions
This file was deleted.

vss-accessor/src/lib.rs

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)