Skip to content

Commit

Permalink
Fix #12: parse parenthesis in object properties.
Browse files Browse the repository at this point in the history
  • Loading branch information
vanderlee committed Dec 17, 2016
1 parent cdab57b commit fda03f3
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 20 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
68 changes: 51 additions & 17 deletions SwaggerGen/Swagger/Type/ObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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}'");
Expand All @@ -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 !== '');
}
}

Expand Down Expand Up @@ -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;
}

}
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
41 changes: 40 additions & 1 deletion tests/Swagger/Type/ObjectTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)');
}
Expand Down Expand Up @@ -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));
}

}
28 changes: 28 additions & 0 deletions tests/issues/Issue0012Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

/**
* Tests issue 12; @rest\response 200 object(modifications:array(ModificationHistory),users:array(User))
* https://github.com/vanderlee/PHPSwaggerGen/issues/12
*/
class Issue0012Test extends PHPUnit_Framework_TestCase
{

public function testIssue()
{
$object = new \SwaggerGen\SwaggerGen();
$array = $object->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));
}

}

0 comments on commit fda03f3

Please sign in to comment.