From f34ebee3bdeef3bec098787dad62481b65ad167d Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 16 Jul 2025 12:33:03 +1200 Subject: [PATCH 1/2] Add an 'extras' property to comments --- wp_api/src/comments.rs | 5 ++++- .../tests/test_comments_immut.rs | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/wp_api/src/comments.rs b/wp_api/src/comments.rs index 1380fb9e..b0642072 100644 --- a/wp_api/src/comments.rs +++ b/wp_api/src/comments.rs @@ -1,5 +1,5 @@ use crate::{ - UserAvatarSize, UserId, WpApiParamOrder, WpResponseString, + JsonValue, UserAvatarSize, UserId, WpApiParamOrder, WpResponseString, date::WpGmtDateTime, impl_as_query_value_for_new_type, impl_as_query_value_from_to_string, posts::PostId, @@ -528,6 +528,9 @@ pub struct SparseComment { pub comment_type: Option, #[WpContext(edit, embed, view)] pub author_avatar_urls: Option>, + #[serde(flatten)] + #[WpContext(edit, embed, view)] + pub extras: Option, // meta field is omitted for now: https://github.com/Automattic/wordpress-rs/issues/422 } diff --git a/wp_api_integration_tests/tests/test_comments_immut.rs b/wp_api_integration_tests/tests/test_comments_immut.rs index 0d27d41f..f288388b 100644 --- a/wp_api_integration_tests/tests/test_comments_immut.rs +++ b/wp_api_integration_tests/tests/test_comments_immut.rs @@ -1,4 +1,5 @@ use wp_api::{ + JsonValue, comments::{ CommentId, CommentListParams, CommentRetrieveParams, CommentStatus, CommentType, SparseCommentFieldWithEditContext, SparseCommentFieldWithEmbedContext, @@ -207,6 +208,23 @@ async fn list_comments_with_edit_context_parse_author_avatar_urls( }); } +#[tokio::test] +#[parallel] +async fn parse_extras() { + let comment = api_client() + .comments() + .retrieve_with_edit_context(&FIRST_COMMENT_ID, &CommentRetrieveParams::default()) + .await + .assert_response() + .data; + match comment.extras { + JsonValue::Object(ref map) => { + assert!(map.contains_key("_links")); + } + _ => panic!("Expected extras to be an object"), + } +} + #[template] #[rstest] #[case::default(CommentListParams::default())] From 56f6562c33f99435c49147321601e26410d4252a Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 5 Aug 2025 21:08:42 +1200 Subject: [PATCH 2/2] Parse additional data in WP.com comments API --- wp_api/src/comments.rs | 6 +- wp_api/src/lib.rs | 35 +++++++++++ wp_api/src/uniffi_serde.rs | 2 +- wp_api/src/wp_com/endpoint.rs | 1 + .../wp_com/endpoint/extensions/comments.rs | 38 ++++++++++++ wp_api/src/wp_com/endpoint/extensions/mod.rs | 1 + .../tests/test_comments_immut.rs | 59 +++++++++++++++++-- 7 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 wp_api/src/wp_com/endpoint/extensions/comments.rs create mode 100644 wp_api/src/wp_com/endpoint/extensions/mod.rs diff --git a/wp_api/src/comments.rs b/wp_api/src/comments.rs index b0642072..55f71a28 100644 --- a/wp_api/src/comments.rs +++ b/wp_api/src/comments.rs @@ -1,5 +1,5 @@ use crate::{ - JsonValue, UserAvatarSize, UserId, WpApiParamOrder, WpResponseString, + AnyJson, UserAvatarSize, UserId, WpApiParamOrder, WpResponseString, date::WpGmtDateTime, impl_as_query_value_for_new_type, impl_as_query_value_from_to_string, posts::PostId, @@ -8,7 +8,7 @@ use crate::{ }, }; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, num::ParseIntError, str::FromStr}; +use std::{collections::HashMap, num::ParseIntError, str::FromStr, sync::Arc}; use strum_macros::IntoStaticStr; use wp_contextual::WpContextual; @@ -530,7 +530,7 @@ pub struct SparseComment { pub author_avatar_urls: Option>, #[serde(flatten)] #[WpContext(edit, embed, view)] - pub extras: Option, + pub additional_fields: Option>, // meta field is omitted for now: https://github.com/Automattic/wordpress-rs/issues/422 } diff --git a/wp_api/src/lib.rs b/wp_api/src/lib.rs index c8a40984..2a9e6f8c 100644 --- a/wp_api/src/lib.rs +++ b/wp_api/src/lib.rs @@ -1,5 +1,6 @@ use plugins::*; use serde::{Deserialize, Serialize}; +use serde_json::Value; use std::collections::HashMap; use users::*; use wp_localization::{MessageBundle, WpMessages, WpSupportsLocalization}; @@ -118,6 +119,14 @@ pub enum JsonValue { Object(HashMap), } +/// Similar to `JsonValue`, but exported as a Uniffi object. +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, uniffi::Object)] +#[uniffi::export(Eq, Hash)] +pub struct AnyJson { + #[serde(flatten)] + pub raw: Value, +} + uniffi::custom_newtype!(WpResponseString, Option); #[derive(Debug, Serialize, Deserialize)] #[serde(try_from = "BoolOrString")] @@ -235,4 +244,30 @@ mod tests { fn test_orderby_string_conversion(#[case] orderby: WpApiParamOrder) { assert_eq!(orderby, orderby.to_string().parse().unwrap()); } + + #[derive(Deserialize, Debug)] + struct Person { + name: String, + #[serde(flatten)] + other_fields: AnyJson, + } + + #[test] + fn test_parse_any_json() { + let json = r#"{"name": "Alice", "age": 30, "city": "Wonderland"}"#; + let person: Person = serde_json::from_str(json).unwrap(); + assert_eq!(person.name, "Alice"); + assert_eq!( + person.other_fields.raw, + serde_json::json!({"age": 30, "city": "Wonderland"}) + ); + } + + #[test] + fn test_parse_empty_any_json() { + let json = r#"{"name": "Alice"}"#; + let person: Person = serde_json::from_str(json).unwrap(); + assert_eq!(person.name, "Alice"); + assert_eq!(person.other_fields.raw, serde_json::json!({})); + } } diff --git a/wp_api/src/uniffi_serde.rs b/wp_api/src/uniffi_serde.rs index 41b53ba6..48d2f6ad 100644 --- a/wp_api/src/uniffi_serde.rs +++ b/wp_api/src/uniffi_serde.rs @@ -2,7 +2,7 @@ use wp_localization::{MessageBundle, WpMessages, WpSupportsLocalization}; use wp_localization_macro::WpDeriveLocalizable; #[derive(Debug, thiserror::Error, uniffi::Error, WpDeriveLocalizable)] -pub(crate) enum UniffiSerializationError { +pub enum UniffiSerializationError { Serde { reason: String }, } diff --git a/wp_api/src/wp_com/endpoint.rs b/wp_api/src/wp_com/endpoint.rs index 9c91f4b8..ccd283af 100644 --- a/wp_api/src/wp_com/endpoint.rs +++ b/wp_api/src/wp_com/endpoint.rs @@ -6,6 +6,7 @@ use crate::{ use std::sync::Arc; use strum::IntoEnumIterator; +pub mod extensions; pub mod followers_endpoint; pub mod jetpack_connection_endpoint; pub mod oauth2; diff --git a/wp_api/src/wp_com/endpoint/extensions/comments.rs b/wp_api/src/wp_com/endpoint/extensions/comments.rs new file mode 100644 index 00000000..b0bdb863 --- /dev/null +++ b/wp_api/src/wp_com/endpoint/extensions/comments.rs @@ -0,0 +1,38 @@ +use serde::Deserialize; + +use crate::{AnyJson, uniffi_serde::UniffiSerializationError}; + +#[derive(Debug, Deserialize, uniffi::Record)] +pub struct WpComCommentExtension { + #[serde(rename = "extended_post")] + pub post: Option, + #[serde(rename = "extended_i_replied")] + pub i_replied: bool, + #[serde(rename = "extended_like_count")] + pub like_count: u32, + #[serde(rename = "extended_i_like")] + pub i_like: bool, +} + +#[derive(Debug, Deserialize, uniffi::Record)] +pub struct WpComCommentExtensionPostInfo { + pub id: u64, + pub title: String, + #[serde(rename = "type")] + pub kind: String, + pub link: String, +} + +#[uniffi::export(with_foreign)] +pub trait WpComCommentExtensionProvider: Send + Sync { + fn parse_extension(&self) -> Result; +} + +#[uniffi::export] +impl WpComCommentExtensionProvider for AnyJson { + fn parse_extension(&self) -> Result { + serde_json::to_string(&self.raw) + .and_then(|json| serde_json::from_str(&json)) + .map_err(Into::into) + } +} diff --git a/wp_api/src/wp_com/endpoint/extensions/mod.rs b/wp_api/src/wp_com/endpoint/extensions/mod.rs new file mode 100644 index 00000000..a5c2bb6d --- /dev/null +++ b/wp_api/src/wp_com/endpoint/extensions/mod.rs @@ -0,0 +1 @@ +pub mod comments; diff --git a/wp_api_integration_tests/tests/test_comments_immut.rs b/wp_api_integration_tests/tests/test_comments_immut.rs index f288388b..fb97764b 100644 --- a/wp_api_integration_tests/tests/test_comments_immut.rs +++ b/wp_api_integration_tests/tests/test_comments_immut.rs @@ -1,5 +1,5 @@ +use serde_json::Value; use wp_api::{ - JsonValue, comments::{ CommentId, CommentListParams, CommentRetrieveParams, CommentStatus, CommentType, SparseCommentFieldWithEditContext, SparseCommentFieldWithEmbedContext, @@ -7,8 +7,9 @@ use wp_api::{ }, posts::PostId, users::UserAvatarSize, + wp_com::{WpComBaseUrl, endpoint::WpComDotOrgApiUrlResolver}, }; -use wp_api_integration_tests::prelude::*; +use wp_api_integration_tests::{WpComTestCredentials, prelude::*}; #[tokio::test] #[apply(list_cases)] @@ -217,14 +218,64 @@ async fn parse_extras() { .await .assert_response() .data; - match comment.extras { - JsonValue::Object(ref map) => { + match comment.additional_fields.raw { + Value::Object(ref map) => { assert!(map.contains_key("_links")); } _ => panic!("Expected extras to be an object"), } } +#[tokio::test] +#[parallel] +#[ignore] +async fn wpcom_comment_extension() { + // You'll need to replace the site ID and comment ID with valid ones. + let site_id = "site_id".to_string(); + let comment_id = CommentId(2); + let client = WpApiClient::new( + Arc::new(WpComDotOrgApiUrlResolver::new( + site_id, + WpComBaseUrl::Production, + )), + WpApiClientDelegate { + auth_provider: Arc::new(WpAuthenticationProvider::static_with_auth( + WpAuthentication::Bearer { + token: WpComTestCredentials::instance().bearer_token.to_string(), + }, + )), + request_executor: Arc::new(ReqwestRequestExecutor::default()), + middleware_pipeline: Arc::new(WpApiMiddlewarePipeline::default()), + app_notifier: Arc::new(EmptyAppNotifier), + }, + ); + use wp_api::wp_com::endpoint::extensions::comments::WpComCommentExtensionProvider; + + let comment = client + .comments() + .retrieve_with_view_context(&comment_id, &CommentRetrieveParams::default()) + .await + .assert_response() + .data; + assert!(comment.additional_fields.parse_extension().is_ok()); + + let comment = client + .comments() + .retrieve_with_edit_context(&comment_id, &CommentRetrieveParams::default()) + .await + .assert_response() + .data; + assert!(comment.additional_fields.parse_extension().is_ok()); + + let comment = client + .comments() + .retrieve_with_embed_context(&comment_id, &CommentRetrieveParams::default()) + .await + .assert_response() + .data; + assert!(comment.additional_fields.parse_extension().is_ok()); +} + #[template] #[rstest] #[case::default(CommentListParams::default())]