Skip to content

Commit 9e8a7b6

Browse files
committed
Introduce PostListFilter for metadata collection API
Replace PostListParams with PostListFilter in the metadata collection API. PostListFilter exposes only filter-relevant fields, excluding pagination, instance-specific (include/exclude), and date range fields that are incompatible with the metadata sync model. Changes: - Add PostListFilter type with documented field exclusions - Rename post_list_params_cache_key to post_list_filter_cache_key - Update create_post_metadata_collection_with_edit_context to use PostListFilter - Update Kotlin extensions and example app ViewModel
1 parent a2eddd1 commit 9e8a7b6

File tree

8 files changed

+296
-186
lines changed

8 files changed

+296
-186
lines changed

native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/cache/kotlin/PostServiceExtensions.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package rs.wordpress.cache.kotlin
22

33
import uniffi.wp_api.PostEndpointType
4-
import uniffi.wp_api.PostListParams
54
import uniffi.wp_mobile.AnyPostFilter
65
import uniffi.wp_mobile.FullEntityAnyPostWithEditContext
6+
import uniffi.wp_mobile.PostListFilter
77
import uniffi.wp_mobile.PostService
88
import uniffi.wp_mobile_cache.EntityId
99

@@ -51,13 +51,13 @@ fun PostService.getObservablePostCollectionWithEditContext(
5151
* can show appropriate feedback for each item.
5252
*
5353
* @param endpointType The post endpoint type (Posts, Pages, or Custom)
54-
* @param params Post list API parameters (status, author, categories, etc.)
54+
* @param filter Filter parameters (status, author, categories, etc.)
5555
* @return Observable metadata collection that notifies on database changes
5656
*/
5757
fun PostService.getObservablePostMetadataCollectionWithEditContext(
5858
endpointType: PostEndpointType,
59-
params: PostListParams
59+
filter: PostListFilter
6060
): ObservableMetadataCollection {
61-
val collection = this.createPostMetadataCollectionWithEditContext(endpointType, params)
61+
val collection = this.createPostMetadataCollectionWithEditContext(endpointType, filter)
6262
return createObservableMetadataCollection(collection)
6363
}

native/kotlin/example/composeApp/src/commonMain/kotlin/rs/wordpress/example/shared/ui/postmetadatacollection/PostMetadataCollectionViewModel.kt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import rs.wordpress.cache.kotlin.getObservablePostMetadataCollectionWithEditCont
1313
import rs.wordpress.cache.kotlin.hasMorePages
1414
import rs.wordpress.cache.kotlin.isSyncing
1515
import uniffi.wp_api.PostEndpointType
16-
import uniffi.wp_api.PostListParams
1716
import uniffi.wp_mobile.ListInfo
1817
import uniffi.wp_mobile.PostItemState
18+
import uniffi.wp_mobile.PostListFilter
1919
import uniffi.wp_mobile.PostMetadataCollectionItem
2020
import uniffi.wp_mobile.SyncResult
2121
import uniffi.wp_mobile.WpSelfHostedService
@@ -25,7 +25,7 @@ import uniffi.wp_mobile_cache.ListState
2525
* UI state for the post metadata collection screen
2626
*/
2727
data class PostMetadataCollectionState(
28-
val currentParams: PostListParams,
28+
val currentFilter: PostListFilter,
2929
val listInfo: ListInfo? = null,
3030
val lastSyncResult: SyncResult? = null,
3131
val lastError: String? = null
@@ -51,7 +51,7 @@ data class PostMetadataCollectionState(
5151

5252
val filterDisplayName: String
5353
get() {
54-
val statuses = currentParams.status
54+
val statuses = currentFilter.status
5555
return when {
5656
statuses.isEmpty() -> "All Posts"
5757
statuses.any { it.toString().contains("draft", ignoreCase = true) } -> "Drafts"
@@ -61,7 +61,7 @@ data class PostMetadataCollectionState(
6161
}
6262

6363
val filterStatusString: String?
64-
get() = currentParams.status.firstOrNull()?.toString()?.lowercase()
64+
get() = currentFilter.status.firstOrNull()?.toString()?.lowercase()
6565
}
6666

6767
/**
@@ -114,7 +114,7 @@ class PostMetadataCollectionViewModel(
114114
) {
115115
private val viewModelScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
116116

117-
private val _state = MutableStateFlow(PostMetadataCollectionState(currentParams = PostListParams()))
117+
private val _state = MutableStateFlow(PostMetadataCollectionState(currentFilter = PostListFilter()))
118118
val state: StateFlow<PostMetadataCollectionState> = _state.asStateFlow()
119119

120120
private val _items = MutableStateFlow<List<PostItemDisplayData>>(emptyList())
@@ -123,7 +123,7 @@ class PostMetadataCollectionViewModel(
123123
private var observableCollection: ObservableMetadataCollection? = null
124124

125125
init {
126-
createObservableCollection(_state.value.currentParams)
126+
createObservableCollection(_state.value.currentFilter)
127127
loadItemsFromCollection()
128128
}
129129

@@ -132,16 +132,16 @@ class PostMetadataCollectionViewModel(
132132
*/
133133
fun setFilter(status: String?) {
134134
val postStatus = status?.let { uniffi.wp_api.parsePostStatus(it) }
135-
val newParams = PostListParams(
135+
val newFilter = PostListFilter(
136136
status = if (postStatus != null) listOf(postStatus) else emptyList()
137137
)
138138

139139
observableCollection?.close()
140-
createObservableCollection(newParams)
140+
createObservableCollection(newFilter)
141141

142142
// Read persisted state from database (single query)
143143
_state.value = PostMetadataCollectionState(
144-
currentParams = newParams,
144+
currentFilter = newFilter,
145145
listInfo = observableCollection?.listInfo(),
146146
lastSyncResult = null,
147147
lastError = null
@@ -216,11 +216,11 @@ class PostMetadataCollectionViewModel(
216216
}
217217
}
218218

219-
private fun createObservableCollection(params: PostListParams) {
219+
private fun createObservableCollection(filter: PostListFilter) {
220220
val postService = selfHostedService.posts()
221221
val observable = postService.getObservablePostMetadataCollectionWithEditContext(
222222
PostEndpointType.Posts,
223-
params
223+
filter
224224
)
225225

226226
// Data observer: refresh list contents when data changes

wp_mobile/src/cache_key.rs

Lines changed: 42 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
//! Cache key generation for metadata collections.
22
//!
33
//! This module provides functions to generate deterministic cache keys from
4-
//! API parameters. The cache key is used to identify unique list configurations
4+
//! filter parameters. The cache key is used to identify unique list configurations
55
//! in the metadata store.
66
77
use url::Url;
88
use wp_api::{
9-
posts::{PostListParams, PostListParamsField},
9+
posts::PostListParamsField,
1010
request::endpoint::posts_endpoint::PostEndpointType,
1111
url_query::AsQueryValue,
1212
};
1313

14+
use crate::filters::PostListFilter;
15+
1416
/// Generates a cache key segment from a `PostEndpointType`.
1517
///
1618
/// Uses a `post_type_` prefix to avoid conflicts with custom post type names
@@ -66,137 +68,76 @@ impl QueryPairsExt for url::form_urlencoded::Serializer<'_, url::UrlQuery<'_>> {
6668
}
6769
}
6870

69-
/// Generates a deterministic cache key from `PostListParams`.
71+
/// Generates a deterministic cache key from `PostListFilter`.
7072
///
71-
/// This function explicitly includes only filter-relevant fields, excluding
72-
/// pagination and instance-specific fields. Each excluded field has a comment
73-
/// explaining why it's not part of the cache key.
73+
/// All fields in `PostListFilter` are included in the cache key since it only
74+
/// contains filter-relevant fields (pagination, instance-specific, and date
75+
/// range fields are excluded by design in `PostListFilter`).
7476
///
7577
/// # Arguments
76-
/// * `params` - The post list parameters to generate a cache key from
78+
/// * `filter` - The post list filter to generate a cache key from
7779
///
7880
/// # Returns
79-
/// A URL query string containing only the filter-relevant parameters,
81+
/// A URL query string containing the filter parameters in alphabetical order,
8082
/// suitable for use as a cache key suffix.
8183
///
8284
/// # Example
8385
/// ```ignore
84-
/// let params = PostListParams {
86+
/// let filter = PostListFilter {
8587
/// status: vec![PostStatus::Publish],
8688
/// author: vec![UserId(5)],
8789
/// ..Default::default()
8890
/// };
89-
/// let key = post_list_params_cache_key(&params);
91+
/// let key = post_list_filter_cache_key(&filter);
9092
/// // key = "author=5&status=publish"
9193
/// ```
92-
pub fn post_list_params_cache_key(params: &PostListParams) -> String {
94+
pub fn post_list_filter_cache_key(filter: &PostListFilter) -> String {
9395
let mut url = Url::parse("https://cache-key-generator.local").expect("valid base URL");
9496

9597
{
9698
let mut q = url.query_pairs_mut();
9799

98-
// ============================================================
99-
// EXCLUDED FIELDS (not part of cache key)
100-
// ============================================================
101-
102-
// `page` - Excluded: pagination is managed by the collection, not the filter
103-
// `per_page` - Excluded: pagination is managed by the collection, not the filter
104-
// `offset` - Excluded: pagination is managed by the collection, not the filter
105-
// `include` - Excluded: instance-specific, used for fetching specific posts by ID
106-
// `exclude` - Excluded: instance-specific, used for excluding specific posts by ID
107-
108-
// ============================================================
109-
// INCLUDED FIELDS (alphabetically ordered for determinism)
110-
// ============================================================
111-
112-
// after - Filter: limit to posts published after this date
113-
q.append_option(PostListParamsField::After.into(), params.after.as_ref());
114-
115-
// author - Filter: limit to posts by specific authors
116-
q.append_vec(PostListParamsField::Author.into(), &params.author);
100+
// All fields in PostListFilter are included (alphabetically ordered for determinism).
101+
// Fields excluded from PostListFilter (pagination, instance-specific, date ranges)
102+
// are documented in the PostListFilter type definition.
117103

118-
// author_exclude - Filter: exclude posts by specific authors
104+
q.append_vec(PostListParamsField::Author.into(), &filter.author);
119105
q.append_vec(
120106
PostListParamsField::AuthorExclude.into(),
121-
&params.author_exclude,
107+
&filter.author_exclude,
122108
);
123-
124-
// before - Filter: limit to posts published before this date
125-
q.append_option(PostListParamsField::Before.into(), params.before.as_ref());
126-
127-
// categories - Filter: limit to posts in specific categories
128-
q.append_vec(PostListParamsField::Categories.into(), &params.categories);
129-
130-
// categories_exclude - Filter: exclude posts in specific categories
109+
q.append_vec(PostListParamsField::Categories.into(), &filter.categories);
131110
q.append_vec(
132111
PostListParamsField::CategoriesExclude.into(),
133-
&params.categories_exclude,
112+
&filter.categories_exclude,
134113
);
135-
136-
// menu_order - Filter: limit by menu order (for hierarchical post types)
137114
q.append_option(
138115
PostListParamsField::MenuOrder.into(),
139-
params.menu_order.as_ref(),
140-
);
141-
142-
// modified_after - Filter: limit to posts modified after this date
143-
q.append_option(
144-
PostListParamsField::ModifiedAfter.into(),
145-
params.modified_after.as_ref(),
146-
);
147-
148-
// modified_before - Filter: limit to posts modified before this date
149-
q.append_option(
150-
PostListParamsField::ModifiedBefore.into(),
151-
params.modified_before.as_ref(),
116+
filter.menu_order.as_ref(),
152117
);
153-
154-
// order - Ordering: affects which posts appear on each page
155-
q.append_option(PostListParamsField::Order.into(), params.order.as_ref());
156-
157-
// orderby - Ordering: affects which posts appear on each page
158-
q.append_option(PostListParamsField::Orderby.into(), params.orderby.as_ref());
159-
160-
// parent - Filter: limit to posts with specific parent (hierarchical)
161-
q.append_option(PostListParamsField::Parent.into(), params.parent.as_ref());
162-
163-
// parent_exclude - Filter: exclude posts with specific parents
118+
q.append_option(PostListParamsField::Order.into(), filter.order.as_ref());
119+
q.append_option(PostListParamsField::Orderby.into(), filter.orderby.as_ref());
120+
q.append_option(PostListParamsField::Parent.into(), filter.parent.as_ref());
164121
q.append_vec(
165122
PostListParamsField::ParentExclude.into(),
166-
&params.parent_exclude,
123+
&filter.parent_exclude,
167124
);
168-
169-
// search - Filter: limit to posts matching search string
170-
q.append_option(PostListParamsField::Search.into(), params.search.as_ref());
171-
172-
// search_columns - Filter: which columns to search in
125+
q.append_option(PostListParamsField::Search.into(), filter.search.as_ref());
173126
q.append_vec(
174127
PostListParamsField::SearchColumns.into(),
175-
&params.search_columns,
128+
&filter.search_columns,
176129
);
177-
178-
// slug - Filter: limit to posts with specific slugs
179-
q.append_vec(PostListParamsField::Slug.into(), &params.slug);
180-
181-
// status - Filter: limit to posts with specific statuses
182-
q.append_vec(PostListParamsField::Status.into(), &params.status);
183-
184-
// sticky - Filter: limit to sticky or non-sticky posts
185-
q.append_option(PostListParamsField::Sticky.into(), params.sticky.as_ref());
186-
187-
// tags - Filter: limit to posts with specific tags
188-
q.append_vec(PostListParamsField::Tags.into(), &params.tags);
189-
190-
// tags_exclude - Filter: exclude posts with specific tags
130+
q.append_vec(PostListParamsField::Slug.into(), &filter.slug);
131+
q.append_vec(PostListParamsField::Status.into(), &filter.status);
132+
q.append_option(PostListParamsField::Sticky.into(), filter.sticky.as_ref());
133+
q.append_vec(PostListParamsField::Tags.into(), &filter.tags);
191134
q.append_vec(
192135
PostListParamsField::TagsExclude.into(),
193-
&params.tags_exclude,
136+
&filter.tags_exclude,
194137
);
195-
196-
// tax_relation - Filter: relationship between taxonomy filters (AND/OR)
197138
q.append_option(
198139
PostListParamsField::TaxRelation.into(),
199-
params.tax_relation.as_ref(),
140+
filter.tax_relation.as_ref(),
200141
);
201142
}
202143

@@ -209,72 +150,43 @@ mod tests {
209150
use wp_api::posts::PostStatus;
210151

211152
#[test]
212-
fn test_empty_params_produces_empty_key() {
213-
let params = PostListParams::default();
214-
let key = post_list_params_cache_key(&params);
153+
fn test_empty_filter_produces_empty_key() {
154+
let filter = PostListFilter::default();
155+
let key = post_list_filter_cache_key(&filter);
215156
assert_eq!(key, "");
216157
}
217158

218159
#[test]
219160
fn test_status_filter() {
220-
let params = PostListParams {
161+
let filter = PostListFilter {
221162
status: vec![PostStatus::Publish],
222163
..Default::default()
223164
};
224-
let key = post_list_params_cache_key(&params);
165+
let key = post_list_filter_cache_key(&filter);
225166
assert_eq!(key, "status=publish");
226167
}
227168

228169
#[test]
229170
fn test_multiple_statuses() {
230-
let params = PostListParams {
171+
let filter = PostListFilter {
231172
status: vec![PostStatus::Publish, PostStatus::Draft],
232173
..Default::default()
233174
};
234-
let key = post_list_params_cache_key(&params);
175+
let key = post_list_filter_cache_key(&filter);
235176
assert_eq!(key, "status=publish%2Cdraft");
236177
}
237178

238-
#[test]
239-
fn test_pagination_fields_excluded() {
240-
let params = PostListParams {
241-
page: Some(5),
242-
per_page: Some(20),
243-
offset: Some(100),
244-
status: vec![PostStatus::Publish],
245-
..Default::default()
246-
};
247-
let key = post_list_params_cache_key(&params);
248-
// Should only contain status, not page/per_page/offset
249-
assert_eq!(key, "status=publish");
250-
}
251-
252-
#[test]
253-
fn test_include_exclude_fields_excluded() {
254-
use wp_api::posts::PostId;
255-
256-
let params = PostListParams {
257-
include: vec![PostId(1), PostId(2)],
258-
exclude: vec![PostId(3), PostId(4)],
259-
status: vec![PostStatus::Draft],
260-
..Default::default()
261-
};
262-
let key = post_list_params_cache_key(&params);
263-
// Should only contain status, not include/exclude
264-
assert_eq!(key, "status=draft");
265-
}
266-
267179
#[test]
268180
fn test_multiple_filters_alphabetically_ordered() {
269181
use wp_api::users::UserId;
270182

271-
let params = PostListParams {
183+
let filter = PostListFilter {
272184
status: vec![PostStatus::Publish],
273185
author: vec![UserId(5)],
274186
search: Some("hello".to_string()),
275187
..Default::default()
276188
};
277-
let key = post_list_params_cache_key(&params);
189+
let key = post_list_filter_cache_key(&filter);
278190
// Fields should be in alphabetical order: author, search, status
279191
assert_eq!(key, "author=5&search=hello&status=publish");
280192
}

0 commit comments

Comments
 (0)