Skip to content

Commit

Permalink
Better Error reporting, be more specific in error testing
Browse files Browse the repository at this point in the history
  • Loading branch information
theseer committed Jul 28, 2023
1 parent c0dae49 commit 9bab4dc
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 21 deletions.
153 changes: 133 additions & 20 deletions src/viewmodel/ViewModelRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace Templado\Engine;

use function array_key_exists;
use function array_reverse;
use function gettype;
use function is_iterable;
use function is_object;
Expand Down Expand Up @@ -104,7 +105,7 @@ private function registerPrefix(string $prefixString): void {

if (count($parts) !== 2) {
throw new ViewModelRendererException(
'Invalid prefix definition',
sprintf('Invalid prefix definition "%s"', $prefixString),
ViewModelRendererException::InvalidPrefixDefinition
);
}
Expand All @@ -128,14 +129,24 @@ private function registerPrefix(string $prefixString): void {
method_exists($this->rootModel, '__get') => $this->rootModel->{$method},

default => throw new ViewModelRendererException(
sprintf('Cannot resolve prefix request for "%s"', $method),
$this->buildExceptionMessage(
null,
$this->rootModel,
$method,
sprintf('Cannot resolve prefix request for "%s"', $method)
),
ViewModelRendererException::PrefixResolvingFailed
)
};

if (!is_object($result)) {
throw new ViewModelRendererException(
'Prefix type must be an object',
$this->buildExceptionMessage(
null,
$this->rootModel,
$method,
'Prefix type must be an object'
),
ViewModelRendererException::WrongTypeForPrefix
);
}
Expand Down Expand Up @@ -169,14 +180,24 @@ private function resolveResource(string $resource): object {
method_exists($model, '__get') => $model->{$resource},

default => throw new ViewModelRendererException(
sprintf('Cannot resolve resource request for "%s"', $resource),
$this->buildExceptionMessage(
null,
$this->rootModel,
$resource,
'Cannot resolve resource request'
),
ViewModelRendererException::ResourceResolvingFailed
)
};

if (!is_object($result)) {
throw new ViewModelRendererException(
'Resouce type must be a object',
$this->buildExceptionMessage(
null,
$this->rootModel,
$resource,
sprintf('Resouce type "%s" not supported - must be a object', gettype($result))
),
ViewModelRendererException::WrongTypeForResource
);
}
Expand All @@ -187,7 +208,7 @@ private function resolveResource(string $resource): object {
private function modelForPrefix(string $prefix): ?object {
if (!array_key_exists($prefix, $this->prefixModels)) {
throw new ViewModelRendererException(
'No model set for prefix',
sprintf('No model set for prefix "%s"', $prefix),
ViewModelRendererException::NoModelForPrefix
);
}
Expand All @@ -211,7 +232,12 @@ private function modelSupportsVocab(object $model, string $requiredVocab): void

if (!is_string($modelVocab)) {
throw new ViewModelRendererException(
'Result of vocab query must be of type string',
$this->buildExceptionMessage(
null,
$model,
'vocab',
sprintf('Unsupported type "%s" - result of vocab query must be of type string', gettype($modelVocab))
),
ViewModelRendererException::WrongTypeForVocab
);
}
Expand Down Expand Up @@ -258,15 +284,25 @@ private function processProperty(DOMElement $context, object $model): object {
method_exists($model, '__get') => $model->{$property},

default => throw new ViewModelRendererException(
sprintf('Cannot resolve property request for "%s"', $property),
$this->buildExceptionMessage(
$context,
$model,
$property,
'No accessor for property'
),
ViewModelRendererException::ResolvingPropertyFailed
)
};

if ($context->hasAttribute('typeof')) {
if (!is_iterable($result) && !is_object($result)) {
throw new ViewModelRendererException(
'TypeOf handling requires object / list of objects',
$this->buildExceptionMessage(
$context,
$model,
$property,
sprintf('Unsupported type "%s" - typeOf handling requires object / list of objects', gettype($result))
),
ViewModelRendererException::WrongTypeForTypeOf
);
}
Expand Down Expand Up @@ -306,7 +342,12 @@ private function processProperty(DOMElement $context, object $model): object {
}

throw new ViewModelRendererException(
'Unsupported type',
$this->buildExceptionMessage(
$context,
$model,
$property,
sprintf('Unsupported type "%s"', gettype($result))
),
ViewModelRendererException::UnsupportedTypeForProperty
);
}
Expand All @@ -324,30 +365,37 @@ private function conditionalApply(DOMElement $context, object|iterable $model):
foreach ($model as $current) {
if (!is_object($current)) {
throw new ViewModelRendererException(
'Model must be an object when used for type of checks',
sprintf('Model must be an object when used for type of checks (%s)', $this->getModelPath($context)),
ViewModelRendererException::WrongTypeForTypeOf
);
}

if (!method_exists($current, 'typeOf')) {
throw new ViewModelRendererException(
'Model must provide method typeOf for type of checks',
sprintf('Model must provide method typeOf for type of checks (%s)', $this->getModelPath($context)),
ViewModelRendererException::TypeOfMethodRequired
);
}

$typeOf = $current->typeOf();

$matches = $this->xp->query(
sprintf(
'following-sibling::*[@property="%s" and @typeof="%s"]',
$context->getAttribute('property'),
$current->typeOf()
$typeOf
),
$myPointer
);

if ($matches->count() === 0) {
throw new ViewModelRendererException(
'No matching types found',
$this->buildExceptionMessage(
$context,
$current,
'typeOf',
sprintf('No matching types for "%s" found', $typeOf)
),
ViewModelRendererException::NoMatch
);
}
Expand Down Expand Up @@ -390,8 +438,13 @@ private function iterableApply(DOMElement $context, iterable $list): void {

if ($context->isSameNode($ownerDocument->documentElement)) {
throw new ViewModelRendererException(
'Cannot apply multiple on root element',
ViewModelRendererException::MultipleRootElements
$this->buildExceptionMessage(
$context,
$this->rootModel,
'???',
'Cannot apply multiple models to root element'
),
ViewModelRendererException::IterableForRootElement
);
}

Expand All @@ -400,7 +453,7 @@ private function iterableApply(DOMElement $context, iterable $list): void {

$myPointer = $parent->insertBefore($this->pointer->cloneNode(), $context);

foreach ($list as $model) {
foreach ($list as $pos => $model) {
$clone = $context->cloneNode(true);
$parent->insertBefore($clone, $myPointer);

Expand Down Expand Up @@ -430,7 +483,11 @@ private function iterableApply(DOMElement $context, iterable $list): void {
}

throw new ViewModelRendererException(
'Unsupported type of model in list',
sprintf(
'Unsupported type "%s" in list (%s)',
gettype($model),
$this->getModelPath($context) . '[' . $pos . ']'
),
ViewModelRendererException::UnsupportedTypeForProperty
);
}
Expand Down Expand Up @@ -462,7 +519,12 @@ private function objectApply(DOMElement $context, object $model): void {

if (!is_string($textContent)) {
throw new ViewModelRendererException(
'Cannot use non string type for text content',
$this->buildExceptionMessage(
$context,
$model,
'asString',
sprintf('Cannot use non string type (%s) for text content', gettype($textContent))
),
ViewModelRendererException::StringRequired
);
}
Expand Down Expand Up @@ -515,7 +577,12 @@ private function objectApply(DOMElement $context, object $model): void {
}

throw new ViewModelRendererException(
\sprintf('Unsupported type "%s" for attribute', gettype($result)),
$this->buildExceptionMessage(
$context,
$model,
$name,
\sprintf('Unsupported type "%s" for attribute', gettype($result)),
),
ViewModelRendererException::WrongTypeForAttribute
);
}
Expand All @@ -540,4 +607,50 @@ private function documentApply(DOMElement $context, Document $model): void {
);
}
}

private function getModelPath(DOMElement $context): string {
$list = [$context->getAttribute('property')];

while ($context = $context->parentNode) {
if ($context instanceof DOMDocument) {
break;
}
assert($context instanceof DOMElement);

if (!$context->hasAttribute('property')) {
continue;
}

$property = $context->getAttribute('property');
$pos = 0;
$sibling = $context;

while ($sibling = $sibling->previousSibling) {
assert($sibling instanceof DOMElement);

if ($sibling->getAttribute('property') === $property) {
$pos++;
}
}

if ($pos > 0) {
$property .= '[' . $pos . ']';
}

$list[] = $property;
}

$list[] = 'root';

return implode(' > ', array_reverse($list));
}

private function buildExceptionMessage(?DOMElement $context, object $model, string $method, string $message): string {
return \sprintf(
'%s: %s (%s)',
$model::class . '::' . $method,
$message,
$context !== null ? $this->getModelPath($context) : 'root > ' . $method
);
}
}
2 changes: 1 addition & 1 deletion src/viewmodel/ViewModelRendererException.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ final class ViewModelRendererException extends Exception {
public const UnsupportedTypeForProperty = 12;
public const TypeOfMethodRequired = 13;
public const NoMatch = 14;
public const MultipleRootElements = 15;
public const IterableForRootElement = 15;
public const StringRequired = 16;
public const WrongTypeForAttribute = 17;
}
Loading

0 comments on commit 9bab4dc

Please sign in to comment.