-
Notifications
You must be signed in to change notification settings - Fork 7
Add VSS Http thin client implementation for get/put/listKeyVersions api's #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
/target | ||
/vss-accessor/src/proto/ | ||
/src/proto/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,18 @@ | ||
[workspace] | ||
members = [ | ||
"vss-accessor", | ||
] | ||
[package] | ||
name = "vss-client" | ||
version = "0.1.0" | ||
edition = "2021" | ||
build = "build.rs" | ||
|
||
[dependencies] | ||
prost = "0.11.9" | ||
reqwest = { version = "0.11.13", features = ["rustls-tls"] } | ||
|
||
[dev-dependencies] | ||
|
||
[build-dependencies] | ||
prost-build = { version = "0.11.3" } | ||
reqwest = { version = "0.11.13", features = ["blocking"] } | ||
|
||
[features] | ||
genproto = [] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
use ::prost::Message; | ||
use reqwest; | ||
use reqwest::Client; | ||
|
||
use crate::error::VssError; | ||
use crate::types::{ | ||
GetObjectRequest, GetObjectResponse, ListKeyVersionsRequest, ListKeyVersionsResponse, PutObjectRequest, | ||
PutObjectResponse, | ||
}; | ||
|
||
/// Thin-client to access a hosted instance of Versioned Storage Service (VSS). | ||
/// The provided [`VssClient`] API is minimalistic and is congruent to the VSS server-side API. | ||
pub struct VssClient { | ||
base_url: String, | ||
client: Client, | ||
} | ||
|
||
impl VssClient { | ||
/// Constructs a [`VssClient`] using `base_url` as the VSS server endpoint. | ||
pub fn new(base_url: &str) -> Self { | ||
let client = Client::new(); | ||
Self { base_url: String::from(base_url), client } | ||
} | ||
|
||
/// Fetches a value against a given `key` in `request`. | ||
/// Makes a service call to the `GetObject` endpoint of the VSS server. | ||
/// For API contract/usage, refer to docs for [`GetObjectRequest`] and [`GetObjectResponse`]. | ||
pub async fn get_object(&self, request: &GetObjectRequest) -> Result<GetObjectResponse, VssError> { | ||
let url = format!("{}/getObject", self.base_url); | ||
|
||
let raw_response = self.client.post(url).body(request.encode_to_vec()).send().await?; | ||
let status = raw_response.status(); | ||
let payload = raw_response.bytes().await?; | ||
|
||
if status.is_success() { | ||
let response = GetObjectResponse::decode(&payload[..])?; | ||
Ok(response) | ||
} else { | ||
Err(VssError::new(status, payload)) | ||
} | ||
} | ||
|
||
/// Writes multiple [`PutObjectRequest::transaction_items`] as part of a single transaction. | ||
/// Makes a service call to the `PutObject` endpoint of the VSS server, with multiple items. | ||
/// Items in the `request` are written in a single all-or-nothing transaction. | ||
/// For API contract/usage, refer to docs for [`PutObjectRequest`] and [`PutObjectResponse`]. | ||
pub async fn put_object(&self, request: &PutObjectRequest) -> Result<PutObjectResponse, VssError> { | ||
let url = format!("{}/putObjects", self.base_url); | ||
|
||
let response_raw = self.client.post(url).body(request.encode_to_vec()).send().await?; | ||
let status = response_raw.status(); | ||
let payload = response_raw.bytes().await?; | ||
|
||
if status.is_success() { | ||
let response = PutObjectResponse::decode(&payload[..])?; | ||
Ok(response) | ||
} else { | ||
Err(VssError::new(status, payload)) | ||
} | ||
} | ||
|
||
/// Lists keys and their corresponding version for a given [`ListKeyVersionsRequest::store_id`]. | ||
/// Makes a service call to the `ListKeyVersions` endpoint of the VSS server. | ||
/// For API contract/usage, refer to docs for [`ListKeyVersionsRequest`] and [`ListKeyVersionsResponse`]. | ||
pub async fn list_key_versions( | ||
&self, request: &ListKeyVersionsRequest, | ||
) -> Result<ListKeyVersionsResponse, VssError> { | ||
let url = format!("{}/listKeyVersions", self.base_url); | ||
|
||
let response_raw = self.client.post(url).body(request.encode_to_vec()).send().await?; | ||
let status = response_raw.status(); | ||
let payload = response_raw.bytes().await?; | ||
|
||
if status.is_success() { | ||
let response = ListKeyVersionsResponse::decode(&payload[..])?; | ||
Ok(response) | ||
} else { | ||
Err(VssError::new(status, payload)) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
use crate::types::{ErrorCode, ErrorResponse}; | ||
use prost::bytes::Bytes; | ||
use prost::{DecodeError, Message}; | ||
use reqwest::StatusCode; | ||
use std::error::Error; | ||
use std::fmt::{Display, Formatter}; | ||
|
||
/// When there is an error while writing to VSS storage, the response contains a relevant error code. | ||
/// A mapping from a VSS server error codes. Refer to [`ErrorResponse`] docs for more | ||
/// information regarding each error code and corresponding use-cases. | ||
#[derive(Debug)] | ||
pub enum VssError { | ||
InvalidRequestError(String), | ||
ConflictError(String), | ||
InternalServerError(String), | ||
InternalError(String), | ||
} | ||
|
||
impl VssError { | ||
/// Create new instance of `VssError` | ||
pub fn new(status: StatusCode, payload: Bytes) -> VssError { | ||
match ErrorResponse::decode(&payload[..]) { | ||
Ok(error_response) => VssError::from(error_response), | ||
Err(e) => { | ||
let message = | ||
format!("Unable to decode ErrorResponse from server, HttpStatusCode: {}, DecodeErr: {}", status, e); | ||
VssError::InternalError(message) | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl Display for VssError { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
VssError::InvalidRequestError(message) => { | ||
write!(f, "Request sent to VSS Storage was invalid: {}", message) | ||
} | ||
VssError::ConflictError(message) => { | ||
write!(f, "Potential version conflict in write operation: {}", message) | ||
} | ||
VssError::InternalServerError(message) => { | ||
write!(f, "InternalServerError: {}", message) | ||
} | ||
VssError::InternalError(message) => { | ||
write!(f, "InternalError: {}", message) | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl Error for VssError {} | ||
|
||
impl From<ErrorResponse> for VssError { | ||
fn from(error_response: ErrorResponse) -> Self { | ||
match error_response.error_code() { | ||
ErrorCode::InvalidRequestException => VssError::InvalidRequestError(error_response.message), | ||
ErrorCode::ConflictException => VssError::ConflictError(error_response.message), | ||
ErrorCode::InternalServerException => VssError::InternalServerError(error_response.message), | ||
_ => VssError::InternalError(format!( | ||
"VSS responded with an unknown error code: {}, message: {}", | ||
error_response.error_code, error_response.message | ||
)), | ||
} | ||
} | ||
} | ||
|
||
impl From<DecodeError> for VssError { | ||
fn from(err: DecodeError) -> Self { | ||
VssError::InternalError(err.to_string()) | ||
} | ||
} | ||
|
||
impl From<reqwest::Error> for VssError { | ||
fn from(err: reqwest::Error) -> Self { | ||
VssError::InternalError(err.to_string()) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,6 @@ | ||
#![deny(rustdoc::broken_intra_doc_links)] | ||
#![deny(rustdoc::private_intra_doc_links)] | ||
|
||
pub mod client; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Depending of how many objects we expect to expose overall, we might want to consider exposing some part of the (important) objects from the crate root. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that case maybe client should have been in lib ? (contrary to our previous discussion to move it out of that) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can always re-export in that case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, i think re-exporting makes sense 👍 |
||
pub mod error; | ||
pub mod types; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're definitely missing some crate-level and module-level docs here and below. Also an initial usage example would be nice-to-have on the landing page. Currently this is just blank:

(Still think we should enable the
missing_docs
lint from the start)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, let's add that now. Didn't realize that needed to be explicit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As i understand, module docs just need 1-liner description, will add that.
As for usage doc at crate level, i think i would add it pre-release as we grow and add rest of the things.
Let me know if this sounds good:
client - Implements a thin-client[
VssClient
] to access a hosted instance of Versioned Storage Service (VSS).error - Implements the error type[
VssError
] returned on interacting with [VSSClient
]types - Contains request/response types generated from API definition of VSS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now #6