Skip to content

Commit 3c6bf16

Browse files
committed
added validator logic for meta views WIP
1 parent 3cb526f commit 3c6bf16

File tree

3 files changed

+174
-7
lines changed

3 files changed

+174
-7
lines changed

app/helpers/Swagger/AnnotationHelper.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Symfony\Component\Console\Input\InputInterface;
1414
use Symfony\Component\Console\Output\OutputInterface;
1515
use Doctrine\Common\Annotations\AnnotationReader;
16+
use \PrimitiveFormatValidators;
1617
use DateTime;
1718

1819

@@ -484,6 +485,12 @@ private static function extractFormatData(array $annotations) {
484485
return $format;
485486
}
486487

488+
/**
489+
* Checks all @checked_param annotations of a method and returns a map from parameter names to their formats.
490+
* @param string $className The name of the containing class.
491+
* @param string $methodName The name of the method.
492+
* @return array
493+
*/
487494
public static function extractMethodCheckedParams(string $className, string $methodName): array {
488495
$annotations = self::getMethodAnnotations($className, $methodName);
489496
$filtered = self::filterAnnotations($annotations, "@checked_param");
@@ -526,6 +533,9 @@ public static function getClassFormats(string $className) {
526533
return $formats;
527534
}
528535

536+
/**
537+
* Creates a mapping from formats to class names, where the class defines the format.
538+
*/
529539
public static function getFormatDefinitions() {
530540
///TODO: this should be more sophisticated
531541
$classes = get_declared_classes();
@@ -548,4 +558,36 @@ public static function getFormatDefinitions() {
548558

549559
return $formatClassMap;
550560
}
561+
562+
/**
563+
* Extracts all primitive validator methods (starting with "validate") and returns a map from format to a callback.
564+
* The callbacks have one parameter that is passed to the validator.
565+
*/
566+
private static function getPrimitiveValidators(): array {
567+
$instance = new PrimitiveFormatValidators();
568+
$className = get_class($instance);
569+
$methodNames = get_class_methods($className);
570+
571+
$validators = [];
572+
foreach ($methodNames as $methodName) {
573+
// all validation methods start with validate
574+
if (!str_starts_with($methodName, "validate"))
575+
continue;
576+
577+
$annotations = self::getMethodAnnotations($className, $methodName);
578+
$format = self::extractFormatData($annotations);
579+
$callback = function($param) use ($instance, $methodName) { return $instance->$methodName($param); };
580+
$validators[$format] = $callback;
581+
}
582+
583+
return $validators;
584+
}
585+
586+
private static function getMetaValidators(): array {
587+
return [];
588+
}
589+
590+
private static function getValidators(): array {
591+
return array_merge(self::getPrimitiveValidators(), self::getMetaValidators());
592+
}
551593
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
// the string values have to match the return string of gettype()
4+
enum PhpTypes: string {
5+
case String = "string";
6+
case Int = "integer";
7+
case Double = "double";
8+
case Object = "object";
9+
case Null = "NULL";
10+
}
11+
12+
class PrimitiveFormatValidators {
13+
/**
14+
* @format uuid
15+
*/
16+
public function validateUuid($uuid) {
17+
if (!self::checkType($uuid, PhpTypes::String))
18+
return false;
19+
20+
return preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/', $uuid) === 1;
21+
}
22+
23+
private static function checkType($value, PhpTypes $type) {
24+
return gettype($value) === $type->value;
25+
}
26+
}
27+

app/model/view/MetaView.php

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ function getTypedParams() {
2828
foreach ($paramsToValues as $paramName=>$paramValue) {
2929
$format = $paramsToFormat[$paramName];
3030

31+
// the parameter name was not present in the annotations
3132
if (!array_key_exists($paramName, $paramsToFormat)) {
3233
///TODO: return 500
3334
echo "Error: unknown param format: $paramName\n";
@@ -40,14 +41,15 @@ function getTypedParams() {
4041

4142
// fill the new object with the param values
4243
///TODO: handle nested formated objects
43-
foreach ($paramValue as $key=>$value) {
44+
foreach ($paramValue as $propertyName=>$propertyValue) {
4445
///TODO: return 404
45-
if (!array_key_exists($key, $classFormat)) {
46+
// the property was not present in the class definition
47+
if (!array_key_exists($propertyName, $classFormat)) {
4648
echo "Error: unknown param: $paramName\n";
4749
return [];
4850
}
4951

50-
$obj->$key = $value;
52+
$obj->$propertyName = $propertyValue;
5153
}
5254

5355
$paramToTypedMap[$paramName] = $obj;
@@ -73,24 +75,117 @@ function getParamNamesToValuesMap($backtrace): array {
7375
}
7476
}
7577

78+
/**
79+
* Parses format string enriched by nullability and array modifiers.
80+
* In case the format contains array, this data class can be recursive.
81+
* Example: string?[]? can either be null or of string?[] type, an array of nullable strings
82+
* Example2: string[]?[] is an array of null or string arrays
83+
*/
84+
class FormatParser {
85+
public bool $nullable = false;
86+
public bool $isArray = false;
87+
// contains the format stripped of the nullability ?, null if it is an array
88+
public ?string $format = null;
89+
// contains the format definition of nested elements, null if it is not an array
90+
public ?FormatParser $nested = null;
91+
92+
public function __construct(string $format) {
93+
// check nullability
94+
if (str_ends_with($format, "?")) {
95+
$this->nullable = true;
96+
$format = substr($format, 0, -1);
97+
}
98+
99+
// check array
100+
if (str_ends_with($format, "[]")) {
101+
$this->isArray = true;
102+
$format = substr($format, 0, -2);
103+
$this->nested = new FormatParser($format);
104+
}
105+
else {
106+
$this->format = $format;
107+
}
108+
}
109+
}
110+
76111

77112
class MetaFormat {
113+
// validates primitive formats of intrinsic PHP types
114+
///TODO: make this static somehow (or cached)
115+
private $validators;
116+
117+
public function __construct() {
118+
$this->validators = AnnotationHelper::getValidators();
119+
}
120+
121+
78122
/**
79123
* Validates the given format.
80124
* @return bool Returns whether the format and all nested formats are valid.
81125
*/
82126
public function validate() {
83-
return true;
127+
// check whether all higher level contracts hold
128+
if (!$this->validateSelf())
129+
return false;
130+
131+
// check properties
132+
$selfFormat = AnnotationHelper::getClassFormats(get_class($this));
133+
foreach ($selfFormat as $propertyName=>$propertyFormat) {
134+
///TODO: check if this is true
135+
/// if the property is checked by type only, there is no need to check it as an invalid assignment would rise an error
136+
$value = $this->$propertyName;
137+
$format = $propertyFormat["format"];
138+
if ($format === null)
139+
continue;
140+
141+
// enables parsing more complicated formats (string[]?, string?[], string?[][]?, ...)
142+
$parsedFormat = new FormatParser($format);
143+
if (!$this->recursiveFormatChecker($value, $parsedFormat))
144+
return false;
145+
}
146+
147+
}
148+
149+
private function recursiveFormatChecker($value, FormatParser $parsedFormat) {
150+
// enables parsing more complicated formats (string[]?, string?[], string?[][]?, ...)
151+
152+
// check nullability
153+
if ($value === null)
154+
return $parsedFormat->nullable;
155+
156+
// handle arrays
157+
if ($parsedFormat->isArray) {
158+
if (!is_array($value))
159+
return false;
160+
161+
// if any element fails, the whole format fails
162+
foreach ($value as $element) {
163+
if (!$this->recursiveFormatChecker($element, $parsedFormat->nested))
164+
return false;
165+
}
166+
return true;
167+
}
168+
169+
///TODO: raise an error
170+
// check whether the validator exists
171+
if (!array_key_exists($parsedFormat->format, $this->validators)) {
172+
echo "Error: missing validator for format: " . $parsedFormat->format . "\n";
173+
return false;
174+
}
175+
176+
return $this->validators[$parsedFormat->format]($value);
84177
}
85178

86179
/**
87180
* Validates this format. Automatically called by the validate method on all fields.
88181
* Primitive formats should always override this, composite formats might want to override
89182
* this in case more complex contracts need to be enforced.
183+
* This method should not check the format of nested types.
90184
* @return bool Returns whether the format is valid.
91185
*/
92-
protected function validate_this() {
93-
186+
protected function validateSelf() {
187+
// there are no constraints by default
188+
return true;
94189
}
95190
}
96191

@@ -146,10 +241,13 @@ class TestView extends MetaView {
146241
* @checked_param format:group group
147242
* @checked_param format:uuid user_id
148243
*/
149-
function endpoint($group) {
244+
function endpoint($group, $user_id) {
150245
$params = $this->getTypedParams();
151246
$formattedGroup = $params["group"];
152247
var_dump($formattedGroup);
248+
249+
// $a = new GroupFormat();
250+
// $a->validate();
153251
}
154252

155253
// the names of the format and the output do not have to be identical, the strings in the desired data format refer the output names

0 commit comments

Comments
 (0)