Skip to content

Commit 8aa9bb2

Browse files
committed
Add lazy schema factories with runtime enum validation
Convert several input schemas from static arrays to callable factories so that post types, taxonomies, post statuses, user roles, and MIME types are resolved at schema-exposure time rather than at construction time. - Add `resolve_input_schema()` to invoke callables or pass arrays through - Add `enum_string_prop()` and runtime registry helpers for post types, taxonomies, post statuses, roles, and MIME types - Add `validate_post_status()`, `validate_mime_type()`, and `validate_user_role()` with 400 errors on unknown values - Apply status/role/mime-type validation in `search_content`, `search_content_items`, `save_content`, `upload_media`, and `save_user` - Wrap schemas in `ContentManagementTools`, `MediaTools`, `SiteManagementTools`, `TaxonomyTools` as factory callables - Extend `tests/run.php` stubs for `get_post_stati`, `get_taxonomies`, `get_editable_roles`, `get_allowed_mime_types`, and add assertions that late-registered types and statuses appear in lazily-resolved schemas
1 parent 7c61f73 commit 8aa9bb2

9 files changed

Lines changed: 516 additions & 70 deletions

src/Abilities.php

Lines changed: 262 additions & 14 deletions
Large diffs are not rendered by default.

src/Tools/CommentManagementTools.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ private function add_comment_abilities() {
2828
$this->add_ability( self::INTERNAL_PREFIX . 'list-comments', 'List Comments', 'List WordPress comments with filtering and pagination', $this->schema(
2929
array(
3030
'post_id' => $this->int_prop( 'Post ID.' ),
31-
'status' => $this->string_prop( 'Comment status: approve, hold, spam, trash, or all.', 'all' ),
31+
'status' => $this->string_prop( 'Comment query status. Use a status accepted by WordPress comment queries; all returns every discoverable status.', 'all' ),
3232
'search' => $this->string_prop( 'Search term.' ),
3333
'page' => $this->int_prop( 'Page number.', 1 ),
3434
'per_page' => $this->int_prop( 'Comments per page.', 20 ),
@@ -48,7 +48,7 @@ private function add_comment_abilities() {
4848
'content' => $this->string_prop( 'Comment content.' ),
4949
'author_name' => $this->string_prop( 'Author name.' ),
5050
'author_email' => $this->string_prop( 'Author email.' ),
51-
'status' => $this->string_prop( 'Comment status: approve, approved, hold, unapproved, spam, or trash.', 'hold' ),
51+
'status' => $this->string_prop( 'Comment moderation status accepted by WordPress comment APIs.', 'hold' ),
5252
)
5353
), function ( $params ) {
5454
return $this->save_comment_tool( $params );

src/Tools/ContentManagementTools.php

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ private function add_content_abilities() {
3434
'_builtin' => $this->bool_prop( 'Filter by built-in status.' ),
3535
)
3636
);
37-
$content_id_schema = $this->schema(
37+
$content_id_schema = function () {
38+
return $this->schema(
3839
array(
39-
'post_type' => $this->string_prop( 'Post type slug.' ),
40+
'post_type' => $this->enum_string_prop( 'Registered post type slug.', $this->get_registered_post_type_slugs() ),
4041
'id' => $this->int_prop( 'Content item ID.' ),
4142
'slug' => $this->string_prop( 'Content item slug. Used only when id is omitted.' ),
4243
'fields' => array(
@@ -46,20 +47,8 @@ private function add_content_abilities() {
4647
),
4748
),
4849
array( 'post_type' )
49-
);
50-
$taxonomy_query_schema = array(
51-
'type' => 'object',
52-
'description' => 'Filter by a taxonomy registered to the post type.',
53-
'properties' => array(
54-
'taxonomy' => $this->string_prop( 'Taxonomy slug.' ),
55-
'terms' => array(
56-
'type' => 'array',
57-
'description' => 'Term IDs, slugs, or names.',
58-
'items' => array( 'type' => array( 'integer', 'string' ) ),
59-
),
60-
),
61-
'additionalProperties' => false,
62-
);
50+
);
51+
};
6352
$date_query_schema = array(
6453
'type' => 'object',
6554
'description' => 'Filter by content dates.',
@@ -82,11 +71,26 @@ private function add_content_abilities() {
8271
'description' => 'Post meta values keyed by meta key.',
8372
'additionalProperties' => array( 'type' => array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' ) ),
8473
);
85-
$search_content_schema = $this->schema(
74+
$search_content_schema = function () use ( $date_query_schema ) {
75+
$taxonomy_query_schema = array(
76+
'type' => 'object',
77+
'description' => 'Filter by a taxonomy registered to the post type.',
78+
'properties' => array(
79+
'taxonomy' => $this->enum_string_prop( 'Registered taxonomy slug.', $this->get_registered_taxonomy_slugs() ),
80+
'terms' => array(
81+
'type' => 'array',
82+
'description' => 'Term IDs, slugs, or names.',
83+
'items' => array( 'type' => array( 'integer', 'string' ) ),
84+
),
85+
),
86+
'additionalProperties' => false,
87+
);
88+
89+
return $this->schema(
8690
array(
87-
'post_type' => $this->string_prop( 'Post type slug.' ),
91+
'post_type' => $this->enum_string_prop( 'Registered post type slug.', $this->get_registered_post_type_slugs() ),
8892
'query' => $this->string_prop( 'Free-text search term.' ),
89-
'status' => $this->string_prop( 'Post status.', 'publish' ),
93+
'status' => $this->enum_string_prop( 'Registered post status or the WordPress query any pseudo-status.', $this->get_registered_post_status_names( true ), 'publish' ),
9094
'author' => $this->int_prop( 'Author user ID.' ),
9195
'taxonomy_query' => $taxonomy_query_schema,
9296
'date_query' => $date_query_schema,
@@ -96,31 +100,36 @@ private function add_content_abilities() {
96100
'per_page' => $this->int_prop( 'Items per page.', 10 ),
97101
),
98102
array( 'post_type' )
99-
);
100-
$save_content_schema = $this->schema(
103+
);
104+
};
105+
$save_content_schema = function () use ( $taxonomies_schema, $meta_schema ) {
106+
return $this->schema(
101107
array(
102-
'post_type' => $this->string_prop( 'Post type slug.' ),
108+
'post_type' => $this->enum_string_prop( 'Registered post type slug.', $this->get_registered_post_type_slugs() ),
103109
'id' => $this->int_prop( 'Content item ID. Omit to create a new item.' ),
104110
'title' => $this->string_prop( 'Title. Required when creating content.' ),
105111
'content' => $this->string_prop( 'Content.' ),
106112
'excerpt' => $this->string_prop( 'Excerpt.' ),
107-
'status' => $this->string_prop( 'Status.', 'draft' ),
113+
'status' => $this->enum_string_prop( 'Registered post status.', $this->get_registered_post_status_names(), 'draft' ),
108114
'author' => $this->int_prop( 'Author user ID.' ),
109115
'parent_id' => $this->int_prop( 'Parent content ID. Only valid for hierarchical post types.' ),
110116
'taxonomies' => $taxonomies_schema,
111117
'meta' => $meta_schema,
112118
'featured_media_id' => $this->int_prop( 'Featured media attachment ID.' ),
113119
),
114120
array( 'post_type' )
115-
);
116-
$delete_content_schema = $this->schema(
121+
);
122+
};
123+
$delete_content_schema = function () {
124+
return $this->schema(
117125
array(
118-
'post_type' => $this->string_prop( 'Post type slug.' ),
126+
'post_type' => $this->enum_string_prop( 'Registered post type slug.', $this->get_registered_post_type_slugs() ),
119127
'id' => $this->int_prop( 'Content item ID.' ),
120128
'force' => $this->bool_prop( 'Bypass trash and permanently delete.', false ),
121129
),
122130
array( 'post_type', 'id' )
123-
);
131+
);
132+
};
124133

125134
$this->add_ability( self::INTERNAL_PREFIX . 'list-post-types', 'List Post Types', 'List registered WordPress post types with runtime validation metadata', $list_post_types_schema, function ( $params ) {
126135
return $this->list_post_types( $params );

src/Tools/MediaTools.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@ trait MediaTools {
2121
* @return void
2222
*/
2323
private function add_media_abilities() {
24-
$list_schema = $this->schema(
24+
$list_schema = function () {
25+
return $this->schema(
2526
array(
2627
'search' => $this->string_prop( 'Search term.' ),
27-
'mime_type' => $this->string_prop( 'MIME type filter.' ),
28+
'mime_type' => $this->enum_string_prop( 'Allowed upload MIME type filter.', $this->get_allowed_mime_types() ),
2829
'page' => $this->int_prop( 'Page number.', 1 ),
2930
'per_page' => $this->int_prop( 'Items per page.', 10 ),
3031
)
31-
);
32+
);
33+
};
3234
$id_schema = $this->schema( array( 'id' => $this->int_prop( 'Media item ID.' ) ), array( 'id' ) );
3335
$update_schema = $this->schema(
3436
array(
@@ -40,15 +42,17 @@ private function add_media_abilities() {
4042
),
4143
array( 'id' )
4244
);
43-
$upload_schema = $this->schema(
45+
$upload_schema = function () {
46+
return $this->schema(
4447
array(
4548
'filename' => $this->string_prop( 'File name.' ),
46-
'mime_type' => $this->string_prop( 'MIME type.' ),
49+
'mime_type' => $this->enum_string_prop( 'Allowed upload MIME type.', $this->get_allowed_mime_types() ),
4750
'base64' => $this->string_prop( 'Base64-encoded file contents.' ),
4851
'title' => $this->string_prop( 'Title.' ),
4952
),
5053
array( 'filename', 'base64' )
51-
);
54+
);
55+
};
5256

5357
$this->add_ability( self::INTERNAL_PREFIX . 'list-media', 'List Media', 'List WordPress media items with pagination and filtering', $list_schema, function ( $params ) {
5458
return $this->search_content_items( 'attachment', array_merge( array( 'status' => 'inherit' ), $params ) );

src/Tools/PluginManagementTools.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ trait PluginManagementTools {
2525
private function add_plugin_abilities() {
2626
$plugin_file_schema = $this->schema(
2727
array(
28-
'plugin_file' => $this->string_prop( 'Plugin file path, such as akismet/akismet.php.' ),
28+
'plugin_file' => $this->string_prop( 'Installed plugin file path.' ),
2929
),
3030
array( 'plugin_file' )
3131
);

src/Tools/SiteManagementTools.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,29 +21,35 @@ trait SiteManagementTools {
2121
* @return void
2222
*/
2323
private function add_site_abilities() {
24-
$this->add_ability( self::INTERNAL_PREFIX . 'users-search', 'Search Users', 'Search and filter WordPress users with pagination', $this->schema(
24+
$users_search_schema = function () {
25+
return $this->schema(
2526
array(
2627
'search' => $this->string_prop( 'Search term.' ),
27-
'role' => $this->string_prop( 'User role.' ),
28+
'role' => $this->enum_string_prop( 'Editable user role slug.', $this->get_editable_role_slugs() ),
2829
'page' => $this->int_prop( 'Page number.', 1 ),
2930
'per_page' => $this->int_prop( 'Users per page.', 10 ),
3031
)
31-
), function ( $params ) {
32+
);
33+
};
34+
35+
$this->add_ability( self::INTERNAL_PREFIX . 'users-search', 'Search Users', 'Search and filter WordPress users with pagination', $users_search_schema, function ( $params ) {
3236
return $this->search_users( $params );
3337
}, true, 'list_users' );
3438

3539
$user_get_schema = $this->schema( array( 'id' => $this->int_prop( 'User ID.' ) ), array( 'id' ) );
36-
$user_write_schema = $this->schema(
40+
$user_write_schema = function () {
41+
return $this->schema(
3742
array(
3843
'id' => $this->int_prop( 'User ID. Omit to create a new user.' ),
3944
'username' => $this->string_prop( 'Username.' ),
4045
'email' => $this->string_prop( 'Email address. Required when creating a user.' ),
4146
'password' => $this->string_prop( 'Password. Required when creating a user.' ),
4247
'first_name' => $this->string_prop( 'First name.' ),
4348
'last_name' => $this->string_prop( 'Last name.' ),
44-
'role' => $this->string_prop( 'Role.' ),
49+
'role' => $this->enum_string_prop( 'Editable user role slug.', $this->get_editable_role_slugs() ),
4550
)
46-
);
51+
);
52+
};
4753

4854
$this->add_ability( self::INTERNAL_PREFIX . 'get-user', 'Get User', 'Get a WordPress user by ID', $user_get_schema, function ( $params ) {
4955
return $this->get_user( (int) $params['id'] );

src/Tools/TaxonomyTools.php

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,45 @@ trait TaxonomyTools {
2323
* @return void
2424
*/
2525
private function add_taxonomy_abilities() {
26-
$taxonomy_schema = $this->schema(
26+
$taxonomy_schema = function () {
27+
return $this->schema(
2728
array(
28-
'taxonomy' => $this->string_prop( 'Taxonomy name.' ),
29+
'taxonomy' => $this->enum_string_prop( 'Registered taxonomy slug.', $this->get_registered_taxonomy_slugs() ),
2930
),
3031
array( 'taxonomy' )
31-
);
32-
$term_get_schema = $this->schema(
32+
);
33+
};
34+
$term_get_schema = function () {
35+
return $this->schema(
3336
array(
34-
'taxonomy' => $this->string_prop( 'Taxonomy name.' ),
37+
'taxonomy' => $this->enum_string_prop( 'Registered taxonomy slug.', $this->get_registered_taxonomy_slugs() ),
3538
'id' => $this->int_prop( 'Term ID.' ),
3639
),
3740
array( 'taxonomy', 'id' )
38-
);
39-
$term_save_schema = $this->schema(
41+
);
42+
};
43+
$term_save_schema = function () {
44+
return $this->schema(
4045
array(
41-
'taxonomy' => $this->string_prop( 'Taxonomy name.' ),
46+
'taxonomy' => $this->enum_string_prop( 'Registered taxonomy slug.', $this->get_registered_taxonomy_slugs() ),
4247
'id' => $this->int_prop( 'Term ID. Omit to create a new term.' ),
4348
'name' => $this->string_prop( 'Term name.' ),
4449
'slug' => $this->string_prop( 'Term slug.' ),
4550
'description' => $this->string_prop( 'Description.' ),
4651
),
4752
array( 'taxonomy' )
48-
);
53+
);
54+
};
4955

50-
$this->add_ability( self::INTERNAL_PREFIX . 'list-taxonomies', 'List Taxonomies', 'List registered WordPress taxonomies', $this->schema(
56+
$list_taxonomies_schema = function () {
57+
return $this->schema(
5158
array(
52-
'post_type' => $this->string_prop( 'Filter taxonomies by object type.' ),
59+
'post_type' => $this->enum_string_prop( 'Filter taxonomies by registered object type.', $this->get_registered_post_type_slugs() ),
5360
)
54-
), function ( $params ) {
61+
);
62+
};
63+
64+
$this->add_ability( self::INTERNAL_PREFIX . 'list-taxonomies', 'List Taxonomies', 'List registered WordPress taxonomies', $list_taxonomies_schema, function ( $params ) {
5565
return $this->list_taxonomies_tool( $params );
5666
} );
5767

@@ -171,7 +181,7 @@ private function validate_taxonomy( $taxonomy ) {
171181
return Response::error( 'This ability requires a WordPress runtime.', 500 );
172182
}
173183

174-
$taxonomy = sanitize_key( $taxonomy );
184+
$taxonomy = $this->sanitize_key_value( $taxonomy );
175185
if ( ! taxonomy_exists( $taxonomy ) ) {
176186
return Response::error( 'Taxonomy not found: ' . $taxonomy, 404 );
177187
}

src/Tools/ThemeManagementTools.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ trait ThemeManagementTools {
2525
private function add_theme_abilities() {
2626
$stylesheet_schema = $this->schema(
2727
array(
28-
'stylesheet' => $this->string_prop( 'Theme stylesheet directory name.' ),
28+
'stylesheet' => $this->string_prop( 'Installed theme stylesheet directory name.' ),
2929
),
3030
array( 'stylesheet' )
3131
);

0 commit comments

Comments
 (0)