From 6ef2fc99b19dce24e032556749de14947187a5a6 Mon Sep 17 00:00:00 2001 From: Sam Burba Date: Wed, 17 Apr 2019 19:22:49 -0400 Subject: [PATCH] Rename JsonContext to JsonPath --- docs/index.md | 8 +-- src/Adapter.php | 4 +- src/ArrayAdapter.php | 6 +- src/InvalidConfigurationException.php | 4 +- src/JsonContext.php | 44 ------------- src/JsonFormatException.php | 4 +- src/JsonPath.php | 48 ++++++++++++++ src/StrictJson.php | 66 +++++++++---------- src/Type.php | 8 +-- .../Adapters/AdapterThatSupportsNoTypes.php | 4 +- .../AdapterThatThrowsJsonFormatException.php | 8 +-- .../AdapterThatThrowsRuntimeException.php | 4 +- .../Adapters/DefaultIfNullAdapter.php | 4 +- .../IntPropClassAdapterThatAddsFour.php | 8 +-- test/Fixtures/Docs/DateAdapter.php | 4 +- test/Fixtures/Docs/LenientBooleanAdapter.php | 4 +- test/StrictJsonTest.php | 2 +- 17 files changed, 117 insertions(+), 113 deletions(-) delete mode 100644 src/JsonContext.php create mode 100644 src/JsonPath.php diff --git a/docs/index.md b/docs/index.md index f3b14df..004d578 100644 --- a/docs/index.md +++ b/docs/index.md @@ -143,13 +143,13 @@ For example, you can create a custom class adapter like this: ```php $item) { - $mapped_items[] = $delegate->mapDecoded($item, $this->type, $context->withArrayIndex($idx)); + $mapped_items[] = $delegate->mapDecoded($item, $this->type, $path->withArrayIndex($idx)); } return $mapped_items; } diff --git a/src/InvalidConfigurationException.php b/src/InvalidConfigurationException.php index af04cda..73e520a 100644 --- a/src/InvalidConfigurationException.php +++ b/src/InvalidConfigurationException.php @@ -7,9 +7,9 @@ class InvalidConfigurationException extends RuntimeException { - public function __construct(string $message, JsonContext $context, Throwable $previous = null) + public function __construct(string $message, JsonPath $path, Throwable $previous = null) { - $message = $message . ' at path ' . $context->__toString(); + $message = $message . ' at path ' . $path->__toString(); parent::__construct($message, 0, $previous); } } diff --git a/src/JsonContext.php b/src/JsonContext.php deleted file mode 100644 index 6128026..0000000 --- a/src/JsonContext.php +++ /dev/null @@ -1,44 +0,0 @@ -context = $context; - } - - public static function root() - { - return new JsonContext(); - } - - /** - * @param int $index The array index - * @return JsonContext A new JsonContext that represents indexing into the array of the current context - */ - public function withArrayIndex(int $index) - { - return new JsonContext($this->context . "[$index]"); - } - - /** - * @param string $property_name - * @return JsonContext A new JsonContext that represents accessing a property of the current context - */ - public function withProperty(string $property_name) - { - return new JsonContext($this->context . ".$property_name"); - } - - public function __toString() - { - return $this->context == '$' ? '' : $this->context; - } -} diff --git a/src/JsonFormatException.php b/src/JsonFormatException.php index 06fe41d..c50f012 100644 --- a/src/JsonFormatException.php +++ b/src/JsonFormatException.php @@ -7,9 +7,9 @@ class JsonFormatException extends Exception { - public function __construct($message, JsonContext $context, Throwable $previous = null) + public function __construct($message, JsonPath $path, Throwable $previous = null) { - $message = $message . ' at path ' . $context->__toString(); + $message = $message . ' at path ' . $path->__toString(); parent::__construct($message, 0, $previous); } } diff --git a/src/JsonPath.php b/src/JsonPath.php new file mode 100644 index 0000000..d3cadd6 --- /dev/null +++ b/src/JsonPath.php @@ -0,0 +1,48 @@ +path = $path; + } + + /** + * A JsonPath at the root of the JSON + * @return JsonPath + */ + public static function root() + { + return new JsonPath(); + } + + /** + * @param int $index The array index + * @return JsonPath A new JsonPath that represents indexing into the array of the current path + */ + public function withArrayIndex(int $index) + { + return new JsonPath($this->path . "[$index]"); + } + + /** + * @param string $property_name + * @return JsonPath A new JsonPath that represents accessing a property of the current path + */ + public function withProperty(string $property_name) + { + return new JsonPath($this->path . ".$property_name"); + } + + public function __toString() + { + return $this->path == '$' ? '' : $this->path; + } +} diff --git a/src/StrictJson.php b/src/StrictJson.php index 9f4f91c..86b2cf0 100644 --- a/src/StrictJson.php +++ b/src/StrictJson.php @@ -58,7 +58,7 @@ public static function builder(): StrictJsonBuilder public function map(string $json, $type) { $type = $type instanceof Type ? $type : Type::ofClass($type); - return $this->mapDecoded($this->safeDecode($json), $type, JsonContext::root()); + return $this->mapDecoded($this->safeDecode($json), $type, JsonPath::root()); } /** @@ -74,7 +74,7 @@ public function map(string $json, $type) public function mapToArrayOf(string $json, $type) { $type = $type instanceof Type ? $type : Type::ofClass($type); - return $this->mapWithAdapter($this->safeDecode($json), new ArrayAdapter($type), JsonContext::root()); + return $this->mapWithAdapter($this->safeDecode($json), new ArrayAdapter($type), JsonPath::root()); } /** @@ -82,33 +82,33 @@ public function mapToArrayOf(string $json, $type) * * @param mixed $decoded_json An associative array or other primitive * @param Type $target_type Either a class name or a scalar name (i.e. string, int, float, bool) - * @param JsonContext $context The current parsing context, or null if being called at the root of the decoded JSON + * @param JsonPath $path The current JSON parsing path, or null if being called at the root of the decoded JSON * * @return mixed * @throws JsonFormatException */ - public function mapDecoded($decoded_json, Type $target_type, JsonContext $context) + public function mapDecoded($decoded_json, Type $target_type, JsonPath $path) { $adapter = $this->type_adapters[$target_type->getTypeName()] ?? null; if ($adapter !== null) { - return $this->mapWithAdapter($decoded_json, $adapter, $context); + return $this->mapWithAdapter($decoded_json, $adapter, $path); } if ($target_type->isScalar()) { - return $this->mapScalar($decoded_json, $target_type, $context); + return $this->mapScalar($decoded_json, $target_type, $path); } if ($target_type->isClass()) { - return $this->mapClass($decoded_json, $target_type, $context); + return $this->mapClass($decoded_json, $target_type, $path); } if ($target_type->isArray()) { - throw new InvalidConfigurationException("Cannot map to arrays directly, use StrictJson::mapToArrayOf()", $context); + throw new InvalidConfigurationException("Cannot map to arrays directly, use StrictJson::mapToArrayOf()", $path); } // It should be impossible to get here, Type should either be a scalar, a class, or an array //@codeCoverageIgnoreStart - throw new InvalidConfigurationException("Target type \"$target_type\" is not a scalar type or valid class and has no registered type adapter", $context); + throw new InvalidConfigurationException("Target type \"$target_type\" is not a scalar type or valid class and has no registered type adapter", $path); //@codeCoverageIgnoreEnd } @@ -117,14 +117,14 @@ public function mapDecoded($decoded_json, Type $target_type, JsonContext $contex * * @param string|int|array|bool|float $value The decoded json value to adapt * @param Adapter $adapter Object with a fromJson method - * @param JsonContext $context The current decoding context + * @param JsonPath $path The current decoding path * * @return mixed Whatever the adapter returns * @throws JsonFormatException If the provided value doesn't match the value the adapter expects */ - private function mapWithAdapter($value, Adapter $adapter, JsonContext $context) + private function mapWithAdapter($value, Adapter $adapter, JsonPath $path) { - $context = $context ?? JsonContext::root(); + $path = $path ?? JsonPath::root(); $supports_value = false; foreach ($adapter->fromTypes() as $supported_type) { @@ -147,38 +147,38 @@ private function mapWithAdapter($value, Adapter $adapter, JsonContext $context) } else { throw new InvalidConfigurationException( "Adapter $adapter_class does not support any types! (fromTypes must return an non-empty array)", - $context + $path ); } - throw new JsonFormatException("Expected $expectation, found $json_type (using $adapter_class)", $context); + throw new JsonFormatException("Expected $expectation, found $json_type (using $adapter_class)", $path); } try { - return $adapter->fromJson($value, $this, $context); + return $adapter->fromJson($value, $this, $path); } /** @noinspection PhpRedundantCatchClauseInspection */ catch (JsonFormatException $e) { throw $e; } catch (Exception $e) { $adapter_class = get_class($adapter); - throw new InvalidConfigurationException("Adapter {$adapter_class} threw an exception", $context, $e); + throw new InvalidConfigurationException("Adapter {$adapter_class} threw an exception", $path, $e); } } /** * @param $decoded_json * @param Type $target_type - * @param JsonContext $context + * @param JsonPath $path * * @return mixed * @throws JsonFormatException */ - private function mapScalar($decoded_json, Type $target_type, JsonContext $context) + private function mapScalar($decoded_json, Type $target_type, JsonPath $path) { if ($target_type->allowsValue($decoded_json)) { return $decoded_json; } else { $json_type = gettype($decoded_json); - throw new JsonFormatException("Value is of type $json_type, expected type $target_type", $context); + throw new JsonFormatException("Value is of type $json_type, expected type $target_type", $path); } } @@ -186,12 +186,12 @@ private function mapScalar($decoded_json, Type $target_type, JsonContext $contex /** * @param $parsed_json * @param Type $type - * @param JsonContext $context + * @param JsonPath $path * * @return object * @throws JsonFormatException */ - private function mapClass($parsed_json, Type $type, JsonContext $context): object + private function mapClass($parsed_json, Type $type, JsonPath $path): object { try { $class = new ReflectionClass($type->getTypeName()); @@ -199,39 +199,39 @@ private function mapClass($parsed_json, Type $type, JsonContext $context): objec } catch (ReflectionException $e) { // Right now it's not possible to trigger this exception from the public API, because the only method that // calls this checks if $classname is a class first - throw new InvalidConfigurationException("Type $type is not a valid class", $context, $e); + throw new InvalidConfigurationException("Type $type is not a valid class", $path, $e); // @codeCoverageIgnoreEnd } $constructor = $class->getConstructor(); if ($constructor === null) { - throw new InvalidConfigurationException("Type $type does not have a valid constructor", $context); + throw new InvalidConfigurationException("Type $type does not have a valid constructor", $path); } $parameters = $constructor->getParameters(); $constructor_args = []; foreach ($parameters as $parameter) { $parameter_name = $parameter->getName(); if ($parameter->getType() === null) { - throw new InvalidConfigurationException("$type::__construct has parameter named $parameter_name with no specified type", $context); + throw new InvalidConfigurationException("$type::__construct has parameter named $parameter_name with no specified type", $path); } if (array_key_exists($parameter_name, $parsed_json)) { $value = $parsed_json[$parameter_name]; $adapter = $this->parameter_adapters[$type->getTypeName()][$parameter_name] ?? null; - $param_context = $context->withProperty($parameter_name); - $param_type = Type::from($parameter, $param_context); + $param_path = $path->withProperty($parameter_name); + $param_type = Type::from($parameter, $param_path); if ($adapter !== null) { - $value = $this->mapWithAdapter($value, $adapter, $param_context); + $value = $this->mapWithAdapter($value, $adapter, $param_path); } elseif ($param_type->isArray()) { // Catch the array case here, even though it would be caught in mapDecoded, because we have more // information for a more helpful error message throw new InvalidConfigurationException( "$type::__construct has parameter name $parameter_name of type array with no parameter adapter\n" . "(Use StrictJson::builder()->addArrayParameterAdapter(...) to register an array adapter for this class)", - $param_context + $param_path ); } else { - $value = $this->mapDecoded($value, Type::from($parameter, $param_context), $param_context); + $value = $this->mapDecoded($value, Type::from($parameter, $param_path), $param_path); } $constructor_args[] = $value; } elseif ($parameter->isDefaultValueAvailable()) { @@ -239,19 +239,19 @@ private function mapClass($parsed_json, Type $type, JsonContext $context): objec /** @noinspection PhpUnhandledExceptionInspection */ $constructor_args[] = $parameter->getDefaultValue(); } else { - throw new JsonFormatException("$type::__construct has non-optional parameter named $parameter_name that does not exist in JSON", $context); + throw new JsonFormatException("$type::__construct has non-optional parameter named $parameter_name that does not exist in JSON", $path); } } try { return $class->newInstanceArgs($constructor_args); } catch (InvalidArgumentException $e) { - throw new JsonFormatException("{$type->getTypeName()} threw a validation exception in the constructor", $context, $e); + throw new JsonFormatException("{$type->getTypeName()} threw a validation exception in the constructor", $path, $e); } catch (Exception $e) { $encoded_args = json_encode($constructor_args); throw new InvalidConfigurationException( "Unable to construct object of type {$type->getTypeName()} with args $encoded_args", - $context, + $path, $e ); } @@ -272,7 +272,7 @@ private function safeDecode(string $json) $err = json_last_error_msg(); throw new JsonFormatException( "Unable to parse invalid JSON ($err): $json", - JsonContext::root() + JsonPath::root() ); } return $decoded; diff --git a/src/Type.php b/src/Type.php index f06f173..3efea06 100644 --- a/src/Type.php +++ b/src/Type.php @@ -54,11 +54,11 @@ public static function bool(): Type /** * @param ReflectionParameter $parameter - * @param JsonContext $context + * @param JsonPath $path * @return Type * @internal */ - public static function from(ReflectionParameter $parameter, JsonContext $context): Type + public static function from(ReflectionParameter $parameter, JsonPath $path): Type { $parameter_type = $parameter->getType()->getName(); if ($parameter->isArray()) { @@ -68,7 +68,7 @@ public static function from(ReflectionParameter $parameter, JsonContext $context } elseif (class_exists($parameter_type)) { return new Type($parameter_type, $parameter->allowsNull()); } else { - throw new InvalidConfigurationException("Unsupported type $parameter_type", $context); + throw new InvalidConfigurationException("Unsupported type $parameter_type", $path); } } @@ -81,7 +81,7 @@ public static function from(ReflectionParameter $parameter, JsonContext $context public static function ofClass(string $class): Type { if (!class_exists($class)) { - throw new InvalidConfigurationException("Type \"$class\" is not a valid class", JsonContext::root()); + throw new InvalidConfigurationException("Type \"$class\" is not a valid class", JsonPath::root()); } return new Type($class, false); } diff --git a/test/Fixtures/Adapters/AdapterThatSupportsNoTypes.php b/test/Fixtures/Adapters/AdapterThatSupportsNoTypes.php index eb8adc6..7e482af 100644 --- a/test/Fixtures/Adapters/AdapterThatSupportsNoTypes.php +++ b/test/Fixtures/Adapters/AdapterThatSupportsNoTypes.php @@ -3,13 +3,13 @@ namespace Burba\StrictJson\Fixtures\Adapters; use Burba\StrictJson\Adapter; -use Burba\StrictJson\JsonContext; +use Burba\StrictJson\JsonPath; use Burba\StrictJson\StrictJson; use Burba\StrictJson\Type; class AdapterThatSupportsNoTypes implements Adapter { - public function fromJson($decoded_json, StrictJson $delegate, JsonContext $context): ?string + public function fromJson($decoded_json, StrictJson $delegate, JsonPath $path): ?string { return null; } diff --git a/test/Fixtures/Adapters/AdapterThatThrowsJsonFormatException.php b/test/Fixtures/Adapters/AdapterThatThrowsJsonFormatException.php index df12254..6d5e577 100644 --- a/test/Fixtures/Adapters/AdapterThatThrowsJsonFormatException.php +++ b/test/Fixtures/Adapters/AdapterThatThrowsJsonFormatException.php @@ -3,7 +3,7 @@ namespace Burba\StrictJson\Fixtures\Adapters; use Burba\StrictJson\Adapter; -use Burba\StrictJson\JsonContext; +use Burba\StrictJson\JsonPath; use Burba\StrictJson\JsonFormatException; use Burba\StrictJson\StrictJson; use Burba\StrictJson\Type; @@ -14,12 +14,12 @@ class AdapterThatThrowsJsonFormatException implements Adapter /** * @param $decoded_json * @param StrictJson $delegate - * @param JsonContext $context + * @param JsonPath $path * @throws JsonFormatException */ - public function fromJson($decoded_json, StrictJson $delegate, JsonContext $context) + public function fromJson($decoded_json, StrictJson $delegate, JsonPath $path) { - throw new JsonFormatException("I'm a very bad adapter", $context); + throw new JsonFormatException("I'm a very bad adapter", $path); } /** diff --git a/test/Fixtures/Adapters/AdapterThatThrowsRuntimeException.php b/test/Fixtures/Adapters/AdapterThatThrowsRuntimeException.php index f05320c..5d4782f 100644 --- a/test/Fixtures/Adapters/AdapterThatThrowsRuntimeException.php +++ b/test/Fixtures/Adapters/AdapterThatThrowsRuntimeException.php @@ -3,14 +3,14 @@ namespace Burba\StrictJson\Fixtures\Adapters; use Burba\StrictJson\Adapter; -use Burba\StrictJson\JsonContext; +use Burba\StrictJson\JsonPath; use Burba\StrictJson\StrictJson; use Burba\StrictJson\Type; use RuntimeException; class AdapterThatThrowsRuntimeException implements Adapter { - public function fromJson($decoded_json, StrictJson $delegate, JsonContext $context) + public function fromJson($decoded_json, StrictJson $delegate, JsonPath $path) { throw new RuntimeException("I'm a very bad adapter"); } diff --git a/test/Fixtures/Adapters/DefaultIfNullAdapter.php b/test/Fixtures/Adapters/DefaultIfNullAdapter.php index 0164dee..dcd07dd 100644 --- a/test/Fixtures/Adapters/DefaultIfNullAdapter.php +++ b/test/Fixtures/Adapters/DefaultIfNullAdapter.php @@ -3,7 +3,7 @@ namespace Burba\StrictJson\Fixtures\Adapters; use Burba\StrictJson\Adapter; -use Burba\StrictJson\JsonContext; +use Burba\StrictJson\JsonPath; use Burba\StrictJson\StrictJson; use Burba\StrictJson\Type; @@ -20,7 +20,7 @@ public function __construct(float $default_value) $this->default_value = $default_value; } - public function fromJson($decoded_json, StrictJson $delegate, JsonContext $context): float + public function fromJson($decoded_json, StrictJson $delegate, JsonPath $path): float { return $decoded_json === null ? $this->default_value : $decoded_json; } diff --git a/test/Fixtures/Adapters/IntPropClassAdapterThatAddsFour.php b/test/Fixtures/Adapters/IntPropClassAdapterThatAddsFour.php index 058b8b9..db48c8b 100644 --- a/test/Fixtures/Adapters/IntPropClassAdapterThatAddsFour.php +++ b/test/Fixtures/Adapters/IntPropClassAdapterThatAddsFour.php @@ -4,7 +4,7 @@ use Burba\StrictJson\Adapter; use Burba\StrictJson\Fixtures\HasIntProp; -use Burba\StrictJson\JsonContext; +use Burba\StrictJson\JsonPath; use Burba\StrictJson\JsonFormatException; use Burba\StrictJson\StrictJson; use Burba\StrictJson\Type; @@ -14,17 +14,17 @@ class IntPropClassAdapterThatAddsFour implements Adapter /** * @param array $decoded_json * @param StrictJson $delegate - * @param JsonContext $context + * @param JsonPath $path * @return HasIntProp * * @throws JsonFormatException */ - public function fromJson($decoded_json, StrictJson $delegate, JsonContext $context): HasIntProp + public function fromJson($decoded_json, StrictJson $delegate, JsonPath $path): HasIntProp { $original_number = $delegate->mapDecoded( $decoded_json['int_prop'], Type::int(), - $context->withProperty('int_prop') + $path->withProperty('int_prop') ); return new HasIntProp($original_number + 4); } diff --git a/test/Fixtures/Docs/DateAdapter.php b/test/Fixtures/Docs/DateAdapter.php index 0543455..dceeb5f 100644 --- a/test/Fixtures/Docs/DateAdapter.php +++ b/test/Fixtures/Docs/DateAdapter.php @@ -3,14 +3,14 @@ namespace Burba\StrictJson\Fixtures\Docs; use Burba\StrictJson\Adapter; -use Burba\StrictJson\JsonContext; +use Burba\StrictJson\JsonPath; use Burba\StrictJson\StrictJson; use Burba\StrictJson\Type; use DateTime; class DateAdapter implements Adapter { - public function fromJson($decoded_json, StrictJson $delegate, JsonContext $context): DateTime + public function fromJson($decoded_json, StrictJson $delegate, JsonPath $path): DateTime { return DateTime::createFromFormat(DateTime::ISO8601, $decoded_json); } diff --git a/test/Fixtures/Docs/LenientBooleanAdapter.php b/test/Fixtures/Docs/LenientBooleanAdapter.php index 3574acb..a0d549c 100644 --- a/test/Fixtures/Docs/LenientBooleanAdapter.php +++ b/test/Fixtures/Docs/LenientBooleanAdapter.php @@ -3,13 +3,13 @@ namespace Burba\StrictJson\Fixtures\Docs; use Burba\StrictJson\Adapter; -use Burba\StrictJson\JsonContext; +use Burba\StrictJson\JsonPath; use Burba\StrictJson\StrictJson; use Burba\StrictJson\Type; class LenientBooleanAdapter implements Adapter { - public function fromJson($decoded_json, StrictJson $delegate, JsonContext $context): bool + public function fromJson($decoded_json, StrictJson $delegate, JsonPath $path): bool { return (bool)$decoded_json; } diff --git a/test/StrictJsonTest.php b/test/StrictJsonTest.php index 4f6d237..aa2bf50 100644 --- a/test/StrictJsonTest.php +++ b/test/StrictJsonTest.php @@ -78,7 +78,7 @@ public function testMapDecodedWithArray() $mapper = new StrictJson(); $this->expectException(InvalidConfigurationException::class); $this->expectExceptionMessage('Cannot map to arrays directly, use StrictJson::mapToArrayOf() at path '); - $mapper->mapDecoded($decoded_json, Type::array(), JsonContext::root()); + $mapper->mapDecoded($decoded_json, Type::array(), JsonPath::root()); } /**