Создание Схемы Маршрута

Схема в REST API — это полное описание маршрута, оно рассказывает нам о маршруте все. Подробнее о том, что такое Схема, читайте в другом разделе этого руководства, а здесь мы рассмотрим как создавать схему, когда создается произвольный маршрут.

JSON Схема

Формат JSON схемы это для REST — это отдельная разработка, у которой есть документация и сайт json-schema.org.

Если на запрос отдавать просто JSON ответ, то Клиенты (пользователей маршрутов) ничего не будут знать о том какие именно данные они получают (хорошо если они интуитивно понятны, но такое бывает не всегда). Используя схему мы упростим понимание данных для клиентов, а также улучшит нашу кодовую базу. Схема поможет лучше структурировать данные, чтобы приложения могли легче «рассуждать» о взаимодействиях с REST API. Также наличие схемы упрощает тестирование, дает возможность обнаружения (в некоторых моментах).

Можно создавать маршруты без описания Схемы, но в этом случае многое будет непонятно для Клиента, поэтому при создании маршрута, рекомендуется создавать (описывать) его схему!

Создание схемы может показаться глупым занятием и какой-то ненужной работой, но если вы создаете обнаруживаемые и легко расширяемые конечные точки, использовать схему просто необходимо!

Схема маршрута состоит из «Схемы ресурса» и «Схемы эндпоинтов» (см. подробнее). Ниже рассмотрим, как создавать каждую из этих схем.

Структура схемы

Базовая структура схемы содержит всего несколько элементов.

  • $schema — Ссылка на документацию по схеме. Например: http://json-schema.org/draft-04/schema#.

  • title — Название схемы. Обычно это заголовок для человека, но в WordPress это поле создано для прочтения программами. Примеры названия роутов для разных типов данных: post, page, ярлык типа записи, tag, ярлык таксонмии, comment.

  • type — Тип данных который будет получен Клиентом. Тут может быть указан любой из семи примитивных типов. В WordPress тут почти всегда указывается тип object, даже для эндпоинтов коллекций которые возвращают массив объектов.

  • properties — Список свойств (параметров) которые содержит объект и аргументы каждого свойства. Каждое свойство само по себе также является схемой, только без верхнеуровнего аргумента $schema. Для отличия можно сказать что это под-схема.

Пример создания такой схемы из ядра WP:

$schema = array(
	'$schema'    => 'http://json-schema.org/draft-04/schema#',
	'title'      => $this->post_type,
	'type'       => 'object',
	// Base properties for every Post.
	'properties' => array(
		'id'           => array(
			'description' => __( 'Unique identifier for the object.' ),
			'type'        => 'integer',
			'context'     => array( 'view', 'edit', 'embed' ),
			'readonly'    => true,
		),
		'date'         => array(
			'description' => __( "The date the object was published, in the site's timezone." ),
			'type'        => array( 'string', 'null' ),
			'format'      => 'date-time',
			'context'     => array( 'view', 'edit', 'embed' ),
		),
		'guid'         => array(
			'description' => __( 'The globally unique identifier for the object.' ),
			'type'        => 'object',
			'context'     => array( 'view', 'edit' ),
			'readonly'    => true,
			'properties'  => array(
				'raw'      => array(
					'description' => __( 'GUID for the object, as it exists in the database.' ),
					'type'        => 'string',
					'context'     => array( 'edit' ),
					'readonly'    => true,
				),
				'rendered' => array(
					'description' => __( 'GUID for the object, transformed for display.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
			),
		),
		...
	),
);

Схема ресурсов

Схема ресурса указывает, какие поля существуют у объекта (поста, рубрики и т.д.). Схему ресурса можно указать при регистрации маршрута.

Давайте посмотрим, как создать схему ресурса-комментариев:

// регистрируем маршрут (роут).
add_action( 'rest_api_init', 'kama_register_my_comment_route' );

function kama_register_my_comment_route() {

	register_rest_route( 'my-namespace/v1', '/comments', array(
		// регистрируем
		array(
			'methods'  => 'GET',
			'callback' => 'kama_get_comments',
		),
		// регистрируем схему ("схема" приравнивается к запросу OPTIONS)
		'schema' => 'kama_get_comment_schema',
	) );
}

# Получает 5 последних комментов и отдает их как REST ответ.
function kama_get_comments( $request ) {

	$data = array();

	$comments = get_comments( array(
		'post_per_page' => 5,
	) );

	// нет комментов выходим
	if ( empty( $comments ) )
		return rest_ensure_response( $data );

	foreach ( $comments as $comment ) {

		// добавляем только те поля которые указаны в схеме
		$schema = kama_get_comment_schema();

		$comment_data = array();

		// переименовываем поля в более понятные
		if ( isset( $schema['properties']['id'] ) )
			$comment_data['id'] = (int) $comment->comment_id;

		if ( isset( $schema['properties']['author'] ) )
			$comment_data['author'] = (int) $comment->user_id;

		if ( isset( $schema['properties']['content'] ) )
			$comment_data['content'] = apply_filters( 'comment_text', $comment->comment_content, $comment );

		$response = rest_ensure_response( $comment_data );

		$data[] = _kama_prepare_for_collection( $response );
	}

	// вернем все данные комментариев.
	return rest_ensure_response( $data );
}

# Подготавливает ответ для вставки в коллекцию ответов.
# Код скопирован из класса WP_REST_Controller.
function _kama_prepare_for_collection( $response ) {
	if ( ! ( $response instanceof WP_REST_Response ) )
		return $response;

	$data = (array) $response->get_data();
	$server = rest_get_server();

	if ( method_exists( $server, 'get_compact_response_links' ) )
		$links = call_user_func( array( $server, 'get_compact_response_links' ), $response );
	else
		$links = call_user_func( array( $server, 'get_response_links' ), $response );

	if ( ! empty( $links ) )
		$data['_links'] = $links;

	return $data;
}

# Полчает нашу схему для комментариев.
function kama_get_comment_schema() {
	$schema = array(
		// показывает какую версию схемы мы используем - это draft 4
		'$schema'              => 'http://json-schema.org/draft-04/schema#',
		// определяет ресурс который описывает схема
		'title'                => 'comment',
		'type'                 => 'object',
		// в JSON схеме нужно указывать свойства в атрибуете 'properties'.
		'properties'           => array(
			'id' => array(
				'description'  => esc_html__( 'Unique identifier for the object.', 'my-textdomain' ),
				'type'         => 'integer',
				'context'      => array( 'view', 'edit', 'embed' ),
				'readonly'     => true,
			),
			'author' => array(
				'description'  => esc_html__( 'The id of the user object, if author was a user.', 'my-textdomain' ),
				'type'         => 'integer',
			),
			'content' => array(
				'description'  => esc_html__( 'The content for the object.', 'my-textdomain' ),
				'type'         => 'string',
			),
		),
	);

	return $schema;
}

В строках 31-44 можно видеть, что в ответ попадают только те данные комментария, которые указаны в созданной нами схеме.

После создания схемы таким образом, мы можем увидеть её сделав OPTIONS запрос на текущий маршрут.

Когда мы предоставили Схему ресурса, этот ресурс становится обнаруживаемым через запрос OPTIONS к текущему маршруту.

Создание Схемы ресурса - это только одна часть общей Схемы маршрута. Вторая - это создание схемы параметров конечных точек (см. ниже).

Примеры схем ресурса:

Схема эндпоинтов и их параметров

Схема эндпоинтов описывает методы, по которым можно обратиться к эндпоинту и его параметры.

При регистрации маршрута всегда указываются его эндпоинты и параметры этих эндпоинтов (если параметры вообще нужны). Для каждого параметра можно указать его: описание (description), тип значения (type), является ли параметр обязательным (required). Все это попадет в Схему эндпоинта.

Рассмотрим пример, создания эндпоинта с параметром my_arg, для которого укажем поля показываемые в схеме и функции проверки/очистки значения:

// регистрация роута.
add_action( 'rest_api_init', 'kama_register_my_route' );
function kama_register_my_route() {

	register_rest_route( 'my-namespace/v1', '/schema-arg', array(
		// регистрация эндпоинта
		array(
			'methods'  => 'GET',
			'callback' => 'kama_get_item',
			// схема аргументов (параметров) эндпоинта. Эти параметры появятся в схеме маршрута.
			'args'     => array(
				'arg_str' => array(
					'description'       => esc_html__('This is the argument our endpoint returns.','dom'),
					'type'              => 'string',
					'validate_callback' => 'kama_validate_params',
					'sanitize_callback' => 'kama_sanitize_params',
					'required'          => true,
				),
				'arg_int' => array(
					'description'       => esc_html__('This is the argument our endpoint returns.','dom'),
					'type'              => 'integer',
					'default'           => 10,
					'validate_callback' => 'kama_validate_params',
					'sanitize_callback' => 'kama_sanitize_params',
				),
				// и т.д.
			),
		),
	) );

}

## Вернет параметры как ответ на запрос.
function kama_get_item( $request ) {
	// код вызовет ошибку "arg_str не установлен", если параметр не передан в запросе.
	// это потому что мы испльзовали required в схеме.
	return rest_ensure_response( $request['arg_str'] );
}

/**
 * Функция проверки значения параметра.
 *
 * @param mixed           $value   Значение параметра.
 * @param WP_REST_Request $request Объект текущего запроса.
 * @param string          $param   Название параметра.
 */
function kama_validate_params( $value, $request, $param ) {

	$attributes = $request->get_attributes();

	$param_attr = & $attributes['args'][ $param ];

	// передан параметр из схемы
	if ( isset( $attributes['args'][ $param ] ) ) {
		// убедимся что значение параметра является нужным типом (строкой, чилом)
		if (
			( 'string' === $param_attr['type'] && ! is_string( $value ) )
			||
			( 'integer' === $param_attr['type'] && ! is_numeric( $value ) )
		) {
			return new WP_Error( 'rest_invalid_param',
				sprintf( esc_html__('%s is not of type %s','dom'), $param, $param_attr ),
				array( 'status' => 400 )
			);
		}
	}
	// передан неизвестный параметр
	else {
		return new WP_Error( 'rest_invalid_param',
			sprintf( esc_html__('%s was not registered as a request argument.','dom'), $param ),
			array( 'status' => 400 )
		);
	}

	// ели мы дошли до сюда, значит данные прошили проверку
	return true;
}

/**
 * Функция очистки значения параметра.
 *
 * @param mixed           $value   Значение параметра.
 * @param WP_REST_Request $request Объект текущего запроса.
 * @param string          $param   Название параметра.
 */
function kama_sanitize_params( $value, $request, $param ) {

	$attributes = $request->get_attributes();

	// передан параметр из схемы
	if ( isset( $attributes['args'][ $param ] ) ) {
		// если значение параметра является строкой, очищаем как строку.
		if ( 'string' === $attributes['args'][ $param ]['type'] )
			return sanitize_text_field( $value );

		// если значение параметра является числом, очищаем как число.
		if ( 'integer' === $attributes['args'][ $param ]['type'] )
			return (int) $value;

	}
	// передан неизвестный параметр
	else {
		return new WP_Error( 'rest_invalid_param',
			sprintf( esc_html__('%s was not registered as a request argument.','dom'), $param ),
			array( 'status' => 400 )
		);
	}

	// если мы дошил до сюда, то видимо в этом коде есть какая-то ошибка. До этого момента мы доходить никак не должны.
	return new WP_Error( 'rest_api_sad',
		esc_html__('Something went terribly wrong.','dom'),
		array( 'status' => 500 )
	);

}

В приведенном выше примере мы используем функции проверки и очистки только для одного параметра запроса my_arg. Однако, мы также можем использовать эти функции проверки и очистки для любого другого параметра, который должен быть строкой (для которой мы задали схему). По мере роста кода и конечных точек, схема поможет сохранить легкий и поддерживаемый код. Проверять и очищать значения параметров, можно и без схемы, однако в этом случае будет сложнее следить, какие функции очистки/проверки и где используются. Также, добавляя схему для параметров запроса, мы показываем Клиентам нашу схему параметров. Это поможет Клиентам не отправлять недопустимые параметры в запросах в API.

Примеры схемы параметров