Skip to content

Create /templates endpoint #723

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions wp_api/src/api_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@ pub enum WpErrorCode {
TaxonomyInvalid,
#[serde(rename = "rest_template_already_trashed")]
TemplateAlreadyTrashed,
#[serde(rename = "rest_template_insert_error")]
TemplateInsertError,
#[serde(rename = "rest_template_not_found")]
TemplateNotFound,
#[serde(rename = "rest_term_invalid")]
Expand Down
7 changes: 7 additions & 0 deletions wp_api/src/request/endpoint/templates_endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ enum TemplatesRequest {
List,
#[contextual_get(url = "/templates/<template_id>", output = crate::templates::SparseTemplate, filter_by = crate::templates::SparseTemplateField)]
Retrieve,
#[post(url = "/templates", params = &crate::templates::TemplateCreateParams, output = crate::templates::TemplateWithEditContext)]
Create,
#[delete(url = "/templates/<template_id>", output = crate::templates::TemplateDeleteResponse)]
Delete,
#[delete(url = "/templates/<template_id>", output = crate::templates::TemplateWithEditContext)]
Expand Down Expand Up @@ -80,6 +82,11 @@ mod tests {
use rstest::*;
use std::sync::Arc;

#[rstest]
fn create_template(endpoint: TemplatesRequestEndpoint) {
validate_wp_v2_endpoint(endpoint.create(), "/templates");
}

#[rstest]
#[case(TemplateListParams::default(), "")]
#[case(generate!(TemplateListParams, (post_id, Some(PostId(2)))), "wp_id=2")]
Expand Down
43 changes: 43 additions & 0 deletions wp_api/src/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,46 @@ pub struct TemplateUpdateParams {
// don't seem to take place when they are included in the request. So, we decided not to
// include them until we figure out under which conditions they'll be allowed to update.
}

#[derive(Debug, Serialize, uniffi::Record)]
pub struct TemplateCreateParams {
// Unique slug identifying the template.
pub slug: String,
// Theme identifier for the template.
#[uniffi(default = None)]
#[serde(skip_serializing_if = "Option::is_none")]
pub theme: Option<String>,
// Content of template.
#[uniffi(default = None)]
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
// Title of template.
#[uniffi(default = None)]
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
// Description of template.
#[uniffi(default = None)]
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
// The ID for the author of the template.
#[uniffi(default = None)]
#[serde(skip_serializing_if = "Option::is_none")]
pub author: Option<UserId>,
// https://developer.wordpress.org/rest-api/reference/wp_templates/#create-a-template
// The documentation includes `status` & `type` parameters, but the created templates don't
// seem to take these into account. So, we decided not to include them until we figure out
// under which conditions they'll be taken into account.
}

impl TemplateCreateParams {
pub fn new(slug: String) -> Self {
Self {
slug,
theme: None,
content: None,
title: None,
description: None,
author: None,
}
}
}
13 changes: 12 additions & 1 deletion wp_api_integration_tests/tests/test_templates_err.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
use wp_api::templates::{TemplateId, TemplateUpdateParams};
use wp_api::templates::{TemplateCreateParams, TemplateId, TemplateUpdateParams};
use wp_api_integration_tests::prelude::*;

#[tokio::test]
#[parallel]
async fn create_template_err_empty_content() {
// Creating a template requires `title` or `content`
api_client()
.templates()
.create(&TemplateCreateParams::new("foo".to_string()))
.await
.assert_wp_error(WpErrorCode::EmptyContent)
}

#[tokio::test]
#[parallel]
async fn delete_template_err_invalid_template() {
Expand Down
105 changes: 95 additions & 10 deletions wp_api_integration_tests/tests/test_templates_mut.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,75 @@
use macro_helper::generate_update_test;
use wp_api::templates::{
SparseTemplateContent, SparseTemplateContentWrapper, SparseTemplateTitle,
SparseTemplateTitleWrapper, TemplateId, TemplateStatus, TemplateUpdateParams,
TemplateWithEditContext,
SparseTemplateTitleWrapper, TemplateCreateParams, TemplateId, TemplateStatus,
TemplateUpdateParams, TemplateWithEditContext,
};
use wp_api_integration_tests::prelude::*;

const TEST_SLUG: &str = "foo_template_slug";
const TEST_TITLE: &str = "foo template title";

#[tokio::test]
#[serial]
async fn create_template_with_slug_and_content() {
let content = "foo template content";
let mut params = TemplateCreateParams::new(TEST_SLUG.to_string());
params.content = Some(content.to_string());
test_create_template(&params, |created_template| {
assert_slug(&created_template);
assert_eq!(
created_template.content,
SparseTemplateContentWrapper::Object(SparseTemplateContent {
raw: Some(content.to_string()),
rendered: None,
protected: None,
block_version: None
})
);
})
.await;
}

#[tokio::test]
#[serial]
async fn create_template_with_slug_and_title() {
let mut params = TemplateCreateParams::new(TEST_SLUG.to_string());
params.title = Some(TEST_TITLE.to_string());
test_create_template(&params, |created_template| {
assert_slug(&created_template);
assert_title(&created_template);
})
.await;
}

#[tokio::test]
#[serial]
async fn create_template_with_slug_title_and_theme() {
let theme = "foo template theme";
let mut params = TemplateCreateParams::new(TEST_SLUG.to_string());
params.title = Some(TEST_TITLE.to_string());
params.theme = Some(theme.to_string());
test_create_template(&params, |created_template| {
assert_slug(&created_template);
assert_title(&created_template);
assert_eq!(created_template.theme, theme);
})
.await;
}

#[tokio::test]
#[serial]
async fn create_template_with_slug_title_and_author() {
let mut params = TemplateCreateParams::new(TEST_SLUG.to_string());
params.title = Some(TEST_TITLE.to_string());
params.author = Some(SECOND_USER_ID);
test_create_template(&params, |created_template| {
assert_title(&created_template);
assert_eq!(created_template.author, SECOND_USER_ID);
})
.await;
}

#[tokio::test]
#[serial]
async fn delete_template() {
Expand Down Expand Up @@ -70,15 +134,9 @@ generate_update_test!(
generate_update_test!(
update_title,
title,
"new_title".to_string(),
TEST_TITLE.to_string(),
|updated_template| {
assert_eq!(
updated_template.title,
SparseTemplateTitleWrapper::Object(SparseTemplateTitle {
raw: Some("new_title".to_string()),
rendered: Some("new_title".to_string())
})
);
assert_title(&updated_template);
}
);
generate_update_test!(
Expand Down Expand Up @@ -113,6 +171,33 @@ where
RestoreServer::db().await;
}

async fn test_create_template<F>(params: &TemplateCreateParams, assert: F)
where
F: Fn(TemplateWithEditContext),
{
let response = api_client()
.templates()
.create(params)
.await
.assert_response();
assert(response.data);
RestoreServer::db().await;
}

fn assert_slug(template: &TemplateWithEditContext) {
assert_eq!(template.slug, TEST_SLUG);
}

fn assert_title(template: &TemplateWithEditContext) {
assert_eq!(
template.title,
SparseTemplateTitleWrapper::Object(SparseTemplateTitle {
raw: Some(TEST_TITLE.to_string()),
rendered: Some(TEST_TITLE.to_string())
})
);
}

mod macro_helper {
macro_rules! generate_update_test {
($ident:ident, $field:ident, $new_value:expr, $assertion:expr) => {
Expand Down