探讨 WordPress REST API 中 Schema 验证机制

WordPress REST API Schema 验证机制详解

各位同学,大家好!今天我们来深入探讨 WordPress REST API 的 Schema 验证机制。Schema 验证是构建健壮、可靠的 API 的关键组成部分,它确保传入和传出的数据符合预定义的结构,从而避免因数据类型错误、缺失字段或无效值导致的错误。

1. 什么是 Schema?

在 REST API 的上下文中,Schema 本质上就是一个描述数据结构的蓝图。它定义了 API 期望接收和返回的数据的形状,包括:

  • 数据类型 (Data Type): 例如字符串、整数、布尔值、数组、对象等。
  • 字段名称 (Field Name): 每个字段的名称。
  • 字段描述 (Field Description): 字段的用途和含义,有助于 API 文档的生成。
  • 是否必填 (Required): 指明字段是否必须存在。
  • 默认值 (Default Value): 当字段未提供时使用的默认值。
  • 验证规则 (Validation Rules): 定义字段值的有效范围和格式。 例如,最小长度、最大长度、正则表达式等。
  • 枚举值 (Enum): 允许字段取值的集合。

Schema 的常见格式包括 JSON Schema 和 OpenAPI Schema。WordPress REST API 使用基于 JSON Schema 的格式。

2. WordPress REST API 如何使用 Schema?

WordPress REST API 利用 Schema 来规范 API 的行为,主要体现在以下两个方面:

  • 请求验证 (Request Validation): 验证客户端发送的请求数据是否符合预定义的 Schema。如果请求数据不符合 Schema,API 会返回错误。
  • 响应序列化 (Response Serialization): 确保 API 返回的数据符合 Schema。这有助于客户端准确地解析和处理响应数据。

3. Schema 的定义和注册

在 WordPress 中,Schema 通常在注册自定义 REST API 路由时定义。可以使用 register_rest_route() 函数来注册路由,并在 args 参数中指定 Schema。

add_action( 'rest_api_init', function () {
  register_rest_route( 'my-plugin/v1', '/items', array(
    'methods'  => 'POST',
    'callback' => 'my_plugin_create_item',
    'args'     => array(
      'title' => array(
        'required'    => true,
        'type'        => 'string',
        'description' => 'The title of the item.',
        'sanitize_callback' => 'sanitize_text_field',
        'validate_callback' => 'rest_validate_request_arg',
      ),
      'content' => array(
        'type'        => 'string',
        'description' => 'The content of the item.',
        'sanitize_callback' => 'wp_kses_post',
        'validate_callback' => 'rest_validate_request_arg',
      ),
      'status'  => array(
        'type'          => 'string',
        'description'   => 'The status of the item.',
        'default'       => 'draft',
        'enum'          => array( 'draft', 'pending', 'publish', 'private' ),
        'sanitize_callback' => 'sanitize_key',
        'validate_callback' => 'rest_validate_request_arg',
      ),
    ),
  ) );
} );

function my_plugin_create_item( WP_REST_Request $request ) {
  $title   = $request['title'];
  $content = $request['content'];
  $status  = $request['status'];

  // 创建文章...
  $post_id = wp_insert_post( array(
    'post_title'   => $title,
    'post_content' => $content,
    'post_status'  => $status,
  ) );

  if ( is_wp_error( $post_id ) ) {
    return new WP_Error( 'my_plugin_create_error', 'Failed to create item.', array( 'status' => 500 ) );
  }

  return rest_ensure_response( array( 'id' => $post_id ) );
}

在这个例子中,args 数组定义了 titlecontentstatus 三个参数的 Schema。

4. Schema 参数详解

下面详细解释 args 数组中常用的参数:

参数名 数据类型 描述
required boolean 指示该参数是否是必需的。如果为 true,则在请求中必须包含该参数。
type string 指定参数的数据类型。常见的类型包括 stringintegernumberbooleanarrayobject
description string 描述参数的用途和含义。这对于生成 API 文档非常有用。
default mixed 指定参数的默认值。如果请求中未包含该参数,则使用默认值。
enum array 定义参数允许的取值集合。参数的值必须是 enum 数组中的一个。
sanitize_callback callable 指定用于清理参数值的回调函数。该函数接收参数值作为输入,并返回清理后的值。
validate_callback callable 指定用于验证参数值的回调函数。该函数接收参数值作为输入,并返回 true 如果参数值有效,否则返回 falseWP_Error 对象。
items array 仅在 typearray 时使用。定义数组中元素的 Schema。
properties array 仅在 typeobject 时使用。定义对象中属性的 Schema。
format string 指定参数的格式。例如,对于 string 类型,可以指定 emailuridatedate-time 等格式。
minimum number 仅在 typeintegernumber 时使用。指定参数的最小值。
maximum number 仅在 typeintegernumber 时使用。指定参数的最大值。
minLength integer 仅在 typestring 时使用。指定参数的最小长度。
maxLength integer 仅在 typestring 时使用。指定参数的最大长度。
pattern string 仅在 typestring 时使用。指定参数必须匹配的正则表达式。

5. 内置的 Sanitize 和 Validate Callback 函数

WordPress 提供了许多内置的 Sanitize 和 Validate Callback 函数,可以方便地用于 Schema 定义。

  • Sanitize Callback:

    • sanitize_text_field():对字符串进行清理,移除 HTML 标签和特殊字符。
    • wp_kses_post():使用 WordPress 的 KSES (Kses HTML filter) 过滤 HTML 内容,只允许安全的标签和属性。
    • sanitize_email():清理 Email 地址,确保其格式正确。
    • absint():将值转换为绝对整数。
    • sanitize_key(): 清理用作数据库键或 URL 中的键的字符串。只允许小写字母、数字和连字符。
  • Validate Callback:

    • rest_validate_request_arg():这是一个通用的验证函数,它会根据参数的 type 和其他约束条件(如 enumminimummaximum 等)来验证参数值。强烈建议使用这个函数作为默认的验证回调,因为它已经处理了大多数常见的验证场景。

6. 自定义 Sanitize 和 Validate Callback 函数

除了使用内置的函数外,还可以创建自定义的 Sanitize 和 Validate Callback 函数,以满足特定的需求。

  • 自定义 Sanitize Callback:

    function my_plugin_sanitize_url( $url ) {
      // 确保 URL 以 http:// 或 https:// 开头
      if ( strpos( $url, 'http://' ) !== 0 && strpos( $url, 'https://' ) !== 0 ) {
        $url = 'http://' . $url;
      }
      return esc_url_raw( $url ); // 使用 WordPress 的 esc_url_raw() 函数进行转义
    }
  • 自定义 Validate Callback:

    function my_plugin_validate_date( $date, $request, $param ) {
      // 使用 strtotime() 函数检查日期是否有效
      if ( strtotime( $date ) === false ) {
        return new WP_Error( 'my_plugin_invalid_date', 'The date is invalid.', array( 'status' => 400 ) );
      }
      return true;
    }

    注意: Validate Callback 函数必须返回 true 如果参数有效,否则返回 falseWP_Error 对象。如果返回 WP_Error 对象,API 会返回一个错误响应。

7. 处理数组和对象类型的 Schema

当参数的 typearrayobject 时,需要使用 itemsproperties 参数来定义数组元素或对象属性的 Schema。

  • 数组类型的 Schema:

    'tags' => array(
      'type'        => 'array',
      'description' => 'An array of tags.',
      'items'       => array(
        'type' => 'string', // 数组中的元素是字符串
      ),
      'sanitize_callback' => 'my_plugin_sanitize_array',
      'validate_callback' => 'rest_validate_request_arg',
    ),
    
    function my_plugin_sanitize_array( $array ) {
      if ( ! is_array( $array ) ) {
        return array();
      }
      return array_map( 'sanitize_text_field', $array );
    }
  • 对象类型的 Schema:

    'author' => array(
      'type'        => 'object',
      'description' => 'The author of the item.',
      'properties'  => array(
        'name' => array(
          'type'        => 'string',
          'description' => 'The name of the author.',
          'sanitize_callback' => 'sanitize_text_field',
          'validate_callback' => 'rest_validate_request_arg',
        ),
        'email' => array(
          'type'        => 'string',
          'format'      => 'email',
          'description' => 'The email of the author.',
          'sanitize_callback' => 'sanitize_email',
          'validate_callback' => 'rest_validate_request_arg',
        ),
      ),
    ),

8. 使用 WP_REST_Request 对象

在 API 回调函数中,可以使用 WP_REST_Request 对象来访问请求参数。WP_REST_Request 对象提供了一些方法来获取参数值,例如:

  • $request->get_param( $param_name ):获取指定参数的值。
  • $request->get_query_params():获取 URL 查询参数。
  • $request->get_body_params():获取请求体中的参数(通常用于 POST 和 PUT 请求)。
  • $request->get_json_params():获取 JSON 格式的请求体参数。
  • $request[ $param_name ]: 直接使用数组访问方式。

9. 错误处理

当请求数据不符合 Schema 时,WordPress REST API 会自动返回一个错误响应。可以使用 WP_Error 对象来创建自定义的错误响应。

function my_plugin_create_item( WP_REST_Request $request ) {
  $title = $request['title'];

  if ( empty( $title ) ) {
    return new WP_Error( 'my_plugin_missing_title', 'The title is required.', array( 'status' => 400 ) );
  }

  // ...
}

WP_Error 对象包含以下属性:

  • $code: 错误代码(字符串)。
  • $message: 错误消息(字符串)。
  • $data: 附加数据(数组)。 通常包含 status 键,用于指定 HTTP 状态码。

10. 示例:更复杂的 Schema 结构

假设我们需要创建一个 API 来管理书籍信息,书籍信息包括:

  • title (string, 必填): 书籍标题
  • author (object, 必填): 作者信息,包含:
    • name (string, 必填): 作者姓名
    • email (string, 必填, email格式): 作者邮箱
  • publication_date (string, 可选, date格式): 出版日期
  • tags (array, 可选): 标签列表,每个标签都是一个字符串
  • price (number, 必填, 最小值0): 价格
add_action( 'rest_api_init', function () {
  register_rest_route( 'my-plugin/v1', '/books', array(
    'methods'  => 'POST',
    'callback' => 'my_plugin_create_book',
    'args'     => array(
      'title' => array(
        'required'    => true,
        'type'        => 'string',
        'description' => 'The title of the book.',
        'sanitize_callback' => 'sanitize_text_field',
        'validate_callback' => 'rest_validate_request_arg',
      ),
      'author' => array(
        'required'    => true,
        'type'        => 'object',
        'description' => 'The author of the book.',
        'properties'  => array(
          'name' => array(
            'required'    => true,
            'type'        => 'string',
            'description' => 'The name of the author.',
            'sanitize_callback' => 'sanitize_text_field',
            'validate_callback' => 'rest_validate_request_arg',
          ),
          'email' => array(
            'required'    => true,
            'type'        => 'string',
            'format'      => 'email',
            'description' => 'The email of the author.',
            'sanitize_callback' => 'sanitize_email',
            'validate_callback' => 'rest_validate_request_arg',
          ),
        ),
      ),
      'publication_date' => array(
        'type'        => 'string',
        'format'      => 'date',
        'description' => 'The publication date of the book.',
        'sanitize_callback' => 'sanitize_text_field', // 可以使用更合适的日期清理函数
        'validate_callback' => 'my_plugin_validate_date',
      ),
      'tags' => array(
        'type'        => 'array',
        'description' => 'An array of tags.',
        'items'       => array(
          'type' => 'string',
        ),
        'sanitize_callback' => 'my_plugin_sanitize_array',
        'validate_callback' => 'rest_validate_request_arg',
      ),
      'price' => array(
        'required'    => true,
        'type'        => 'number',
        'description' => 'The price of the book.',
        'minimum'     => 0,
        'sanitize_callback' => 'floatval', // 确保是浮点数
        'validate_callback' => 'rest_validate_request_arg',
      ),
    ),
  ) );
} );

function my_plugin_create_book( WP_REST_Request $request ) {
  $title          = $request['title'];
  $author         = $request['author'];
  $publication_date = $request['publication_date'];
  $tags           = $request['tags'];
  $price          = $request['price'];

  //  简单验证 author 是否是数组
  if ( ! is_array( $author ) || ! isset( $author['name'] ) || ! isset( $author['email'] ) ) {
    return new WP_Error( 'my_plugin_invalid_author', 'The author must be an object with name and email properties.', array( 'status' => 400 ) );
  }

  // 创建书籍的相关逻辑
  $book_data = array(
    'title'          => $title,
    'author_name'    => $author['name'],
    'author_email'   => $author['email'],
    'publication_date' => $publication_date,
    'tags'           => $tags,
    'price'          => $price,
  );

  // 假设存储到自定义表中
  global $wpdb;
  $table_name = $wpdb->prefix . 'my_books';

  $result = $wpdb->insert(
    $table_name,
    $book_data,
    array(
      '%s', '%s', '%s', '%s', '%s', '%f' // 数据类型
    )
  );

  if ( false === $result ) {
    return new WP_Error( 'my_plugin_create_book_error', 'Failed to create book.', array( 'status' => 500 ) );
  }

  $book_id = $wpdb->insert_id;

  return rest_ensure_response( array( 'id' => $book_id ) );
}

function my_plugin_validate_date( $date, $request, $param ) {
  if ( empty( $date ) ) {
    return true; // 允许为空,因为 publication_date 不是必需的
  }

  $d = DateTime::createFromFormat('Y-m-d', $date);
  if ( !($d && $d->format('Y-m-d') == $date) ) {
    return new WP_Error( 'my_plugin_invalid_date', 'The date format is invalid. Use YYYY-MM-DD.', array( 'status' => 400 ) );
  }
  return true;
}

function my_plugin_sanitize_array( $array ) {
  if ( ! is_array( $array ) ) {
    return array();
  }
  return array_map( 'sanitize_text_field', $array );
}

11. 总结

Schema 验证是构建健壮 API 的基石,它通过预定义的结构来规范 API 的数据交互。通过 register_rest_route 函数和 args 参数,我们可以定义 API 的 Schema,并使用内置或自定义的 Sanitize 和 Validate Callback 函数来确保数据的有效性和安全性。 良好的错误处理机制同样重要,能够帮助客户端更好地理解 API 的行为并处理潜在的问题。 遵循这些原则,能够构建出高质量、易于维护的 WordPress REST API。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注