From 8b6b677ed7667e3100ac774e185f035cdf1a8f0c Mon Sep 17 00:00:00 2001 From: fvideau Date: Mon, 26 Jun 2017 17:43:47 +0200 Subject: [PATCH 1/6] added support for members and default on attributes --- src/Annotation/Attribute.php | 10 ++++++++++ src/Blueprint.php | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Annotation/Attribute.php b/src/Annotation/Attribute.php index 1317c21..e3e5f3e 100644 --- a/src/Annotation/Attribute.php +++ b/src/Annotation/Attribute.php @@ -27,8 +27,18 @@ class Attribute */ public $description; + /** + * @var string + */ + public $default; + /** * @var mixed */ public $sample; + + /** + * @array + */ + public $members; } diff --git a/src/Blueprint.php b/src/Blueprint.php index b95a66e..5b070fb 100644 --- a/src/Blueprint.php +++ b/src/Blueprint.php @@ -223,7 +223,19 @@ protected function appendAttributes(&$contents, Collection $attributes, $indent $attribute->required ? 'required' : 'optional', $attribute->description ); + + if (isset($attribute->default)) { + $this->appendSection($contents, sprintf('Default: %s', $attribute->default), 2, 1); + } + + if (isset($attribute->members)) { + $this->appendSection($contents, 'Members', 2, 1); + foreach ($attribute->members as $member) { + $this->appendSection($contents, sprintf('`%s` - %s', $member->identifier, $member->description), 3, 1); + } + } }); + } /** From a036a78e689488504331ad9ce9c8d614c85a0af3 Mon Sep 17 00:00:00 2001 From: fvideau Date: Fri, 30 Jun 2017 10:57:03 +0200 Subject: [PATCH 2/6] Use trusty as dist to avoid failing tests --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 35bd6f4..60a2f59 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: php +dist: trusty + php: - 5.5.9 - 5.5 From bd058300b1079ebfb64ce2c85d1fc037b54ca335 Mon Sep 17 00:00:00 2001 From: fvideau Date: Mon, 10 Jul 2017 09:10:02 +0200 Subject: [PATCH 3/6] first version of dingo sellsy --- src/Blueprint.php | 220 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 209 insertions(+), 11 deletions(-) diff --git a/src/Blueprint.php b/src/Blueprint.php index 5b070fb..92e7d6a 100644 --- a/src/Blueprint.php +++ b/src/Blueprint.php @@ -9,6 +9,11 @@ use Illuminate\Filesystem\Filesystem; use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\SimpleAnnotationReader; +use Dingo\Blueprint\Annotation\Attributes; +use Dingo\Blueprint\Annotation\Attribute; +use Dingo\Blueprint\Annotation\Member; +use Dingo\Blueprint\Annotation\Parameters; +use Dingo\Blueprint\Annotation\Parameter; class Blueprint { @@ -100,12 +105,12 @@ public function generate(Collection $controllers, $name, $version, $includePath } if ($annotations = $this->reader->getMethodAnnotations($method)) { - if (! $actions->contains($method)) { + if (!$actions->contains($method)) { + $annotations = $this->fillAnnotations($annotations, $method); $actions->push(new Action($method, new Collection($annotations))); } } } - $annotations = new Collection($this->reader->getClassAnnotations($controller)); return new Resource($controller->getName(), $controller, $annotations, $actions); @@ -118,6 +123,182 @@ public function generate(Collection $controllers, $name, $version, $includePath return $contents; } + /** + * Add the parameters and attributes objects to annotations if needed + * + * @param array $annotations the annotations to fill + * @param object $method the method we are working on + * + * @return array + */ + private function fillAnnotations(array $annotations, $method) + { + $controllerName = substr($method->class, strrpos($method->class, '\\')); + if ($controllerName != '\\OAuthController') { + $rulesClass = 'App\\Validators\\Rules' . $controllerName; + $parameters = $this->extractParams($this->getMethodUri($annotations)); + + $annotations[] = $this->getAttributes($rulesClass::$rules[$method->name], $parameters); + $annotations[] = $this->getParameters($rulesClass::$rules[$method->name], $parameters); + } + return $annotations; + } + + /** + * Get the URI of a method from it's annotations + * + * @param array $annotations + * + * @return string + */ + private function getMethodUri(array $annotations) + { + return array_first($annotations, function ($key, $annotation) { + $type = 'Dingo\\Blueprint\\Annotation\\Method\\Method'; + return is_object($annotation) ? $annotation instanceof $type : $key instanceof $type; + })->uri; + } + + /** + * Gets a list of all parameters name from mutiple arrays extracted from the uri + * + * @param array $uriParams + * + * @return array + */ + private function extractParams($uri) { + preg_match_all("/{(.*?)}/", $uri, $matches); + return $this->parseParams($matches[1]); + } + + /** + * Parse the params as they are in the uri into correct array + * + * @param array $uriParams + * + * @return array + */ + private function parseParams($uriParams) + { + $params = []; + + foreach ($uriParams as $uriParam) { + ltrim($uriParam, '?'); + $uriParamExploded = explode(',', $uriParam); + foreach ($uriParamExploded as $param) { + $params[] = $param; + } + } + return $params; + } + + /** + * Transforms a laravel validation array in Attribute for dingo blueprint + * + * @param array $rules + * + * @return array + */ + private function getAttributes($rules, $parameters) + { + $attributes = new Attributes(); + + foreach ($rules as $identifier => $attrInfos) { + if (in_array($identifier, $parameters)) { + continue; + } + $attribute = $this->parseInfos(new Attribute(), $attrInfos); + $attribute->identifier = $identifier; + + $attributes->value[] = $attribute; + } + return $attributes->value ? $attributes : null; + } + + /** + * Transforms a laravel validation array in Parameter for dingo blueprint + * + * @param array $rules + * + * @return array + */ + private function getParameters($rules, $params) + { + $parameters = new Parameters(); + + foreach ($rules as $identifier => $paramInfos) { + if (!in_array($identifier, $params)) { + continue; + } + $parameter = $this->parseInfos(new Parameter(), $paramInfos); + $parameter->identifier = $identifier; + + $parameters->value[] = $parameter; + } + return $parameters->value ? $parameters : null; + } + + /** + * Parse the validation array to fill a parameter or an attribute + * + * @param Parameter|Attribute $toFill + * @param array $infos + * + * @return Parameter|Attribute + */ + private function parseInfos($toFill, $infos) + { + $toFill->description = $infos['description']; + + $propertiesExploded = explode('|', $infos['rules']); + foreach ($propertiesExploded as $property) { + switch ($property) { + case 'numeric': + case 'integer': + $toFill->type = 'number'; + continue 2; + case 'array': + $toFill->type = 'object'; + continue 2; + case 'present': + case 'required': + $toFill->required = true; + continue 2; + } + $tofill = $this->parseComplexProperties($toFill, $property); + } + return $toFill; + } + + /** + * Parse the property to find enum or defaults + * + * @param Parameter|Attribute $toFill + * @param string $property + * + * @return Parameter|Attribute + */ + private function parseComplexProperties($toFill, $property) + { + $propertyExploded = explode(':', $property); + if (count($propertyExploded) > 1) { + switch ($propertyExploded[0]) { + case 'default': + $toFill->default = $propertyExploded[1]; + break; + case 'in': + $members = explode(',', $propertyExploded[1]); + foreach ($members as $identifier) { + $member = new Member(); + $member->identifier = $identifier; + $toFill->members[] = $member; + } + break; + } + } + return $toFill; + } + /** * Generate the documentation contents from the resources collection. * @@ -131,6 +312,7 @@ protected function generateContentsFromResources(Collection $resources, $name) $contents = ''; $contents .= $this->getFormat(); + $contents .= $this->getHost(); $contents .= $this->line(2); $contents .= sprintf('# %s', $name); $contents .= $this->line(2); @@ -209,9 +391,11 @@ protected function appendAttributes(&$contents, Collection $attributes, $indent $this->appendSection($contents, 'Attributes', $indent); $attributes->each(function ($attribute) use (&$contents, $indent) { + $explodedIdentifier = explode('.', $attribute->identifier); + $indent += count($explodedIdentifier); $contents .= $this->line(); - $contents .= $this->tab(1 + $indent); - $contents .= sprintf('+ %s', $attribute->identifier); + $contents .= $this->tab($indent); + $contents .= sprintf('+ %s', $explodedIdentifier[count($explodedIdentifier) - 1]); if ($attribute->sample) { $contents .= sprintf(': %s', $attribute->sample); @@ -219,19 +403,19 @@ protected function appendAttributes(&$contents, Collection $attributes, $indent $contents .= sprintf( ' (%s, %s) - %s', - $attribute->type, + $attribute->members ? sprintf('enum[%s]', $attribute->type) : $attribute->type, $attribute->required ? 'required' : 'optional', $attribute->description ); if (isset($attribute->default)) { - $this->appendSection($contents, sprintf('Default: %s', $attribute->default), 2, 1); + $this->appendSection($contents, sprintf('Default: %s', $attribute->default), $indent + 1, 1); } if (isset($attribute->members)) { - $this->appendSection($contents, 'Members', 2, 1); + $this->appendSection($contents, 'Members', $indent + 1, 1); foreach ($attribute->members as $member) { - $this->appendSection($contents, sprintf('`%s` - %s', $member->identifier, $member->description), 3, 1); + $this->appendSection($contents, sprintf('`%s` - %s', $member->identifier, $member->description), $indent + 2, 1); } } }); @@ -253,10 +437,14 @@ protected function appendParameters(&$contents, Collection $parameters) $parameters->each(function ($parameter) use (&$contents) { $contents .= $this->line(); $contents .= $this->tab(); + $contents .= sprintf('+ %s', $parameter->identifier); + + if ($parameter->example) { + $contents .= sprintf(': %s', $parameter->example); + } + $contents .= sprintf( - '+ %s:%s (%s, %s) - %s', - $parameter->identifier, - $parameter->example ? " `{$parameter->example}`" : '', + ' (%s, %s) - %s', $parameter->members ? sprintf('enum[%s]', $parameter->type) : $parameter->type, $parameter->required ? 'required' : 'optional', $parameter->description @@ -324,6 +512,9 @@ protected function appendRequest(&$contents, $request, Resource $resource) $contents .= ' ('.$request->contentType.')'; + if (!isset($request->headers['Authorization'])) { + $request->headers['Authorization'] = 'OAuth: oauth_consumer_key={consumer_key},oauth_signature={consumer_secret}&{user_secret},oauth_signature_method=PLAINTEXT,oauth_nonce={random_string},oauth_timestamp={current_timestamp},oauth_token={user_token}'; + } if (! empty($request->headers) || $resource->hasRequestHeaders()) { $this->appendHeaders($contents, array_merge($resource->getRequestHeaders(), $request->headers)); } @@ -466,4 +657,11 @@ protected function getFormat() { return 'FORMAT: 1A'; } + + private function getHost() + { + if (class_exists('\Illuminate\Config\Repository')) { + return $this->line(1) . 'HOST: https://' . app('config')->get('api.domain') . '/' . app('config')->get('api.prefix'); + } + } } From c178c14a6fcfb6ec92f02b56ef4aed08945bbf71 Mon Sep 17 00:00:00 2001 From: fvideau Date: Tue, 18 Jul 2017 15:40:41 +0200 Subject: [PATCH 4/6] added check to determine if an array in laravel validator is an oject or an array in apiary --- src/Blueprint.php | 62 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/src/Blueprint.php b/src/Blueprint.php index 92e7d6a..2b3eb15 100644 --- a/src/Blueprint.php +++ b/src/Blueprint.php @@ -138,8 +138,11 @@ private function fillAnnotations(array $annotations, $method) $rulesClass = 'App\\Validators\\Rules' . $controllerName; $parameters = $this->extractParams($this->getMethodUri($annotations)); - $annotations[] = $this->getAttributes($rulesClass::$rules[$method->name], $parameters); - $annotations[] = $this->getParameters($rulesClass::$rules[$method->name], $parameters); + $rules = $rulesClass::$rules[$method->name]; + if (count($rules)) { + $annotations[] = $this->getAttributes($rules, $parameters); + $annotations[] = $this->getParameters($rules, $parameters); + } } return $annotations; } @@ -203,15 +206,17 @@ private function getAttributes($rules, $parameters) { $attributes = new Attributes(); - foreach ($rules as $identifier => $attrInfos) { + reset($rules); + do { + $identifier = key($rules); if (in_array($identifier, $parameters)) { continue; } - $attribute = $this->parseInfos(new Attribute(), $attrInfos); + $attribute = $this->parseInfos(new Attribute(), $rules); $attribute->identifier = $identifier; $attributes->value[] = $attribute; - } + } while (next($rules)); return $attributes->value ? $attributes : null; } @@ -226,28 +231,58 @@ private function getParameters($rules, $params) { $parameters = new Parameters(); - foreach ($rules as $identifier => $paramInfos) { + reset($rules); + do { + $identifier = key($rules); if (!in_array($identifier, $params)) { continue; } - $parameter = $this->parseInfos(new Parameter(), $paramInfos); + $parameter = $this->parseInfos(new Parameter(), $rules); $parameter->identifier = $identifier; $parameters->value[] = $parameter; - } + } while (next($rules)); return $parameters->value ? $parameters : null; } + /** + * Determine if an array in a laravel validator is an array or an object in apiary + * + * @param array $rules + * + * @return string object or array + */ + private function findArrayType(array $rules) + { + $key = key($rules); + $key2 = false; + if (next($rules)) { + $key2 = key($rules); + } + $key3 = false; + if (next($rules)) { + $key3 = key($rules); + } + $toMatch = '/^' . preg_quote($key) . '\.[a-zA-Z0-9]*$/'; + if ($key2 && preg_match($toMatch, $key2) === 1 && $key3 && preg_match($toMatch, $key2)) { + $type = 'object'; + } else { + $type = 'array'; + } + return $type; + } + /** * Parse the validation array to fill a parameter or an attribute * * @param Parameter|Attribute $toFill - * @param array $infos + * @param array $rules * * @return Parameter|Attribute */ - private function parseInfos($toFill, $infos) + private function parseInfos($toFill, array $rules) { + $infos = current($rules); $toFill->description = $infos['description']; $propertiesExploded = explode('|', $infos['rules']); @@ -258,7 +293,7 @@ private function parseInfos($toFill, $infos) $toFill->type = 'number'; continue 2; case 'array': - $toFill->type = 'object'; + $toFill->type = findArrayType($rules); continue 2; case 'present': case 'required': @@ -395,7 +430,8 @@ protected function appendAttributes(&$contents, Collection $attributes, $indent $indent += count($explodedIdentifier); $contents .= $this->line(); $contents .= $this->tab($indent); - $contents .= sprintf('+ %s', $explodedIdentifier[count($explodedIdentifier) - 1]); + $identifier = $explodedIdentifier[count($explodedIdentifier) - 1]; + $contents .= sprintf('+ %s', $identifier == '*' ? '' : $identifier); if ($attribute->sample) { $contents .= sprintf(': %s', $attribute->sample); @@ -515,7 +551,7 @@ protected function appendRequest(&$contents, $request, Resource $resource) if (!isset($request->headers['Authorization'])) { $request->headers['Authorization'] = 'OAuth: oauth_consumer_key={consumer_key},oauth_signature={consumer_secret}&{user_secret},oauth_signature_method=PLAINTEXT,oauth_nonce={random_string},oauth_timestamp={current_timestamp},oauth_token={user_token}'; } - if (! empty($request->headers) || $resource->hasRequestHeaders()) { + if (!empty($request->headers) || $resource->hasRequestHeaders()) { $this->appendHeaders($contents, array_merge($resource->getRequestHeaders(), $request->headers)); } From ee1583ee9cae0506ab3b20b3f3bea26f618bd437 Mon Sep 17 00:00:00 2001 From: fvideau Date: Thu, 10 Aug 2017 17:11:15 +0200 Subject: [PATCH 5/6] fix a bug that prevent the documentation generation from working --- src/Blueprint.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blueprint.php b/src/Blueprint.php index 2b3eb15..ca20afe 100644 --- a/src/Blueprint.php +++ b/src/Blueprint.php @@ -293,7 +293,7 @@ private function parseInfos($toFill, array $rules) $toFill->type = 'number'; continue 2; case 'array': - $toFill->type = findArrayType($rules); + $toFill->type = $this->findArrayType($rules); continue 2; case 'present': case 'required': From 7f17a0cad301cbbd64c7e6e13bc916ccd77ec745 Mon Sep 17 00:00:00 2001 From: fvideau Date: Wed, 23 Aug 2017 14:06:44 +0200 Subject: [PATCH 6/6] fixed optional parameters in blueprint --- src/Blueprint.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blueprint.php b/src/Blueprint.php index ca20afe..c8e47b7 100644 --- a/src/Blueprint.php +++ b/src/Blueprint.php @@ -186,7 +186,7 @@ private function parseParams($uriParams) $params = []; foreach ($uriParams as $uriParam) { - ltrim($uriParam, '?'); + $uriParam = ltrim($uriParam, '?'); $uriParamExploded = explode(',', $uriParam); foreach ($uriParamExploded as $param) { $params[] = $param;