This commit is contained in:
2026-07-02 15:54:39 -06:00
commit 9883323161
17470 changed files with 4470592 additions and 0 deletions
@@ -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
@@ -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;
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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 );
}
}
@@ -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;
}
}
@@ -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();
}
@@ -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();
}
}
@@ -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,
);
}
}
@@ -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,
);
}
}
@@ -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(),
);
}
}
@@ -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,
);
}
}
@@ -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,
);
}
}
@@ -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,
);
}
}
@@ -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,
);
}
}
@@ -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,
);
}
}
@@ -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,
);
}
}
@@ -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,
);
}
}
@@ -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,
);
}
}