Skip to content

Commit

Permalink
added support for nested formats
Browse files Browse the repository at this point in the history
  • Loading branch information
eceltov committed Mar 2, 2025
1 parent 311f567 commit 18501e1
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 8 deletions.
46 changes: 38 additions & 8 deletions app/V1Module/presenters/base/BasePresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ private function processParams(ReflectionMethod $reflection)
// use a method specialized for formats if there is a format available
$format = MetaFormatHelper::extractFormatFromAttribute($reflection);
if ($format !== null) {
$this->processParamsFormat($format);
$this->requestFormatInstance = $this->processParamsFormat($format, null);
return;
}

Expand All @@ -239,7 +239,18 @@ private function processParamsLoose(array $paramData)
}
}

private function processParamsFormat(string $format)
/**
* Processes parameters defined by a format. Request parameters are validated and a format instance with
* parameter values created.
* @param string $format The format defining the parameters.
* @param ?array $valueDictionary If not null, a nested format instance will be created. The values will be taken
* from here instead of the request object. Format validation ignores parameter type (path, query or post).
* A top-level format will be created if null.
* @throws \App\Exceptions\InternalServerException Thrown when the format definition is corrupted/absent.
* @throws \App\Exceptions\BadRequestException Thrown when the request parameter values do not conform to the definition.
* @return MetaFormat Returns a format instance with values filled from the request object.
*/
private function processParamsFormat(string $format, ?array $valueDictionary): MetaFormat
{
// get the parsed attribute data from the format fields
$formatToFieldDefinitionsMap = FormatCache::getFormatToFieldDefinitionsMap();
Expand All @@ -250,15 +261,34 @@ private function processParamsFormat(string $format)
// maps field names to their attribute data
$nameToFieldDefinitionsMap = $formatToFieldDefinitionsMap[$format];

///TODO: handle nested MetaFormat creation
$formatInstance = MetaFormatHelper::createFormatInstance($format);
foreach ($nameToFieldDefinitionsMap as $fieldName => $requestParamData) {
///TODO: path parameters are not checked yet
if ($requestParamData->type == Type::Path) {
continue;
$value = null;
// top-level format
if ($valueDictionary === null) {
///TODO: path parameters are not checked yet
if ($requestParamData->type == Type::Path) {
continue;
}

$value = $this->getValueFromParamData($requestParamData);
// nested format
} else {
// Instead of retrieving the values with the getRequest call, use the provided $valueDictionary.
// This makes the nested format ignore the parameter type (path, query, post) which is intended.
// The data for this nested format cannot be spread across multiple param types, but it could be
// if this was not a nested format but the top level format.
if (array_key_exists($requestParamData->name, $valueDictionary)) {
$value = $valueDictionary[$requestParamData->name];
}
}

$value = $this->getValueFromParamData($requestParamData);
// handle nested format creation
// replace the value dictionary stored in $value with a format instance
$nestedFormatName = $requestParamData->getFormatName();
if ($nestedFormatName !== null) {
$value = $this->processParamsFormat($nestedFormatName, $value);
}

// this throws if the value is invalid
$formatInstance->checkedAssign($fieldName, $value);
Expand All @@ -269,7 +299,7 @@ private function processParamsFormat(string $format)
throw new BadRequestException("All request fields are valid but additional structural constraints failed.");
}

$this->requestFormatInstance = $formatInstance;
return $formatInstance;
}

/**
Expand Down
18 changes: 18 additions & 0 deletions app/helpers/MetaFormats/RequestParamData.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Exceptions\InternalServerException;
use App\Exceptions\InvalidArgumentException;
use App\Helpers\MetaFormats\Validators\VArray;
use App\Helpers\MetaFormats\Validators\VFormat;
use App\Helpers\Swagger\AnnotationParameterData;
use Exception;

Expand Down Expand Up @@ -76,6 +77,23 @@ public function conformsToDefinition(mixed $value)
}
}

/**
* Returns the format name if the parameter should be interpreted as a format and not as a primitive type.
* @return ?string Returns the format name or null if the param represents a primitive type.
*/
public function getFormatName(): ?string
{
// all format params have to have a VFormat validator
foreach ($this->validators as $validator) {
if ($validator instanceof VFormat) {
return $validator->format;
}
}

// return null for primitive types
return null;
}

private function hasValidators(): bool
{
if (is_array($this->validators)) {
Expand Down
39 changes: 39 additions & 0 deletions app/helpers/MetaFormats/Validators/VFormat.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace App\Helpers\MetaFormats\Validators;

use App\Exceptions\InternalServerException;
use App\Helpers\MetaFormats\FormatCache;
use App\Helpers\MetaFormats\MetaFormat;

/**
* Validates formats. Accepts any format derived of the base MetaFormat.
* Format fields are validated by validators added to the fields.
*/
class VFormat
{
public const SWAGGER_TYPE = "object";
public string $format;

public function __construct(string $format)
{
$this->format = $format;

// throw immediatelly if the format does not exist
if (!FormatCache::formatExists($format)) {
throw new InternalServerException("Format $format does not exist.");
}
}

public function getExampleValue()
{
///TODO
return "0";
}

public function validate(mixed $value)
{
// fine-grained checking is done in the properties
return $value instanceof MetaFormat;
}
}

0 comments on commit 18501e1

Please sign in to comment.