From 03d2260737cc02ce7bb6431efbe925118832f241 Mon Sep 17 00:00:00 2001 From: Sebastian Jeltsch Date: Mon, 28 Apr 2025 11:16:50 +0200 Subject: [PATCH] Change Rust client's arguments to a builder approach (as opposed to leaking the struct) for better forward compatibility. --- client/trailbase-rs/src/lib.rs | 103 +++++++++++++----- client/trailbase-rs/tests/integration_test.rs | 71 +++++------- docs/examples/record_api_rs/src/list.rs | 15 +-- 3 files changed, 110 insertions(+), 79 deletions(-) diff --git a/client/trailbase-rs/src/lib.rs b/client/trailbase-rs/src/lib.rs index 06a7270f..6c8d3aed 100644 --- a/client/trailbase-rs/src/lib.rs +++ b/client/trailbase-rs/src/lib.rs @@ -71,28 +71,31 @@ pub struct Tokens { pub csrf_token: Option, } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, PartialEq)] pub struct Pagination { - pub cursor: Option, - pub limit: Option, - pub offset: Option, + cursor: Option, + limit: Option, + offset: Option, } impl Pagination { - pub fn with(limit: impl Into>, cursor: impl Into>) -> Pagination { - return Pagination { - limit: limit.into(), - cursor: cursor.into(), - offset: None, - }; + pub fn new() -> Self { + return Self::default(); } - pub fn with_limit(limit: impl Into>) -> Pagination { - return Pagination::with(limit, None); + pub fn with_limit(mut self, limit: impl Into>) -> Pagination { + self.limit = limit.into(); + return self; } - pub fn with_cursor(cursor: impl Into>) -> Pagination { - return Pagination::with(None, cursor); + pub fn with_cursor(mut self, cursor: impl Into>) -> Pagination { + self.cursor = cursor.into(); + return self; + } + + pub fn with_offset(mut self, offset: impl Into>) -> Pagination { + self.offset = offset.into(); + return self; } } @@ -141,7 +144,7 @@ impl RecordId<'_> for i64 { pub trait ReadArgumentsTrait<'a> { fn serialized_id(self) -> Cow<'a, str>; - fn expand(&self) -> Option<&'a [&'a str]>; + fn expand(&self) -> Option<&Vec<&'a str>>; } impl<'a, T: RecordId<'a>> ReadArgumentsTrait<'a> for T { @@ -149,15 +152,26 @@ impl<'a, T: RecordId<'a>> ReadArgumentsTrait<'a> for T { return self.serialized_id(); } - fn expand(&self) -> Option<&'a [&'a str]> { + fn expand(&self) -> Option<&Vec<&'a str>> { return None; } } -#[derive(Debug, Default)] +#[derive(Clone, Debug, PartialEq)] pub struct ReadArguments<'a, T: RecordId<'a>> { - pub id: T, - pub expand: Option<&'a [&'a str]>, + id: T, + expand: Option>, +} + +impl<'a, T: RecordId<'a>> ReadArguments<'a, T> { + pub fn new(id: T) -> Self { + return Self { id, expand: None }; + } + + pub fn with_expand(mut self, expand: impl AsRef<[&'a str]>) -> Self { + self.expand = Some(expand.as_ref().to_vec()); + return self; + } } impl<'a, T: RecordId<'a>> ReadArgumentsTrait<'a> for ReadArguments<'a, T> { @@ -165,8 +179,8 @@ impl<'a, T: RecordId<'a>> ReadArgumentsTrait<'a> for ReadArguments<'a, T> { return self.id.serialized_id(); } - fn expand(&self) -> Option<&'a [&'a str]> { - return self.expand; + fn expand(&self) -> Option<&Vec<&'a str>> { + return self.expand.as_ref(); } } @@ -236,13 +250,44 @@ pub struct RecordApi { name: String, } -#[derive(Default)] +#[derive(Clone, Debug, Default, PartialEq)] pub struct ListArguments<'a> { - pub pagination: Pagination, - pub order: Option<&'a [&'a str]>, - pub filters: Option<&'a [&'a str]>, - pub expand: Option<&'a [&'a str]>, - pub count: bool, + pagination: Pagination, + order: Option>, + filters: Option>, + expand: Option>, + count: bool, +} + +impl<'a> ListArguments<'a> { + pub fn new() -> Self { + return ListArguments::default(); + } + + pub fn with_pagination(mut self, pagination: Pagination) -> Self { + self.pagination = pagination; + return self; + } + + pub fn with_order(mut self, order: impl AsRef<[&'a str]>) -> Self { + self.order = Some(order.as_ref().to_vec()); + return self; + } + + pub fn with_filters(mut self, filters: impl AsRef<[&'a str]>) -> Self { + self.filters = Some(filters.as_ref().to_vec()); + return self; + } + + pub fn with_expand(mut self, expand: impl AsRef<[&'a str]>) -> Self { + self.expand = Some(expand.as_ref().to_vec()); + return self; + } + + pub fn with_count(mut self, count: bool) -> Self { + self.count = count; + return self; + } } impl RecordApi { @@ -266,13 +311,13 @@ impl RecordApi { if let Some(order) = args.order { if !order.is_empty() { - params.push((Cow::Borrowed("order"), Cow::Owned(to_list(order)))); + params.push((Cow::Borrowed("order"), Cow::Owned(to_list(&order)))); } } if let Some(expand) = args.expand { if !expand.is_empty() { - params.push((Cow::Borrowed("expand"), Cow::Owned(to_list(expand)))); + params.push((Cow::Borrowed("expand"), Cow::Owned(to_list(&expand)))); } } diff --git a/client/trailbase-rs/tests/integration_test.rs b/client/trailbase-rs/tests/integration_test.rs index fabcee1e..1e3dd483 100644 --- a/client/trailbase-rs/tests/integration_test.rs +++ b/client/trailbase-rs/tests/integration_test.rs @@ -169,23 +169,19 @@ async fn records_test() { { // List one specific message. let filter = format!("text_not_null={}", messages[0]); - let filters = vec![filter.as_str()]; let response = api - .list::(ListArguments { - filters: Some(&filters), - ..Default::default() - }) + .list::(ListArguments::new().with_filters([filter.as_str()])) .await .unwrap(); assert_eq!(response.records.len(), 1); let second_response = api - .list::(ListArguments { - pagination: Pagination::with_cursor(response.cursor), - filters: Some(&filters), - ..Default::default() - }) + .list::( + ListArguments::new() + .with_filters(&[filter.as_str()]) + .with_pagination(Pagination::new().with_cursor(response.cursor)), + ) .await .unwrap(); @@ -196,12 +192,12 @@ async fn records_test() { // List all the messages let filter = format!("text_not_null[like]=% =?&{now}"); let records_ascending: ListResponse = api - .list(ListArguments { - order: Some(&["+text_not_null"]), - filters: Some(&[&filter]), - count: true, - ..Default::default() - }) + .list( + ListArguments::new() + .with_order(["+text_not_null"]) + .with_filters([filter.as_str()]) + .with_count(true), + ) .await .unwrap(); @@ -214,11 +210,11 @@ async fn records_test() { assert_eq!(Some(2), records_ascending.total_count); let records_descending: ListResponse = api - .list(ListArguments { - order: Some(&["-text_not_null"]), - filters: Some(&[&filter]), - ..Default::default() - }) + .list( + ListArguments::new() + .with_order(["-text_not_null"]) + .with_filters([filter.as_str()]), + ) .await .unwrap(); @@ -277,10 +273,7 @@ async fn expand_foreign_records_test() { { let comment: Comment = api - .read(ReadArguments { - id: 1, - expand: Some(&["post"]), - }) + .read(ReadArguments::new(1).with_expand(["post"])) .await .unwrap(); assert_eq!(1, comment.id); @@ -291,12 +284,12 @@ async fn expand_foreign_records_test() { { let comments: ListResponse = api - .list(ListArguments { - pagination: Pagination::with_limit(2), - order: Some(&["-id"]), - expand: Some(&["author", "post"]), - ..Default::default() - }) + .list( + ListArguments::new() + .with_pagination(Pagination::new().with_limit(2)) + .with_order(["-id"]) + .with_expand(["author", "post"]), + ) .await .unwrap(); @@ -311,16 +304,12 @@ async fn expand_foreign_records_test() { let second = &comments.records[0]; let offset_comments: ListResponse = api - .list(ListArguments { - pagination: Pagination { - limit: Some(1), - offset: Some(1), - ..Default::default() - }, - order: Some(&["-id"]), - expand: Some(&["author", "post"]), - ..Default::default() - }) + .list( + ListArguments::new() + .with_pagination(Pagination::new().with_limit(1).with_offset(1)) + .with_order(["-id"]) + .with_expand(["author", "post"]), + ) .await .unwrap(); diff --git a/docs/examples/record_api_rs/src/list.rs b/docs/examples/record_api_rs/src/list.rs index b234da30..8248017c 100644 --- a/docs/examples/record_api_rs/src/list.rs +++ b/docs/examples/record_api_rs/src/list.rs @@ -4,15 +4,12 @@ pub async fn list(client: &Client) -> anyhow::Result