From fda03f3e7ea042f38e4c9b3f452176f9e036d8a5 Mon Sep 17 00:00:00 2001 From: Martijn Date: Sat, 17 Dec 2016 20:20:57 +0100 Subject: [PATCH] Fix #12: parse parenthesis in object properties. --- CHANGELOG.md | 5 ++ README.md | 2 +- SwaggerGen/Swagger/Type/ObjectType.php | 68 +++++++++++++++++++------- TODO.md | 2 +- tests/Swagger/Type/ObjectTypeTest.php | 41 +++++++++++++++- tests/issues/Issue0012Test.php | 28 +++++++++++ 6 files changed, 126 insertions(+), 20 deletions(-) create mode 100644 tests/issues/Issue0012Test.php diff --git a/CHANGELOG.md b/CHANGELOG.md index f7778f0..2c6a052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## 2.3.10 - 2016-12-17 +### Fixed +- Correctly parse parenthesis in object properties. Fixes issue #12 by +ObliviousHarmony. + ## 2.3.9 - 2016-11-17 ### Fixed - Supply custom `format` for type `uuid`. diff --git a/README.md b/README.md index 683d5b9..e138cb9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # SwaggerGen -Version 2.3.9 +Version 2.3.10 [![License](https://img.shields.io/github/license/vanderlee/PHPSwaggerGen.svg)]() [![Build Status](https://travis-ci.org/vanderlee/PHPSwaggerGen.svg?branch=master)](https://travis-ci.org/vanderlee/PHPSwaggerGen) diff --git a/SwaggerGen/Swagger/Type/ObjectType.php b/SwaggerGen/Swagger/Type/ObjectType.php index 31eb11b..61626fe 100644 --- a/SwaggerGen/Swagger/Type/ObjectType.php +++ b/SwaggerGen/Swagger/Type/ObjectType.php @@ -12,13 +12,12 @@ */ class ObjectType extends AbstractType { - - const REGEX_PROP_START = '/([^:\?]+)(\?)?:'; - const REGEX_PROP_FORMAT = '[a-z]+'; - const REGEX_PROP_PROPERTIES = '(?:\(.*\))?'; - const REGEX_PROP_RANGE = '(?:[[<][^,]*,[^,]*[\\]>])?'; - const REGEX_PROP_DEFAULT = '(?:=.+?)?'; - const REGEX_PROP_END = '(?:,|$)/i'; + const REGEX_PROP_START = '/^'; + const REGEX_PROP_NAME = '([^?:]+)'; + const REGEX_PROP_REQUIRED = '(\?)?'; + const REGEX_PROP_ASSIGN = ':'; + const REGEX_PROP_DEFINITION = '(.+)'; + const REGEX_PROP_END = '$/'; private $minProperties = null; private $maxProperties = null; @@ -32,7 +31,7 @@ class ObjectType extends AbstractType protected function parseDefinition($definition) { $definition = self::trim($definition); - + $match = array(); if (preg_match(self::REGEX_START . self::REGEX_FORMAT . self::REGEX_CONTENT . self::REGEX_RANGE . self::REGEX_END, $definition, $match) !== 1) { throw new \SwaggerGen\Exception("Unparseable object definition: '{$definition}'"); @@ -53,16 +52,18 @@ private function parseFormat($definition, $match) private function parseProperties($definition, $match) { if (!empty($match[2])) { - $prop_matches = array(); - if (preg_match_all(self::REGEX_PROP_START . '(' . self::REGEX_PROP_FORMAT . self::REGEX_PROP_PROPERTIES . self::REGEX_PROP_RANGE . self::REGEX_PROP_DEFAULT . ')' . self::REGEX_PROP_END, $match[2], $prop_matches, PREG_SET_ORDER) === 0) { - throw new \SwaggerGen\Exception("Unparseable properties definition: '{$definition}'"); - } - foreach ($prop_matches as $prop_match) { - $this->properties[$prop_match[1]] = new Property($this, $prop_match[3]); - if ($prop_match[2] !== '?') { - $this->required[$prop_match[1]] = true; + do { + if (($property = self::extract_property($match[2])) !== '') { + $prop_match = array(); + if (preg_match(self::REGEX_PROP_START . self::REGEX_PROP_NAME . self::REGEX_PROP_REQUIRED . self::REGEX_PROP_ASSIGN . self::REGEX_PROP_DEFINITION . self::REGEX_PROP_END, $property, $prop_match) !== 1) { + throw new \SwaggerGen\Exception("Unparseable property definition: '{$property}'"); + } + $this->properties[$prop_match[1]] = new Property($this, $prop_match[3]); + if ($prop_match[2] !== '?') { + $this->required[$prop_match[1]] = true; + } } - } + } while ($property !== ''); } } @@ -156,4 +157,37 @@ public function __toString() return __CLASS__; } + /** + * Extract a property from a comma-separated list of properties. + * + * i.e. `a(x(x,x)),b(x)` returns `a(x(x,x))` and changes `$subject` into `b(x)`. + * + * @param string $properties string variable + * @return string the extracted string + */ + private static function extract_property(&$properties) + { + $property = ''; + + $depth = 0; + $index = 0; + while ($index < strlen($properties)) { + $character = $properties{$index++}; + + if (strpos('([<', $character) !== false) { + ++$depth; + } elseif (strpos(')]>', $character) !== false) { + --$depth; + } elseif ($character === ',' && $depth === 0) { + break; + } + + $property .= $character; + } + + $properties = substr($properties, $index); + + return $property; + } + } diff --git a/TODO.md b/TODO.md index 51a2cea..2604a35 100644 --- a/TODO.md +++ b/TODO.md @@ -6,7 +6,7 @@ ## Document * `x-...` on AbstractObject context -## Code +## Code * Internal redesign of `handleCommand` Do a `handleStatement` instead. * Command to move to `swagger` context; `goto`? * Aliases for force global parameters and/or responses. diff --git a/tests/Swagger/Type/ObjectTypeTest.php b/tests/Swagger/Type/ObjectTypeTest.php index 9361834..acc8186 100644 --- a/tests/Swagger/Type/ObjectTypeTest.php +++ b/tests/Swagger/Type/ObjectTypeTest.php @@ -95,7 +95,7 @@ public function testConstructEmptyProperties() */ public function testConstructBadProperties() { - $this->setExpectedException('\SwaggerGen\Exception', "Unparseable properties definition: 'object(1)'"); + $this->setExpectedException('\SwaggerGen\Exception', "Unparseable property definition: '1'"); $object = new SwaggerGen\Swagger\Type\ObjectType($this->parent, 'object(1)'); } @@ -319,4 +319,43 @@ public function testCommandPropertyOptional() ), $object->toArray()); } + public function testObjectProperties() + { + $object = new \SwaggerGen\SwaggerGen(); + $array = $object->getSwagger(array(' + api Test + endpoint /test + method GET something + response 200 object(a:array(A),b:array(B)) + ')); + + $this->assertSame('{"swagger":2,"info":{"title":"undefined","version":0}' + . ',"paths":{"\/test":{"get":{"tags":["Test"],"summary":"something"' + . ',"responses":{"200":{"description":"OK","schema":{"type":"object","required":["a","b"]' + . ',"properties":{"a":{"type":"array","items":{"$ref":"#\/definitions\/A"}}' + . ',"b":{"type":"array","items":{"$ref":"#\/definitions\/B"}}}}}}}}}' + . ',"tags":[{"name":"Test"}]}', json_encode($array, JSON_NUMERIC_CHECK)); + } + + public function testDeepObjectProperties() + { + $object = new \SwaggerGen\SwaggerGen(); + $array = $object->getSwagger(array(' + api Test + endpoint /test + method GET something + response 200 object(a:object(c:csv(C),d:int),b?:array(B)) + ')); + + $this->assertSame('{"swagger":2,"info":{"title":"undefined","version":0}' + . ',"paths":{"\/test":{"get":{"tags":["Test"],"summary":"something"' + . ',"responses":{"200":{"description":"OK","schema":{"type":"object"' + . ',"required":["a"],"properties":{' + . '"a":{"type":"object","required":["c","d"],"properties":{' + . '"c":{"type":"array","items":{"$ref":"#\/definitions\/C"}}' + . ',"d":{"type":"integer","format":"int32"}}}' + . ',"b":{"type":"array","items":{"$ref":"#\/definitions\/B"}}}}}}}}}' + . ',"tags":[{"name":"Test"}]}', json_encode($array, JSON_NUMERIC_CHECK)); + } + } diff --git a/tests/issues/Issue0012Test.php b/tests/issues/Issue0012Test.php new file mode 100644 index 0000000..6e0b0ef --- /dev/null +++ b/tests/issues/Issue0012Test.php @@ -0,0 +1,28 @@ +getSwagger(array(' + api Test + endpoint /test + method GET something + response 200 object(modifications:array(ModificationHistory),users:array(User)) + ')); + + $this->assertSame('{"swagger":2,"info":{"title":"undefined","version":0}' + . ',"paths":{"\/test":{"get":{"tags":["Test"],"summary":"something"' + . ',"responses":{"200":{"description":"OK","schema":{"type":"object","required":["modifications","users"]' + . ',"properties":{"modifications":{"type":"array","items":{"$ref":"#\/definitions\/ModificationHistory"}}' + . ',"users":{"type":"array","items":{"$ref":"#\/definitions\/User"}}}}}}}}}' + . ',"tags":[{"name":"Test"}]}', json_encode($array, JSON_NUMERIC_CHECK)); + } + +} \ No newline at end of file