diff --git a/src/cli.rs b/src/cli.rs index 730fcb8..eb754ba 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -136,7 +136,8 @@ impl Cli { #[must_use] pub fn get_share_request(&self) -> NewShareRequest { - NewShareRequest::new(&self.share_name, self.description.as_ref(), self.max_views) + NewShareRequest::new(&self.share_name, self.max_views) + .description(self.description.as_ref()) } #[must_use] diff --git a/src/sharry/json.rs b/src/sharry/json.rs index 5e523d9..f6292c9 100644 --- a/src/sharry/json.rs +++ b/src/sharry/json.rs @@ -1,31 +1,41 @@ -#![allow(non_snake_case)] use serde::{Deserialize, Serialize}; +/// Request Body sent to the Sharry API for creating a new share +/// +/// - impl `Serialize` to send in a HTTP request #[derive(Serialize, Debug)] pub struct NewShareRequest { name: String, validity: u32, description: Option, - maxViews: u32, + #[serde(rename = "maxViews")] + max_views: u32, password: Option, } impl NewShareRequest { - pub fn new( - name: impl Into, - description: Option>, - max_views: u32, - ) -> Self { + pub fn new(name: impl Into, max_views: u32) -> Self { Self { name: name.into(), + // apparently, the "validity" param doesn't make any difference validity: 0, - description: description.map(Into::into), - maxViews: max_views, + description: None, + max_views, + // new shares are private by default, setting a password doesn't really make sense password: None, } } + + /// set a human‐readable description + pub fn description(mut self, desc: Option>) -> Self { + self.description = desc.map(Into::into); + self + } } +/// Request Body received from the Sharry API when creating a new share +/// +/// - impl `Deserialize` to parse from a HTTP request #[derive(Deserialize, Debug)] pub struct NewShareResponse { pub success: bool, @@ -33,6 +43,9 @@ pub struct NewShareResponse { pub id: String, } +/// Request Body received from the Sharry API when pinging a share's notification hook +/// +/// - impl `Deserialize` to parse from a HTTP request #[derive(Deserialize, Debug)] #[allow(dead_code)] pub struct NotifyShareResponse { @@ -46,22 +59,61 @@ mod tests { #[test] fn nsreq_new_sets_fields_correctly() { - let name = "myname"; - let desc = "desc"; - let views = 7; + let cases: Vec<(&str, u32)> = vec![ + // simple ASCII name, small view count + ("alice", 1), + // underscores, mid-range views + ("bob_smith", 10), + // unicode in the name + ("漢字ユーザー", 5), + // empty name edge case + ("", 3), + // zero views + ("charlie", 0), + // very large view count + ("delta", u32::MAX), + // name with spaces and punctuation + ("user name!", 42), + // name with mixed case + ("FooBar", 7), + ]; - let req = NewShareRequest::new(name, Some(desc), views); + for (name, views) in cases { + let req = NewShareRequest::new(name, views); - assert_eq!(req.name, name); - assert_eq!(req.validity, 0); - assert_eq!(req.description.as_deref(), Some(desc)); - assert_eq!(req.maxViews, views); - assert!(req.password.is_none()); + assert_eq!(req.name, name); + assert_eq!(req.max_views, views); + + // unset fields + assert_eq!(req.validity, 0); + assert!(req.description.is_none()); + assert!(req.password.is_none()); + } } #[test] - fn nsreq_new_allows_none_description() { - let req = NewShareRequest::new("whatever", None::<&str>, 0); - assert!(req.description.is_none()); + fn nsreq_new_allows_setting_description() { + let longstr = "y".repeat(256); + + let cases = vec![ + // simple alphanumeric + "A simple test user", + // whitespace & punctuation + "Bob’s description: loves Rust!", + // unicode + "日本語の説明", + // long string + longstr.as_str(), + // empty + "", + // only whitespace + " ", + ]; + + for desc in cases { + let req = NewShareRequest::new("whatever", 0).description(Some(desc)); + assert!(req.description.is_some()); + assert_eq!(req.description.unwrap(), desc); + } } }