Skip to content
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

fixes #32 add validation method to endpoint trait and implement bulk write payload size validation #42

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 85 additions & 3 deletions src/endpoints/workerskv/write_bulk.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::framework::endpoint::{Endpoint, Method};

use crate::framework::{response::ApiFailure, response::ApiErrors, response::ApiError, endpoint::{Endpoint, Method}};
use reqwest;
use std::collections::HashMap;
/// Write Key-Value Pairs in Bulk
/// Writes multiple key-value pairs to Workers KV at once.
/// A 404 is returned if a write action is for a namespace ID the account doesn't have.
Expand All @@ -23,7 +24,37 @@ impl<'a> Endpoint<(), (), Vec<KeyValuePair>> for WriteBulk<'a> {
fn body(&self) -> Option<Vec<KeyValuePair>> {
Some(self.bulk_key_value_pairs.clone())
}
// default content-type is already application/json
fn validate(&self) -> Result<(), ApiFailure> {
if let Some(body) = self.body() {
// this matches the serialization in HttpApiClient
let json = serde_json::to_string(&body).map_err(|e|
ApiFailure::Error(
reqwest::StatusCode::BAD_REQUEST,
ApiErrors {
errors: vec![ApiError {
code: 400,
message: format!("request body is malformed, failed json serialization: {}", e),
other: HashMap::new(),
}],
other: HashMap::new(),
}))?;

if json.len() >= 100_000_000 {
return Err(ApiFailure::Error(
reqwest::StatusCode::PAYLOAD_TOO_LARGE,
ApiErrors {
errors: vec![ApiError {
code: 413,
message: "request payload too large, must be less than 100MB".to_owned(),
other: HashMap::new(),
}],
other: HashMap::new(),
},
));
}
}
Ok(())
}
}

#[serde_with::skip_serializing_none]
Expand All @@ -35,3 +66,54 @@ pub struct KeyValuePair {
pub expiration_ttl: Option<i64>,
pub base64: Option<bool>,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn write_bulk_validator_failure() {
let write_bulk_endpoint = WriteBulk{
account_identifier: "test_account_id",
namespace_identifier: "test_namespace",
bulk_key_value_pairs: vec![
KeyValuePair {
key: "test".to_owned(),
value: "X".repeat(100_000_000),
expiration: None,
expiration_ttl: None,
base64: None
}
]
};

match write_bulk_endpoint.validate() {
Ok(_) => assert!(false, "payload too large and validator passed incorrectly"),
Err(_) => assert!(true)
}
}

#[test]
fn write_bulk_validator_success() {
let write_bulk_endpoint = WriteBulk{
account_identifier: "test_account_id",
namespace_identifier: "test_namespace",
bulk_key_value_pairs: vec![
KeyValuePair {
key: "test".to_owned(),
// max is 99,999,972 chars for the val
// the other 28 chars are taken by the key, property names, and json formatting chars
value: "x".repeat(99_999_950),
expiration: None,
expiration_ttl: None,
base64: None
}
]
};

match write_bulk_endpoint.validate() {
Ok(_) => assert!(true),
Err(_) => assert!(false, "payload within bounds and validator failed incorrectly")
}
}
}
7 changes: 7 additions & 0 deletions src/framework/endpoint.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::framework::response::ApiResult;
use crate::framework::response::ApiFailure;
use crate::framework::Environment;

use serde::Serialize;
use url::Url;

Expand All @@ -11,6 +13,7 @@ pub enum Method {
Patch,
}


pub trait Endpoint<ResultType = (), QueryType = (), BodyType = ()>
where
ResultType: ApiResult,
Expand All @@ -25,6 +28,10 @@ where
fn body(&self) -> Option<BodyType> {
None
}
/// Some endpoints dont need to validate. That's OK.
fn validate(&self) -> Result<(), ApiFailure> {
Ok(())
}
fn url(&self, environment: &Environment) -> Url {
Url::from(environment).join(&self.path()).unwrap()
}
Expand Down
3 changes: 2 additions & 1 deletion src/framework/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ impl<'a> ApiClient for HttpApiClient {
}
}

endpoint.validate()?;

// Build the request
let mut request = self
.http_client
Expand All @@ -97,7 +99,6 @@ impl<'a> ApiClient for HttpApiClient {
request = request.auth(&self.credentials);

let response = request.send()?;

map_api_response(response)
}
}