initial
This commit is contained in:
+77
@@ -0,0 +1,77 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.2.8] - 2026-06-08
|
||||
### Changed
|
||||
- Internal: No longer require automattic/jetpack-changelogger as a per-project dev dependency. [#48225]
|
||||
|
||||
## [0.2.7] - 2025-12-11
|
||||
### Fixed
|
||||
- Ensure proper flags are used with `json_encode()`. [#46092]
|
||||
|
||||
## [0.2.6] - 2025-08-21
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.2.5] - 2025-05-15
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.2.4] - 2025-03-26
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.2.3] - 2025-03-18
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [0.2.2] - 2025-03-05
|
||||
### Changed
|
||||
- Update dependencies. [#42002]
|
||||
|
||||
## [0.2.1] - 2025-02-12
|
||||
### Fixed
|
||||
- Code: Remove extra params on function calls. [#41263]
|
||||
|
||||
## [0.2.0] - 2024-11-28
|
||||
### Changed
|
||||
- Updated dependencies. [#40286]
|
||||
|
||||
### Removed
|
||||
- General: Update minimum PHP version to 7.2. [#40147]
|
||||
|
||||
## [0.1.3] - 2024-11-04
|
||||
### Added
|
||||
- Enable test coverage. [#39961]
|
||||
|
||||
## [0.1.2] - 2024-10-10
|
||||
### Fixed
|
||||
- Fix missing types in phpdoc comments. [#39648]
|
||||
|
||||
## [0.1.1] - 2024-08-29
|
||||
### Changed
|
||||
- Cleanup utils [#39025]
|
||||
|
||||
## 0.1.0 - 2024-08-22
|
||||
### Added
|
||||
- Initial version. [#38563]
|
||||
|
||||
### Changed
|
||||
- Updated package dependencies. [#39004]
|
||||
|
||||
[0.2.8]: https://github.com/Automattic/jetpack-schema/compare/v0.2.7...v0.2.8
|
||||
[0.2.7]: https://github.com/Automattic/jetpack-schema/compare/v0.2.6...v0.2.7
|
||||
[0.2.6]: https://github.com/Automattic/jetpack-schema/compare/v0.2.5...v0.2.6
|
||||
[0.2.5]: https://github.com/Automattic/jetpack-schema/compare/v0.2.4...v0.2.5
|
||||
[0.2.4]: https://github.com/Automattic/jetpack-schema/compare/v0.2.3...v0.2.4
|
||||
[0.2.3]: https://github.com/Automattic/jetpack-schema/compare/v0.2.2...v0.2.3
|
||||
[0.2.2]: https://github.com/Automattic/jetpack-schema/compare/v0.2.1...v0.2.2
|
||||
[0.2.1]: https://github.com/Automattic/jetpack-schema/compare/v0.2.0...v0.2.1
|
||||
[0.2.0]: https://github.com/Automattic/jetpack-schema/compare/v0.1.3...v0.2.0
|
||||
[0.1.3]: https://github.com/Automattic/jetpack-schema/compare/v0.1.2...v0.1.3
|
||||
[0.1.2]: https://github.com/Automattic/jetpack-schema/compare/v0.1.1...v0.1.2
|
||||
[0.1.1]: https://github.com/Automattic/jetpack-schema/compare/v0.1.0...v0.1.1
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema;
|
||||
|
||||
class Schema_Context {
|
||||
|
||||
private $name;
|
||||
private $data;
|
||||
private $path = array();
|
||||
|
||||
/**
|
||||
* @var array $log Log of notable actions taken during parsing.
|
||||
*/
|
||||
private $log = array();
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct( $name ) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function add_to_path( $key ) {
|
||||
$this->path[] = $key;
|
||||
}
|
||||
|
||||
private function trace( $depth_limit = 15 ) {
|
||||
|
||||
if ( ! Utils::is_debug() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$trace = array();
|
||||
// This is fine, it's guarded by `SCHEMA_TRACE` constant.
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
|
||||
foreach ( debug_backtrace() as $stack_frame ) {
|
||||
if ( isset( $stack_frame['line'], $stack_frame['file'] ) ) {
|
||||
$filename_pieces = explode( '.', basename( $stack_frame['file'] ), 2 );
|
||||
$trace[] = $filename_pieces[0] . ':' . $stack_frame['line'];
|
||||
} elseif ( isset( $stack_frame['class'], $stack_frame['function'] ) ) {
|
||||
$trace[] = $stack_frame['class'] . '::' . $stack_frame['function'];
|
||||
}
|
||||
|
||||
--$depth_limit;
|
||||
if ( $depth_limit <= 0 ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $trace;
|
||||
}
|
||||
|
||||
public function log( $message, $data, $error = null ) {
|
||||
if ( ! Utils::is_debug() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$meta = array(
|
||||
'name' => $this->get_name(),
|
||||
'path' => $this->get_path(),
|
||||
);
|
||||
|
||||
$trace = defined( 'SCHEMA_TRACE' ) && \SCHEMA_TRACE > 0 ? $this->trace( \SCHEMA_TRACE ) : null;
|
||||
if ( $trace ) {
|
||||
$meta['trace'] = $trace;
|
||||
}
|
||||
|
||||
if ( $error instanceof Schema_Error ) {
|
||||
$meta['error_message'] = $error->getMessage();
|
||||
$meta['value'] = $error->get_value();
|
||||
}
|
||||
|
||||
$this->log[] = array(
|
||||
'message' => $message,
|
||||
'meta' => $meta,
|
||||
);
|
||||
}
|
||||
|
||||
public function verbose_log( $message, $data ) {
|
||||
if ( ! Utils::is_verbose() ) {
|
||||
return;
|
||||
}
|
||||
$this->log( $message, $data );
|
||||
}
|
||||
|
||||
public function get_log() {
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
public function get_path() {
|
||||
$path = $this->name;
|
||||
if ( ! empty( $this->path ) ) {
|
||||
$path .= '.' . implode( '.', $this->path );
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function remove_path( $key ) {
|
||||
$index = array_search( $key, $this->path, true );
|
||||
if ( $index !== false ) {
|
||||
unset( $this->path[ $index ] );
|
||||
}
|
||||
// Reindex the array.
|
||||
$this->path = array_values( $this->path );
|
||||
}
|
||||
|
||||
public function get_name() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function set_data( $data ) {
|
||||
if ( ! isset( $this->data ) ) {
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
||||
|
||||
public function get_data() {
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema;
|
||||
|
||||
class Schema_Error extends \RuntimeException {
|
||||
private $value;
|
||||
private $context;
|
||||
|
||||
public function __construct( $message, $value, $context = null ) {
|
||||
$this->value = $value;
|
||||
$this->context = $context;
|
||||
parent::__construct( $message );
|
||||
}
|
||||
|
||||
public function get_value() {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function get_context() {
|
||||
return $this->context;
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema;
|
||||
|
||||
use Automattic\Jetpack\Schema\Modifiers\Modifier_Fallback;
|
||||
use Automattic\Jetpack\Schema\Types\Type_Literal;
|
||||
use Automattic\Jetpack\Schema\Types\Type_Void;
|
||||
|
||||
class Schema_Parser implements Parser {
|
||||
/**
|
||||
* Each Schema entry has a Parser that's able to parse a value.
|
||||
*
|
||||
* @var Parser
|
||||
*/
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* @var Schema_Context|null
|
||||
*/
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* @param Parser $parser
|
||||
*/
|
||||
public function __construct( Parser $parser ) {
|
||||
$this->parser = $parser;
|
||||
}
|
||||
|
||||
public function set_context( Schema_Context $context ) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->parser->__toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow combining multiple types of schemas internally for easier fallbacks.
|
||||
* For a public or API, use `Schema::either()` instead.
|
||||
*
|
||||
* @param Parser $parser
|
||||
*
|
||||
* @return $this
|
||||
* @see Schema::either()
|
||||
*/
|
||||
private function or( Parser $parser ) {
|
||||
if ( $this->parser instanceof Modifier_Fallback ) {
|
||||
$this->parser->add_fallback_parser( $parser );
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Keep track of the current parser
|
||||
$current_parser = $this->parser;
|
||||
// Replace the current parser with a new Modifier_Fallback parser
|
||||
$this->parser = new Modifier_Fallback();
|
||||
// Add the current parser back
|
||||
$this->parser->add_fallback_parser( $current_parser );
|
||||
// Add the new parser
|
||||
$this->parser->add_fallback_parser( $parser );
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a fallback value for the schema type when the input data is invalid.
|
||||
*
|
||||
* @param mixed $default_value The fallback value to use when the input data is invalid.
|
||||
*
|
||||
* @throws Schema_Error When the input data is invalid and debug mode is enabled.
|
||||
*/
|
||||
public function fallback( $default_value ) {
|
||||
|
||||
// In debug mode: Ensure that the fallback value can be parsed.
|
||||
if ( Utils::is_debug() ) {
|
||||
$this->parse( $default_value );
|
||||
}
|
||||
|
||||
$this->or( new Type_Literal( $default_value ) );
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn this schema into a nullable schema.
|
||||
* This means that the schema will accept `null` as a valid value.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function nullable() {
|
||||
$this->or( new Type_Void() );
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the input data according to the schema type.
|
||||
*
|
||||
* @param mixed $value The input data to be parsed.
|
||||
*
|
||||
* @return mixed The parsed data according to the schema type.
|
||||
* @throws Schema_Error When the input data is invalid.
|
||||
*/
|
||||
public function parse( $value, $context = null ) {
|
||||
|
||||
$context = $context ?? $this->context ?? new Schema_Context( 'unknown' );
|
||||
$context->set_data( $value );
|
||||
$parser = $this->parser;
|
||||
|
||||
try {
|
||||
$context->verbose_log(
|
||||
"Parse: {$parser}",
|
||||
array(
|
||||
'value' => $value,
|
||||
)
|
||||
);
|
||||
return $parser->parse( $value, $context );
|
||||
} catch ( Schema_Error $e ) {
|
||||
$context->log( "Schema_Error: {$this}->parse failed.", array(), $e );
|
||||
throw new Schema_Error( $e->getMessage(), $e->get_value(), $context );
|
||||
}
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return $this->schema();
|
||||
}
|
||||
|
||||
public function schema() {
|
||||
return $this->parser->schema();
|
||||
}
|
||||
|
||||
public function has_fallback() {
|
||||
try {
|
||||
$this->get_fallback();
|
||||
return true;
|
||||
} catch ( Schema_Error $e ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function get_fallback() {
|
||||
if ( $this->parser instanceof Modifier_Fallback ) {
|
||||
$parsers = $this->parser->get_parsers();
|
||||
foreach ( $parsers as $parser ) {
|
||||
if ( $parser instanceof Type_Literal ) {
|
||||
return $parser->parse( null, $this->context );
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Schema_Error( 'No fallback value defined for this schema', null, $this->context );
|
||||
}
|
||||
|
||||
public function get_log() {
|
||||
return $this->context->get_log() ?? array();
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema;
|
||||
|
||||
use Automattic\Jetpack\Schema\Modifiers\Modifier_Fallback;
|
||||
use Automattic\Jetpack\Schema\Types\Type_Any;
|
||||
use Automattic\Jetpack\Schema\Types\Type_Any_JSON;
|
||||
use Automattic\Jetpack\Schema\Types\Type_Array;
|
||||
use Automattic\Jetpack\Schema\Types\Type_Assoc_Array;
|
||||
use Automattic\Jetpack\Schema\Types\Type_Boolean;
|
||||
use Automattic\Jetpack\Schema\Types\Type_Enum;
|
||||
use Automattic\Jetpack\Schema\Types\Type_Float;
|
||||
use Automattic\Jetpack\Schema\Types\Type_Number;
|
||||
use Automattic\Jetpack\Schema\Types\Type_String;
|
||||
use Automattic\Jetpack\Schema\Types\Type_Void;
|
||||
|
||||
/**
|
||||
* The Schema class is a factory for creating and managing validation rules based on specific
|
||||
* schema types. It is a central point for defining the structure of your data
|
||||
* and ensuring that it conforms to the expected format.
|
||||
*
|
||||
* The class provides static methods for creating Schema instances with different data types,
|
||||
* making it easy to build complex validation rules with a clean and readable syntax.
|
||||
*
|
||||
* The purpose of the Schema class is to provide a consistent way to parse structured data.
|
||||
*
|
||||
* Use this to ensure that your data adheres to the defined schema and avoid issues
|
||||
* caused by incorrect data types or missing properties.
|
||||
*
|
||||
* How to use the Schema class:
|
||||
*
|
||||
* 1. Define your schema structure using the static methods provided by the Schema
|
||||
* class, such as `as_string()`, `as_array()`, `as_boolean()`, `as_number()`,
|
||||
* `as_float()`, and `enum()`. These methods return Schema instances
|
||||
* for the respective data types.
|
||||
*
|
||||
* 2. Optionally, you can chain additional methods on the Schema instances
|
||||
* to further customize the validation behavior. For example, you can use
|
||||
* `fallback()` to specify a default value when the input data is invalid or
|
||||
* `nullable()` to allow null values.
|
||||
*
|
||||
* 3. Parse your data using the Schema instances. Call the `parse()` method on the
|
||||
* Schema instance, passing in the input data to be parsed.
|
||||
* The method will return the parsed data or a default
|
||||
* value (if specified) when the input data is invalid.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* $my_schema = Schema::as_array(
|
||||
* [
|
||||
* 'name' => Schema::as_string(),
|
||||
* 'age' => Schema::as_number()->fallback(0),
|
||||
* 'is_active' => Schema::as_boolean()->nullable(),
|
||||
* 'tags' => Schema::as_array(Schema::as_string())
|
||||
* ]
|
||||
* );
|
||||
*
|
||||
* $input_data = [
|
||||
* 'name' => 'John Doe',
|
||||
* 'age' => 30,
|
||||
* 'is_active' => null,
|
||||
* 'tags' => ['tag1', 'tag2']
|
||||
* ];
|
||||
*
|
||||
* $parsed_data = $my_schema->parse($input_data);
|
||||
*/
|
||||
class Schema {
|
||||
const PACKAGE_VERSION = '0.2.8';
|
||||
|
||||
public static function as_string() {
|
||||
return new Schema_Parser( new Type_String() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Parser $parser - The parser to apply to each array item when $data is parsed.
|
||||
*
|
||||
* @return Schema_Parser
|
||||
*/
|
||||
public static function as_array( Parser $parser ) {
|
||||
return new Schema_Parser( new Type_Array( $parser ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $assoc_parser_array - An associative array of ["key" => "Parser"] pairs
|
||||
*
|
||||
* @return Schema_Parser
|
||||
*/
|
||||
public static function as_assoc_array( $assoc_parser_array ) {
|
||||
return new Schema_Parser( new Type_Assoc_Array( $assoc_parser_array ) );
|
||||
}
|
||||
|
||||
public static function as_boolean() {
|
||||
return new Schema_Parser( new Type_Boolean() );
|
||||
}
|
||||
|
||||
public static function as_number() {
|
||||
return new Schema_Parser( new Type_Number() );
|
||||
}
|
||||
|
||||
public static function as_float() {
|
||||
return new Schema_Parser( new Type_Float() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $allowed_values - An array of values that are allowed for this enum.
|
||||
*
|
||||
* @return Schema_Parser
|
||||
*/
|
||||
public static function enum( $allowed_values ) {
|
||||
return new Schema_Parser( new Type_Enum( $allowed_values ) );
|
||||
}
|
||||
|
||||
public static function any_json_data() {
|
||||
return new Schema_Parser( new Type_Any_JSON() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a schema as void - it should have no data worth keeping, and
|
||||
* will always parse to null.
|
||||
*/
|
||||
public static function as_void() {
|
||||
return new Schema_Parser( new Type_Void() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Use With Caution! This will not parse the data - it will simply return it as-is.
|
||||
* This is useful for delivering read-only data that we don't need to parse server-side.
|
||||
*
|
||||
* @see Type_Any
|
||||
*/
|
||||
public static function as_unsafe_any() {
|
||||
return new Schema_Parser( new Type_Any() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @var \Automattic\Jetpack\Schema\Parser $parser - The parser to apply to each array item when $data is parsed.
|
||||
*/
|
||||
public static function either( ...$parsers ) {
|
||||
$or = new Modifier_Fallback();
|
||||
foreach ( $parsers as $parser ) {
|
||||
$or->add_fallback_parser( $parser );
|
||||
}
|
||||
return new Schema_Parser( $or );
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema;
|
||||
|
||||
class Utils {
|
||||
|
||||
private static $mode;
|
||||
|
||||
/**
|
||||
* Is the current environment a development environment?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_debug(): bool {
|
||||
return (
|
||||
( defined( 'SCHEMA_DEBUG' ) && \SCHEMA_DEBUG )
|
||||
||
|
||||
self::$mode === 'debug'
|
||||
||
|
||||
self::$mode === 'debug-verbose'
|
||||
);
|
||||
}
|
||||
|
||||
public static function is_verbose() {
|
||||
return (
|
||||
self::is_debug()
|
||||
&&
|
||||
(
|
||||
self::$mode === 'debug-verbose'
|
||||
||
|
||||
( defined( 'SCHEMA_VERBOSE' ) && \SCHEMA_VERBOSE )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function set_mode( $mode ) {
|
||||
$valid_modes = array( 'debug', 'debug-verbose', null );
|
||||
|
||||
if ( ! in_array( $mode, $valid_modes, true ) ) {
|
||||
self::$mode = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
self::$mode = $mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The schema generated by the parser can be very verbose and hard to read.
|
||||
* This is a helper utility that converts the schema into a more human-readable format.
|
||||
*
|
||||
* @param Parser $parser The parser instance.
|
||||
* This is the same parser instance that was used to generate the schema.
|
||||
* It is used to convert the schema into a human-readable format.
|
||||
* @param bool $encode Whether to encode the schema as a JSON string.
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public static function describe(
|
||||
Parser $parser,
|
||||
$encode = false
|
||||
) {
|
||||
// Process the top-level schema array
|
||||
$description = self::human_readable_schema( $parser->schema() );
|
||||
|
||||
if ( $encode === true ) {
|
||||
return $description;
|
||||
}
|
||||
// Convert the processed schema to a JSON-like string
|
||||
return wp_json_encode( $description, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive function that will crawl through the schema,
|
||||
* and call on itself to process each sub-item.
|
||||
*
|
||||
* @param mixed $item The item to process.
|
||||
*
|
||||
* @return mixed The processed item.
|
||||
*/
|
||||
private static function human_readable_schema(
|
||||
$item
|
||||
) {
|
||||
// If the item is an associative array with 'type' as a key, return its value directly
|
||||
if ( is_array( $item ) && isset( $item['type'] ) && count( $item ) === 1 ) {
|
||||
return $item['type'];
|
||||
}
|
||||
|
||||
// If the item is an associative array with 'type' and 'value', process the value
|
||||
if ( isset( $item['type'], $item['value'] ) && is_array( $item ) ) {
|
||||
return self::human_readable_schema( $item['value'] );
|
||||
}
|
||||
|
||||
// If the item is any other kind of array, process each sub-item
|
||||
if ( is_array( $item ) ) {
|
||||
$result = array();
|
||||
foreach ( $item as $key => $value ) {
|
||||
$result[ $key ] = self::human_readable_schema( $value );
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema;
|
||||
|
||||
/**
|
||||
* The Parser interface defines the contract for schema type classes.
|
||||
*
|
||||
* Each schema type class implementing this interface should provide a parse
|
||||
* method to handle converting the input value to the correct data type.
|
||||
*
|
||||
* The purpose of the interface is to provide a consistent way to work with
|
||||
* different schema types, ensuring that all schema type classes have a common
|
||||
* method to perform the necessary data handling.
|
||||
*/
|
||||
interface Parser extends \JsonSerializable {
|
||||
/**
|
||||
* The parse method t is responsible for parsing input value.
|
||||
*
|
||||
* If the input value is valid, the method should return the parsed value.
|
||||
* If the input value is invalid, the method should return a default value.
|
||||
* or throw an exception, depending on the implementation.
|
||||
*
|
||||
* @param mixed $value The input value to be parsed.
|
||||
* @param Schema_Context $context Schema validation metadata.
|
||||
*
|
||||
* @return mixed The parsed value.
|
||||
* @throws \RuntimeException If the input value is invalid.
|
||||
*/
|
||||
public function parse( $value, $context );
|
||||
|
||||
/**
|
||||
* The describe method is responsible for returning a description of the schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function schema();
|
||||
|
||||
/**
|
||||
* The __toString method is responsible for returning a string representation of the schema.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString();
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema\Modifiers;
|
||||
|
||||
use Automattic\Jetpack\Schema\Parser;
|
||||
use Automattic\Jetpack\Schema\Schema_Error;
|
||||
use Automattic\Jetpack\Schema\Utils;
|
||||
|
||||
class Modifier_Fallback implements Parser {
|
||||
private $parsers = array();
|
||||
|
||||
public function add_fallback_parser( Parser $parser ) {
|
||||
$this->parsers[] = $parser;
|
||||
}
|
||||
|
||||
public function parse( $value, $context ) {
|
||||
$parsers_failed = array();
|
||||
foreach ( $this->parsers as $key => $parser ) {
|
||||
try {
|
||||
// Attempt to parse the value with the current parser
|
||||
return $parser->parse( $value, $context );
|
||||
} catch ( Schema_Error $error ) {
|
||||
|
||||
if ( Utils::is_debug() ) {
|
||||
$next_parser = $this->parsers[ $key + 1 ] ?? 'none';
|
||||
$data = array(
|
||||
'parser' => (string) $parser,
|
||||
'next_parser' => (string) $next_parser,
|
||||
'parsers_available' => $this->parsers,
|
||||
);
|
||||
$value_type = gettype( $value );
|
||||
$context->log( "Fallback($parser): Failed to parse $value_type.", $data, $error );
|
||||
}
|
||||
|
||||
$parsers_failed[] = (string) $parser;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$message = 'Failed to parse value using: ' . implode( ' or ', $parsers_failed );
|
||||
|
||||
// If none of the parsers succeed, throw Schema_Error
|
||||
throw new Schema_Error( $message, $value );
|
||||
}
|
||||
|
||||
public function get_parsers() {
|
||||
return $this->parsers;
|
||||
}
|
||||
|
||||
public function schema() {
|
||||
return array(
|
||||
'type' => 'or',
|
||||
'value' => array_map(
|
||||
function ( $parser ) {
|
||||
return $parser->schema();
|
||||
},
|
||||
$this->parsers
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
$result = array();
|
||||
foreach ( $this->parsers as $parser ) {
|
||||
$result[] = (string) $parser;
|
||||
}
|
||||
return implode( ' OR ', $result );
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return $this->schema();
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema\Types;
|
||||
|
||||
use Automattic\Jetpack\Schema\Parser;
|
||||
use Automattic\Jetpack\Schema\Schema_Error;
|
||||
|
||||
class Type_Any_JSON implements Parser {
|
||||
public function parse( $value, $_context ) {
|
||||
|
||||
if ( ! is_array( $value ) ) {
|
||||
$message = 'JSON Data must be an array';
|
||||
throw new Schema_Error( $message, $value );
|
||||
}
|
||||
// Attempt to encode the JSON data and throw errors if it fails
|
||||
if ( false === wp_json_encode( $value, JSON_UNESCAPED_SLASHES ) ) {
|
||||
$message = 'JSON Data must be valid JSON';
|
||||
throw new Schema_Error( $message, $value );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return 'any_json';
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return $this->schema();
|
||||
}
|
||||
public function schema() {
|
||||
return array(
|
||||
'type' => (string) $this,
|
||||
);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema\Types;
|
||||
|
||||
use Automattic\Jetpack\Schema\Parser;
|
||||
|
||||
/**
|
||||
* ! USE WITH CAUTION !
|
||||
* This schema will not parse values. Use only when you're sure that the data can be trusted.
|
||||
* For example - this can be used to deliver readonly data to the client.
|
||||
*/
|
||||
class Type_Any implements Parser {
|
||||
|
||||
public function parse( $value, $_context ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return 'any';
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return $this->schema();
|
||||
}
|
||||
public function schema() {
|
||||
return array(
|
||||
'type' => (string) $this,
|
||||
);
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema\Types;
|
||||
|
||||
use Automattic\Jetpack\Schema\Parser;
|
||||
use Automattic\Jetpack\Schema\Schema_Context;
|
||||
use Automattic\Jetpack\Schema\Schema_Error;
|
||||
|
||||
class Type_Array implements Parser {
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* Array type takes in a parser in the constructor and
|
||||
* will parse each value in the array using the parser.
|
||||
*
|
||||
* @param Parser $parser - the parser to use.
|
||||
*/
|
||||
public function __construct( Parser $parser ) {
|
||||
$this->parser = $parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* This parse method expects that the $data passed to it is
|
||||
* an array of other Parser instances.
|
||||
*
|
||||
* @param array $value - an array of something to be parsed.
|
||||
* @param Schema_Context $context - Schema context.
|
||||
*
|
||||
* @return array
|
||||
* @throws Schema_Error If $value is not an array.
|
||||
*/
|
||||
public function parse( $value, $context ) {
|
||||
if ( ! is_array( $value ) ) {
|
||||
$message = "Expected an array, received '" . gettype( $value ) . "'";
|
||||
throw new Schema_Error( $message, $value );
|
||||
}
|
||||
|
||||
$parsed = array();
|
||||
foreach ( $value as $key => $item ) {
|
||||
$parsed[ $key ] = $this->parser->parse( $item, $context );
|
||||
}
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return "array({$this->parser})";
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return $this->schema();
|
||||
}
|
||||
|
||||
public function schema() {
|
||||
return array(
|
||||
'type' => 'array',
|
||||
'value' => $this->parser->schema(),
|
||||
);
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema\Types;
|
||||
|
||||
use Automattic\Jetpack\Schema\Parser;
|
||||
use Automattic\Jetpack\Schema\Schema_Error;
|
||||
use Automattic\Jetpack\Schema\Utils;
|
||||
|
||||
class Type_Assoc_Array implements Parser {
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* Assoc Array type takes in a parser in the constructor and
|
||||
* will parse each keyed value in the array using the parser.
|
||||
*
|
||||
* @param Parser[] $assoc_parser_array - An associative array of parsers to use.
|
||||
* @throws Schema_Error - Only in Debug mode: if the $assoc_parser_array is not an associative array.
|
||||
*/
|
||||
public function __construct( $assoc_parser_array ) {
|
||||
$this->parser = $assoc_parser_array;
|
||||
if ( ! is_array( $assoc_parser_array ) && Utils::is_debug() ) {
|
||||
$message = "Expected an associative array of parsers, received '" . gettype( $assoc_parser_array ) . "'";
|
||||
throw new Schema_Error( $message, $assoc_parser_array );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This parse method expects that the $data passed to it is
|
||||
* an associative array of values.
|
||||
*
|
||||
* It will then loop over each key that was provided in the constructor
|
||||
* and pull the value based on that key from the $data array.
|
||||
*
|
||||
* @param array|object $value
|
||||
*
|
||||
* @return array
|
||||
* @throws Schema_Error - If the $data passed to it is not an associative array.
|
||||
*/
|
||||
public function parse( $value, $context ) {
|
||||
// Allow coercing stdClass objects (often returned from json_decode) to an assoc array.
|
||||
if ( is_object( $value ) && $value instanceof \stdClass ) {
|
||||
$value = (array) $value;
|
||||
}
|
||||
|
||||
if ( ! is_array( $value ) || $this->is_sequential_array( $value ) ) {
|
||||
$message = "Expected an associative array, received '" . gettype( $value ) . "'";
|
||||
throw new Schema_Error( $message, $value );
|
||||
}
|
||||
$output = array();
|
||||
foreach ( $this->parser as $key => $parser ) {
|
||||
|
||||
if ( null !== $context ) {
|
||||
$context->add_to_path( $key );
|
||||
}
|
||||
|
||||
if ( ! isset( $value[ $key ] ) ) {
|
||||
$value[ $key ] = null;
|
||||
}
|
||||
|
||||
$parsed = $parser->parse( $value[ $key ], $context );
|
||||
// @TODO Document this behavior.
|
||||
// At the moment, values that are null are dropped from assoc arrays.
|
||||
// to match the Zod behavior.
|
||||
if ( $parsed !== null ) {
|
||||
$output[ $key ] = $parsed;
|
||||
}
|
||||
|
||||
if ( null !== $context ) {
|
||||
$context->remove_path( $key );
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
private function is_sequential_array( $arr ) {
|
||||
if ( array() === $arr ) {
|
||||
return false;
|
||||
}
|
||||
return array_keys( $arr ) === range( 0, count( $arr ) - 1 );
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return 'assoc_array';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return $this->schema();
|
||||
}
|
||||
|
||||
public function schema() {
|
||||
$results = array();
|
||||
foreach ( $this->parser as $key => $parser ) {
|
||||
$results[ $key ] = $parser->schema();
|
||||
}
|
||||
return array(
|
||||
'type' => 'assoc_array',
|
||||
'value' => $results,
|
||||
);
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema\Types;
|
||||
|
||||
use Automattic\Jetpack\Schema\Parser;
|
||||
use Automattic\Jetpack\Schema\Schema_Error;
|
||||
|
||||
class Type_Boolean implements Parser {
|
||||
public function parse( $value, $_context ) {
|
||||
if ( is_bool( $value ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$loose_values = array(
|
||||
// Numbers used as booleans
|
||||
'1',
|
||||
'0',
|
||||
1,
|
||||
0,
|
||||
// WordPress can return empty string for false.
|
||||
'',
|
||||
);
|
||||
if ( ! in_array( $value, $loose_values, true ) ) {
|
||||
throw new Schema_Error( 'Invalid boolean value', $value );
|
||||
}
|
||||
return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return 'boolean';
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return $this->schema();
|
||||
}
|
||||
|
||||
public function schema() {
|
||||
return array(
|
||||
'type' => (string) $this,
|
||||
);
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema\Types;
|
||||
|
||||
use Automattic\Jetpack\Schema\Parser;
|
||||
use Automattic\Jetpack\Schema\Schema_Error;
|
||||
|
||||
class Type_Enum implements Parser {
|
||||
|
||||
/**
|
||||
* @var $valid_values array The list of valid values for the enum.
|
||||
*/
|
||||
protected $valid_values;
|
||||
|
||||
public function __construct( $valid_values ) {
|
||||
$this->valid_values = $valid_values;
|
||||
}
|
||||
|
||||
public function parse( $value, $_context ) {
|
||||
if ( ! in_array( $value, $this->valid_values, true ) ) {
|
||||
$message = sprintf( 'Invalid value \'%s\'. Expected one of: %s', $value, implode( ', ', $this->valid_values ) );
|
||||
throw new Schema_Error( $message, $value );
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
$valid_values = implode( ',', $this->valid_values );
|
||||
return "enum($valid_values)";
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return $this->schema();
|
||||
}
|
||||
|
||||
public function schema() {
|
||||
$valid_values = $this->valid_values;
|
||||
foreach ( $valid_values as $key => $value ) {
|
||||
if ( is_object( $value ) && method_exists( $value, 'schema' ) ) {
|
||||
$valid_values[ $key ] = $value->schema();
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'type' => 'enum',
|
||||
'value' => $valid_values,
|
||||
);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema\Types;
|
||||
|
||||
use Automattic\Jetpack\Schema\Parser;
|
||||
use Automattic\Jetpack\Schema\Schema_Error;
|
||||
|
||||
class Type_Float implements Parser {
|
||||
public function parse( $value, $_context ) {
|
||||
if ( ! is_numeric( $value ) ) {
|
||||
throw new Schema_Error( 'Invalid number', $value );
|
||||
}
|
||||
return (float) $value;
|
||||
}
|
||||
public function __toString() {
|
||||
return 'float';
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return $this->schema();
|
||||
}
|
||||
public function schema() {
|
||||
return array(
|
||||
'type' => (string) $this,
|
||||
);
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema\Types;
|
||||
|
||||
use Automattic\Jetpack\Schema\Parser;
|
||||
|
||||
class Type_Literal implements Parser {
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $literal_value;
|
||||
|
||||
public function __construct( $literal_value ) {
|
||||
$this->literal_value = $literal_value;
|
||||
}
|
||||
|
||||
public function parse( $_value, $_context ) {
|
||||
return $this->literal_value;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
if ( is_string( $this->literal_value ) ) {
|
||||
return 'literal_value("' . $this->literal_value . '")';
|
||||
}
|
||||
$type = gettype( $this->literal_value );
|
||||
return "literal_value($type)";
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return $this->schema();
|
||||
}
|
||||
|
||||
public function schema() {
|
||||
return array(
|
||||
'type' => (string) $this,
|
||||
);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema\Types;
|
||||
|
||||
use Automattic\Jetpack\Schema\Parser;
|
||||
use Automattic\Jetpack\Schema\Schema_Error;
|
||||
|
||||
class Type_Number implements Parser {
|
||||
|
||||
public function parse( $value, $_context ) {
|
||||
if ( ! is_numeric( $value ) ) {
|
||||
throw new Schema_Error( 'Invalid number', $value );
|
||||
}
|
||||
return (int) $value;
|
||||
}
|
||||
public function __toString() {
|
||||
return 'number';
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return $this->schema();
|
||||
}
|
||||
public function schema() {
|
||||
return array(
|
||||
'type' => (string) $this,
|
||||
);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema\Types;
|
||||
|
||||
use Automattic\Jetpack\Schema\Parser;
|
||||
use Automattic\Jetpack\Schema\Schema_Error;
|
||||
|
||||
class Type_String implements Parser {
|
||||
|
||||
public function parse( $value, $_context ) {
|
||||
|
||||
if ( ! is_scalar( $value ) ) {
|
||||
throw new Schema_Error( 'Expected a string, received ' . gettype( $value ), $value );
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return 'string';
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return $this->schema();
|
||||
}
|
||||
|
||||
public function schema() {
|
||||
return array(
|
||||
'type' => (string) $this,
|
||||
);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack\Schema\Types;
|
||||
|
||||
use Automattic\Jetpack\Schema\Parser;
|
||||
use Automattic\Jetpack\Schema\Schema_Error;
|
||||
use Automattic\Jetpack\Schema\Utils;
|
||||
|
||||
/**
|
||||
* This schema represents no data whatsoever. It will always return null.
|
||||
*/
|
||||
class Type_Void implements Parser {
|
||||
|
||||
public function parse( $value, $_context ) {
|
||||
if ( ! empty( $value ) && Utils::is_debug() ) {
|
||||
throw new Schema_Error( 'Void type cannot have any data.', $value );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function __toString() {
|
||||
return 'void';
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return $this->schema();
|
||||
}
|
||||
|
||||
public function schema() {
|
||||
return array(
|
||||
'type' => (string) $this,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user