From ba74c5a38e8f94246bfb02aab817583796435c6a Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Mon, 12 Jan 2026 21:18:10 +0000 Subject: [PATCH] - Some minor on request debugger --- skeleton/EntityGenerate/DeleteFormContext.php | 2 +- src/Attribute/ContextField.php | 7 +- src/Entity/Error.php | 79 --------------- src/Entity/Field/Date.php | 8 ++ src/Entity/Field/Datetime.php | 11 +++ src/Entity/Field/Time.php | 8 ++ src/EntityDescriptor.php | 16 +++ src/Form/Delete.php | 15 ++- src/Form/Save.php | 12 ++- src/FormDescriptor.php | 2 + src/LeanApiTrait.php | 99 ++++++++++++------- src/Middleware/ApiRenderer.php | 2 +- view/lean-api/entity_descriptor.phtml | 91 ++++++++++------- view/lean-api/form_descriptor.phtml | 71 ++++++------- view/lean-api/request_debugger.phtml | 16 ++- view/lean-api/route_descriptor.phtml | 5 +- view/lean/layout/docs.phtml | 13 ++- 17 files changed, 255 insertions(+), 202 deletions(-) delete mode 100644 src/Entity/Error.php create mode 100644 src/Entity/Field/Date.php create mode 100644 src/Entity/Field/Datetime.php create mode 100644 src/Entity/Field/Time.php diff --git a/skeleton/EntityGenerate/DeleteFormContext.php b/skeleton/EntityGenerate/DeleteFormContext.php index 2a5df04..27baa44 100644 --- a/skeleton/EntityGenerate/DeleteFormContext.php +++ b/skeleton/EntityGenerate/DeleteFormContext.php @@ -13,7 +13,7 @@ class %DELETE_FORM_CONTEXT_CLASSNAME% extends \Lean\Api\Lib\FormContext public function valid(? %ENTITY_NS%\%CLASSNAME% $entity = null) : bool { - if ( ! $this->getEntity()->isLoaded() ) { + if ( $entity === null ) { $this->pushErrorMessage('lean.api.form.delete.error.entity'); } diff --git a/src/Attribute/ContextField.php b/src/Attribute/ContextField.php index 7305f86..be963af 100644 --- a/src/Attribute/ContextField.php +++ b/src/Attribute/ContextField.php @@ -17,8 +17,8 @@ class ContextField public ?int $maxLength = null, public ?string $regexFormat = null, public ?string $example = null, - public bool $hidden = false, public mixed $default = null, + public bool $hidden = false, ) {} public function assertValueSpecs(mixed $value, bool $isNew) : void @@ -39,10 +39,7 @@ class ContextField } if ($this->regexFormat) { - if (! is_string($value)) { - throw new RegexFormatException("Property type must be a string if a regexFormat is provided."); - } - elseif (! preg_match($this->regexFormat, $value)) { + if (! preg_match($this->regexFormat, $value)) { throw new RegexFormatException("Regex format {$this->regexFormat} is not valid for value {$value}."); } } diff --git a/src/Entity/Error.php b/src/Entity/Error.php deleted file mode 100644 index 2dc2468..0000000 --- a/src/Entity/Error.php +++ /dev/null @@ -1,79 +0,0 @@ -message) > 255 ? mb_substr($this->message, 0, 255) . "[...]" : $this->message; - } - - public static function searchRequest(...$arguments) : SearchRequestInterface - { - return new #[SearchRequest\Attribute\SearchRequestParameter(Error::class)] class(...$arguments) extends SearchRequest\SearchRequest { - #[SearchRequest\Attribute\SearchWhere(source: SearchRequest\Attribute\PropertyValueSource::RequestAttribute)] - public int $id; - - #[SearchWhere(method: SearchMethodEnum::Like)] - public string $message; - - #[SearchOrderBy(parameter: "created_at",)] - public string $createdAt = SearchOrderBy::DESCENDING; - }; - } -} \ No newline at end of file diff --git a/src/Entity/Field/Date.php b/src/Entity/Field/Date.php new file mode 100644 index 0000000..7064d3f --- /dev/null +++ b/src/Entity/Field/Date.php @@ -0,0 +1,8 @@ +getTypes(); + if ($property->value ?? false) { + $default = $property->value instanceof \BackedEnum ? $property->value->value : $property->value; + } + else { + $default = $field->object->attributes['default'] ?? null; + } + $fields[] = [ 'name' => $property->name, 'description' => $entityField->object->description ?? "", @@ -45,6 +52,7 @@ class EntityDescriptor 'allowNulls' => $property->allowsNull(), 'length' => $field->object->length ?? null, 'readonly' => $field->object->readonly, + 'default' => $default, ]; } } @@ -57,8 +65,15 @@ class EntityDescriptor $field = $property->getAttribute(SearchParameter::class); if ($field) { + $possibleValues = null; $types = $property->getTypes(); + foreach($types as $type) { + if (! $type->builtIn && enum_exists($type->type)) { + $possibleValues = implode(', ', array_map(fn($e) => $e->value, $type->type::cases())); + } + } + $searchFields[] = [ 'name' => $property->name, 'description' => $field->object->description, @@ -66,6 +81,7 @@ class EntityDescriptor 'tag' => $field->tag, 'type' => implode(' | ', array_map(fn($e) => $e->type, $types)), 'allowNulls' => $property->allowsNull(), + 'possibleValues' => $possibleValues, 'default' => $this->displayValue($property->value ?? "") ]; } diff --git a/src/Form/Delete.php b/src/Form/Delete.php index 39af55c..8163b21 100644 --- a/src/Form/Delete.php +++ b/src/Form/Delete.php @@ -8,13 +8,26 @@ use Ulmus\Entity\EntityInterface; abstract class Delete extends Form implements \Picea\Ui\Method\FormInterface { - public function execute(FormContextInterface $context) : void + public function validate(FormContextInterface $context) : bool + { + if ( ! $this->getEntity()->isLoaded() ) { + $context->pushMessage($this->message::generateError( + $this->lang('lean.api.form.delete.error.entity') + )); + } + + return $context->valid($this->getEntity()->isLoaded() ? $this->getEntity() : null); + } + + public function execute(FormContextInterface $context) : mixed { try { if ( $this->getEntity()::repository()->destroy($this->getEntity()) ) { $context->pushMessage($this->message::generateSuccess( $this->lang('lean.api.form.delete.success.save') )); + + return true; } else { throw new \InvalidArgumentException($this->lang('lean.api.form.delete.error.save')); diff --git a/src/Form/Save.php b/src/Form/Save.php index dff63ba..f7a0415 100644 --- a/src/Form/Save.php +++ b/src/Form/Save.php @@ -3,7 +3,7 @@ namespace Lean\Api\Form; use CSLSJ\Lean\Form\Session\EmailContext; -use Picea\Ui\Method\{ FormContextInterface, }; +use Picea\Ui\Method\{FormContext, FormContextInterface}; use Lean\Api\Attribute\ContextField; use Lean\Api\Exception\MandatoryFieldException; @@ -19,6 +19,8 @@ abstract class Save extends Form implements \Picea\Ui\Method\FormInterface { protected array $reflectedProperties; + protected string $contextClass = FormContext::class; + public function initialize(FormContextInterface $context) : void { if ( ! $this->getEntity()->isLoaded() ) { @@ -46,12 +48,14 @@ abstract class Save extends Form implements \Picea\Ui\Method\FormInterface { if ($entity->isLoaded()) { if (property_exists($entity, 'updatedAt') && $entity->repository()->generateDatasetDiff($entity) ) { - $entity->updatedAt = new Datetime(); + $cls = $entity::resolveEntity()->field('updatedAt')->type->type; + $entity->updatedAt = new $cls(); } } else { if (property_exists($entity, 'createdAt') && empty($entity->createdAt)) { - $entity->createdAt = new Datetime(); + $cls = $entity::resolveEntity()->field('createdAt')->type->type; + $entity->createdAt = new $cls(); } } @@ -82,7 +86,7 @@ abstract class Save extends Form implements \Picea\Ui\Method\FormInterface { if ($attribute) { try { - $attribute->object->assertValueSpecs($context->$name ?? null, $this->getEntity()->isLoaded()); + $attribute->object->assertValueSpecs($context->$name ?? null, ! $this->getEntity()->isLoaded()); } catch (MandatoryFieldException $e) { throw new MandatoryFieldException("An error occured with field '$name': " . $e->getMessage()); } diff --git a/src/FormDescriptor.php b/src/FormDescriptor.php index fad6fda..409f702 100644 --- a/src/FormDescriptor.php +++ b/src/FormDescriptor.php @@ -28,6 +28,7 @@ class FormDescriptor $fields[] = [ 'name' => $property->name, + 'mandatory' => $field->object->mandatory, 'description' => $field->object->description, 'type' => $field->object->type ?? $types, 'allowNulls' => $property->allowsNull(), @@ -35,6 +36,7 @@ class FormDescriptor 'minLength' => $field->object->minLength ?? null, 'maxLength' => $field->object->maxLength ?? null, 'example' => $field->object->example ?? null, + 'default' => isset($field->object->default) ? json_encode($field->object->default) : null, 'values' => $this->generateExampleValues($types), ]; } diff --git a/src/LeanApiTrait.php b/src/LeanApiTrait.php index 80b8299..ea79fde 100644 --- a/src/LeanApiTrait.php +++ b/src/LeanApiTrait.php @@ -80,29 +80,31 @@ trait LeanApiTrait $entity = $entityType::repository()->filterServerRequest($search)->loadOne() ?? false; if (! $entity ) { - throw new \InvalidArgumentException(sprintf("L'entré pour l'entité demandé (%s) est introuvable avec le ou les arguments fournis '%s'", $entityType, json_encode($request->getAttributes()))); + throw new \InvalidArgumentException(sprintf("L'entré pour l'entité demandé (%s) est introuvable avec le ou les arguments fournis '%s'", $entityType, json_encode($request->getAttributes(), \JSON_PARTIAL_OUTPUT_ON_ERROR))); } - return $request->withAttribute("lean.searchRequest", - [ - $resultKey ?: $entityType => new class($search, $entity) implements ApiSearchRequestInterface { - public function __construct( - public readonly SearchRequestInterface $search, - public readonly EntityInterface $entity, - ) {} + if ($request->getAttribute("lean.searchRequest") === null) { + $request = $request->withAttribute("lean.searchRequest", $this->searchEntityResult()); + } - public function getSearch(): SearchRequestInterface - { - return $this->search; - } + $request->getAttribute("lean.searchRequest")[$resultKey ?: $entityType] = new class($search, $entity) implements ApiSearchRequestInterface { + public function __construct( + public readonly SearchRequestInterface $search, + public readonly EntityInterface $entity, + ) {} - public function getResult(): EntityCollection|EntityInterface - { - return $this->entity; - } - } - ] + $request->getAttribute("lean.searchRequest", []) - ); + public function getSearch(): SearchRequestInterface + { + return $this->search; + } + + public function getResult(): EntityCollection|EntityInterface + { + return $this->entity; + } + }; + + return $request; } #[Ignore] @@ -115,25 +117,50 @@ trait LeanApiTrait throw new \InvalidArgumentException(sprintf("L'entré pour l'entité demandé (%s) est introuvable avec le ou les arguments fournis '%s'", $entityType, json_encode($request->getAttributes()))); } - return $request->withAttribute("lean.searchRequest", - [ - $resultKey ?: $entityType => new class($search, $entity) implements ApiSearchRequestInterface { - public function __construct( - public readonly SearchRequestInterface $search, - public readonly EntityCollection $collection, - ) {} + if ($request->getAttribute("lean.searchRequest") === null) { + $request = $request->withAttribute("lean.searchRequest", $this->searchEntityResult()); + } - public function getSearch(): SearchRequestInterface - { - return $this->search; - } + $request->getAttribute("lean.searchRequest")[$resultKey ?: $entityType] = new class($search, $entity) implements ApiSearchRequestInterface { + public function __construct( + public readonly SearchRequestInterface $search, + public readonly EntityCollection $collection, + ) {} - public function getResult(): EntityCollection|EntityInterface - { - return $this->collection; - } + public function getSearch(): SearchRequestInterface + { + return $this->search; + } + + public function getResult(): EntityCollection|EntityInterface + { + return $this->collection; + } + }; + + return $request; + } + + #[Ignore] + protected function searchEntityResult() :\ArrayObject { + return new class()extends \ArrayObject implements ApiSearchRequestInterface { + + public function getSearch(): SearchRequestInterface + { + if ($this->count() !== 1) { + throw new \LogicException("Search results contains more than one results. Impossible to continue without precising which one you needs."); } - ] + $request->getAttribute("lean.searchRequest", []) - ); + + return $this[array_key_last($this->getArrayCopy())]->getSearch(); + } + + public function getResult() : EntityInterface|EntityCollection { + if ($this->count() !== 1) { + throw new \LogicException("Search results contains more than one results. Impossible to continue without precising which one you needs."); + } + + return $this[array_key_last($this->getArrayCopy())]->getResult(); + } + }; } } \ No newline at end of file diff --git a/src/Middleware/ApiRenderer.php b/src/Middleware/ApiRenderer.php index e6c3ef2..90b9857 100644 --- a/src/Middleware/ApiRenderer.php +++ b/src/Middleware/ApiRenderer.php @@ -24,7 +24,7 @@ class ApiRenderer implements MiddlewareInterface { $response = $handler->handle($request); } catch(\Throwable $ex) { - # $errorId = $this->saveError($request, $ex)->id; + if (static::awaitingJson($request)) { return HttpFactory::createJsonResponse([ diff --git a/view/lean-api/entity_descriptor.phtml b/view/lean-api/entity_descriptor.phtml index 60331b6..8144456 100644 --- a/view/lean-api/entity_descriptor.phtml +++ b/view/lean-api/entity_descriptor.phtml @@ -10,30 +10,39 @@
{% foreach $entities as $name => $entity %} -
+

{{ $entity['className'] }}

{{ $entity['description'] }}

-
    - {% foreach $entity['fields'] as $field %} -
  1. -
    -
    ${{ $field['name'] }} {{ $field['description'] }}
    -
    +
    +
    Champs
    +
      + {% foreach $entity['fields'] as $field %} +
    1. +
      +
      + ${{ $field['name'] }} {% if ! $field['allowNulls'] %}*{% endif %} + {{ $field['description'] }} +
      +
      -
      -
      Champ SQL : {{ $field['fieldName'] }}
      -
      Attribut : {{ $field['tag'] }}
      -
      Type(s) : {{ $field['type'] }}
      -
      Nullable {{ yesOrNo($field['allowNulls']) }}
      -
      Taille : {{ length($field['length']) }}
      -
      Lecture seule : {{ yesOrNo($field['readonly']) }}
      -
      -
    2. - {% endforeach %} -
    +
    +
    Champ SQL : {{ $field['fieldName'] }}
    +
    Attribut : {{ $field['tag'] }}
    +
    Type(s) : {{ $field['type'] }}
    +
    Nullable {{ yesOrNo($field['allowNulls']) }}
    + {% if $field['default'] %} +
    Valeur par défaut : {{ $field['default'] }}
    + {% endif %} +
    Taille : {{ length($field['length']) }}
    +
    Lecture seule : {{ yesOrNo($field['readonly']) }}
    +
    +
  2. + {% endforeach %} +
+
{% if $entity['searchRequestFields'] %} @@ -44,25 +53,35 @@
{{ $entity['searchRequestDescription'] }}
{% endif %} -
    - {% foreach $entity['searchRequestFields'] as $field %} -
  1. -
    -
    {{ $field['name'] }} {{ $field['description'] }}
    -
    -
    -
    Paramètre de requête (GET) : {{ $field['parameter'] }}
    -
    Attribut : {{ $field['tag'] }}
    -
    Type(s) : {{ $field['type'] }}
    -
    Nullable : {{ yesOrNo($field['allowNulls']) }}
    - {% if $field['default'] !== "" %} -
    Valeur par défault : {{ $this->displayValue($field['default']) }}
    - {% endif %} -
    -
  2. - {% endforeach %} -
+
+
Champs
+ +
    + {% foreach $entity['searchRequestFields'] as $field %} +
  1. +
    +
    + {{ $field['name'] }} + {{ $field['description'] }} +
    +
    + +
    +
    Paramètre de requête (GET) : {{ $field['parameter'] }}
    +
    Attribut : {{ $field['tag'] }}
    +
    Type(s) : {{ $field['type'] }}
    + {% if $field['possibleValues'] ?? false %} +
    Valeurs possibles : {{ $field['possibleValues'] }}
    + {% endif %} + {% if $field['default'] !== "" %} +
    Valeur par défault : {{ $this->displayValue($field['default']) }}
    + {% endif %} +
    +
  2. + {% endforeach %} +
+
{% endif %} {% or %} diff --git a/view/lean-api/form_descriptor.phtml b/view/lean-api/form_descriptor.phtml index dbc879d..404088a 100644 --- a/view/lean-api/form_descriptor.phtml +++ b/view/lean-api/form_descriptor.phtml @@ -13,42 +13,47 @@
{{ $form['description'] }}

-
Champs
-
    - {% foreach $form['fields'] as $field %} -
  1. -
    -
    - {{ $field['name'] }} - {{ $field['description'] }} +
    +
    Champs
    + +
      + {% foreach $form['fields'] as $field %} +
    1. +
      +
      + {{ $field['name'] }} {% if $field['mandatory'] %}*{% endif %} + {{ $field['description'] }} +
      -
    -
    -
    Variable POST / champ JSON : {{ $field['name'] }}
    -
    Type(s) : {{ implode(' | ', array_map(fn($e) => $e->type, $field['type'])) }}
    -
    Nullable : {{ yesOrNo($field['allowNulls']) }}
    - {% if $field['regexPattern'] !== null %} -
    Pattern regex : {{ $field['regexPattern'] }}
    - {% endif %} - {% if $field['minLength'] !== null %} -
    Taille min. : {{ $field['minLength'] }}
    - {% endif %} - {% if $field['maxLength'] !== null %} -
    Taille max. : {{ $field['maxLength'] }}
    - {% endif %} - {% if $field['example'] !== null || $field['values'] %} -
    Exemple : {{ $field['example'] }}
    - {% endif %} - - {% if $field['values'] %} -
    Valeurs possibles : [ {{= implode(', ', $field['values']) }} ]
    - {% endif %} -
    -
  2. - {% endforeach %} -
+
+
Variable POST / champ JSON : {{ $field['name'] }}
+
Type(s) : {{ implode(' | ', array_map(fn($e) => $e->type, $field['type'])) }}
+
Nullable : {{ yesOrNo($field['allowNulls']) }}
+ {% if $field['regexPattern'] !== null %} +
Pattern regex : {{ $field['regexPattern'] }}
+ {% endif %} + {% if $field['minLength'] !== null %} +
Taille min. : {{ $field['minLength'] }}
+ {% endif %} + {% if $field['maxLength'] !== null %} +
Taille max. : {{ $field['maxLength'] }}
+ {% endif %} + {% if $field['example'] !== null || $field['values'] %} +
Exemple : {{ $field['example'] }}
+ {% endif %} + {% if $field['default'] !== null %} +
Default : {{ $field['default'] }}
+ {% endif %} + {% if $field['values'] %} +
Valeurs possibles : [ {{= implode(', ', $field['values']) }} ]
+ {% endif %} +
+ + {% endforeach %} + + {% or %} {% _ "none" %} diff --git a/view/lean-api/request_debugger.phtml b/view/lean-api/request_debugger.phtml index 101f983..39c55fd 100644 --- a/view/lean-api/request_debugger.phtml +++ b/view/lean-api/request_debugger.phtml @@ -24,7 +24,7 @@
{% ui:text "url" %} - {% ui:text "token", $this->session->jwt %} + {% ui:text "token", $this->session->jwt, [ 'placeholder' => "JWT Token" ] %}
@@ -85,6 +85,7 @@ method = requestHead.querySelector(".method"), input = requestHead.querySelector("[name='url']"), token = requestHead.querySelector("[name='token']"), + tokenValue = sessionStorage.getItem("token"), button = requestHead.querySelector(".request-btn"); // Editor @@ -98,6 +99,11 @@ responseEditorElement = responseResponse.querySelector('#response'); input.addEventListener('keydown', evt => evt.keyCode === 13 ? button.click() : null); + token.addEventListener('change', evt => sessionStorage.setItem("token", evt.target.value)); + + if (tokenValue) { + token.value = tokenValue; + } document.addEventListener("DOMContentLoaded", (evt) => { document.querySelectorAll(".form-name").forEach(elem => { @@ -111,10 +117,12 @@ let json = {}; formData.fields.forEach((field) => { - json[field.name] = field.allowNulls ? null : ""; + const value = field.default ? field.default : ( field.allowNulls ? null : "" ); + + json[field.name] = value === "[]" ? [] : value; }); - editor.setValue(JSON.stringify(json,null,2), -1); + editor.setValue(JSON.stringify(json, null, 2), -1); }); }); @@ -148,6 +156,7 @@ launchRequest(requestMethod, input.value, editor.getValue()) .then((response) => { + console.log(response); responseHead.querySelector('.response-code').innerText = response.status; responseHead.querySelector('.response-message').innerText = response.statusText; @@ -156,6 +165,7 @@ return response.text(); }) .then(body => { + console.log(aceMode); if (aceMode === "json") { body = JSON.stringify(JSON.parse(body), null, 2); responseEditorElement.innerHTML = body; diff --git a/view/lean-api/route_descriptor.phtml b/view/lean-api/route_descriptor.phtml index 61a63c7..4ab8cb4 100644 --- a/view/lean-api/route_descriptor.phtml +++ b/view/lean-api/route_descriptor.phtml @@ -20,9 +20,10 @@ {{ $route['name'] }}
- {% foreach ['admin', 'school' ] as $privilege %} + à venir + {# foreach ['admin', 'school' ] as $privilege %} {{ $privilege }} - {% endforeach %} + {% endforeach #} diff --git a/view/lean/layout/docs.phtml b/view/lean/layout/docs.phtml index 97a9861..c6da57e 100644 --- a/view/lean/layout/docs.phtml +++ b/view/lean/layout/docs.phtml @@ -18,7 +18,7 @@ {% endsection %}