Skip to content
This repository has been archived by the owner on Jan 15, 2024. It is now read-only.

External reference resolution improvements, refs #72 #74

Merged
merged 4 commits into from
Mar 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Invert your workflow (contract first) using Swagger ([Open API](https://openapis
Aimed to be lightweight, this bundle does not depend on FOSRestBundle or Twig.

## Important Notes
* SwaggerBundle only supports json in- and output, and only YAML Swagger definitions
* SwaggerBundle only supports json in- and output
* This bundle is currently actively maintained.
* Go to the [release page](https://github.com/kleijnweb/swagger-bundle/releases) to find details about the latest release.

Expand All @@ -18,6 +18,7 @@ A minimal example is also [available](https://github.com/kleijnweb/symfony-swagg

## This bundle will..

* Handle both JSON and YAML swagger specs transparently.
* Configure routing based on your Swagger documents(s), accounting for things like type, enums and pattern matches.
* Validate body and parameters based on your Swagger documents(s).
* Coerce query and path parameters to their defined types when possible.
Expand All @@ -31,7 +32,6 @@ A minimal example is also [available](https://github.com/kleijnweb/symfony-swagg
* Handle Form posts.
* Generate your API documentation. Use your Swagger documents, plenty of options.
* Mix well with GUI bundles. The bundle is biased towards lightweight API-only apps.
* Work with JSON Swagger documents.
* Do content negotiation or support XML.

# Usage
Expand Down
26 changes: 14 additions & 12 deletions src/Document/DocumentRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,23 @@ class DocumentRepository
*/
private $cache;

/**
* @var Loader
*/
private $loader;

/**
* Initializes a new Repository.
*
* @param string $basePath
* @param Cache $cache
* @param Loader $loader
*/
public function __construct($basePath = null, Cache $cache = null)
public function __construct($basePath = null, Cache $cache = null, Loader $loader = null)
{
$this->basePath = $basePath;
$this->cache = $cache;
$this->loader = $loader ?: new Loader();
}

/**
Expand All @@ -64,27 +71,22 @@ public function get($documentPath)
}

/**
* @param string $documentPath
* @param string $uri
*
* @return SwaggerDocument
* @throws ResourceNotReadableException
*/
private function load($documentPath)
private function load($uri)
{
if ($this->cache && $document = $this->cache->fetch($documentPath)) {
if ($this->cache && $document = $this->cache->fetch($uri)) {
return $document;
}

if (!is_readable($documentPath)) {
throw new ResourceNotReadableException("Document '$documentPath' is not readable");
}

$parser = new YamlParser();
$resolver = new RefResolver($parser->parse((string)file_get_contents($documentPath)), $documentPath);
$document = new SwaggerDocument($documentPath, $resolver->resolve());
$resolver = new RefResolver($this->loader->load($uri), $uri);
$document = new SwaggerDocument($uri, $resolver->resolve());

if ($this->cache) {
$this->cache->save($documentPath, $document);
$this->cache->save($uri, $document);
}

return $document;
Expand Down
16 changes: 16 additions & 0 deletions src/Document/Exception/ResourceNotDecodableException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the KleijnWeb\SwaggerBundle package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace KleijnWeb\SwaggerBundle\Document\Exception;

/**
* @author John Kleijn <john@kleijnweb.nl>
*/
class ResourceNotDecodableException extends InvalidReferenceException
{
}
68 changes: 68 additions & 0 deletions src/Document/Loader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php
/*
* This file is part of the KleijnWeb\SwaggerBundle package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace KleijnWeb\SwaggerBundle\Document;

use KleijnWeb\SwaggerBundle\Document\Exception\ResourceNotDecodableException;
use KleijnWeb\SwaggerBundle\Document\Exception\ResourceNotReadableException;
use Symfony\Component\Yaml\Exception\ParseException;

/**
* @author John Kleijn <john@kleijnweb.nl>
*/
class Loader
{
/**
* @var YamlParser
*/
private $yamlParser;

/**
* @param YamlParser $yamlParser
*/
public function __construct(YamlParser $yamlParser = null)
{
$this->yamlParser = $yamlParser ?: new YamlParser();
}

/**
* @param string $uri
*
* @return \stdClass
* @throws ResourceNotDecodableException
* @throws ResourceNotReadableException
*/
public function load($uri)
{
$exception = new ResourceNotReadableException("Failed reading '$uri'");

set_error_handler(function () use ($exception) {
throw $exception;
});
$response = file_get_contents($uri);
restore_error_handler();

if (false === $response) {
throw $exception;
}
if (preg_match('/\b(yml|yaml)\b/', $uri)) {
try {
$content = $this->yamlParser->parse($response);
} catch (ParseException $e) {
throw new ResourceNotDecodableException("Failed to parse '$uri' as YAML", 0, $e);
}

return $content;
}
if (!$content = json_decode($response)) {
throw new ResourceNotDecodableException("Failed to parse '$uri' as JSON");
}

return $content;
}
}
100 changes: 45 additions & 55 deletions src/Document/RefResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,19 @@ class RefResolver
/**
* @var YamlParser
*/
private $yamlParser;
private $loader;

/**
* @param object $definition
* @param string $uri
* @param YamlParser $yamlParser
* @param object $definition
* @param string $uri
* @param Loader $loader
*/
public function __construct($definition, $uri, YamlParser $yamlParser = null)
public function __construct($definition, $uri, Loader $loader = null)
{
$this->definition = $definition;
$uriSegs = $this->parseUri($uri);
if (!$uriSegs['proto']) {
$uri = realpath($uri);
}
$this->uri = $uri;
$this->uri = $uri;
$this->directory = dirname($this->uri);
$this->yamlParser = $yamlParser ?: new YamlParser();
$this->loader = $loader ?: new Loader();
}

/**
Expand Down Expand Up @@ -96,7 +92,7 @@ public function unresolve()
private function resolveRecursively(&$current, $document = null, $uri = null)
{
$document = $document ?: $this->definition;
$uri = $uri ?: $this->uri;
$uri = $uri ?: $this->uri;

if (is_array($current)) {
foreach ($current as &$value) {
Expand All @@ -109,10 +105,10 @@ private function resolveRecursively(&$current, $document = null, $uri = null)
$current = $this->lookup($uri, $document);
$this->resolveRecursively($current, $document, $uri);
} else {
$uriSegs = $this->parseUri($uri);
$normalizedUri = $this->normalizeUri($uriSegs);
$uriSegs = $this->parseUri($uri);
$normalizedUri = $this->normalizeFileUri($uriSegs);
$externalDocument = $this->loadExternal($normalizedUri);
$current = $this->lookup($uriSegs['segment'], $externalDocument, $normalizedUri);
$current = $this->lookup($uriSegs['fragment'], $externalDocument, $normalizedUri);
$this->resolveRecursively($current, $externalDocument, $normalizedUri);
}
if (is_object($current)) {
Expand Down Expand Up @@ -189,44 +185,37 @@ private function lookupRecursively(array $segments, $context)
}

/**
* @param string $uri
* @param string $fileUrl
*
* @return object
* @throws ResourceNotReadableException
*/
private function loadExternal($uri)
private function loadExternal($fileUrl)
{
$exception = new ResourceNotReadableException("Failed reading '$uri'");

set_error_handler(function () use ($exception) {
throw $exception;
});
$response = file_get_contents($uri);
restore_error_handler();

if (false === $response) {
throw $exception;
}
if (preg_match('/\b(yml|yaml)\b/', $uri)) {
return $this->yamlParser->parse($response);
}

return json_decode($response);
return $this->loader->load($fileUrl);
}


/**
* @param array $uriSegs
*
* @return string
*/
private function normalizeUri(array $uriSegs)
private function normalizeFileUri(array $uriSegs)
{
return
$uriSegs['proto'] . $uriSegs['host']
. rtrim($uriSegs['root'], '/') . '/'
. (!$uriSegs['root'] ? ltrim("$this->directory/", '/') : '')
. $uriSegs['path'];
$path = $uriSegs['path'];
$auth = !$uriSegs['user'] ? '' : "{$uriSegs['user']}:{$uriSegs['pass']}@";
$query = !$uriSegs['query'] ? '' : "?{$uriSegs['query']}";
$port = !$uriSegs['port'] ? '' : ":{$uriSegs['port']}";
$host = !$uriSegs['host'] ? '' : "{$uriSegs['scheme']}://$auth{$uriSegs['host']}{$port}";

if (substr($path, 0, 1) !== '/') {
$path = "$this->directory/$path";
if (substr($this->directory, 0, 1) === '/') {
//Assume working directory is web root
$path = ltrim($path, '/');
}
}

return "{$host}{$path}{$query}";
}

/**
Expand All @@ -237,22 +226,23 @@ private function normalizeUri(array $uriSegs)
private function parseUri($uri)
{
$defaults = [
'root' => '',
'proto' => '',
'host' => '',
'path' => '',
'segment' => ''
'scheme' => '',
'host' => '',
'port' => '',
'user' => '',
'pass' => '',
'path' => '',
'query' => '',
'fragment' => ''
];
$pattern = '@'
. '(?P<proto>[a-z]+\://)?'
. '(?P<host>[0-9a-z\.\@\:]+\.[a-z]+)?'
. '(?P<root>/)?'
. '(?P<path>[^#]*)'
. '(?P<segment>#.*)?'
. '@';

preg_match($pattern, $uri, $matches);
if (0 === strpos($uri, 'file://')) {
// parse_url botches this up
preg_match('@file://(?P<path>[^#]*)(?P<fragment>#.*)?@', $uri, $matches);

return array_merge($defaults, array_intersect_key($matches, $defaults));
}

return array_merge($defaults, array_intersect_key($matches, $defaults));
return array_merge($defaults, array_intersect_key(parse_url($uri), $defaults));
}
}
13 changes: 10 additions & 3 deletions src/Test/ApiTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,16 @@ trait ApiTestCase
*/
public static function initSchemaManager($swaggerPath)
{
$swaggerContents = file_get_contents($swaggerPath);
$swaggerJson = json_decode(
preg_match('/\.(yaml|yml)$/', $swaggerPath)
? json_encode(Yaml::parse($swaggerContents))
: $swaggerContents
);

$validator = new Validator();
$validator->check(
json_decode(json_encode(Yaml::parse(file_get_contents($swaggerPath)))),
$swaggerJson,
json_decode(file_get_contents(__DIR__ . '/../../assets/swagger-schema.json'))
);

Expand All @@ -69,8 +76,8 @@ public static function initSchemaManager($swaggerPath)
);
}

$repository = new DocumentRepository(dirname($swaggerPath));
self::$document = $repository->get(basename($swaggerPath));
$repository = new DocumentRepository(dirname($swaggerPath));
self::$document = $repository->get(basename($swaggerPath));

vfsStreamWrapper::register();
vfsStreamWrapper::setRoot(new vfsStreamDirectory('root'));
Expand Down
Loading