Skip to content

Commit cc9fa67

Browse files
authored
Automate SparseField implementations with serde rename support (#872)
* Move SparseField trait implementations to appropriate modules Relocate SparseField implementations from request::endpoint modules to their corresponding type modules for better organization and module cohesion. Changes: - Move impl SparseField for SparsePostFieldWith*Context from posts_endpoint to posts module - Move impl SparseField for SparseMediaFieldWith*Context from media_endpoint to media module - Move impl SparseField for SparseTemplateFieldWith*Context from templates_endpoint to templates module - Move impl SparseField for SparseSearchResultFieldWith*Context from search_endpoint to search_results module - Move impl SparseField for SparseCommentFieldWith*Context from comments_endpoint to comments module - Add SparseField import to each type module - Clean up unused SparseField imports from endpoint modules - Preserve all field name mappings and custom serialization logic * Add comprehensive unit tests for SparseField trait mappings Add unit tests to validate all custom field name mappings in SparseField trait implementations across all WordPress REST API entity types. Changes: - Add tests for Posts: PostType → "type" mapping across all contexts - Add tests for Media: PostId → "post", PostType → "type" mappings - Add tests for Templates: TemplateType → "type", PostId → "wp_id" mappings - Add tests for Search Results: ObjectType → "type", ObjectSubtype → "subtype" mappings - Add tests for Comments: CommentType → "type" mapping across all contexts - Include regular field mappings (Id → "id") to verify default behavior - Test all applicable contexts (edit, embed, view) for each entity type - Use rstest parameterized tests for clean, declarative test cases - Follow naming pattern test_as_mapped_field_name_* for future method rename All 35 tests pass, confirming current manual SparseField implementations work correctly. These tests will serve as regression protection when transitioning to macro-generated implementations. * Automate SparseField implementations via WpContextual macro Phase 1: Remove redundant manual SparseField implementations by enhancing the WpContextual macro to automatically generate SparseField trait implementations that extract and use serde rename attributes. Changes: - Enhanced WpContextual macro to extract serde rename attributes and generate appropriate SparseField implementations - Removed manual SparseField implementations from comments, media, posts, search_results, and templates modules - Eliminated redundant default_sparse_field_implementation_from_field_name macro and all its usage - Cleaned up imports and moved SparseField imports to test modules where needed The macro now automatically handles field name mapping using serde rename attributes, eliminating the need for manual implementations and reducing code duplication while maintaining full test compatibility. * Rename SparseField method to be more explicit about its purpose Rename `as_str()` to `as_mapped_field_name()` to clearly indicate that this method returns the API field name that may differ from the Rust field name due to serde rename attributes. Changes: - Update SparseField trait method signature from `as_str()` to `as_mapped_field_name()` - Update all implementations and test assertions to use the new method name - Update WpContextual macro to generate the renamed method - Update request builder to use the new method for `_fields` query parameters - Improve library documentation to clarify serde rename support - Remove unused `as_field_name()` method generation from WpContextual macro This makes the API more self-documenting and clearly distinguishes between raw field names and their mapped API equivalents. * Fix wp_contextual tests by adding SparseField trait for tests
1 parent bf384da commit cc9fa67

34 files changed

+340
-422
lines changed

wp_api/src/comments.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,13 +482,12 @@ fn comment_type_to_string(comment_type: CommentType) -> String {
482482
mod tests {
483483
use super::*;
484484
use crate::{
485-
generate,
485+
SparseField, generate,
486486
unit_test_common::{
487487
assert_expected_and_from_query_pairs, unit_test_example_date_as_option,
488488
unit_test_example_date_as_query_value,
489489
},
490490
};
491-
492491
use rstest::*;
493492

494493
#[rstest]
@@ -566,4 +565,34 @@ mod tests {
566565
"page=11&per_page=22&search=s_q&{after}&author=111%2C112&author_exclude=211%2C212&author_email=a_email%40example.com&{before}&exclude=1111%2C1112&include=2111%2C2112&offset=11111&order=desc&orderby=type&parent=44444%2C44445&parent_exclude=55555%2C55556&post=66666%2C66667&status=spam&type=pingback&password=p_q"
567566
)
568567
}
568+
569+
#[rstest]
570+
#[case(SparseCommentFieldWithEditContext::Id, "id")]
571+
#[case(SparseCommentFieldWithEditContext::CommentType, "type")]
572+
fn test_as_mapped_field_name_for_edit_context(
573+
#[case] field: SparseCommentFieldWithEditContext,
574+
#[case] expected_mapped_field_name: &str,
575+
) {
576+
assert_eq!(field.as_mapped_field_name(), expected_mapped_field_name);
577+
}
578+
579+
#[rstest]
580+
#[case(SparseCommentFieldWithEmbedContext::Id, "id")]
581+
#[case(SparseCommentFieldWithEmbedContext::CommentType, "type")]
582+
fn test_as_mapped_field_name_for_embed_context(
583+
#[case] field: SparseCommentFieldWithEmbedContext,
584+
#[case] expected_mapped_field_name: &str,
585+
) {
586+
assert_eq!(field.as_mapped_field_name(), expected_mapped_field_name);
587+
}
588+
589+
#[rstest]
590+
#[case(SparseCommentFieldWithViewContext::Id, "id")]
591+
#[case(SparseCommentFieldWithViewContext::CommentType, "type")]
592+
fn test_as_mapped_field_name_for_view_context(
593+
#[case] field: SparseCommentFieldWithViewContext,
594+
#[case] expected_mapped_field_name: &str,
595+
) {
596+
assert_eq!(field.as_mapped_field_name(), expected_mapped_field_name);
597+
}
569598
}

wp_api/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ pub enum WpApiParamOrder {
9191
impl_as_query_value_from_to_string!(WpApiParamOrder);
9292

9393
trait SparseField {
94-
fn as_str(&self) -> &str;
94+
fn as_mapped_field_name(&self) -> &str;
9595
}
9696

9797
trait OptionFromStr {

wp_api/src/media.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ pub struct SparseMediaCaption {
528528
mod tests {
529529
use super::*;
530530
use crate::{
531-
generate,
531+
SparseField, generate,
532532
posts::PostId,
533533
unit_test_common::{
534534
assert_expected_and_from_query_pairs, unit_test_example_date_as_option,
@@ -675,4 +675,36 @@ mod tests {
675675
serde_json::Value::Object(serde_json::Map::new())
676676
);
677677
}
678+
679+
#[rstest]
680+
#[case(SparseMediaFieldWithEditContext::Id, "id")]
681+
#[case(SparseMediaFieldWithEditContext::PostId, "post")]
682+
#[case(SparseMediaFieldWithEditContext::PostType, "type")]
683+
fn test_as_mapped_field_name_for_edit_context(
684+
#[case] field: SparseMediaFieldWithEditContext,
685+
#[case] expected_mapped_field_name: &str,
686+
) {
687+
assert_eq!(field.as_mapped_field_name(), expected_mapped_field_name);
688+
}
689+
690+
#[rstest]
691+
#[case(SparseMediaFieldWithEmbedContext::Id, "id")]
692+
#[case(SparseMediaFieldWithEmbedContext::PostType, "type")]
693+
fn test_as_mapped_field_name_for_embed_context(
694+
#[case] field: SparseMediaFieldWithEmbedContext,
695+
#[case] expected_mapped_field_name: &str,
696+
) {
697+
assert_eq!(field.as_mapped_field_name(), expected_mapped_field_name);
698+
}
699+
700+
#[rstest]
701+
#[case(SparseMediaFieldWithViewContext::Id, "id")]
702+
#[case(SparseMediaFieldWithViewContext::PostId, "post")]
703+
#[case(SparseMediaFieldWithViewContext::PostType, "type")]
704+
fn test_as_mapped_field_name_for_view_context(
705+
#[case] field: SparseMediaFieldWithViewContext,
706+
#[case] expected_mapped_field_name: &str,
707+
) {
708+
assert_eq!(field.as_mapped_field_name(), expected_mapped_field_name);
709+
}
678710
}

wp_api/src/posts.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ impl PostFormat {
579579
mod tests {
580580
use super::*;
581581
use crate::{
582-
generate,
582+
SparseField, generate,
583583
unit_test_common::{
584584
assert_expected_and_from_query_pairs, unit_test_example_date_as_option,
585585
unit_test_example_date_as_query_value,
@@ -644,6 +644,36 @@ mod tests {
644644
assert_expected_and_from_query_pairs(params, expected_query);
645645
}
646646

647+
#[rstest]
648+
#[case(SparsePostFieldWithEditContext::Id, "id")]
649+
#[case(SparsePostFieldWithEditContext::PostType, "type")]
650+
fn test_as_mapped_field_name_for_edit_context(
651+
#[case] field: SparsePostFieldWithEditContext,
652+
#[case] expected_mapped_field_name: &str,
653+
) {
654+
assert_eq!(field.as_mapped_field_name(), expected_mapped_field_name);
655+
}
656+
657+
#[rstest]
658+
#[case(SparsePostFieldWithEmbedContext::Id, "id")]
659+
#[case(SparsePostFieldWithEmbedContext::PostType, "type")]
660+
fn test_as_mapped_field_name_for_embed_context(
661+
#[case] field: SparsePostFieldWithEmbedContext,
662+
#[case] expected_mapped_field_name: &str,
663+
) {
664+
assert_eq!(field.as_mapped_field_name(), expected_mapped_field_name);
665+
}
666+
667+
#[rstest]
668+
#[case(SparsePostFieldWithViewContext::Id, "id")]
669+
#[case(SparsePostFieldWithViewContext::PostType, "type")]
670+
fn test_as_mapped_field_name_for_view_context(
671+
#[case] field: SparsePostFieldWithViewContext,
672+
#[case] expected_mapped_field_name: &str,
673+
) {
674+
assert_eq!(field.as_mapped_field_name(), expected_mapped_field_name);
675+
}
676+
647677
fn expected_query_pairs_for_post_list_params_with_all_fields() -> String {
648678
let after = unit_test_example_date_as_query_value("after");
649679
let modified_after = unit_test_example_date_as_query_value("modified_after");

wp_api/src/request/endpoint.rs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -134,22 +134,6 @@ impl ApiUrlResolver for WpOrgSiteApiUrlResolver {
134134
}
135135
}
136136

137-
mod macros {
138-
macro_rules! default_sparse_field_implementation_from_field_name {
139-
($ident:ident) => {
140-
paste::paste! {
141-
impl SparseField for $ident {
142-
fn as_str(&self) -> &str {
143-
self.as_field_name()
144-
}
145-
}
146-
}
147-
};
148-
}
149-
150-
pub(crate) use default_sparse_field_implementation_from_field_name;
151-
}
152-
153137
#[cfg(test)]
154138
mod tests {
155139
use super::*;

wp_api/src/request/endpoint/application_passwords_endpoint.rs

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
use wp_derive_request_builder::WpDerivedRequest;
2-
3-
use crate::SparseField;
4-
use crate::application_passwords::{
5-
ApplicationPasswordCreateParams, ApplicationPasswordDeleteAllResponse,
6-
ApplicationPasswordDeleteResponse, ApplicationPasswordUpdateParams, ApplicationPasswordUuid,
7-
ApplicationPasswordWithEditContext, ApplicationPasswordWithEmbedContext,
8-
ApplicationPasswordWithViewContext, SparseApplicationPasswordFieldWithEditContext,
9-
SparseApplicationPasswordFieldWithEmbedContext, SparseApplicationPasswordFieldWithViewContext,
10-
SparseApplicationPasswordWithEditContext, SparseApplicationPasswordWithEmbedContext,
11-
SparseApplicationPasswordWithViewContext,
12-
};
13-
use crate::users::UserId;
14-
151
use super::{AsNamespace, DerivedRequest, WpNamespace};
2+
use crate::{
3+
application_passwords::{
4+
ApplicationPasswordCreateParams, ApplicationPasswordDeleteAllResponse,
5+
ApplicationPasswordDeleteResponse, ApplicationPasswordUpdateParams,
6+
ApplicationPasswordUuid, ApplicationPasswordWithEditContext,
7+
ApplicationPasswordWithEmbedContext, ApplicationPasswordWithViewContext,
8+
SparseApplicationPasswordFieldWithEditContext,
9+
SparseApplicationPasswordFieldWithEmbedContext,
10+
SparseApplicationPasswordFieldWithViewContext, SparseApplicationPasswordWithEditContext,
11+
SparseApplicationPasswordWithEmbedContext, SparseApplicationPasswordWithViewContext,
12+
},
13+
users::UserId,
14+
};
15+
use wp_derive_request_builder::WpDerivedRequest;
1616

1717
#[derive(WpDerivedRequest)]
1818
enum ApplicationPasswordsRequest {
@@ -38,16 +38,6 @@ impl DerivedRequest for ApplicationPasswordsRequest {
3838
}
3939
}
4040

41-
super::macros::default_sparse_field_implementation_from_field_name!(
42-
SparseApplicationPasswordFieldWithEditContext
43-
);
44-
super::macros::default_sparse_field_implementation_from_field_name!(
45-
SparseApplicationPasswordFieldWithEmbedContext
46-
);
47-
super::macros::default_sparse_field_implementation_from_field_name!(
48-
SparseApplicationPasswordFieldWithViewContext
49-
);
50-
5141
#[cfg(test)]
5242
mod tests {
5343
use super::*;

wp_api/src/request/endpoint/categories_endpoint.rs

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
use super::{AsNamespace, DerivedRequest, WpNamespace};
2-
use crate::{
3-
SparseField,
4-
categories::{
5-
CategoryId, CategoryListParams, SparseCategoryFieldWithEditContext,
6-
SparseCategoryFieldWithEmbedContext, SparseCategoryFieldWithViewContext,
7-
},
8-
};
2+
use crate::categories::{CategoryId, CategoryListParams};
93
use wp_derive_request_builder::WpDerivedRequest;
104

115
#[derive(WpDerivedRequest)]
@@ -37,22 +31,15 @@ impl DerivedRequest for CategoriesRequest {
3731
}
3832
}
3933

40-
super::macros::default_sparse_field_implementation_from_field_name!(
41-
SparseCategoryFieldWithEditContext
42-
);
43-
super::macros::default_sparse_field_implementation_from_field_name!(
44-
SparseCategoryFieldWithEmbedContext
45-
);
46-
super::macros::default_sparse_field_implementation_from_field_name!(
47-
SparseCategoryFieldWithViewContext
48-
);
49-
5034
#[cfg(test)]
5135
mod tests {
5236
use super::*;
5337
use crate::{
5438
WpApiParamOrder,
55-
categories::{CategoryId, WpApiParamCategoriesOrderBy},
39+
categories::{
40+
CategoryId, SparseCategoryFieldWithEditContext, SparseCategoryFieldWithEmbedContext,
41+
SparseCategoryFieldWithViewContext, WpApiParamCategoriesOrderBy,
42+
},
5643
generate,
5744
posts::PostId,
5845
request::endpoint::{

wp_api/src/request/endpoint/comments_endpoint.rs

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
use crate::SparseField;
2-
use crate::comments::{
3-
CommentId, CommentListParams, CommentUpdateParams, SparseCommentFieldWithEditContext,
4-
SparseCommentFieldWithEmbedContext, SparseCommentFieldWithViewContext,
5-
};
1+
use crate::comments::{CommentId, CommentListParams, CommentUpdateParams};
62
use wp_derive_request_builder::WpDerivedRequest;
73

84
use super::{AsNamespace, DerivedRequest, WpNamespace};
@@ -36,41 +32,15 @@ impl DerivedRequest for CommentsRequest {
3632
}
3733
}
3834

39-
impl SparseField for SparseCommentFieldWithEditContext {
40-
fn as_str(&self) -> &str {
41-
match self {
42-
Self::CommentType => "type",
43-
_ => self.as_field_name(),
44-
}
45-
}
46-
}
47-
48-
impl SparseField for SparseCommentFieldWithEmbedContext {
49-
fn as_str(&self) -> &str {
50-
match self {
51-
Self::CommentType => "type",
52-
_ => self.as_field_name(),
53-
}
54-
}
55-
}
56-
57-
impl SparseField for SparseCommentFieldWithViewContext {
58-
fn as_str(&self) -> &str {
59-
match self {
60-
Self::CommentType => "type",
61-
_ => self.as_field_name(),
62-
}
63-
}
64-
}
65-
6635
#[cfg(test)]
6736
mod tests {
6837
use super::*;
6938
use crate::{
7039
UserId, WpApiParamOrder,
7140
comments::{
7241
CommentDeleteParams, CommentId, CommentRetrieveParams, CommentStatus, CommentType,
73-
WpApiParamCommentsOrderBy,
42+
SparseCommentFieldWithEditContext, SparseCommentFieldWithEmbedContext,
43+
SparseCommentFieldWithViewContext, WpApiParamCommentsOrderBy,
7444
},
7545
generate,
7646
posts::PostId,

wp_api/src/request/endpoint/media_endpoint.rs

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
use super::{AsNamespace, DerivedRequest, WpEndpointUrl, WpNamespace};
22
use crate::{
3-
SparseField,
43
api_error::WpApiError,
5-
media::{
6-
MediaCreateParams, MediaId, MediaListParams, MediaUpdateParams, MediaWithEditContext,
7-
SparseMediaFieldWithEditContext, SparseMediaFieldWithEmbedContext,
8-
SparseMediaFieldWithViewContext,
9-
},
4+
media::{MediaCreateParams, MediaId, MediaListParams, MediaUpdateParams, MediaWithEditContext},
105
request::{
116
CONTENT_TYPE_MULTIPART, NetworkRequestAccessor, ParsedResponse, RequestMethod,
127
WpNetworkHeaderMap, WpNetworkResponse,
@@ -44,35 +39,6 @@ impl DerivedRequest for MediaRequest {
4439
}
4540
}
4641

47-
impl SparseField for SparseMediaFieldWithEditContext {
48-
fn as_str(&self) -> &str {
49-
match self {
50-
Self::PostId => "post",
51-
Self::PostType => "type",
52-
_ => self.as_field_name(),
53-
}
54-
}
55-
}
56-
57-
impl SparseField for SparseMediaFieldWithEmbedContext {
58-
fn as_str(&self) -> &str {
59-
match self {
60-
Self::PostType => "type",
61-
_ => self.as_field_name(),
62-
}
63-
}
64-
}
65-
66-
impl SparseField for SparseMediaFieldWithViewContext {
67-
fn as_str(&self) -> &str {
68-
match self {
69-
Self::PostId => "post",
70-
Self::PostType => "type",
71-
_ => self.as_field_name(),
72-
}
73-
}
74-
}
75-
7642
impl MediaRequestEndpoint {
7743
pub fn create(&self) -> crate::request::endpoint::ApiEndpointUrl {
7844
Arc::unwrap_or_clone(self.api_url_resolver.resolve(
@@ -239,7 +205,10 @@ mod tests {
239205
use super::*;
240206
use crate::{
241207
UserId, WpApiParamOrder, generate,
242-
media::{MediaId, MediaStatus, MediaTypeParam},
208+
media::{
209+
MediaId, MediaStatus, MediaTypeParam, SparseMediaFieldWithEditContext,
210+
SparseMediaFieldWithEmbedContext, SparseMediaFieldWithViewContext,
211+
},
243212
posts::{PostId, WpApiParamPostsOrderBy, WpApiParamPostsSearchColumn},
244213
request::endpoint::{
245214
ApiUrlResolver,

0 commit comments

Comments
 (0)