.gitignore000064400000000055144760130460006541 0ustar00phpunit.xml composer.lock vendor/ artifacts/ .travis.yml000064400000000456144760130460006667 0ustar00sudo: false language: php php: - 5.5 - 5.6 - 7.0 - 7.1 - hhvm - nightly matrix: fast_finish: true allow_failures: - php: 7.1 - php: hhvm - php: nightly before_script: - composer self-update - composer install --no-interaction --prefer-source --dev script: make test CHANGELOG.md000064400000036317144760130460006374 0ustar00# Change Log ## [1.1.2](https://github.com/guzzle/guzzle-services/tree/1.1.2) (2017-05-19) [Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.1.1...1.1.2) **Closed issues:** - Operations extends is broken in 1.1.1 [\#145](https://github.com/guzzle/guzzle-services/issues/145) - Default values ignored in 1.1 [\#146](https://github.com/guzzle/guzzle-services/issues/146) ## [1.1.1](https://github.com/guzzle/guzzle-services/tree/1.1.1) (2017-05-15) [Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.1.0...1.1.1) **Closed issues:** - Filters are applied twice [\#134](https://github.com/guzzle/guzzle-services/issues/134) - Is it possible to NOT urlencode a specific uri parameter value? [\#97](https://github.com/guzzle/guzzle-services/issues/97) **Merged pull requests:** - Fix minor typos in documentation. [\#139](https://github.com/guzzle/guzzle-services/pull/139) ([forevermatt](https://github.com/forevermatt)) - Do not mutate command at validation [\#135](https://github.com/guzzle/guzzle-services/pull/135) ([danizord](https://github.com/danizord)) - Added tests for JSON array of arrays and array of objects [\#131](https://github.com/guzzle/guzzle-services/pull/131) ([selfcatering](https://github.com/selfcatering)) ## [1.1.0](https://github.com/guzzle/guzzle-services/tree/1.1.0) (2017-01-31) [Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.0.1...1.1.0) **Closed issues:** - Grab a list of objects when they are not located at top level of a json response \(HATEOAS\) [\#90](https://github.com/guzzle/guzzle-services/issues/90) - Regression of Issue \#51 - XmlLocation response not handling multiple tags of the same name correctly [\#82](https://github.com/guzzle/guzzle-services/issues/82) - PUT requests with parameters with location of "postField" result in Exception [\#78](https://github.com/guzzle/guzzle-services/issues/78) - Allow to provide Post Body as an Array [\#77](https://github.com/guzzle/guzzle-services/issues/77) **Merged pull requests:** - Bring more flexibility to query params serialization [\#132](https://github.com/guzzle/guzzle-services/pull/132) ([bakura10](https://github.com/bakura10)) - Allow to fix validation for parameters with a format [\#130](https://github.com/guzzle/guzzle-services/pull/130) ([bakura10](https://github.com/bakura10)) ## [1.0.1](https://github.com/guzzle/guzzle-services/tree/1.0.1) (2017-01-13) [Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.0.0...1.0.1) **Implemented enhancements:** - guzzle 6 [\#89](https://github.com/guzzle/guzzle-services/issues/89) - Set a name when pushing ValidatedDescriptionHandler to stack [\#127](https://github.com/guzzle/guzzle-services/issues/127) **Fixed bugs:** - combine method in Uri [\#101](https://github.com/guzzle/guzzle-services/issues/101) - Undefined Variable [\#88](https://github.com/guzzle/guzzle-services/issues/88) - Regression in array parameter serialization [\#128](https://github.com/guzzle/guzzle-services/issues/128) - Unable to POST multiple multipart parameters [\#123](https://github.com/guzzle/guzzle-services/issues/123) - Fix serialization of query params [\#129](https://github.com/guzzle/guzzle-services/pull/129) ([bakura10](https://github.com/bakura10)) **Closed issues:** - Tag pre 1.0.0 release [\#121](https://github.com/guzzle/guzzle-services/issues/121) - Adjust inline documentation of Parameter [\#120](https://github.com/guzzle/guzzle-services/issues/120) - postField location not recognized after upgrading to 1.0 [\#119](https://github.com/guzzle/guzzle-services/issues/119) - Create a new release for the guzzle6 branch [\#118](https://github.com/guzzle/guzzle-services/issues/118) - Compatibility problem with PHP7.0 ? [\#116](https://github.com/guzzle/guzzle-services/issues/116) - What is the correct type of Parameter static option [\#113](https://github.com/guzzle/guzzle-services/issues/113) - Improve the construction of baseUri in Description [\#112](https://github.com/guzzle/guzzle-services/issues/112) - Please create version tag for current master branch [\#110](https://github.com/guzzle/guzzle-services/issues/110) - Problems with postField params [\#98](https://github.com/guzzle/guzzle-services/issues/98) ## [1.0.0](https://github.com/guzzle/guzzle-services/tree/1.0.0) (2016-11-24) [Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.6.0...1.0.0) **Closed issues:** - AbstractClient' not found [\#117](https://github.com/guzzle/guzzle-services/issues/117) **Merged pull requests:** - Make Guzzle Services compatible with Guzzle6 [\#109](https://github.com/guzzle/guzzle-services/pull/109) ([Konafets](https://github.com/Konafets)) ## [0.6.0](https://github.com/guzzle/guzzle-services/tree/0.6.0) (2016-10-21) [Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.5.0...0.6.0) **Closed issues:** - Broken composer install [\#111](https://github.com/guzzle/guzzle-services/issues/111) - The visit\(\) method is expected to return a RequestInterface but it doesn't in JsonLocation [\#106](https://github.com/guzzle/guzzle-services/issues/106) - Allow parameters in baseUrl [\#102](https://github.com/guzzle/guzzle-services/issues/102) - Have default params at client construction, gone away? [\#100](https://github.com/guzzle/guzzle-services/issues/100) - Runtime Exception Error is always empty [\#99](https://github.com/guzzle/guzzle-services/issues/99) - PHP Fatal error: Unsupported operand types in guzzlehttp/guzzle-services/src/GuzzleClient.php on line 72 [\#95](https://github.com/guzzle/guzzle-services/issues/95) - Date of next version [\#94](https://github.com/guzzle/guzzle-services/issues/94) - Map null reponse values to defined reponse model properties [\#91](https://github.com/guzzle/guzzle-services/issues/91) - Map a json-array into a Model [\#80](https://github.com/guzzle/guzzle-services/issues/80) - If property specified in json model but empty, notice raised [\#75](https://github.com/guzzle/guzzle-services/issues/75) - Allow primitive response types for operations [\#73](https://github.com/guzzle/guzzle-services/issues/73) - Allow shortened definition of properties in models [\#71](https://github.com/guzzle/guzzle-services/issues/71) - Where's the ServiceDescriptionLoader/AbstractConfigLoader? [\#68](https://github.com/guzzle/guzzle-services/issues/68) - errorResposnes from operation is never used [\#66](https://github.com/guzzle/guzzle-services/issues/66) - Updating the description [\#65](https://github.com/guzzle/guzzle-services/issues/65) - Parameter type validation is too strict [\#7](https://github.com/guzzle/guzzle-services/issues/7) **Merged pull requests:** - fix code example [\#115](https://github.com/guzzle/guzzle-services/pull/115) ([snoek09](https://github.com/snoek09)) - Bug Fix for GuzzleClient constructor [\#96](https://github.com/guzzle/guzzle-services/pull/96) ([peterfox](https://github.com/peterfox)) - add plugin section to readme [\#93](https://github.com/guzzle/guzzle-services/pull/93) ([gimler](https://github.com/gimler)) - Allow mapping null response values to defined response model properties [\#92](https://github.com/guzzle/guzzle-services/pull/92) ([shaun785](https://github.com/shaun785)) - Updated exception message for better debugging [\#85](https://github.com/guzzle/guzzle-services/pull/85) ([stovak](https://github.com/stovak)) - Gracefully handle null return from $this-\>getConfig\('defaults'\) [\#84](https://github.com/guzzle/guzzle-services/pull/84) ([fuhry](https://github.com/fuhry)) - Fixing issue \#82 to address regression for handling elements with the sa... [\#83](https://github.com/guzzle/guzzle-services/pull/83) ([sprak3000](https://github.com/sprak3000)) - Fix for specified property but no value in json \(notice for undefined in... [\#76](https://github.com/guzzle/guzzle-services/pull/76) ([rfink](https://github.com/rfink)) - Add ErrorHandler subscriber [\#67](https://github.com/guzzle/guzzle-services/pull/67) ([bakura10](https://github.com/bakura10)) ## [0.5.0](https://github.com/guzzle/guzzle-services/tree/0.5.0) (2014-12-23) [Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.4.0...0.5.0) **Closed issues:** - Does it supports custom class instantiate to define an operation using a service description [\#62](https://github.com/guzzle/guzzle-services/issues/62) - Tag version 0.4.0 [\#61](https://github.com/guzzle/guzzle-services/issues/61) - XmlLocation not adding attributes to non-leaf child nodes [\#52](https://github.com/guzzle/guzzle-services/issues/52) - XmlLocation response not handling multiple tags of the same name correctly [\#51](https://github.com/guzzle/guzzle-services/issues/51) - Validation Bug [\#47](https://github.com/guzzle/guzzle-services/issues/47) - CommandException doesn't contain response data [\#44](https://github.com/guzzle/guzzle-services/issues/44) - \[Fix included\] XmlLocation requires text value to have attributes [\#37](https://github.com/guzzle/guzzle-services/issues/37) - Question: Mocking a Response does not throw exception [\#35](https://github.com/guzzle/guzzle-services/issues/35) - allow default 'location' on Model [\#26](https://github.com/guzzle/guzzle-services/issues/26) - create mock subscriber requests from descriptions [\#25](https://github.com/guzzle/guzzle-services/issues/25) **Merged pull requests:** - Documentation: Add 'boolean-string' as a supported "format" value [\#63](https://github.com/guzzle/guzzle-services/pull/63) ([jwcobb](https://github.com/jwcobb)) ## [0.4.0](https://github.com/guzzle/guzzle-services/tree/0.4.0) (2014-11-03) [Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.3.0...0.4.0) **Closed issues:** - Exceptions Thrown From Subscribers Are Ignored? [\#58](https://github.com/guzzle/guzzle-services/issues/58) - Totally Broken With Guzzle 5 [\#57](https://github.com/guzzle/guzzle-services/issues/57) - GuzzleHTTP/Command Dependency fail [\#50](https://github.com/guzzle/guzzle-services/issues/50) - Request parameter PathLocation [\#46](https://github.com/guzzle/guzzle-services/issues/46) - Requesting a new version tag [\#45](https://github.com/guzzle/guzzle-services/issues/45) - CommandException expects second parameter to be CommandTransaction instance [\#43](https://github.com/guzzle/guzzle-services/issues/43) - Cannot add Autorization header to my requests [\#39](https://github.com/guzzle/guzzle-services/issues/39) - Resouce Itterators [\#36](https://github.com/guzzle/guzzle-services/issues/36) - Question [\#33](https://github.com/guzzle/guzzle-services/issues/33) - query location array can be comma separated [\#31](https://github.com/guzzle/guzzle-services/issues/31) - Automatically returns array from command? [\#30](https://github.com/guzzle/guzzle-services/issues/30) - Arrays nested under objects in JSON response broken? [\#27](https://github.com/guzzle/guzzle-services/issues/27) - Question? [\#23](https://github.com/guzzle/guzzle-services/issues/23) **Merged pull requests:** - Bump the version in the readme [\#60](https://github.com/guzzle/guzzle-services/pull/60) ([GrahamCampbell](https://github.com/GrahamCampbell)) - Bump the next version to 0.4 [\#56](https://github.com/guzzle/guzzle-services/pull/56) ([GrahamCampbell](https://github.com/GrahamCampbell)) - Fixed the guzzlehttp/command version constraint [\#55](https://github.com/guzzle/guzzle-services/pull/55) ([GrahamCampbell](https://github.com/GrahamCampbell)) - Work with latest Guzzle 5 and Command updates [\#54](https://github.com/guzzle/guzzle-services/pull/54) ([mtdowling](https://github.com/mtdowling)) - Addressing Issue \#51 & Issue \#52 [\#53](https://github.com/guzzle/guzzle-services/pull/53) ([sprak3000](https://github.com/sprak3000)) - added description interface to extend it [\#49](https://github.com/guzzle/guzzle-services/pull/49) ([danieledangeli](https://github.com/danieledangeli)) - Update readme to improve documentation \(\#46\) [\#48](https://github.com/guzzle/guzzle-services/pull/48) ([bonndan](https://github.com/bonndan)) - Fixed the readme version constraint [\#42](https://github.com/guzzle/guzzle-services/pull/42) ([GrahamCampbell](https://github.com/GrahamCampbell)) - Update .travis.yml [\#41](https://github.com/guzzle/guzzle-services/pull/41) ([GrahamCampbell](https://github.com/GrahamCampbell)) - Added a branch alias [\#40](https://github.com/guzzle/guzzle-services/pull/40) ([GrahamCampbell](https://github.com/GrahamCampbell)) - Fixes Response\XmlLocation requires text value [\#38](https://github.com/guzzle/guzzle-services/pull/38) ([magnetik](https://github.com/magnetik)) - Removing unnecessary \(\) from docblock [\#32](https://github.com/guzzle/guzzle-services/pull/32) ([jamiehannaford](https://github.com/jamiehannaford)) - Fix JSON response location so that both is supported: arrays nested unde... [\#28](https://github.com/guzzle/guzzle-services/pull/28) ([ukautz](https://github.com/ukautz)) ## [0.3.0](https://github.com/guzzle/guzzle-services/tree/0.3.0) (2014-06-01) [Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.2.0...0.3.0) **Closed issues:** - Testing Guzzle Services doesn't work [\#19](https://github.com/guzzle/guzzle-services/issues/19) - Description factory [\#18](https://github.com/guzzle/guzzle-services/issues/18) - support to load service description from file [\#15](https://github.com/guzzle/guzzle-services/issues/15) - Update dependency on guzzlehttp/command [\#11](https://github.com/guzzle/guzzle-services/issues/11) **Merged pull requests:** - Add license file [\#22](https://github.com/guzzle/guzzle-services/pull/22) ([siwinski](https://github.com/siwinski)) - Fix 'Invalid argument supplied for foreach\(\)' [\#21](https://github.com/guzzle/guzzle-services/pull/21) ([Olden](https://github.com/Olden)) - Fixed string zero \('0'\) values not being filtered in XML. [\#20](https://github.com/guzzle/guzzle-services/pull/20) ([dragonwize](https://github.com/dragonwize)) ## [0.2.0](https://github.com/guzzle/guzzle-services/tree/0.2.0) (2014-03-30) [Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.1.0...0.2.0) **Closed issues:** - please remove wiki [\#13](https://github.com/guzzle/guzzle-services/issues/13) - Parameter validation fails for union types [\#12](https://github.com/guzzle/guzzle-services/issues/12) - question on integration with Guzzle4 [\#8](https://github.com/guzzle/guzzle-services/issues/8) - typehints for operations property [\#6](https://github.com/guzzle/guzzle-services/issues/6) - improve exception message [\#5](https://github.com/guzzle/guzzle-services/issues/5) **Merged pull requests:** - Update composer.json [\#14](https://github.com/guzzle/guzzle-services/pull/14) ([GrahamCampbell](https://github.com/GrahamCampbell)) - Update composer.json [\#9](https://github.com/guzzle/guzzle-services/pull/9) ([GrahamCampbell](https://github.com/GrahamCampbell)) - some fixes [\#4](https://github.com/guzzle/guzzle-services/pull/4) ([cordoval](https://github.com/cordoval)) - Fix the CommandException path used in ValidateInput [\#2](https://github.com/guzzle/guzzle-services/pull/2) ([mookle](https://github.com/mookle)) - Minor improvements [\#1](https://github.com/guzzle/guzzle-services/pull/1) ([GrahamCampbell](https://github.com/GrahamCampbell)) ## [0.1.0](https://github.com/guzzle/guzzle-services/tree/0.1.0) (2014-03-15) \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*LICENSE000064400000002127144760130460005560 0ustar00Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Makefile000064400000000317144760130460006212 0ustar00all: clean test test: vendor/bin/phpunit coverage: vendor/bin/phpunit --coverage-html=artifacts/coverage view-coverage: open artifacts/coverage/index.html clean: rm -rf artifacts/* .PHONY: coverage README.md000064400000011272144760130460006033 0ustar00# Guzzle Services [![License](https://poser.pugx.org/guzzlehttp/guzzle-services/license)](https://packagist.org/packages/guzzlehttp/guzzle-services) [![Build Status](https://travis-ci.org/guzzle/guzzle-services.svg?branch=master)](https://travis-ci.org/guzzle/guzzle-services) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/guzzle/guzzle-services/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/guzzle/guzzle-services/?branch=master) [![Code Coverage](https://scrutinizer-ci.com/g/guzzle/guzzle-services/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/guzzle/guzzle-services/?branch=master) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/b08be676-b209-40b7-a6df-b6d13e8dff62/mini.png)](https://insight.sensiolabs.com/projects/b08be676-b209-40b7-a6df-b6d13e8dff62) [![Latest Stable Version](https://poser.pugx.org/guzzlehttp/guzzle-services/v/stable)](https://packagist.org/packages/guzzlehttp/guzzle-services) [![Latest Unstable Version](https://poser.pugx.org/guzzlehttp/guzzle-services/v/unstable)](https://packagist.org/packages/guzzlehttp/guzzle-services) [![Total Downloads](https://poser.pugx.org/guzzlehttp/guzzle-services/downloads)](https://packagist.org/packages/guzzlehttp/guzzle-services) Provides an implementation of the Guzzle Command library that uses Guzzle service descriptions to describe web services, serialize requests, and parse responses into easy to use model structures. ```php use GuzzleHttp\Client; use GuzzleHttp\Command\Guzzle\GuzzleClient; use GuzzleHttp\Command\Guzzle\Description; $client = new Client(); $description = new Description([ 'baseUri' => 'http://httpbin.org/', 'operations' => [ 'testing' => [ 'httpMethod' => 'GET', 'uri' => '/get{?foo}', 'responseModel' => 'getResponse', 'parameters' => [ 'foo' => [ 'type' => 'string', 'location' => 'uri' ], 'bar' => [ 'type' => 'string', 'location' => 'query' ] ] ] ], 'models' => [ 'getResponse' => [ 'type' => 'object', 'additionalProperties' => [ 'location' => 'json' ] ] ] ]); $guzzleClient = new GuzzleClient($client, $description); $result = $guzzleClient->testing(['foo' => 'bar']); echo $result['args']['foo']; // bar ``` ## Installing This project can be installed using Composer: ``composer require guzzlehttp/guzzle-services`` For **Guzzle 5**, use ``composer require guzzlehttp/guzzle-services:0.6``. **Note:** If Composer is not installed [globally](https://getcomposer.org/doc/00-intro.md#globally) then you may need to run the preceding Composer commands using ``php composer.phar`` (where ``composer.phar`` is the path to your copy of Composer), instead of just ``composer``. ## Plugins * Load Service description from file [https://github.com/gimler/guzzle-description-loader] ## Transition guide from Guzzle 5.0 to 6.0 ### Change regarding PostField and PostFile The request locations `postField` and `postFile` were removed in favor of `formParam` and `multipart`. If your description looks like ```php [ 'baseUri' => 'http://httpbin.org/', 'operations' => [ 'testing' => [ 'httpMethod' => 'GET', 'uri' => '/get{?foo}', 'responseModel' => 'getResponse', 'parameters' => [ 'foo' => [ 'type' => 'string', 'location' => 'postField' ], 'bar' => [ 'type' => 'string', 'location' => 'postFile' ] ] ] ], ] ``` you need to change `postField` to `formParam` and `postFile` to `multipart`. More documentation coming soon. ## Cookbook ### Changing the way query params are serialized By default, query params are serialized using strict RFC3986 rules, using `http_build_query` method. With this, array params are serialized this way: ```php $client->myMethod(['foo' => ['bar', 'baz']]); // Query params will be foo[0]=bar&foo[1]=baz ``` However, a lot of APIs in the wild require the numeric indices to be removed, so that the query params end up being `foo[]=bar&foo[]=baz`. You can easily change the behaviour by creating your own serializer and overriding the "query" request location: ```php use GuzzleHttp\Command\Guzzle\GuzzleClient; use GuzzleHttp\Command\Guzzle\RequestLocation\QueryLocation; use GuzzleHttp\Command\Guzzle\QuerySerializer\Rfc3986Serializer; use GuzzleHttp\Command\Guzzle\Serializer; $queryLocation = new QueryLocation('query', new Rfc3986Serializer(true)); $serializer = new Serializer($description, ['query' => $queryLocation]); $guzzleClient = new GuzzleClient($client, $description, $serializer); ``` You can also create your own serializer if you have specific needs.composer.json000064400000002555144760130460007302 0ustar00{ "name": "guzzlehttp/guzzle-services", "description": "Provides an implementation of the Guzzle Command library that uses Guzzle service descriptions to describe web services, serialize requests, and parse responses into easy to use model structures.", "type": "library", "license": "MIT", "authors": [ { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" }, { "name": "Jeremy Lindblom", "email": "jeremeamia@gmail.com", "homepage": "https://github.com/jeremeamia" }, { "name": "Stefano Kowalke", "email": "blueduck@mail.org", "homepage": "https://github.com/konafets" } ], "require": { "php": ">=5.5", "guzzlehttp/guzzle": "^6.2", "guzzlehttp/command": "~1.0" }, "require-dev": { "phpunit/phpunit": "~4.0" }, "autoload": { "psr-4": { "GuzzleHttp\\Command\\Guzzle\\": "src/" } }, "autoload-dev": { "psr-4": { "GuzzleHttp\\Tests\\Command\\Guzzle\\": "tests/" } }, "suggest": { "gimler/guzzle-description-loader": "^0.0.4" }, "extra": { "branch-alias": { "dev-master": "1.0.x-dev" } } } phpunit.xml.dist000064400000000543144760130460007726 0ustar00 tests src src/Description.php000064400000015537144760130460010347 0ustar00{$key} = $config[$key]; } } // Set the baseUri // Account for the old style of using baseUrl if (isset($config['baseUrl'])) { $config['baseUri'] = $config['baseUrl']; } $this->baseUri = isset($config['baseUri']) ? new Uri($config['baseUri']) : new Uri(); // Ensure that the models and operations properties are always arrays $this->models = (array) $this->models; $this->operations = (array) $this->operations; // We want to add operations differently than adding the other properties $defaultKeys[] = 'operations'; // Create operations for each operation if (isset($config['operations'])) { foreach ($config['operations'] as $name => $operation) { if (!is_array($operation)) { throw new \InvalidArgumentException('Operations must be arrays'); } $this->operations[$name] = $operation; } } // Get all of the additional properties of the service description and // store them in a data array foreach (array_diff(array_keys($config), $defaultKeys) as $key) { $this->extraData[$key] = $config[$key]; } // Configure the schema formatter if (isset($options['formatter'])) { $this->formatter = $options['formatter']; } else { static $defaultFormatter; if (!$defaultFormatter) { $defaultFormatter = new SchemaFormatter(); } $this->formatter = $defaultFormatter; } } /** * Get the basePath/baseUri of the description * * @return Uri */ public function getBaseUri() { return $this->baseUri; } /** * Get the API operations of the service * * @return Operation[] Returns an array of {@see Operation} objects */ public function getOperations() { return $this->operations; } /** * Check if the service has an operation by name * * @param string $name Name of the operation to check * * @return bool */ public function hasOperation($name) { return isset($this->operations[$name]); } /** * Get an API operation by name * * @param string $name Name of the command * * @return Operation * @throws \InvalidArgumentException if the operation is not found */ public function getOperation($name) { if (!$this->hasOperation($name)) { throw new \InvalidArgumentException("No operation found named $name"); } // Lazily create operations as they are retrieved if (!($this->operations[$name] instanceof Operation)) { $this->operations[$name]['name'] = $name; $this->operations[$name] = new Operation($this->operations[$name], $this); } return $this->operations[$name]; } /** * Get a shared definition structure. * * @param string $id ID/name of the model to retrieve * * @return Parameter * @throws \InvalidArgumentException if the model is not found */ public function getModel($id) { if (!$this->hasModel($id)) { throw new \InvalidArgumentException("No model found named $id"); } // Lazily create models as they are retrieved if (!($this->models[$id] instanceof Parameter)) { $this->models[$id] = new Parameter( $this->models[$id], ['description' => $this] ); } return $this->models[$id]; } /** * Get all models of the service description. * * @return array */ public function getModels() { $models = []; foreach ($this->models as $name => $model) { $models[$name] = $this->getModel($name); } return $models; } /** * Check if the service description has a model by name. * * @param string $id Name/ID of the model to check * * @return bool */ public function hasModel($id) { return isset($this->models[$id]); } /** * Get the API version of the service * * @return string */ public function getApiVersion() { return $this->apiVersion; } /** * Get the name of the API * * @return string */ public function getName() { return $this->name; } /** * Get a summary of the purpose of the API * * @return string */ public function getDescription() { return $this->description; } /** * Format a parameter using named formats. * * @param string $format Format to convert it to * @param mixed $input Input string * * @return mixed */ public function format($format, $input) { return $this->formatter->format($format, $input); } /** * Get arbitrary data from the service description that is not part of the * Guzzle service description specification. * * @param string $key Data key to retrieve or null to retrieve all extra * * @return null|mixed */ public function getData($key = null) { if ($key === null) { return $this->extraData; } elseif (isset($this->extraData[$key])) { return $this->extraData[$key]; } else { return null; } } } src/DescriptionInterface.php000064400000004467144760130460012170 0ustar00 new BodyLocation(), 'header' => new HeaderLocation(), 'reasonPhrase' => new ReasonPhraseLocation(), 'statusCode' => new StatusCodeLocation(), 'xml' => new XmlLocation(), 'json' => new JsonLocation(), ]; } $this->responseLocations = $responseLocations + $defaultResponseLocations; $this->description = $description; $this->process = $process; } /** * Deserialize the response into the specified result representation * * @param ResponseInterface $response * @param RequestInterface|null $request * @param CommandInterface $command * @return Result|ResultInterface|void|ResponseInterface */ public function __invoke(ResponseInterface $response, RequestInterface $request, CommandInterface $command) { // If the user don't want to process the result, just return the plain response here if ($this->process === false) { return $response; } $name = $command->getName(); $operation = $this->description->getOperation($name); $this->handleErrorResponses($response, $request, $command, $operation); // Add a default Model as the result if no matching schema was found if (!($modelName = $operation->getResponseModel())) { // Not sure if this should be empty or contains the response. // Decided to do it how it was in the old version for now. return new Result(); } $model = $operation->getServiceDescription()->getModel($modelName); if (!$model) { throw new \RuntimeException("Unknown model: {$modelName}"); } return $this->visit($model, $response); } /** * Handles visit() and after() methods of the Response locations * * @param Parameter $model * @param ResponseInterface $response * @return Result|ResultInterface|void */ protected function visit(Parameter $model, ResponseInterface $response) { $result = new Result(); $context = ['visitors' => []]; if ($model->getType() === 'object') { $result = $this->visitOuterObject($model, $result, $response, $context); } elseif ($model->getType() === 'array') { $result = $this->visitOuterArray($model, $result, $response, $context); } else { throw new \InvalidArgumentException('Invalid response model: ' . $model->getType()); } // Call the after() method of each found visitor /** @var ResponseLocationInterface $visitor */ foreach ($context['visitors'] as $visitor) { $result = $visitor->after($result, $response, $model); } return $result; } /** * Handles the before() method of Response locations * * @param string $location * @param Parameter $model * @param ResultInterface $result * @param ResponseInterface $response * @param array $context * @return ResultInterface */ private function triggerBeforeVisitor( $location, Parameter $model, ResultInterface $result, ResponseInterface $response, array &$context ) { if (!isset($this->responseLocations[$location])) { throw new \RuntimeException("Unknown location: $location"); } $context['visitors'][$location] = $this->responseLocations[$location]; $result = $this->responseLocations[$location]->before( $result, $response, $model ); return $result; } /** * Visits the outer object * * @param Parameter $model * @param ResultInterface $result * @param ResponseInterface $response * @param array $context * @return ResultInterface */ private function visitOuterObject( Parameter $model, ResultInterface $result, ResponseInterface $response, array &$context ) { $parentLocation = $model->getLocation(); // If top-level additionalProperties is a schema, then visit it $additional = $model->getAdditionalProperties(); if ($additional instanceof Parameter) { // Use the model location if none set on additionalProperties. $location = $additional->getLocation() ?: $parentLocation; $result = $this->triggerBeforeVisitor($location, $model, $result, $response, $context); } // Use 'location' from all individual defined properties, but fall back // to the model location if no per-property location is set. Collect // the properties that need to be visited into an array. $visitProperties = []; foreach ($model->getProperties() as $schema) { $location = $schema->getLocation() ?: $parentLocation; if ($location) { $visitProperties[] = [$location, $schema]; // Trigger the before method on each unique visitor location if (!isset($context['visitors'][$location])) { $result = $this->triggerBeforeVisitor($location, $model, $result, $response, $context); } } } // Actually visit each response element foreach ($visitProperties as $property) { $result = $this->responseLocations[$property[0]]->visit($result, $response, $property[1]); } return $result; } /** * Visits the outer array * * @param Parameter $model * @param ResultInterface $result * @param ResponseInterface $response * @param array $context * @return ResultInterface|void */ private function visitOuterArray( Parameter $model, ResultInterface $result, ResponseInterface $response, array &$context ) { // Use 'location' defined on the top of the model if (!($location = $model->getLocation())) { return; } // Trigger the before method on each unique visitor location if (!isset($context['visitors'][$location])) { $result = $this->triggerBeforeVisitor($location, $model, $result, $response, $context); } // Visit each item in the response $result = $this->responseLocations[$location]->visit($result, $response, $model); return $result; } /** * Reads the "errorResponses" from commands, and trigger appropriate exceptions * * In order for the exception to be properly triggered, all your exceptions must be instance * of "GuzzleHttp\Command\Exception\CommandException". If that's not the case, your exceptions will be wrapped * around a CommandException * * @param ResponseInterface $response * @param RequestInterface $request * @param CommandInterface $command * @param Operation $operation */ protected function handleErrorResponses( ResponseInterface $response, RequestInterface $request, CommandInterface $command, Operation $operation ) { $errors = $operation->getErrorResponses(); // We iterate through each errors in service description. If the descriptor contains both a phrase and // status code, there must be an exact match of both. Otherwise, a match of status code is enough $bestException = null; foreach ($errors as $error) { $code = (int) $error['code']; if ($response->getStatusCode() !== $code) { continue; } if (isset($error['phrase']) && ! ($error['phrase'] === $response->getReasonPhrase())) { continue; } $bestException = $error['class']; // If there is an exact match of phrase + code, then we cannot find a more specialized exception in // the array, so we can break early instead of iterating the remaining ones if (isset($error['phrase'])) { break; } } if (null !== $bestException) { throw new $bestException($response->getReasonPhrase(), $command, null, $request, $response); } // If we reach here, no exception could be match from descriptor, and Guzzle exception will propagate if // option "http_errors" is set to true, which is the default setting. } } src/GuzzleClient.php000064400000012571144760130460010476 0ustar00config = $config; $this->description = $description; $serializer = $this->getSerializer($commandToRequestTransformer); $deserializer = $this->getDeserializer($responseToResultTransformer); parent::__construct($client, $serializer, $deserializer, $commandHandlerStack); $this->processConfig($config); } /** * Returns the command if valid; otherwise an Exception * @param string $name * @param array $args * @return CommandInterface * @throws \InvalidArgumentException */ public function getCommand($name, array $args = []) { if (!$this->description->hasOperation($name)) { $name = ucfirst($name); if (!$this->description->hasOperation($name)) { throw new \InvalidArgumentException( "No operation found named {$name}" ); } } // Merge in default command options $args += $this->getConfig('defaults'); return parent::getCommand($name, $args); } /** * Return the description * * @return DescriptionInterface */ public function getDescription() { return $this->description; } /** * Returns the passed Serializer when set, a new instance otherwise * * @param callable|null $commandToRequestTransformer * @return \GuzzleHttp\Command\Guzzle\Serializer */ private function getSerializer($commandToRequestTransformer) { return $commandToRequestTransformer ==! null ? $commandToRequestTransformer : new Serializer($this->description); } /** * Returns the passed Deserializer when set, a new instance otherwise * * @param callable|null $responseToResultTransformer * @return \GuzzleHttp\Command\Guzzle\Deserializer */ private function getDeserializer($responseToResultTransformer) { $process = (! isset($this->config['process']) || $this->config['process'] === true); return $responseToResultTransformer ==! null ? $responseToResultTransformer : new Deserializer($this->description, $process); } /** * Get the config of the client * * @param array|string $option * @return mixed */ public function getConfig($option = null) { return $option === null ? $this->config : (isset($this->config[$option]) ? $this->config[$option] : []); } /** * @param $option * @param $value */ public function setConfig($option, $value) { $this->config[$option] = $value; } /** * Prepares the client based on the configuration settings of the client. * * @param array $config Constructor config as an array */ protected function processConfig(array $config) { // set defaults as an array if not provided if (!isset($config['defaults'])) { $config['defaults'] = []; } // Add the handlers based on the configuration option $stack = $this->getHandlerStack(); if (!isset($config['validate']) || $config['validate'] === true) { $stack->push(new ValidatedDescriptionHandler($this->description), 'validate_description'); } if (!isset($config['process']) || $config['process'] === true) { // TODO: This belongs to the Deserializer and should be handled there. // Question: What is the result when the Deserializer is bypassed? // Possible answer: The raw response. } } } src/Handler/ValidatedDescriptionHandler.php000064400000005654144760130460015037 0ustar00 */ class ValidatedDescriptionHandler { /** @var SchemaValidator $validator */ private $validator; /** @var DescriptionInterface $description */ private $description; /** * ValidatedDescriptionHandler constructor. * * @param DescriptionInterface $description * @param SchemaValidator|null $schemaValidator */ public function __construct(DescriptionInterface $description, SchemaValidator $schemaValidator = null) { $this->description = $description; $this->validator = $schemaValidator ?: new SchemaValidator(); } /** * @param callable $handler * @return \Closure */ public function __invoke(callable $handler) { return function (CommandInterface $command) use ($handler) { $errors = []; $operation = $this->description->getOperation($command->getName()); foreach ($operation->getParams() as $name => $schema) { $value = $command[$name]; if ($value) { $value = $schema->filter($value); } if (! $this->validator->validate($schema, $value)) { $errors = array_merge($errors, $this->validator->getErrors()); } elseif ($value !== $command[$name]) { // Update the config value if it changed and no validation errors were encountered. // This happen when the user extending an operation // See https://github.com/guzzle/guzzle-services/issues/145 $command[$name] = $value; } } if ($params = $operation->getAdditionalParameters()) { foreach ($command->toArray() as $name => $value) { // It's only additional if it isn't defined in the schema if (! $operation->hasParam($name)) { // Always set the name so that error messages are useful $params->setName($name); if (! $this->validator->validate($params, $value)) { $errors = array_merge($errors, $this->validator->getErrors()); } elseif ($value !== $command[$name]) { $command[$name] = $value; } } } } if ($errors) { throw new CommandException('Validation errors: ' . implode("\n", $errors), $command); } return $handler($command); }; } } src/Operation.php000064400000020333144760130460010012 0ustar00 '', 'httpMethod' => '', 'uri' => '', 'responseModel' => null, 'notes' => '', 'summary' => '', 'documentationUrl' => null, 'deprecated' => false, 'data' => [], 'parameters' => [], 'additionalParameters' => null, 'errorResponses' => [] ]; $this->description = $description === null ? new Description([]) : $description; if (isset($config['extends'])) { $config = $this->resolveExtends($config['extends'], $config); } $this->config = $config + $defaults; // Account for the old style of using responseClass if (isset($config['responseClass'])) { $this->config['responseModel'] = $config['responseClass']; } $this->resolveParameters(); } /** * @return array */ public function toArray() { return $this->config; } /** * Get the service description that the operation belongs to * * @return Description */ public function getServiceDescription() { return $this->description; } /** * Get the params of the operation * * @return Parameter[] */ public function getParams() { return $this->parameters; } /** * Get additionalParameters of the operation * * @return Parameter|null */ public function getAdditionalParameters() { return $this->additionalParameters; } /** * Check if the operation has a specific parameter by name * * @param string $name Name of the param * * @return bool */ public function hasParam($name) { return isset($this->parameters[$name]); } /** * Get a single parameter of the operation * * @param string $name Parameter to retrieve by name * * @return Parameter|null */ public function getParam($name) { return isset($this->parameters[$name]) ? $this->parameters[$name] : null; } /** * Get the HTTP method of the operation * * @return string|null */ public function getHttpMethod() { return $this->config['httpMethod']; } /** * Get the name of the operation * * @return string|null */ public function getName() { return $this->config['name']; } /** * Get a short summary of what the operation does * * @return string|null */ public function getSummary() { return $this->config['summary']; } /** * Get a longer text field to explain the behavior of the operation * * @return string|null */ public function getNotes() { return $this->config['notes']; } /** * Get the documentation URL of the operation * * @return string|null */ public function getDocumentationUrl() { return $this->config['documentationUrl']; } /** * Get the name of the model used for processing the response. * * @return string */ public function getResponseModel() { return $this->config['responseModel']; } /** * Get whether or not the operation is deprecated * * @return bool */ public function getDeprecated() { return $this->config['deprecated']; } /** * Get the URI that will be merged into the generated request * * @return string */ public function getUri() { return $this->config['uri']; } /** * Get the errors that could be encountered when executing the operation * * @return array */ public function getErrorResponses() { return $this->config['errorResponses']; } /** * Get extra data from the operation * * @param string $name Name of the data point to retrieve or null to * retrieve all of the extra data. * * @return mixed|null */ public function getData($name = null) { if ($name === null) { return $this->config['data']; } elseif (isset($this->config['data'][$name])) { return $this->config['data'][$name]; } else { return null; } } /** * @param $name * @param array $config * @return array */ private function resolveExtends($name, array $config) { if (!$this->description->hasOperation($name)) { throw new \InvalidArgumentException('No operation named ' . $name); } // Merge parameters together one level deep $base = $this->description->getOperation($name)->toArray(); $result = $config + $base; if (isset($base['parameters']) && isset($config['parameters'])) { $result['parameters'] = $config['parameters'] + $base['parameters']; } return $result; } /** * Process the description and extract the parameter config * * @return void */ private function resolveParameters() { // Parameters need special handling when adding foreach ($this->config['parameters'] as $name => $param) { if (!is_array($param)) { throw new \InvalidArgumentException( 'Parameters must be arrays' ); } $param['name'] = $name; $this->parameters[$name] = new Parameter( $param, ['description' => $this->description] ); } if ($this->config['additionalParameters']) { if (is_array($this->config['additionalParameters'])) { $this->additionalParameters = new Parameter( $this->config['additionalParameters'], ['description' => $this->description] ); } else { $this->additionalParameters = $this->config['additionalParameters']; } } } } src/Parameter.php000064400000043127144760130460010000 0ustar00originalData = $data; if (isset($options['description'])) { $this->serviceDescription = $options['description']; if (!($this->serviceDescription instanceof DescriptionInterface)) { throw new \InvalidArgumentException('description must be a Description'); } if (isset($data['$ref'])) { if ($model = $this->serviceDescription->getModel($data['$ref'])) { $name = isset($data['name']) ? $data['name'] : null; $data = $model->toArray() + $data; if ($name) { $data['name'] = $name; } } } elseif (isset($data['extends'])) { // If this parameter extends from another parameter then start // with the actual data union in the parent's data (e.g. actual // supersedes parent) if ($extends = $this->serviceDescription->getModel($data['extends'])) { $data += $extends->toArray(); } } } // Pull configuration data into the parameter foreach ($data as $key => $value) { $this->{$key} = $value; } $this->required = (bool) $this->required; $this->data = (array) $this->data; if ($this->filters) { $this->setFilters((array) $this->filters); } if ($this->type == 'object' && $this->additionalProperties === null) { $this->additionalProperties = true; } } /** * Convert the object to an array * * @return array */ public function toArray() { return $this->originalData; } /** * Get the default or static value of the command based on a value * * @param string $value Value that is currently set * * @return mixed Returns the value, a static value if one is present, or a default value */ public function getValue($value) { if ($this->static || ($this->default !== null && $value === null)) { return $this->default; } return $value; } /** * Run a value through the filters OR format attribute associated with the * parameter. * * @param mixed $value Value to filter * * @return mixed Returns the filtered value * @throws \RuntimeException when trying to format when no service * description is available. */ public function filter($value) { // Formats are applied exclusively and supersed filters if ($this->format) { if (!$this->serviceDescription) { throw new \RuntimeException('No service description was set so ' . 'the value cannot be formatted.'); } return $this->serviceDescription->format($this->format, $value); } // Convert Boolean values if ($this->type == 'boolean' && !is_bool($value)) { $value = filter_var($value, FILTER_VALIDATE_BOOLEAN); } // Apply filters to the value if ($this->filters) { foreach ($this->filters as $filter) { if (is_array($filter)) { // Convert complex filters that hold value place holders foreach ($filter['args'] as &$data) { if ($data == '@value') { $data = $value; } elseif ($data == '@api') { $data = $this; } } $value = call_user_func_array( $filter['method'], $filter['args'] ); } else { $value = call_user_func($filter, $value); } } } return $value; } /** * Get the name of the parameter * * @return string */ public function getName() { return $this->name; } /** * Set the name of the parameter * * @param string $name Name to set */ public function setName($name) { $this->name = $name; } /** * Get the key of the parameter, where sentAs will supersede name if it is * set. * * @return string */ public function getWireName() { return $this->sentAs ?: $this->name; } /** * Get the type(s) of the parameter * * @return string|array */ public function getType() { return $this->type; } /** * Get if the parameter is required * * @return bool */ public function isRequired() { return $this->required; } /** * Get the default value of the parameter * * @return string|null */ public function getDefault() { return $this->default; } /** * Get the description of the parameter * * @return string|null */ public function getDescription() { return $this->description; } /** * Get the minimum acceptable value for an integer * * @return int|null */ public function getMinimum() { return $this->minimum; } /** * Get the maximum acceptable value for an integer * * @return int|null */ public function getMaximum() { return $this->maximum; } /** * Get the minimum allowed length of a string value * * @return int */ public function getMinLength() { return $this->minLength; } /** * Get the maximum allowed length of a string value * * @return int|null */ public function getMaxLength() { return $this->maxLength; } /** * Get the maximum allowed number of items in an array value * * @return int|null */ public function getMaxItems() { return $this->maxItems; } /** * Get the minimum allowed number of items in an array value * * @return int */ public function getMinItems() { return $this->minItems; } /** * Get the location of the parameter * * @return string|null */ public function getLocation() { return $this->location; } /** * Get the sentAs attribute of the parameter that used with locations to * sentAs an attribute when it is being applied to a location. * * @return string|null */ public function getSentAs() { return $this->sentAs; } /** * Retrieve a known property from the parameter by name or a data property * by name. When no specific name value is passed, all data properties * will be returned. * * @param string|null $name Specify a particular property name to retrieve * * @return array|mixed|null */ public function getData($name = null) { if (!$name) { return $this->data; } elseif (isset($this->data[$name])) { return $this->data[$name]; } elseif (isset($this->{$name})) { return $this->{$name}; } return null; } /** * Get whether or not the default value can be changed * * @return bool */ public function isStatic() { return $this->static; } /** * Get an array of filters used by the parameter * * @return array */ public function getFilters() { return $this->filters ?: []; } /** * Get the properties of the parameter * * @return Parameter[] */ public function getProperties() { if (!$this->propertiesCache) { $this->propertiesCache = []; foreach (array_keys($this->properties) as $name) { $this->propertiesCache[$name] = $this->getProperty($name); } } return $this->propertiesCache; } /** * Get a specific property from the parameter * * @param string $name Name of the property to retrieve * * @return null|Parameter */ public function getProperty($name) { if (!isset($this->properties[$name])) { return null; } if (!($this->properties[$name] instanceof self)) { $this->properties[$name]['name'] = $name; $this->properties[$name] = new static( $this->properties[$name], ['description' => $this->serviceDescription] ); } return $this->properties[$name]; } /** * Get the additionalProperties value of the parameter * * @return bool|Parameter|null */ public function getAdditionalProperties() { if (is_array($this->additionalProperties)) { $this->additionalProperties = new static( $this->additionalProperties, ['description' => $this->serviceDescription] ); } return $this->additionalProperties; } /** * Get the item data of the parameter * * @return Parameter */ public function getItems() { if (is_array($this->items)) { $this->items = new static( $this->items, ['description' => $this->serviceDescription] ); } return $this->items; } /** * Get the enum of strings that are valid for the parameter * * @return array|null */ public function getEnum() { return $this->enum; } /** * Get the regex pattern that must match a value when the value is a string * * @return string */ public function getPattern() { return $this->pattern; } /** * Get the format attribute of the schema * * @return string */ public function getFormat() { return $this->format; } /** * Set the array of filters used by the parameter * * @param array $filters Array of functions to use as filters * * @return self */ private function setFilters(array $filters) { $this->filters = []; foreach ($filters as $filter) { $this->addFilter($filter); } return $this; } /** * Add a filter to the parameter * * @param string|array $filter Method to filter the value through * * @return self * @throws \InvalidArgumentException */ private function addFilter($filter) { if (is_array($filter)) { if (!isset($filter['method'])) { throw new \InvalidArgumentException( 'A [method] value must be specified for each complex filter' ); } } if (!$this->filters) { $this->filters = [$filter]; } else { $this->filters[] = $filter; } return $this; } /** * Check if a parameter has a specific variable and if it set. * * @param string $var * @return bool */ public function has($var) { if (!is_string($var)) { throw new \InvalidArgumentException('Expected a string. Got: ' . (is_object($var) ? get_class($var) : gettype($var))); } return isset($this->{$var}) && !empty($this->{$var}); } } src/QuerySerializer/QuerySerializerInterface.php000064400000000443144760130460016171 0ustar00removeNumericIndices = $removeNumericIndices; } /** * {@inheritDoc} */ public function aggregate(array $queryParams) { $queryString = http_build_query($queryParams, null, '&', PHP_QUERY_RFC3986); if ($this->removeNumericIndices) { $queryString = preg_replace('/%5B[0-9]+%5D/simU', '%5B%5D', $queryString); } return $queryString; } }src/RequestLocation/AbstractLocation.php000064400000005442144760130460014433 0ustar00locationName = $locationName; } /** * @param CommandInterface $command * @param RequestInterface $request * @param Parameter $param * @return RequestInterface */ public function visit( CommandInterface $command, RequestInterface $request, Parameter $param ) { return $request; } /** * @param CommandInterface $command * @param RequestInterface $request * @param Operation $operation * @return RequestInterface */ public function after( CommandInterface $command, RequestInterface $request, Operation $operation ) { return $request; } /** * Prepare (filter and set desired name for request item) the value for * request. * * @param mixed $value * @param Parameter $param * * @return array|mixed */ protected function prepareValue($value, Parameter $param) { return is_array($value) ? $this->resolveRecursively($value, $param) : $param->filter($value); } /** * Recursively prepare and filter nested values. * * @param array $value Value to map * @param Parameter $param Parameter related to the current key. * * @return array Returns the mapped array */ protected function resolveRecursively(array $value, Parameter $param) { foreach ($value as $name => &$v) { switch ($param->getType()) { case 'object': if ($subParam = $param->getProperty($name)) { $key = $subParam->getWireName(); $value[$key] = $this->prepareValue($v, $subParam); if ($name != $key) { unset($value[$name]); } } elseif ($param->getAdditionalProperties() instanceof Parameter) { $v = $this->prepareValue($v, $param->getAdditionalProperties()); } break; case 'array': if ($items = $param->getItems()) { $v = $this->prepareValue($v, $items); } break; } } return $param->filter($value); } } src/RequestLocation/BodyLocation.php000064400000002214144760130460013557 0ustar00getBody()->getContents(); $value = $command[$param->getName()]; $value = $param->getName() . '=' . $param->filter($value); if ($oldValue !== '') { $value = $oldValue . '&' . $value; } return $request->withBody(Psr7\stream_for($value)); } } src/RequestLocation/FormParamLocation.php000064400000004506144760130460014554 0ustar00formParamsData['form_params'][$param->getWireName()] = $this->prepareValue( $command[$param->getName()], $param ); return $request; } /** * @param CommandInterface $command * @param RequestInterface $request * @param Operation $operation * * @return RequestInterface */ public function after( CommandInterface $command, RequestInterface $request, Operation $operation ) { $data = $this->formParamsData; $this->formParamsData = []; $modify = []; // Add additional parameters to the form_params array $additional = $operation->getAdditionalParameters(); if ($additional && $additional->getLocation() == $this->locationName) { foreach ($command->toArray() as $key => $value) { if (!$operation->hasParam($key)) { $data['form_params'][$key] = $this->prepareValue($value, $additional); } } } $body = http_build_query($data['form_params'], '', '&'); $modify['body'] = Psr7\stream_for($body); $modify['set_headers']['Content-Type'] = $this->contentType; $request = Psr7\modify_request($request, $modify); return $request; } } src/RequestLocation/HeaderLocation.php000064400000003352144760130460014056 0ustar00getName()]; return $request->withHeader($param->getWireName(), $param->filter($value)); } /** * @param CommandInterface $command * @param RequestInterface $request * @param Operation $operation * * @return RequestInterface */ public function after( CommandInterface $command, RequestInterface $request, Operation $operation ) { /** @var Parameter $additional */ $additional = $operation->getAdditionalParameters(); if ($additional && ($additional->getLocation() === $this->locationName)) { foreach ($command->toArray() as $key => $value) { if (!$operation->hasParam($key)) { $request = $request->withHeader($key, $additional->filter($value)); } } } return $request; } } src/RequestLocation/JsonLocation.php000064400000005075144760130460013603 0ustar00jsonContentType = $contentType; } /** * @param CommandInterface $command * @param RequestInterface $request * @param Parameter $param * * @return RequestInterface */ public function visit( CommandInterface $command, RequestInterface $request, Parameter $param ) { $this->jsonData[$param->getWireName()] = $this->prepareValue( $command[$param->getName()], $param ); return $request->withBody(Psr7\stream_for(\GuzzleHttp\json_encode($this->jsonData))); } /** * @param CommandInterface $command * @param RequestInterface $request * @param Operation $operation * * @return MessageInterface */ public function after( CommandInterface $command, RequestInterface $request, Operation $operation ) { $data = $this->jsonData; $this->jsonData = []; // Add additional parameters to the JSON document $additional = $operation->getAdditionalParameters(); if ($additional && ($additional->getLocation() === $this->locationName)) { foreach ($command->toArray() as $key => $value) { if (!$operation->hasParam($key)) { $data[$key] = $this->prepareValue($value, $additional); } } } // Don't overwrite the Content-Type if one is set if ($this->jsonContentType && !$request->hasHeader('Content-Type')) { $request = $request->withHeader('Content-Type', $this->jsonContentType); } return $request->withBody(Psr7\stream_for(\GuzzleHttp\json_encode($data))); } } src/RequestLocation/MultiPartLocation.php000064400000004017144760130460014606 0ustar00multipartData[] = [ 'name' => $param->getWireName(), 'contents' => $this->prepareValue($command[$param->getName()], $param) ]; return $request; } /** * @param CommandInterface $command * @param RequestInterface $request * @param Operation $operation * @return RequestInterface */ public function after( CommandInterface $command, RequestInterface $request, Operation $operation ) { $data = $this->multipartData; $this->multipartData = []; $modify = []; $body = new Psr7\MultipartStream($data); $modify['body'] = Psr7\stream_for($body); $request = Psr7\modify_request($request, $modify); if ($request->getBody() instanceof Psr7\MultipartStream) { // Use a multipart/form-data POST if a Content-Type is not set. $request->withHeader('Content-Type', $this->contentType . $request->getBody()->getBoundary()); } return $request; } } src/RequestLocation/QueryLocation.php000064400000005162144760130460013774 0ustar00querySerializer = $querySerializer ?: new Rfc3986Serializer(); } /** * @param CommandInterface $command * @param RequestInterface $request * @param Parameter $param * * @return RequestInterface */ public function visit( CommandInterface $command, RequestInterface $request, Parameter $param ) { $uri = $request->getUri(); $query = Psr7\parse_query($uri->getQuery()); $query[$param->getWireName()] = $this->prepareValue( $command[$param->getName()], $param ); $uri = $uri->withQuery($this->querySerializer->aggregate($query)); return $request->withUri($uri); } /** * @param CommandInterface $command * @param RequestInterface $request * @param Operation $operation * * @return RequestInterface */ public function after( CommandInterface $command, RequestInterface $request, Operation $operation ) { $additional = $operation->getAdditionalParameters(); if ($additional && $additional->getLocation() == $this->locationName) { foreach ($command->toArray() as $key => $value) { if (!$operation->hasParam($key)) { $uri = $request->getUri(); $query = Psr7\parse_query($uri->getQuery()); $query[$key] = $this->prepareValue( $value, $additional ); $uri = $uri->withQuery($this->querySerializer->aggregate($query)); $request = $request->withUri($uri); } } } return $request; } } src/RequestLocation/RequestLocationInterface.php000064400000002360144760130460016135 0ustar00contentType = $contentType; } /** * @param CommandInterface $command * @param RequestInterface $request * @param Parameter $param * * @return RequestInterface */ public function visit( CommandInterface $command, RequestInterface $request, Parameter $param ) { // Buffer and order the parameters to visit based on if they are // top-level attributes or child nodes. // @link https://github.com/guzzle/guzzle/pull/494 if ($param->getData('xmlAttribute')) { array_unshift($this->buffered, $param); } else { $this->buffered[] = $param; } return $request; } /** * @param CommandInterface $command * @param RequestInterface $request * @param Operation $operation * * @return RequestInterface */ public function after( CommandInterface $command, RequestInterface $request, Operation $operation ) { foreach ($this->buffered as $param) { $this->visitWithValue( $command[$param->getName()], $param, $operation ); } $this->buffered = []; $additional = $operation->getAdditionalParameters(); if ($additional && $additional->getLocation() == $this->locationName) { foreach ($command->toArray() as $key => $value) { if (!$operation->hasParam($key)) { $additional->setName($key); $this->visitWithValue($value, $additional, $operation); } } $additional->setName(null); } // If data was found that needs to be serialized, then do so $xml = ''; if ($this->writer) { $xml = $this->finishDocument($this->writer); } elseif ($operation->getData('xmlAllowEmpty')) { // Check if XML should always be sent for the command $writer = $this->createRootElement($operation); $xml = $this->finishDocument($writer); } if ($xml !== '') { $request = $request->withBody(Psr7\stream_for($xml)); // Don't overwrite the Content-Type if one is set if ($this->contentType && !$request->hasHeader('Content-Type')) { $request = $request->withHeader('Content-Type', $this->contentType); } } $this->writer = null; return $request; } /** * Create the root XML element to use with a request * * @param Operation $operation Operation object * * @return \XMLWriter */ protected function createRootElement(Operation $operation) { static $defaultRoot = ['name' => 'Request']; // If no root element was specified, then just wrap the XML in 'Request' $root = $operation->getData('xmlRoot') ?: $defaultRoot; // Allow the XML declaration to be customized with xmlEncoding $encoding = $operation->getData('xmlEncoding'); $writer = $this->startDocument($encoding); $writer->startElement($root['name']); // Create the wrapping element with no namespaces if no namespaces were present if (!empty($root['namespaces'])) { // Create the wrapping element with an array of one or more namespaces foreach ((array) $root['namespaces'] as $prefix => $uri) { $nsLabel = 'xmlns'; if (!is_numeric($prefix)) { $nsLabel .= ':'.$prefix; } $writer->writeAttribute($nsLabel, $uri); } } return $writer; } /** * Recursively build the XML body * * @param \XMLWriter $writer XML to modify * @param Parameter $param API Parameter * @param mixed $value Value to add */ protected function addXml(\XMLWriter $writer, Parameter $param, $value) { $value = $param->filter($value); $type = $param->getType(); $name = $param->getWireName(); $prefix = null; $namespace = $param->getData('xmlNamespace'); if (false !== strpos($name, ':')) { list($prefix, $name) = explode(':', $name, 2); } if ($type == 'object' || $type == 'array') { if (!$param->getData('xmlFlattened')) { if ($namespace) { $writer->startElementNS(null, $name, $namespace); } else { $writer->startElement($name); } } if ($param->getType() == 'array') { $this->addXmlArray($writer, $param, $value); } elseif ($param->getType() == 'object') { $this->addXmlObject($writer, $param, $value); } if (!$param->getData('xmlFlattened')) { $writer->endElement(); } return; } if ($param->getData('xmlAttribute')) { $this->writeAttribute($writer, $prefix, $name, $namespace, $value); } else { $this->writeElement($writer, $prefix, $name, $namespace, $value); } } /** * Write an attribute with namespace if used * * @param \XMLWriter $writer XMLWriter instance * @param string $prefix Namespace prefix if any * @param string $name Attribute name * @param string $namespace The uri of the namespace * @param string $value The attribute content */ protected function writeAttribute($writer, $prefix, $name, $namespace, $value) { if ($namespace) { $writer->writeAttributeNS($prefix, $name, $namespace, $value); } else { $writer->writeAttribute($name, $value); } } /** * Write an element with namespace if used * * @param \XMLWriter $writer XML writer resource * @param string $prefix Namespace prefix if any * @param string $name Element name * @param string $namespace The uri of the namespace * @param string $value The element content */ protected function writeElement(\XMLWriter $writer, $prefix, $name, $namespace, $value) { if ($namespace) { $writer->startElementNS($prefix, $name, $namespace); } else { $writer->startElement($name); } if (strpbrk($value, '<>&')) { $writer->writeCData($value); } else { $writer->writeRaw($value); } $writer->endElement(); } /** * Create a new xml writer and start a document * * @param string $encoding document encoding * * @return \XMLWriter the writer resource * @throws \RuntimeException if the document cannot be started */ protected function startDocument($encoding) { $this->writer = new \XMLWriter(); if (!$this->writer->openMemory()) { throw new \RuntimeException('Unable to open XML document in memory'); } if (!$this->writer->startDocument('1.0', $encoding)) { throw new \RuntimeException('Unable to start XML document'); } return $this->writer; } /** * End the document and return the output * * @param \XMLWriter $writer * * @return string the writer resource */ protected function finishDocument($writer) { $writer->endDocument(); return $writer->outputMemory(); } /** * Add an array to the XML * * @param \XMLWriter $writer * @param Parameter $param * @param $value */ protected function addXmlArray(\XMLWriter $writer, Parameter $param, &$value) { if ($items = $param->getItems()) { foreach ($value as $v) { $this->addXml($writer, $items, $v); } } } /** * Add an object to the XML * * @param \XMLWriter $writer * @param Parameter $param * @param $value */ protected function addXmlObject(\XMLWriter $writer, Parameter $param, &$value) { $noAttributes = []; // add values which have attributes foreach ($value as $name => $v) { if ($property = $param->getProperty($name)) { if ($property->getData('xmlAttribute')) { $this->addXml($writer, $property, $v); } else { $noAttributes[] = ['value' => $v, 'property' => $property]; } } } // now add values with no attributes foreach ($noAttributes as $element) { $this->addXml($writer, $element['property'], $element['value']); } } /** * @param $value * @param Parameter $param * @param Operation $operation */ private function visitWithValue( $value, Parameter $param, Operation $operation ) { if (!$this->writer) { $this->createRootElement($operation); } $this->addXml($this->writer, $param, $value); } } src/ResponseLocation/AbstractLocation.php000064400000003034144760130460014574 0ustar00locationName = $locationName; } /** * @param ResultInterface $result * @param ResponseInterface $response * @param Parameter $model * @return ResultInterface */ public function before( ResultInterface $result, ResponseInterface $response, Parameter $model ) { return $result; } /** * @param ResultInterface $result * @param ResponseInterface $response * @param Parameter $model * @return ResultInterface */ public function after( ResultInterface $result, ResponseInterface $response, Parameter $model ) { return $result; } /** * @param ResultInterface $result * @param ResponseInterface $response * @param Parameter $param * @return ResultInterface */ public function visit( ResultInterface $result, ResponseInterface $response, Parameter $param ) { return $result; } } src/ResponseLocation/BodyLocation.php000064400000001600144760130460013723 0ustar00getName()] = $param->filter($response->getBody()); return $result; } } src/ResponseLocation/HeaderLocation.php000064400000002207144760130460014222 0ustar00getName(); if ($header = $response->getHeader($param->getWireName())) { if (is_array($header)) { $header = array_shift($header); } $result[$name] = $param->filter($header); } return $result; } } src/ResponseLocation/JsonLocation.php000064400000013320144760130460013741 0ustar00getBody(); $body = $body ?: "{}"; $this->json = \GuzzleHttp\json_decode($body, true); // relocate named arrays, so that they have the same structure as // arrays nested in objects and visit can work on them in the same way if ($model->getType() === 'array' && ($name = $model->getName())) { $this->json = [$name => $this->json]; } return $result; } /** * @param ResultInterface $result * @param ResponseInterface $response * @param Parameter $model * @return ResultInterface */ public function after( ResultInterface $result, ResponseInterface $response, Parameter $model ) { // Handle additional, undefined properties $additional = $model->getAdditionalProperties(); if (!($additional instanceof Parameter)) { return $result; } // Use the model location as the default if one is not set on additional $addLocation = $additional->getLocation() ?: $model->getLocation(); if ($addLocation == $this->locationName) { foreach ($this->json as $prop => $val) { if (!isset($result[$prop])) { // Only recurse if there is a type specified $result[$prop] = $additional->getType() ? $this->recurse($additional, $val) : $val; } } } $this->json = []; return $result; } /** * @param ResultInterface $result * @param ResponseInterface $response * @param Parameter $param * @return Result|ResultInterface */ public function visit( ResultInterface $result, ResponseInterface $response, Parameter $param ) { $name = $param->getName(); $key = $param->getWireName(); // Check if the result should be treated as a list if ($param->getType() == 'array') { // Treat as javascript array if ($name) { // name provided, store it under a key in the array $subArray = isset($this->json[$name]) ? $this->json[$name] : null; $result[$name] = $this->recurse($param, $subArray); } else { // top-level `array` or an empty name $result = new Result(array_merge( $result->toArray(), $this->recurse($param, $this->json) )); } } elseif (isset($this->json[$key])) { $result[$name] = $this->recurse($param, $this->json[$key]); } return $result; } /** * Recursively process a parameter while applying filters * * @param Parameter $param API parameter being validated * @param mixed $value Value to process. * @return mixed|null */ private function recurse(Parameter $param, $value) { if (!is_array($value)) { return $param->filter($value); } $result = []; $type = $param->getType(); if ($type == 'array') { $items = $param->getItems(); foreach ($value as $val) { $result[] = $this->recurse($items, $val); } } elseif ($type == 'object' && !isset($value[0])) { // On the above line, we ensure that the array is associative and // not numerically indexed if ($properties = $param->getProperties()) { foreach ($properties as $property) { $key = $property->getWireName(); if (array_key_exists($key, $value)) { $result[$property->getName()] = $this->recurse( $property, $value[$key] ); // Remove from the value so that AP can later be handled unset($value[$key]); } } } // Only check additional properties if everything wasn't already // handled if ($value) { $additional = $param->getAdditionalProperties(); if ($additional === null || $additional === true) { // Merge the JSON under the resulting array $result += $value; } elseif ($additional instanceof Parameter) { // Process all child elements according to the given schema foreach ($value as $prop => $val) { $result[$prop] = $this->recurse($additional, $val); } } } } return $param->filter($result); } } src/ResponseLocation/ReasonPhraseLocation.php000064400000001667144760130460015435 0ustar00getName()] = $param->filter( $response->getReasonPhrase() ); return $result; } } src/ResponseLocation/ResponseLocationInterface.php000064400000003473144760130460016457 0ustar00getName()] = $param->filter($response->getStatusCode()); return $result; } } src/ResponseLocation/XmlLocation.php000064400000023362144760130460013577 0ustar00xml = simplexml_load_string((string) $response->getBody()); return $result; } /** * @param ResultInterface $result * @param ResponseInterface $response * @param Parameter $model * @return Result|ResultInterface */ public function after( ResultInterface $result, ResponseInterface $response, Parameter $model ) { // Handle additional, undefined properties $additional = $model->getAdditionalProperties(); if ($additional instanceof Parameter && $additional->getLocation() == $this->locationName ) { $result = new Result(array_merge( $result->toArray(), self::xmlToArray($this->xml) )); } $this->xml = null; return $result; } /** * @param ResultInterface $result * @param ResponseInterface $response * @param Parameter $param * @return ResultInterface */ public function visit( ResultInterface $result, ResponseInterface $response, Parameter $param ) { $sentAs = $param->getWireName(); $ns = null; if (strstr($sentAs, ':')) { list($ns, $sentAs) = explode(':', $sentAs); } // Process the primary property if (count($this->xml->children($ns, true)->{$sentAs})) { $result[$param->getName()] = $this->recursiveProcess( $param, $this->xml->children($ns, true)->{$sentAs} ); } return $result; } /** * Recursively process a parameter while applying filters * * @param Parameter $param API parameter being processed * @param \SimpleXMLElement $node Node being processed * @return array */ private function recursiveProcess( Parameter $param, \SimpleXMLElement $node ) { $result = []; $type = $param->getType(); if ($type == 'object') { $result = $this->processObject($param, $node); } elseif ($type == 'array') { $result = $this->processArray($param, $node); } else { // We are probably handling a flat data node (i.e. string or // integer), so let's check if it's childless, which indicates a // node containing plain text. if ($node->children()->count() == 0) { // Retrieve text from node $result = (string) $node; } } // Filter out the value if (isset($result)) { $result = $param->filter($result); } return $result; } /** * @param Parameter $param * @param \SimpleXMLElement $node * @return array */ private function processArray(Parameter $param, \SimpleXMLElement $node) { // Cast to an array if the value was a string, but should be an array $items = $param->getItems(); $sentAs = $items->getWireName(); $result = []; $ns = null; if (strstr($sentAs, ':')) { // Get namespace from the wire name list($ns, $sentAs) = explode(':', $sentAs); } else { // Get namespace from data $ns = $items->getData('xmlNs'); } if ($sentAs === null) { // A general collection of nodes foreach ($node as $child) { $result[] = $this->recursiveProcess($items, $child); } } else { // A collection of named, repeating nodes // (i.e. ) $children = $node->children($ns, true)->{$sentAs}; foreach ($children as $child) { $result[] = $this->recursiveProcess($items, $child); } } return $result; } /** * Process an object * * @param Parameter $param API parameter being parsed * @param \SimpleXMLElement $node Value to process * @return array */ private function processObject(Parameter $param, \SimpleXMLElement $node) { $result = $knownProps = $knownAttributes = []; // Handle known properties if ($properties = $param->getProperties()) { foreach ($properties as $property) { $name = $property->getName(); $sentAs = $property->getWireName(); $knownProps[$sentAs] = 1; if (strpos($sentAs, ':')) { list($ns, $sentAs) = explode(':', $sentAs); } else { $ns = $property->getData('xmlNs'); } if ($property->getData('xmlAttribute')) { // Handle XML attributes $result[$name] = (string) $node->attributes($ns, true)->{$sentAs}; $knownAttributes[$sentAs] = 1; } elseif (count($node->children($ns, true)->{$sentAs})) { // Found a child node matching wire name $childNode = $node->children($ns, true)->{$sentAs}; $result[$name] = $this->recursiveProcess( $property, $childNode ); } } } // Handle additional, undefined properties $additional = $param->getAdditionalProperties(); if ($additional instanceof Parameter) { // Process all child elements according to the given schema foreach ($node->children($additional->getData('xmlNs'), true) as $childNode) { $sentAs = $childNode->getName(); if (!isset($knownProps[$sentAs])) { $result[$sentAs] = $this->recursiveProcess( $additional, $childNode ); } } } elseif ($additional === null || $additional === true) { // Blindly transform the XML into an array preserving as much data // as possible. Remove processed, aliased properties. $array = array_diff_key(self::xmlToArray($node), $knownProps); // Remove @attributes that were explicitly plucked from the // attributes list. if (isset($array['@attributes']) && $knownAttributes) { $array['@attributes'] = array_diff_key($array['@attributes'], $knownProps); if (!$array['@attributes']) { unset($array['@attributes']); } } // Merge it together with the original result $result = array_merge($array, $result); } return $result; } /** * Convert an XML document to an array. * * @param \SimpleXMLElement $xml * @param int $nesting * @param null $ns * * @return array */ private static function xmlToArray( \SimpleXMLElement $xml, $ns = null, $nesting = 0 ) { $result = []; $children = $xml->children($ns, true); foreach ($children as $name => $child) { $attributes = (array) $child->attributes($ns, true); if (!isset($result[$name])) { $childArray = self::xmlToArray($child, $ns, $nesting + 1); $result[$name] = $attributes ? array_merge($attributes, $childArray) : $childArray; continue; } // A child element with this name exists so we're assuming // that the node contains a list of elements if (!is_array($result[$name])) { $result[$name] = [$result[$name]]; } elseif (!isset($result[$name][0])) { // Convert the first child into the first element of a numerically indexed array $firstResult = $result[$name]; $result[$name] = []; $result[$name][] = $firstResult; } $childArray = self::xmlToArray($child, $ns, $nesting + 1); if ($attributes) { $result[$name][] = array_merge($attributes, $childArray); } else { $result[$name][] = $childArray; } } // Extract text from node $text = trim((string) $xml); if ($text === '') { $text = null; } // Process attributes $attributes = (array) $xml->attributes($ns, true); if ($attributes) { if ($text !== null) { $result['value'] = $text; } $result = array_merge($attributes, $result); } elseif ($text !== null) { $result = $text; } // Make sure we're always returning an array if ($nesting == 0 && !is_array($result)) { $result = [$result]; } return $result; } } src/SchemaFormatter.php000064400000007217144760130460011144 0ustar00formatDateTime($value); case 'date-time-http': return $this->formatDateTimeHttp($value); case 'date': return $this->formatDate($value); case 'time': return $this->formatTime($value); case 'timestamp': return $this->formatTimestamp($value); case 'boolean-string': return $this->formatBooleanAsString($value); default: return $value; } } /** * Perform the actual DateTime formatting * * @param int|string|\DateTime $dateTime Date time value * @param string $format Format of the result * * @return string * @throws \InvalidArgumentException */ protected function dateFormatter($dateTime, $format) { if (is_numeric($dateTime)) { return gmdate($format, (int) $dateTime); } if (is_string($dateTime)) { $dateTime = new \DateTime($dateTime); } if ($dateTime instanceof \DateTimeInterface) { static $utc; if (!$utc) { $utc = new \DateTimeZone('UTC'); } return $dateTime->setTimezone($utc)->format($format); } throw new \InvalidArgumentException('Date/Time values must be either ' . 'be a string, integer, or DateTime object'); } /** * Create a ISO 8601 (YYYY-MM-DDThh:mm:ssZ) formatted date time value in * UTC time. * * @param string|integer|\DateTime $value Date time value * * @return string */ private function formatDateTime($value) { return $this->dateFormatter($value, 'Y-m-d\TH:i:s\Z'); } /** * Create an HTTP date (RFC 1123 / RFC 822) formatted UTC date-time string * * @param string|integer|\DateTime $value Date time value * * @return string */ private function formatDateTimeHttp($value) { return $this->dateFormatter($value, 'D, d M Y H:i:s \G\M\T'); } /** * Create a YYYY-MM-DD formatted string * * @param string|integer|\DateTime $value Date time value * * @return string */ private function formatDate($value) { return $this->dateFormatter($value, 'Y-m-d'); } /** * Create a hh:mm:ss formatted string * * @param string|integer|\DateTime $value Date time value * * @return string */ private function formatTime($value) { return $this->dateFormatter($value, 'H:i:s'); } /** * Formats a boolean value as a string * * @param string|integer|bool $value Value to convert to a boolean * 'true' / 'false' value * * @return string */ private function formatBooleanAsString($value) { return filter_var($value, FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'; } /** * Return a UNIX timestamp in the UTC timezone * * @param string|integer|\DateTime $value Time value * * @return int */ private function formatTimestamp($value) { return (int) $this->dateFormatter($value, 'U'); } } src/SchemaValidator.php000064400000026514144760130460011127 0ustar00castIntegerToStringType = $castIntegerToStringType; } /** * @param Parameter $param * @param $value * @return bool */ public function validate(Parameter $param, &$value) { $this->errors = []; $this->recursiveProcess($param, $value); if (empty($this->errors)) { return true; } else { sort($this->errors); return false; } } /** * Get the errors encountered while validating * * @return array */ public function getErrors() { return $this->errors ?: []; } /** * From the allowable types, determine the type that the variable matches * * @param string|array $type Parameter type * @param mixed $value Value to determine the type * * @return string|false Returns the matching type on */ protected function determineType($type, $value) { foreach ((array) $type as $t) { if ($t == 'string' && (is_string($value) || (is_object($value) && method_exists($value, '__toString'))) ) { return 'string'; } elseif ($t == 'object' && (is_array($value) || is_object($value))) { return 'object'; } elseif ($t == 'array' && is_array($value)) { return 'array'; } elseif ($t == 'integer' && is_integer($value)) { return 'integer'; } elseif ($t == 'boolean' && is_bool($value)) { return 'boolean'; } elseif ($t == 'number' && is_numeric($value)) { return 'number'; } elseif ($t == 'numeric' && is_numeric($value)) { return 'numeric'; } elseif ($t == 'null' && !$value) { return 'null'; } elseif ($t == 'any') { return 'any'; } } return false; } /** * Recursively validate a parameter * * @param Parameter $param API parameter being validated * @param mixed $value Value to validate and validate. The value may * change during this validate. * @param string $path Current validation path (used for error reporting) * @param int $depth Current depth in the validation validate * * @return bool Returns true if valid, or false if invalid */ protected function recursiveProcess( Parameter $param, &$value, $path = '', $depth = 0 ) { // Update the value by adding default or static values $value = $param->getValue($value); $required = $param->isRequired(); // if the value is null and the parameter is not required or is static, // then skip any further recursion if ((null === $value && !$required) || $param->isStatic()) { return true; } $type = $param->getType(); // Attempt to limit the number of times is_array is called by tracking // if the value is an array $valueIsArray = is_array($value); // If a name is set then update the path so that validation messages // are more helpful if ($name = $param->getName()) { $path .= "[{$name}]"; } if ($type == 'object') { // Determine whether or not this "value" has properties and should // be traversed $traverse = $temporaryValue = false; // Convert the value to an array if (!$valueIsArray && $value instanceof ToArrayInterface) { $value = $value->toArray(); } if ($valueIsArray) { // Ensure that the array is associative and not numerically // indexed if (isset($value[0])) { $this->errors[] = "{$path} must be an array of properties. Got a numerically indexed array."; return false; } $traverse = true; } elseif ($value === null) { // Attempt to let the contents be built up by default values if // possible $value = []; $temporaryValue = $valueIsArray = $traverse = true; } if ($traverse) { if ($properties = $param->getProperties()) { // if properties were found, validate each property foreach ($properties as $property) { $name = $property->getName(); if (isset($value[$name])) { $this->recursiveProcess($property, $value[$name], $path, $depth + 1); } else { $current = null; $this->recursiveProcess($property, $current, $path, $depth + 1); // Only set the value if it was populated if (null !== $current) { $value[$name] = $current; } } } } $additional = $param->getAdditionalProperties(); if ($additional !== true) { // If additional properties were found, then validate each // against the additionalProperties attr. $keys = array_keys($value); // Determine the keys that were specified that were not // listed in the properties of the schema $diff = array_diff($keys, array_keys($properties)); if (!empty($diff)) { // Determine which keys are not in the properties if ($additional instanceof Parameter) { foreach ($diff as $key) { $this->recursiveProcess($additional, $value[$key], "{$path}[{$key}]", $depth); } } else { // if additionalProperties is set to false and there // are additionalProperties in the values, then fail foreach ($diff as $prop) { $this->errors[] = sprintf('%s[%s] is not an allowed property', $path, $prop); } } } } // A temporary value will be used to traverse elements that // have no corresponding input value. This allows nested // required parameters with default values to bubble up into the // input. Here we check if we used a temp value and nothing // bubbled up, then we need to remote the value. if ($temporaryValue && empty($value)) { $value = null; $valueIsArray = false; } } } elseif ($type == 'array' && $valueIsArray && $param->getItems()) { foreach ($value as $i => &$item) { // Validate each item in an array against the items attribute of the schema $this->recursiveProcess($param->getItems(), $item, $path . "[{$i}]", $depth + 1); } } // If the value is required and the type is not null, then there is an // error if the value is not set if ($required && $value === null && $type != 'null') { $message = "{$path} is " . ($param->getType() ? ('a required ' . implode(' or ', (array) $param->getType())) : 'required'); if ($param->has('description')) { $message .= ': ' . $param->getDescription(); } $this->errors[] = $message; return false; } // Validate that the type is correct. If the type is string but an // integer was passed, the class can be instructed to cast the integer // to a string to pass validation. This is the default behavior. if ($type && (!$type = $this->determineType($type, $value))) { if ($this->castIntegerToStringType && $param->getType() == 'string' && is_integer($value) ) { $value = (string) $value; } else { $this->errors[] = "{$path} must be of type " . implode(' or ', (array) $param->getType()); } } // Perform type specific validation for strings, arrays, and integers if ($type == 'string') { // Strings can have enums which are a list of predefined values if (($enum = $param->getEnum()) && !in_array($value, $enum)) { $this->errors[] = "{$path} must be one of " . implode(' or ', array_map(function ($s) { return '"' . addslashes($s) . '"'; }, $enum)); } // Strings can have a regex pattern that the value must match if (($pattern = $param->getPattern()) && !preg_match($pattern, $value)) { $this->errors[] = "{$path} must match the following regular expression: {$pattern}"; } $strLen = null; if ($min = $param->getMinLength()) { $strLen = strlen($value); if ($strLen < $min) { $this->errors[] = "{$path} length must be greater than or equal to {$min}"; } } if ($max = $param->getMaxLength()) { if (($strLen ?: strlen($value)) > $max) { $this->errors[] = "{$path} length must be less than or equal to {$max}"; } } } elseif ($type == 'array') { $size = null; if ($min = $param->getMinItems()) { $size = count($value); if ($size < $min) { $this->errors[] = "{$path} must contain {$min} or more elements"; } } if ($max = $param->getMaxItems()) { if (($size ?: count($value)) > $max) { $this->errors[] = "{$path} must contain {$max} or fewer elements"; } } } elseif ($type == 'integer' || $type == 'number' || $type == 'numeric') { if (($min = $param->getMinimum()) && $value < $min) { $this->errors[] = "{$path} must be greater than or equal to {$min}"; } if (($max = $param->getMaximum()) && $value > $max) { $this->errors[] = "{$path} must be less than or equal to {$max}"; } } return empty($this->errors); } } src/Serializer.php000064400000013020144760130460010156 0ustar00 new BodyLocation(), 'query' => new QueryLocation(), 'header' => new HeaderLocation(), 'json' => new JsonLocation(), 'xml' => new XmlLocation(), 'formParam' => new FormParamLocation(), 'multipart' => new MultiPartLocation(), ]; } $this->locations = $requestLocations + $defaultRequestLocations; $this->description = $description; } /** * @param CommandInterface $command * @return RequestInterface */ public function __invoke(CommandInterface $command) { $request = $this->createRequest($command); return $this->prepareRequest($command, $request); } /** * Prepares a request for sending using location visitors * * @param CommandInterface $command * @param RequestInterface $request Request being created * @return RequestInterface * @throws \RuntimeException If a location cannot be handled */ protected function prepareRequest( CommandInterface $command, RequestInterface $request ) { $visitedLocations = []; $operation = $this->description->getOperation($command->getName()); // Visit each actual parameter foreach ($operation->getParams() as $name => $param) { /* @var Parameter $param */ $location = $param->getLocation(); // Skip parameters that have not been set or are URI location if ($location == 'uri' || !$command->hasParam($name)) { continue; } if (!isset($this->locations[$location])) { throw new \RuntimeException("No location registered for $name"); } $visitedLocations[$location] = true; $request = $this->locations[$location]->visit($command, $request, $param); } // Ensure that the after() method is invoked for additionalParameters /** @var Parameter $additional */ if ($additional = $operation->getAdditionalParameters()) { $visitedLocations[$additional->getLocation()] = true; } // Call the after() method for each visited location foreach (array_keys($visitedLocations) as $location) { $request = $this->locations[$location]->after($command, $request, $operation); } return $request; } /** * Create a request for the command and operation * * @param CommandInterface $command * * @return RequestInterface * @throws \RuntimeException */ protected function createRequest(CommandInterface $command) { $operation = $this->description->getOperation($command->getName()); // If command does not specify a template, assume the client's base URL. if (null === $operation->getUri()) { return new Request( $operation->getHttpMethod(), $this->description->getBaseUri() ); } return $this->createCommandWithUri($operation, $command); } /** * Create a request for an operation with a uri merged onto a base URI * * @param \GuzzleHttp\Command\Guzzle\Operation $operation * @param \GuzzleHttp\Command\CommandInterface $command * * @return \GuzzleHttp\Psr7\Request */ private function createCommandWithUri( Operation $operation, CommandInterface $command ) { // Get the path values and use the client config settings $variables = []; foreach ($operation->getParams() as $name => $arg) { /* @var Parameter $arg */ if ($arg->getLocation() == 'uri') { if (isset($command[$name])) { $variables[$name] = $arg->filter($command[$name]); if (!is_array($variables[$name])) { $variables[$name] = (string) $variables[$name]; } } } } // Expand the URI template. $uri = \GuzzleHttp\uri_template($operation->getUri(), $variables); return new Request( $operation->getHttpMethod(), Uri::resolve($this->description->getBaseUri(), $uri) ); } } tests/Asset/Exception/CustomCommandException.php000064400000000427144760130460016074 0ustar00 Title tests/DescriptionTest.php000064400000013032144760130460011546 0ustar00operations = [ 'test_command' => [ 'name' => 'test_command', 'description' => 'documentationForCommand', 'httpMethod' => 'DELETE', 'class' => 'FooModel', 'parameters' => [ 'bucket' => ['required' => true], 'key' => ['required' => true] ] ] ]; } public function testConstructor() { $service = new Description(['operations' => $this->operations]); $this->assertEquals(1, count($service->getOperations())); $this->assertFalse($service->hasOperation('foobar')); $this->assertTrue($service->hasOperation('test_command')); } public function testContainsModels() { $d = new Description([ 'operations' => ['foo' => []], 'models' => [ 'Tag' => ['type' => 'object'], 'Person' => ['type' => 'object'] ] ]); $this->assertTrue($d->hasModel('Tag')); $this->assertTrue($d->hasModel('Person')); $this->assertFalse($d->hasModel('Foo')); $this->assertInstanceOf(Parameter::class, $d->getModel('Tag')); $this->assertEquals(['Tag', 'Person'], array_keys($d->getModels())); } public function testCanUseResponseClass() { $d = new Description([ 'operations' => [ 'foo' => ['responseClass' => 'Tag'] ], 'models' => ['Tag' => ['type' => 'object']] ]); $op = $d->getOperation('foo'); $this->assertNotNull($op->getResponseModel()); } /** * @expectedException \InvalidArgumentException */ public function testRetrievingMissingModelThrowsException() { $d = new Description([]); $d->getModel('foo'); } public function testHasAttributes() { $d = new Description([ 'operations' => [], 'name' => 'Name', 'description' => 'Description', 'apiVersion' => '1.24' ]); $this->assertEquals('Name', $d->getName()); $this->assertEquals('Description', $d->getDescription()); $this->assertEquals('1.24', $d->getApiVersion()); } public function testPersistsCustomAttributes() { $data = [ 'operations' => ['foo' => ['class' => 'foo', 'parameters' => []]], 'name' => 'Name', 'description' => 'Test', 'apiVersion' => '1.24', 'auth' => 'foo', 'keyParam' => 'bar' ]; $d = new Description($data); $this->assertEquals('foo', $d->getData('auth')); $this->assertEquals('bar', $d->getData('keyParam')); $this->assertEquals(['auth' => 'foo', 'keyParam' => 'bar'], $d->getData()); $this->assertNull($d->getData('missing')); } /** * @expectedException \InvalidArgumentException */ public function testThrowsExceptionForMissingOperation() { $s = new Description([]); $this->assertNull($s->getOperation('foo')); } /** * @expectedException \InvalidArgumentException */ public function testValidatesOperationTypes() { new Description([ 'operations' => ['foo' => new \stdClass()] ]); } public function testHasbaseUrl() { $description = new Description(['baseUrl' => 'http://foo.com']); $this->assertEquals('http://foo.com', $description->getBaseUri()); } public function testHasbaseUri() { $description = new Description(['baseUri' => 'http://foo.com']); $this->assertEquals('http://foo.com', $description->getBaseUri()); } public function testModelsHaveNames() { $desc = [ 'models' => [ 'date' => ['type' => 'string'], 'user'=> [ 'type' => 'object', 'properties' => [ 'dob' => ['$ref' => 'date'] ] ] ] ]; $s = new Description($desc); $this->assertEquals('string', $s->getModel('date')->getType()); $this->assertEquals('dob', $s->getModel('user')->getProperty('dob')->getName()); } public function testHasOperations() { $desc = ['operations' => ['foo' => ['parameters' => ['foo' => [ 'name' => 'foo' ]]]]]; $s = new Description($desc); $this->assertInstanceOf(Operation::class, $s->getOperation('foo')); $this->assertSame($s->getOperation('foo'), $s->getOperation('foo')); } public function testHasFormatter() { $s = new Description([]); $this->assertNotEmpty($s->format('date', 'now')); } public function testCanUseCustomFormatter() { $formatter = $this->getMockBuilder(SchemaFormatter::class) ->setMethods(['format']) ->getMock(); $formatter->expects($this->once()) ->method('format'); $s = new Description([], ['formatter' => $formatter]); $s->format('time', 'now'); } } tests/DeserializerTest.php000064400000032627144760130460011720 0ustar00serviceClient = $this->getMockBuilder(GuzzleClient::class) ->disableOriginalConstructor() ->getMock(); $this->command = $this->getMockBuilder(CommandInterface::class)->getMock(); } protected function prepareErrorResponses($commandName, array $errors = []) { $this->command->expects($this->once())->method('getName')->will($this->returnValue($commandName)); $description = $this->getMockBuilder(DescriptionInterface::class)->getMock(); $operation = new Operation(['errorResponses' => $errors], $description); $description->expects($this->once()) ->method('getOperation') ->with($commandName) ->will($this->returnValue($operation)); $this->serviceClient->expects($this->once()) ->method('getDescription') ->will($this->returnValue($description)); } public function testDoNothingIfNoException() { $mock = new MockHandler([new Response(200)]); $description = new Description([ 'operations' => [ 'foo' => [ 'uri' => 'http://httpbin.org/{foo}', 'httpMethod' => 'GET', 'responseModel' => 'j', 'parameters' => [ 'bar' => [ 'type' => 'string', 'required' => true, 'location' => 'uri' ] ] ] ], 'models' => [ 'j' => [ 'type' => 'object' ] ] ]); $httpClient = new HttpClient(['handler' => $mock]); $client = new GuzzleClient($httpClient, $description); $client->foo(['bar' => 'baz']); } /** * @expectedException \GuzzleHttp\Tests\Command\Guzzle\Asset\Exception\CustomCommandException */ public function testCreateExceptionWithCode() { $response = new Response(404); $mock = new MockHandler([$response]); $description = new Description([ 'name' => 'Test API', 'baseUri' => 'http://httpbin.org', 'operations' => [ 'foo' => [ 'uri' => '/{foo}', 'httpMethod' => 'GET', 'responseClass' => 'Foo', 'parameters' => [ 'bar' => [ 'type' => 'string', 'required' => true, 'description' => 'Unique user name (alphanumeric)', 'location' => 'json' ], ], 'errorResponses' => [ ['code' => 404, 'class' => CustomCommandException::class] ] ] ], 'models' => [ 'Foo' => [ 'type' => 'object', 'additionalProperties' => [ 'location' => 'json' ] ] ] ]); $httpClient = new HttpClient(['handler' => $mock]); $client = new GuzzleClient($httpClient, $description); $client->foo(['bar' => 'baz']); } public function testNotCreateExceptionIfDoesNotMatchCode() { $response = new Response(401); $mock = new MockHandler([$response]); $description = new Description([ 'name' => 'Test API', 'baseUri' => 'http://httpbin.org', 'operations' => [ 'foo' => [ 'uri' => '/{foo}', 'httpMethod' => 'GET', 'responseClass' => 'Foo', 'parameters' => [ 'bar' => [ 'type' => 'string', 'required' => true, 'description' => 'Unique user name (alphanumeric)', 'location' => 'json' ], ], 'errorResponses' => [ ['code' => 404, 'class' => CustomCommandException::class] ] ] ], 'models' => [ 'Foo' => [ 'type' => 'object', 'additionalProperties' => [ 'location' => 'json' ] ] ] ]); $httpClient = new HttpClient(['handler' => $mock]); $client = new GuzzleClient($httpClient, $description); $client->foo(['bar' => 'baz']); } /** * @expectedException \GuzzleHttp\Tests\Command\Guzzle\Asset\Exception\CustomCommandException */ public function testCreateExceptionWithExactMatchOfReasonPhrase() { $response = new Response(404, [], null, '1.1', 'Bar'); $mock = new MockHandler([$response]); $description = new Description([ 'name' => 'Test API', 'baseUri' => 'http://httpbin.org', 'operations' => [ 'foo' => [ 'uri' => '/{foo}', 'httpMethod' => 'GET', 'responseClass' => 'Foo', 'parameters' => [ 'bar' => [ 'type' => 'string', 'required' => true, 'description' => 'Unique user name (alphanumeric)', 'location' => 'json' ], ], 'errorResponses' => [ ['code' => 404, 'phrase' => 'Bar', 'class' => CustomCommandException::class] ] ] ], 'models' => [ 'Foo' => [ 'type' => 'object', 'additionalProperties' => [ 'location' => 'json' ] ] ] ]); $httpClient = new HttpClient(['handler' => $mock]); $client = new GuzzleClient($httpClient, $description); $client->foo(['bar' => 'baz']); } /** * @expectedException \GuzzleHttp\Tests\Command\Guzzle\Asset\Exception\OtherCustomCommandException */ public function testFavourMostPreciseMatch() { $response = new Response(404, [], null, '1.1', 'Bar'); $mock = new MockHandler([$response]); $description = new Description([ 'name' => 'Test API', 'baseUri' => 'http://httpbin.org', 'operations' => [ 'foo' => [ 'uri' => '/{foo}', 'httpMethod' => 'GET', 'responseClass' => 'Foo', 'parameters' => [ 'bar' => [ 'type' => 'string', 'required' => true, 'description' => 'Unique user name (alphanumeric)', 'location' => 'json' ], ], 'errorResponses' => [ ['code' => 404, 'class' => CustomCommandException::class], ['code' => 404, 'phrase' => 'Bar', 'class' => OtherCustomCommandException::class], ] ] ], 'models' => [ 'Foo' => [ 'type' => 'object', 'additionalProperties' => [ 'location' => 'json' ] ] ] ]); $httpClient = new HttpClient(['handler' => $mock]); $client = new GuzzleClient($httpClient, $description); $client->foo(['bar' => 'baz']); } /** * @expectedException \GuzzleHttp\Command\Exception\CommandException * @expectedExceptionMessage 404 */ public function testDoesNotAddResultWhenExceptionIsPresent() { $description = new Description([ 'operations' => [ 'foo' => [ 'uri' => 'http://httpbin.org/{foo}', 'httpMethod' => 'GET', 'responseModel' => 'j', 'parameters' => [ 'bar' => [ 'type' => 'string', 'required' => true, 'location' => 'uri' ] ] ] ], 'models' => [ 'j' => [ 'type' => 'object' ] ] ]); $mock = new MockHandler([new Response(404)]); $stack = HandlerStack::create($mock); $httpClient = new HttpClient(['handler' => $stack]); $client = new GuzzleClient($httpClient, $description); $client->foo(['bar' => 'baz']); } public function testReturnsExpectedResult() { $loginResponse = new Response( 200, [], '{ "LoginResponse":{ "result":{ "type":4, "username":{ "uid":38664492, "content":"skyfillers-api-test" }, "token":"3FB1F21014D630481D35CBC30CBF4043" }, "status":{ "code":200, "content":"OK" } } }' ); $mock = new MockHandler([$loginResponse]); $description = new Description([ 'name' => 'Test API', 'baseUri' => 'http://httpbin.org', 'operations' => [ 'Login' => [ 'uri' => '/{foo}', 'httpMethod' => 'POST', 'responseClass' => 'LoginResponse', 'parameters' => [ 'username' => [ 'type' => 'string', 'required' => true, 'description' => 'Unique user name (alphanumeric)', 'location' => 'json' ], 'password' => [ 'type' => 'string', 'required' => true, 'description' => 'User\'s password', 'location' => 'json' ], 'response' => [ 'type' => 'string', 'required' => false, 'description' => 'Determines the response type: xml = result content will be xml formatted (default); plain = result content will be simple text, without structure; json = result content will be json formatted', 'location' => 'json' ], 'token' => [ 'type' => 'string', 'required' => false, 'description' => 'Provides the authentication token', 'location' => 'json' ] ] ] ], 'models' => [ 'LoginResponse' => [ 'type' => 'object', 'additionalProperties' => [ 'location' => 'json' ] ] ] ]); $httpClient = new HttpClient(['handler' => $mock]); $client = new GuzzleClient($httpClient, $description); $result = $client->Login([ 'username' => 'test', 'password' => 'test', 'response' => 'json', ]); $expected = [ 'result' => [ 'type' => 4, 'username' => [ 'uid' => 38664492, 'content' => 'skyfillers-api-test' ], 'token' => '3FB1F21014D630481D35CBC30CBF4043' ], 'status' => [ 'code' => 200, 'content' => 'OK' ] ]; $this->assertArraySubset($expected, $result['LoginResponse']); } } tests/GuzzleClientTest.php000064400000113614144760130460011711 0ustar00getServiceClient( [ new Response(200, [], '{"foo":"bar"}'), new Response(200, [], '{"foofoo":"barbar"}'), ], null, $this->commandToRequestTransformer() ); // Synchronous $result1 = $client->doThatThingYouDo(['fizz' => 'buzz']); $this->assertEquals('bar', $result1['foo']); $this->assertEquals('buzz', $result1['_request']['fizz']); $this->assertEquals('doThatThingYouDo', $result1['_request']['action']); // Asynchronous $result2 = $client->doThatThingOtherYouDoAsync(['fizz' => 'buzz'])->wait(); $this->assertEquals('barbar', $result2['foofoo']); $this->assertEquals('doThatThingOtherYouDo', $result2['_request']['action']); } public function testExecuteWithQueryLocation() { $mock = new MockHandler(); $client = $this->getServiceClient( [ new Response(200, [], '{"foo":"bar"}'), new Response(200, [], '{"foo":"bar"}') ], $mock ); $client->doQueryLocation(['foo' => 'Foo']); $this->assertEquals('foo=Foo', $mock->getLastRequest()->getUri()->getQuery()); $client->doQueryLocation([ 'foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz' ]); $last = $mock->getLastRequest(); $this->assertEquals('foo=Foo&bar=Bar&baz=Baz', $last->getUri()->getQuery()); } public function testExecuteWithBodyLocation() { $mock = new MockHandler(); $client = $this->getServiceClient( [ new Response(200, [], '{"foo":"bar"}'), new Response(200, [], '{"foo":"bar"}') ], $mock ); $client->doBodyLocation(['foo' => 'Foo']); $this->assertEquals('foo=Foo', (string) $mock->getLastRequest()->getBody()); $client->doBodyLocation([ 'foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz' ]); $this->assertEquals('foo=Foo&bar=Bar&baz=Baz', (string) $mock->getLastRequest()->getBody()); } public function testExecuteWithJsonLocation() { $mock = new MockHandler(); $client = $this->getServiceClient( [ new Response(200, [], '{"foo":"bar"}'), new Response(200, [], '{"foo":"bar"}') ], $mock ); $client->doJsonLocation(['foo' => 'Foo']); $this->assertEquals('{"foo":"Foo"}', (string) $mock->getLastRequest()->getBody()); $client->doJsonLocation([ 'foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz' ]); $this->assertEquals('{"foo":"Foo","bar":"Bar","baz":"Baz"}', (string) $mock->getLastRequest()->getBody()); } public function testExecuteWithHeaderLocation() { $mock = new MockHandler(); $client = $this->getServiceClient( [ new Response(200, [], '{"foo":"bar"}'), new Response(200, [], '{"foo":"bar"}') ], $mock ); $client->doHeaderLocation(['foo' => 'Foo']); $this->assertEquals(['Foo'], $mock->getLastRequest()->getHeader('foo')); $client->doHeaderLocation([ 'foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz' ]); $this->assertEquals(['Foo'], $mock->getLastRequest()->getHeader('foo')); $this->assertEquals(['Bar'], $mock->getLastRequest()->getHeader('bar')); $this->assertEquals(['Baz'], $mock->getLastRequest()->getHeader('baz')); } public function testExecuteWithXmlLocation() { $mock = new MockHandler(); $client = $this->getServiceClient( [ new Response(200, [], '{"foo":"bar"}'), new Response(200, [], '{"foo":"bar"}') ], $mock ); $client->doXmlLocation(['foo' => 'Foo']); $this->assertEquals( "\nFoo\n", (string) $mock->getLastRequest()->getBody() ); $client->doXmlLocation([ 'foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz' ]); $this->assertEquals( "\nFooBarBaz\n", $mock->getLastRequest()->getBody() ); } public function testExecuteWithMultiPartLocation() { $mock = new MockHandler(); $client = $this->getServiceClient( [ new Response(200, [], '{"foo":"bar"}'), new Response(200, [], '{"foo":"bar"}'), new Response(200, [], '{"foo":"bar"}') ], $mock ); $client->doMultiPartLocation(['foo' => 'Foo']); $multiPartRequestBody = (string) $mock->getLastRequest()->getBody(); $this->assertContains('name="foo"', $multiPartRequestBody); $this->assertContains('Foo', $multiPartRequestBody); $client->doMultiPartLocation([ 'foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz' ]); $multiPartRequestBody = (string) $mock->getLastRequest()->getBody(); $this->assertContains('name="foo"', $multiPartRequestBody); $this->assertContains('Foo', $multiPartRequestBody); $this->assertContains('name="bar"', $multiPartRequestBody); $this->assertContains('Bar', $multiPartRequestBody); $this->assertContains('name="baz"', $multiPartRequestBody); $this->assertContains('Baz', $multiPartRequestBody); $client->doMultiPartLocation([ 'file' => fopen(dirname(__FILE__) . '/Asset/test.html', 'r'), ]); $multiPartRequestBody = (string) $mock->getLastRequest()->getBody(); $this->assertContains('name="file"', $multiPartRequestBody); $this->assertContains('filename="test.html"', $multiPartRequestBody); $this->assertContains('Title', $multiPartRequestBody); } public function testHasConfig() { $client = new HttpClient(); $description = new Description([]); $guzzle = new GuzzleClient( $client, $description, $this->commandToRequestTransformer(), $this->responseToResultTransformer(), null, ['foo' => 'bar'] ); $this->assertSame($client, $guzzle->getHttpClient()); $this->assertSame($description, $guzzle->getDescription()); $this->assertEquals('bar', $guzzle->getConfig('foo')); $this->assertEquals([], $guzzle->getConfig('defaults')); $guzzle->setConfig('abc', 'listen'); $this->assertEquals('listen', $guzzle->getConfig('abc')); } public function testAddsValidateHandlerWhenTrue() { $client = new HttpClient(); $description = new Description([]); $guzzle = new GuzzleClient( $client, $description, $this->commandToRequestTransformer(), $this->responseToResultTransformer(), null, [ 'validate' => true, 'process' => false ] ); $handlers = explode("\n", $guzzle->getHandlerStack()->__toString()); $handlers = array_filter($handlers); $this->assertCount(3, $handlers); } public function testDisablesHandlersWhenFalse() { $client = new HttpClient(); $description = new Description([]); $guzzle = new GuzzleClient( $client, $description, $this->commandToRequestTransformer(), $this->responseToResultTransformer(), null, [ 'validate' => false, 'process' => false ] ); $handlers = explode("\n", $guzzle->getHandlerStack()->__toString()); $handlers = array_filter($handlers); $this->assertCount(1, $handlers); } public function testValidateDescription() { $client = new HttpClient(); $description = new Description( [ 'name' => 'Testing API ', 'baseUri' => 'http://httpbin.org/', 'operations' => [ 'Foo' => [ 'httpMethod' => 'GET', 'uri' => '/get', 'parameters' => [ 'bar' => [ 'type' => 'string', 'required' => false, 'description' => 'Bar', 'location' => 'query' ], 'baz' => [ 'type' => 'string', 'required' => false, 'description' => 'baz', 'location' => 'query' ], ], 'responseModel' => 'Foo' ], ], 'models' => [ 'Foo' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'location' => 'json', 'type' => 'string' ], 'location' => [ 'location' => 'header', 'sentAs' => 'Location', 'type' => 'string' ], 'age' => [ 'location' => 'json', 'type' => 'integer' ], 'statusCode' => [ 'location' => 'statusCode', 'type' => 'integer' ], ], ], ], ] ); $guzzle = new GuzzleClient( $client, $description, null, null, null, [ 'validate' => true, 'process' => false ] ); $command = $guzzle->getCommand('Foo', ['baz' => 'BAZ']); /** @var ResponseInterface $response */ $response = $guzzle->execute($command); $this->assertInstanceOf(Response::class, $response); $this->assertEquals(200, $response->getStatusCode()); } /** * @expectedException \GuzzleHttp\Command\Exception\CommandException * @expectedExceptionMessage Validation errors: [baz] is a required string: baz */ public function testValidateDescriptionFailsDueMissingRequiredParameter() { $client = new HttpClient(); $description = new Description( [ 'name' => 'Testing API ', 'baseUri' => 'http://httpbin.org/', 'operations' => [ 'Foo' => [ 'httpMethod' => 'GET', 'uri' => '/get', 'parameters' => [ 'bar' => [ 'type' => 'string', 'required' => false, 'description' => 'Bar', 'location' => 'query' ], 'baz' => [ 'type' => 'string', 'required' => true, 'description' => 'baz', 'location' => 'query' ], ], 'responseModel' => 'Foo' ], ], 'models' => [ 'Foo' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'location' => 'json', 'type' => 'string' ], 'location' => [ 'location' => 'header', 'sentAs' => 'Location', 'type' => 'string' ], 'age' => [ 'location' => 'json', 'type' => 'integer' ], 'statusCode' => [ 'location' => 'statusCode', 'type' => 'integer' ], ], ], ], ] ); $guzzle = new GuzzleClient( $client, $description, null, null, null, [ 'validate' => true, 'process' => false ] ); $command = $guzzle->getCommand('Foo'); /** @var ResultInterface $result */ $result = $guzzle->execute($command); $this->assertInstanceOf(Result::class, $result); $result = $result->toArray(); $this->assertEquals(200, $result['statusCode']); } /** * @expectedException \GuzzleHttp\Command\Exception\CommandException * @expectedExceptionMessage Validation errors: [baz] must be of type integer */ public function testValidateDescriptionFailsDueTypeMismatch() { $client = new HttpClient(); $description = new Description( [ 'name' => 'Testing API ', 'baseUri' => 'http://httpbin.org/', 'operations' => [ 'Foo' => [ 'httpMethod' => 'GET', 'uri' => '/get', 'parameters' => [ 'bar' => [ 'type' => 'string', 'required' => false, 'description' => 'Bar', 'location' => 'query' ], 'baz' => [ 'type' => 'integer', 'required' => true, 'description' => 'baz', 'location' => 'query' ], ], 'responseModel' => 'Foo' ], ], 'models' => [ 'Foo' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'location' => 'json', 'type' => 'string' ], 'location' => [ 'location' => 'header', 'sentAs' => 'Location', 'type' => 'string' ], 'age' => [ 'location' => 'json', 'type' => 'integer' ], 'statusCode' => [ 'location' => 'statusCode', 'type' => 'integer' ], ], ], ], ] ); $guzzle = new GuzzleClient( $client, $description, null, null, null, [ 'validate' => true, 'process' => false ] ); $command = $guzzle->getCommand('Foo', ['baz' => 'Hello']); /** @var ResultInterface $result */ $result = $guzzle->execute($command); $this->assertInstanceOf(Result::class, $result); $result = $result->toArray(); $this->assertEquals(200, $result['statusCode']); } public function testValidateDescriptionDoesNotFailWhenSendingIntegerButExpectingString() { $client = new HttpClient(); $description = new Description( [ 'name' => 'Testing API ', 'baseUri' => 'http://httpbin.org/', 'operations' => [ 'Foo' => [ 'httpMethod' => 'GET', 'uri' => '/get', 'parameters' => [ 'bar' => [ 'type' => 'string', 'required' => false, 'description' => 'Bar', 'location' => 'query' ], 'baz' => [ 'type' => 'string', 'required' => true, 'description' => 'baz', 'location' => 'query' ], ], 'responseModel' => 'Foo' ], ], 'models' => [ 'Foo' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'location' => 'json', 'type' => 'string' ], 'location' => [ 'location' => 'header', 'sentAs' => 'Location', 'type' => 'string' ], 'age' => [ 'location' => 'json', 'type' => 'integer' ], 'statusCode' => [ 'location' => 'statusCode', 'type' => 'integer' ], ], ], ], ] ); $guzzle = new GuzzleClient($client, $description); $command = $guzzle->getCommand('Foo', ['baz' => 42]); /** @var ResultInterface $result */ $result = $guzzle->execute($command); $this->assertInstanceOf(Result::class, $result); $result = $result->toArray(); $this->assertEquals(200, $result['statusCode']); } public function testMagicMethodExecutesCommands() { $client = new HttpClient(); $description = new Description( [ 'name' => 'Testing API ', 'baseUri' => 'http://httpbin.org/', 'operations' => [ 'Foo' => [ 'httpMethod' => 'GET', 'uri' => '/get', 'parameters' => [ 'bar' => [ 'type' => 'string', 'required' => false, 'description' => 'Bar', 'location' => 'query' ], 'baz' => [ 'type' => 'string', 'required' => true, 'description' => 'baz', 'location' => 'query' ], ], 'responseModel' => 'Foo' ], ], 'models' => [ 'Foo' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'location' => 'json', 'type' => 'string' ], 'location' => [ 'location' => 'header', 'sentAs' => 'Location', 'type' => 'string' ], 'age' => [ 'location' => 'json', 'type' => 'integer' ], 'statusCode' => [ 'location' => 'statusCode', 'type' => 'integer' ], ], ], ], ] ); $guzzle = $this->getMockBuilder(GuzzleClient::class) ->setConstructorArgs([ $client, $description ]) ->setMethods(['execute']) ->getMock(); $guzzle->expects($this->once()) ->method('execute') ->will($this->returnValue('foo')); $this->assertEquals('foo', $guzzle->foo([])); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage No operation found named Foo */ public function testThrowsWhenOperationNotFoundInDescription() { $client = new HttpClient(); $description = new Description([]); $guzzle = new GuzzleClient( $client, $description, $this->commandToRequestTransformer(), $this->responseToResultTransformer() ); $guzzle->getCommand('foo'); } public function testReturnsProcessedResponse() { $client = new HttpClient(); $description = new Description( [ 'name' => 'Testing API ', 'baseUri' => 'http://httpbin.org/', 'operations' => [ 'Foo' => [ 'httpMethod' => 'GET', 'uri' => '/get', 'parameters' => [ 'bar' => [ 'type' => 'string', 'required' => false, 'description' => 'Bar', 'location' => 'query' ], 'baz' => [ 'type' => 'string', 'required' => true, 'description' => 'baz', 'location' => 'query' ], ], 'responseModel' => 'Foo' ], ], 'models' => [ 'Foo' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'location' => 'json', 'type' => 'string' ], 'location' => [ 'location' => 'header', 'sentAs' => 'Location', 'type' => 'string' ], 'age' => [ 'location' => 'json', 'type' => 'integer' ], 'statusCode' => [ 'location' => 'statusCode', 'type' => 'integer' ], ], ], ], ] ); $guzzle = new GuzzleClient($client, $description, null, null); $command = $guzzle->getCommand('foo', ['baz' => 'BAZ']); /** @var ResultInterface $result */ $result = $guzzle->execute($command); $this->assertInstanceOf(Result::class, $result); $result = $result->toArray(); $this->assertEquals(200, $result['statusCode']); } private function getServiceClient( array $responses, MockHandler $mock = null, callable $commandToRequestTransformer = null ) { $mock = $mock ?: new MockHandler(); foreach ($responses as $response) { $mock->append($response); } return new GuzzleClient( new HttpClient([ 'handler' => $mock ]), $this->getDescription(), $commandToRequestTransformer, $this->responseToResultTransformer(), null, ['foo' => 'bar'] ); } private function commandToRequestTransformer() { return function (CommandInterface $command) { $data = $command->toArray(); $data['action'] = $command->getName(); return new Request('POST', '/', [], http_build_query($data)); }; } private function responseToResultTransformer() { return function (ResponseInterface $response, RequestInterface $request, CommandInterface $command) { $data = \GuzzleHttp\json_decode($response->getBody(), true); parse_str($request->getBody(), $data['_request']); return new Result($data); }; } private function getDescription() { return new Description( [ 'name' => 'Testing API ', 'baseUri' => 'http://httpbin.org/', 'operations' => [ 'doThatThingYouDo' => [ 'responseModel' => 'Bar' ], 'doThatThingOtherYouDo' => [ 'responseModel' => 'Foo' ], 'doQueryLocation' => [ 'httpMethod' => 'GET', 'uri' => '/queryLocation', 'parameters' => [ 'foo' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing query request location', 'location' => 'query' ], 'bar' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing query request location', 'location' => 'query' ], 'baz' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing query request location', 'location' => 'query' ] ], 'responseModel' => 'QueryResponse' ], 'doBodyLocation' => [ 'httpMethod' => 'GET', 'uri' => '/bodyLocation', 'parameters' => [ 'foo' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing body request location', 'location' => 'body' ], 'bar' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing body request location', 'location' => 'body' ], 'baz' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing body request location', 'location' => 'body' ] ], 'responseModel' => 'BodyResponse' ], 'doJsonLocation' => [ 'httpMethod' => 'GET', 'uri' => '/jsonLocation', 'parameters' => [ 'foo' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing json request location', 'location' => 'json' ], 'bar' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing json request location', 'location' => 'json' ], 'baz' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing json request location', 'location' => 'json' ] ], 'responseModel' => 'JsonResponse' ], 'doHeaderLocation' => [ 'httpMethod' => 'GET', 'uri' => '/headerLocation', 'parameters' => [ 'foo' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing header request location', 'location' => 'header' ], 'bar' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing header request location', 'location' => 'header' ], 'baz' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing header request location', 'location' => 'header' ] ], 'responseModel' => 'HeaderResponse' ], 'doXmlLocation' => [ 'httpMethod' => 'GET', 'uri' => '/xmlLocation', 'parameters' => [ 'foo' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing xml request location', 'location' => 'xml' ], 'bar' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing xml request location', 'location' => 'xml' ], 'baz' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing xml request location', 'location' => 'xml' ] ], 'responseModel' => 'XmlResponse' ], 'doMultiPartLocation' => [ 'httpMethod' => 'POST', 'uri' => '/multipartLocation', 'parameters' => [ 'foo' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing multipart request location', 'location' => 'multipart' ], 'bar' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing multipart request location', 'location' => 'multipart' ], 'baz' => [ 'type' => 'string', 'required' => false, 'description' => 'Testing multipart request location', 'location' => 'multipart' ], 'file' => [ 'type' => 'any', 'required' => false, 'description' => 'Testing multipart request location', 'location' => 'multipart' ] ], 'responseModel' => 'MultipartResponse' ], ], 'models' => [ 'Foo' => [ 'type' => 'object', 'properties' => [ 'code' => [ 'location' => 'statusCode' ] ] ], 'Bar' => [ 'type' => 'object', 'properties' => [ 'code' => [' location' => 'statusCode' ] ] ] ] ] ); } public function testDocumentationExampleFromReadme() { $client = new HttpClient(); $description = new Description([ 'baseUrl' => 'http://httpbin.org/', 'operations' => [ 'testing' => [ 'httpMethod' => 'GET', 'uri' => '/get{?foo}', 'responseModel' => 'getResponse', 'parameters' => [ 'foo' => [ 'type' => 'string', 'location' => 'uri' ], 'bar' => [ 'type' => 'string', 'location' => 'query' ] ] ] ], 'models' => [ 'getResponse' => [ 'type' => 'object', 'additionalProperties' => [ 'location' => 'json' ] ] ] ]); $guzzle = new GuzzleClient($client, $description); $result = $guzzle->testing(['foo' => 'bar']); $this->assertEquals('bar', $result['args']['foo']); } public function testDescriptionWithExtends() { $client = new HttpClient(); $description = new Description([ 'baseUrl' => 'http://httpbin.org/', 'operations' => [ 'testing' => [ 'httpMethod' => 'GET', 'uri' => '/get', 'responseModel' => 'getResponse', 'parameters' => [ 'foo' => [ 'type' => 'string', 'default' => 'foo', 'location' => 'query' ] ] ], 'testing_extends' => [ 'extends' => 'testing', 'responseModel' => 'getResponse', 'parameters' => [ 'bar' => [ 'type' => 'string', 'location' => 'query' ] ] ], ], 'models' => [ 'getResponse' => [ 'type' => 'object', 'additionalProperties' => [ 'location' => 'json' ] ] ] ]); $guzzle = new GuzzleClient($client, $description); $result = $guzzle->testing_extends(['bar' => 'bar']); $this->assertEquals('bar', $result['args']['bar']); $this->assertEquals('foo', $result['args']['foo']); } } tests/Handler/ValidatedDescriptionHandlerTest.php000064400000006540144760130460016245 0ustar00 [ 'foo' => [ 'uri' => 'http://httpbin.org', 'httpMethod' => 'GET', 'responseModel' => 'j', 'parameters' => [ 'bar' => [ 'type' => 'string', 'required' => true ] ] ] ] ]); $client = new GuzzleClient(new HttpClient(), $description); $client->foo([]); } public function testSuccessfulValidationDoesNotThrow() { $description = new Description([ 'operations' => [ 'foo' => [ 'uri' => 'http://httpbin.org', 'httpMethod' => 'GET', 'responseModel' => 'j', 'parameters' => [] ] ], 'models' => [ 'j' => [ 'type' => 'object' ] ] ]); $client = new GuzzleClient(new HttpClient(), $description); $client->foo([]); } /** * @expectedException \GuzzleHttp\Command\Exception\CommandException * @expectedExceptionMessage Validation errors: [bar] must be of type string */ public function testValidatesAdditionalParameters() { $description = new Description([ 'operations' => [ 'foo' => [ 'uri' => 'http://httpbin.org', 'httpMethod' => 'GET', 'responseModel' => 'j', 'additionalParameters' => [ 'type' => 'string' ] ] ], 'models' => [ 'j' => [ 'type' => 'object' ] ] ]); $client = new GuzzleClient(new HttpClient(), $description); $client->foo(['bar' => new \stdClass()]); } public function testFilterBeforeValidate() { $description = new Description([ 'operations' => [ 'foo' => [ 'uri' => 'http://httpbin.org', 'httpMethod' => 'GET', 'parameters' => [ 'bar' => [ 'location' => 'uri', 'type' => 'string', 'format' => 'date-time', 'required' => true ] ] ] ] ]); $client = new GuzzleClient(new HttpClient(), $description); $client->foo(['bar' => new \DateTimeImmutable()]); // Should not throw any exception } } tests/OperationTest.php000064400000016356144760130460011237 0ustar00 'test', 'summary' => 'doc', 'notes' => 'notes', 'documentationUrl' => 'http://www.example.com', 'httpMethod' => 'POST', 'uri' => '/api/v1', 'responseModel' => 'abc', 'deprecated' => true, 'parameters' => [ 'key' => [ 'required' => true, 'type' => 'string', 'maxLength' => 10, 'name' => 'key' ], 'key_2' => [ 'required' => true, 'type' => 'integer', 'default' => 10, 'name' => 'key_2' ] ] ]); $this->assertEquals('test', $c->getName()); $this->assertEquals('doc', $c->getSummary()); $this->assertEquals('http://www.example.com', $c->getDocumentationUrl()); $this->assertEquals('POST', $c->getHttpMethod()); $this->assertEquals('/api/v1', $c->getUri()); $this->assertEquals('abc', $c->getResponseModel()); $this->assertTrue($c->getDeprecated()); $params = array_map(function ($c) { return $c->toArray(); }, $c->getParams()); $this->assertEquals([ 'key' => [ 'required' => true, 'type' => 'string', 'maxLength' => 10, 'name' => 'key' ], 'key_2' => [ 'required' => true, 'type' => 'integer', 'default' => 10, 'name' => 'key_2' ] ], $params); $this->assertEquals([ 'required' => true, 'type' => 'integer', 'default' => 10, 'name' => 'key_2' ], $c->getParam('key_2')->toArray()); $this->assertNull($c->getParam('afefwef')); $this->assertArrayNotHasKey('parent', $c->getParam('key_2')->toArray()); } public function testDeterminesIfHasParam() { $command = $this->getTestCommand(); $this->assertTrue($command->hasParam('data')); $this->assertFalse($command->hasParam('baz')); } protected function getTestCommand() { return new Operation([ 'parameters' => [ 'data' => ['type' => 'string'] ] ]); } public function testAddsNameToParametersIfNeeded() { $command = new Operation(['parameters' => ['foo' => []]]); $this->assertEquals('foo', $command->getParam('foo')->getName()); } public function testContainsApiErrorInformation() { $command = $this->getOperation(); $this->assertEquals(1, count($command->getErrorResponses())); } public function testHasNotes() { $o = new Operation(['notes' => 'foo']); $this->assertEquals('foo', $o->getNotes()); } public function testHasData() { $o = new Operation(['data' => ['foo' => 'baz', 'bar' => 123]]); $this->assertEquals('baz', $o->getData('foo')); $this->assertEquals(123, $o->getData('bar')); $this->assertNull($o->getData('wfefwe')); $this->assertEquals(['foo' => 'baz', 'bar' => 123], $o->getData()); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMesssage Parameters must be arrays */ public function testEnsuresParametersAreArrays() { new Operation(['parameters' => ['foo' => true]]); } public function testHasDescription() { $s = new Description([]); $o = new Operation([], $s); $this->assertSame($s, $o->getServiceDescription()); } public function testHasAdditionalParameters() { $o = new Operation([ 'additionalParameters' => [ 'type' => 'string', 'name' => 'binks', ], 'parameters' => [ 'foo' => ['type' => 'integer'], ], ]); $this->assertEquals('string', $o->getAdditionalParameters()->getType()); } /** * @return Operation */ protected function getOperation() { return new Operation([ 'name' => 'OperationTest', 'class' => get_class($this), 'parameters' => [ 'test' => ['type' => 'object'], 'bool_1' => ['default' => true, 'type' => 'boolean'], 'bool_2' => ['default' => false], 'float' => ['type' => 'numeric'], 'int' => ['type' => 'integer'], 'date' => ['type' => 'string'], 'timestamp' => ['type' => 'string'], 'string' => ['type' => 'string'], 'username' => ['type' => 'string', 'required' => true, 'filters' => 'strtolower'], 'test_function' => ['type' => 'string', 'filters' => __CLASS__ . '::strtoupper'], ], 'errorResponses' => [ [ 'code' => 503, 'reason' => 'InsufficientCapacity', 'class' => 'Guzzle\\Exception\\RuntimeException', ], ], ]); } public function testCanExtendFromOtherOperations() { $d = new Description([ 'operations' => [ 'A' => [ 'parameters' => [ 'A' => [ 'type' => 'object', 'properties' => ['foo' => ['type' => 'string']] ], 'B' => ['type' => 'string'] ], 'summary' => 'foo' ], 'B' => [ 'extends' => 'A', 'summary' => 'Bar' ], 'C' => [ 'extends' => 'B', 'summary' => 'Bar', 'parameters' => [ 'B' => ['type' => 'number'] ] ] ] ]); $a = $d->getOperation('A'); $this->assertEquals('foo', $a->getSummary()); $this->assertTrue($a->hasParam('A')); $this->assertEquals('string', $a->getParam('B')->getType()); $b = $d->getOperation('B'); $this->assertTrue($a->hasParam('A')); $this->assertEquals('Bar', $b->getSummary()); $this->assertEquals('string', $a->getParam('B')->getType()); $c = $d->getOperation('C'); $this->assertTrue($a->hasParam('A')); $this->assertEquals('Bar', $c->getSummary()); $this->assertEquals('number', $c->getParam('B')->getType()); } } tests/ParameterTest.php000064400000027243144760130460011214 0ustar00 'foo', 'type' => 'bar', 'required' => true, 'default' => '123', 'description' => '456', 'minLength' => 2, 'maxLength' => 5, 'location' => 'body', 'static' => true, 'filters' => ['trim', 'json_encode'] ]; public function testCreatesParamFromArray() { $p = new Parameter($this->data); $this->assertEquals('foo', $p->getName()); $this->assertEquals('bar', $p->getType()); $this->assertTrue($p->isRequired()); $this->assertEquals('123', $p->getDefault()); $this->assertEquals('456', $p->getDescription()); $this->assertEquals(2, $p->getMinLength()); $this->assertEquals(5, $p->getMaxLength()); $this->assertEquals('body', $p->getLocation()); $this->assertTrue($p->isStatic()); $this->assertEquals(['trim', 'json_encode'], $p->getFilters()); $p->setName('abc'); $this->assertEquals('abc', $p->getName()); } /** * @expectedException \InvalidArgumentException */ public function testValidatesDescription() { new Parameter($this->data, ['description' => 'foo']); } public function testCanConvertToArray() { $p = new Parameter($this->data); $this->assertEquals($this->data, $p->toArray()); } public function testUsesStatic() { $d = $this->data; $d['default'] = 'booboo'; $d['static'] = true; $p = new Parameter($d); $this->assertEquals('booboo', $p->getValue('bar')); } public function testUsesDefault() { $d = $this->data; $d['default'] = 'foo'; $d['static'] = null; $p = new Parameter($d); $this->assertEquals('foo', $p->getValue(null)); } public function testReturnsYourValue() { $d = $this->data; $d['static'] = null; $p = new Parameter($d); $this->assertEquals('foo', $p->getValue('foo')); } public function testZeroValueDoesNotCauseDefaultToBeReturned() { $d = $this->data; $d['default'] = '1'; $d['static'] = null; $p = new Parameter($d); $this->assertEquals('0', $p->getValue('0')); } public function testFiltersValues() { $d = $this->data; $d['static'] = null; $d['filters'] = 'strtoupper'; $p = new Parameter($d); $this->assertEquals('FOO', $p->filter('foo')); } /** * @expectedException \RuntimeException * @expectedExceptionMessage No service description */ public function testRequiresServiceDescriptionForFormatting() { $d = $this->data; $d['format'] = 'foo'; $p = new Parameter($d); $p->filter('bar'); } public function testConvertsBooleans() { $p = new Parameter(['type' => 'boolean']); $this->assertEquals(true, $p->filter('true')); $this->assertEquals(false, $p->filter('false')); } public function testUsesArrayByDefaultForFilters() { $d = $this->data; $d['filters'] = null; $p = new Parameter($d); $this->assertEquals([], $p->getFilters()); } public function testAllowsSimpleLocationValue() { $p = new Parameter(['name' => 'myname', 'location' => 'foo', 'sentAs' => 'Hello']); $this->assertEquals('foo', $p->getLocation()); $this->assertEquals('Hello', $p->getSentAs()); } public function testParsesTypeValues() { $p = new Parameter(['type' => 'foo']); $this->assertEquals('foo', $p->getType()); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage A [method] value must be specified for each complex filter */ public function testValidatesComplexFilters() { $p = new Parameter(['filters' => [['args' => 'foo']]]); } public function testAllowsComplexFilters() { $that = $this; $param = new Parameter([ 'filters' => [ [ 'method' => function ($a, $b, $c, $d) use ($that, &$param) { $that->assertEquals('test', $a); $that->assertEquals('my_value!', $b); $that->assertEquals('bar', $c); $that->assertSame($param, $d); return 'abc' . $b; }, 'args' => ['test', '@value', 'bar', '@api'] ] ] ]); $this->assertEquals('abcmy_value!', $param->filter('my_value!')); } public function testAddsAdditionalProperties() { $p = new Parameter([ 'type' => 'object', 'additionalProperties' => ['type' => 'string'] ]); $this->assertInstanceOf('GuzzleHttp\Command\Guzzle\Parameter', $p->getAdditionalProperties()); $this->assertNull($p->getAdditionalProperties()->getAdditionalProperties()); $p = new Parameter(['type' => 'object']); $this->assertTrue($p->getAdditionalProperties()); } public function testAddsItems() { $p = new Parameter([ 'type' => 'array', 'items' => ['type' => 'string'] ]); $this->assertInstanceOf('GuzzleHttp\Command\Guzzle\Parameter', $p->getItems()); $out = $p->toArray(); $this->assertEquals('array', $out['type']); $this->assertInternalType('array', $out['items']); } public function testCanRetrieveKnownPropertiesUsingDataMethod() { $p = new Parameter(['data' => ['name' => 'test'], 'extra' => 'hi!']); $this->assertEquals('test', $p->getData('name')); $this->assertEquals(['name' => 'test'], $p->getData()); $this->assertNull($p->getData('fjnweefe')); $this->assertEquals('hi!', $p->getData('extra')); } public function testHasPattern() { $p = new Parameter(['pattern' => '/[0-9]+/']); $this->assertEquals('/[0-9]+/', $p->getPattern()); } public function testHasEnum() { $p = new Parameter(['enum' => ['foo', 'bar']]); $this->assertEquals(['foo', 'bar'], $p->getEnum()); } public function testSerializesItems() { $p = new Parameter([ 'type' => 'object', 'additionalProperties' => ['type' => 'string'] ]); $this->assertEquals([ 'type' => 'object', 'additionalProperties' => ['type' => 'string'] ], $p->toArray()); } public function testResolvesRefKeysRecursively() { $description = new Description([ 'models' => [ 'JarJar' => ['type' => 'string', 'default' => 'Mesa address tha senate!'], 'Anakin' => ['type' => 'array', 'items' => ['$ref' => 'JarJar']] ], ]); $p = new Parameter(['$ref' => 'Anakin', 'description' => 'added'], ['description' => $description]); $this->assertEquals([ 'description' => 'added', '$ref' => 'Anakin' ], $p->toArray()); } public function testResolvesExtendsRecursively() { $jarJar = ['type' => 'string', 'default' => 'Mesa address tha senate!', 'description' => 'a']; $anakin = ['type' => 'array', 'items' => ['extends' => 'JarJar', 'description' => 'b']]; $description = new Description([ 'models' => ['JarJar' => $jarJar, 'Anakin' => $anakin] ]); // Description attribute will be updated, and format added $p = new Parameter(['extends' => 'Anakin', 'format' => 'date'], ['description' => $description]); $this->assertEquals([ 'format' => 'date', 'extends' => 'Anakin' ], $p->toArray()); } public function testHasKeyMethod() { $p = new Parameter(['name' => 'foo', 'sentAs' => 'bar']); $this->assertEquals('bar', $p->getWireName()); } public function testIncludesNameInToArrayWhenItemsAttributeHasName() { $p = new Parameter([ 'type' => 'array', 'name' => 'Abc', 'items' => [ 'name' => 'Foo', 'type' => 'object' ] ]); $result = $p->toArray(); $this->assertEquals([ 'type' => 'array', 'name' => 'Abc', 'items' => [ 'name' => 'Foo', 'type' => 'object' ] ], $result); } public function dateTimeProvider() { $d = 'October 13, 2012 16:15:46 UTC'; return [ [$d, 'date-time', '2012-10-13T16:15:46Z'], [$d, 'date', '2012-10-13'], [$d, 'timestamp', strtotime($d)], [new \DateTime($d), 'timestamp', strtotime($d)] ]; } /** * @dataProvider dateTimeProvider */ public function testAppliesFormat($d, $format, $result) { $p = new Parameter(['format' => $format], ['description' => new Description([])]); $this->assertEquals($format, $p->getFormat()); $this->assertEquals($result, $p->filter($d)); } public function testHasMinAndMax() { $p = new Parameter([ 'minimum' => 2, 'maximum' => 3, 'minItems' => 4, 'maxItems' => 5, ]); $this->assertEquals(2, $p->getMinimum()); $this->assertEquals(3, $p->getMaximum()); $this->assertEquals(4, $p->getMinItems()); $this->assertEquals(5, $p->getMaxItems()); } public function testHasProperties() { $data = [ 'type' => 'object', 'properties' => [ 'foo' => ['type' => 'string'], 'bar' => ['type' => 'string'], ] ]; $p = new Parameter($data); $this->assertInstanceOf('GuzzleHttp\\Command\\Guzzle\\Parameter', $p->getProperty('foo')); $this->assertSame($p->getProperty('foo'), $p->getProperty('foo')); $this->assertNull($p->getProperty('wefwe')); $properties = $p->getProperties(); $this->assertInternalType('array', $properties); foreach ($properties as $prop) { $this->assertInstanceOf('GuzzleHttp\\Command\\Guzzle\\Parameter', $prop); } $this->assertEquals($data, $p->toArray()); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage Expected a string. Got: array */ public function testThrowsWhenNotPassString() { $emptyParam = new Parameter(); $this->assertFalse($emptyParam->has([])); $this->assertFalse($emptyParam->has(new \stdClass())); $this->assertFalse($emptyParam->has('1')); $this->assertFalse($emptyParam->has(1)); } public function testHasReturnsFalseForWrongOrEmptyValues() { $emptyParam = new Parameter(); $this->assertFalse($emptyParam->has('')); $this->assertFalse($emptyParam->has('description')); $this->assertFalse($emptyParam->has('noExisting')); } public function testHasReturnsTrueForCorrectValues() { $p = new Parameter([ 'minimum' => 2, 'maximum' => 3, 'minItems' => 4, 'maxItems' => 5, ]); $this->assertTrue($p->has('minimum')); $this->assertTrue($p->has('maximum')); $this->assertTrue($p->has('minItems')); $this->assertTrue($p->has('maxItems')); } } tests/QuerySerializer/Rfc3986SerializerTest.php000064400000002113144760130460015516 0ustar00 'bar'], 'foo=bar'], [['foo' => [1, 2]], 'foo[0]=1&foo[1]=2'], [['foo' => ['bar' => 'baz', 'bim' => [4, 5]]], 'foo[bar]=baz&foo[bim][0]=4&foo[bim][1]=5'] ]; } /** * @dataProvider queryProvider */ public function testSerializeQueryParams(array $params, $expectedResult) { $serializer = new Rfc3986Serializer(); $result = $serializer->aggregate($params); $this->assertEquals($expectedResult, urldecode($result)); } public function testCanRemoveNumericIndices() { $serializer = new Rfc3986Serializer(true); $result = $serializer->aggregate(['foo' => ['bar', 'baz'], 'bar' => ['bim' => [4, 5]]]); $this->assertEquals('foo[]=bar&foo[]=baz&bar[bim][]=4&bar[bim][]=5', urldecode($result)); } }tests/RequestLocation/BodyLocationTest.php000064400000001466144760130460015002 0ustar00 'bar']); $request = new Request('POST', 'http://httbin.org'); $param = new Parameter(['name' => 'foo']); $request = $location->visit($command, $request, $param); $this->assertEquals('foo=bar', $request->getBody()->getContents()); } } tests/RequestLocation/FormParamLocationTest.php000064400000003672144760130460015772 0ustar00 'bar']); $request = new Request('POST', 'http://httbin.org'); $param = new Parameter(['name' => 'foo']); $request = $location->visit($command, $request, $param); $operation = new Operation(); $request = $location->after($command, $request, $operation); $this->assertEquals('foo=bar', $request->getBody()->getContents()); $this->assertArraySubset([0 => 'application/x-www-form-urlencoded; charset=utf-8'], $request->getHeader('Content-Type')); } /** * @group RequestLocation */ public function testAddsAdditionalProperties() { $location = new FormParamLocation(); $command = new Command('foo', ['foo' => 'bar']); $command['add'] = 'props'; $request = new Request('POST', 'http://httbin.org', []); $param = new Parameter(['name' => 'foo']); $request = $location->visit($command, $request, $param); $operation = new Operation([ 'additionalParameters' => [ 'location' => 'formParam' ] ]); $request = $location->after($command, $request, $operation); $this->assertEquals('foo=bar&add=props', $request->getBody()->getContents()); } } tests/RequestLocation/HeaderLocationTest.php000064400000003261144760130460015270 0ustar00 'bar']); $request = new Request('POST', 'http://httbin.org'); $param = new Parameter(['name' => 'foo']); $request = $location->visit($command, $request, $param); $header = $request->getHeader('foo'); $this->assertTrue(is_array($header)); $this->assertArraySubset([0 => 'bar'], $request->getHeader('foo')); } /** * @group RequestLocation */ public function testAddsAdditionalProperties() { $location = new HeaderLocation('header'); $command = new Command('foo', ['foo' => 'bar']); $command['add'] = 'props'; $operation = new Operation([ 'additionalParameters' => [ 'location' => 'header' ] ]); $request = new Request('POST', 'http://httbin.org'); $request = $location->after($command, $request, $operation); $header = $request->getHeader('add'); $this->assertTrue(is_array($header)); $this->assertArraySubset([0 => 'props'], $header); } } tests/RequestLocation/JsonLocationTest.php000064400000006357144760130460015022 0ustar00 'bar']); $request = new Request('POST', 'http://httbin.org'); $param = new Parameter(['name' => 'foo']); $location->visit($command, $request, $param); $operation = new Operation(); $request = $location->after($command, $request, $operation); $this->assertEquals('{"foo":"bar"}', $request->getBody()->getContents()); $this->assertArraySubset([0 => 'application/json'], $request->getHeader('Content-Type')); } /** * @group RequestLocation */ public function testVisitsAdditionalProperties() { $location = new JsonLocation('json', 'foo'); $command = new Command('foo', ['foo' => 'bar']); $command['baz'] = ['bam' => [1]]; $request = new Request('POST', 'http://httbin.org'); $param = new Parameter(['name' => 'foo']); $location->visit($command, $request, $param); $operation = new Operation([ 'additionalParameters' => [ 'location' => 'json' ] ]); $request = $location->after($command, $request, $operation); $this->assertEquals('{"foo":"bar","baz":{"bam":[1]}}', $request->getBody()->getContents()); $this->assertEquals([0 => 'foo'], $request->getHeader('Content-Type')); } /** * @group RequestLocation */ public function testVisitsNestedLocation() { $location = new JsonLocation('json'); $command = new Command('foo', ['foo' => 'bar']); $request = new Request('POST', 'http://httbin.org'); $param = new Parameter([ 'name' => 'foo', 'type' => 'object', 'properties' => [ 'baz' => [ 'type' => 'array', 'items' => [ 'type' => 'string', 'filters' => ['strtoupper'] ] ] ], 'additionalProperties' => [ 'type' => 'array', 'items' => [ 'type' => 'string', 'filters' => ['strtolower'] ] ] ]); $command['foo'] = [ 'baz' => ['a', 'b'], 'bam' => ['A', 'B'], ]; $location->visit($command, $request, $param); $operation = new Operation(); $request = $location->after($command, $request, $operation); $this->assertEquals('{"foo":{"baz":["A","B"],"bam":["a","b"]}}', (string) $request->getBody()->getContents()); $this->assertEquals([0 => 'application/json'], $request->getHeader('Content-Type')); } } tests/RequestLocation/MultiPartLocationTest.php000064400000002174144760130460016023 0ustar00 'bar']); $request = new Request('POST', 'http://httbin.org', []); $param = new Parameter(['name' => 'foo']); $request = $location->visit($command, $request, $param); $operation = new Operation(); $request = $location->after($command, $request, $operation); $actual = $request->getBody()->getContents(); $this->assertNotFalse(strpos($actual, 'name="foo"')); $this->assertNotFalse(strpos($actual, 'bar')); } } tests/RequestLocation/QueryLocationTest.php000064400000004776144760130460015221 0ustar00 'bar'], 'foo=bar'], [['foo' => [1, 2]], 'foo[0]=1&foo[1]=2'], [['foo' => ['bar' => 'baz', 'bim' => [4, 5]]], 'foo[bar]=baz&foo[bim][0]=4&foo[bim][1]=5'] ]; } /** * @group RequestLocation */ public function testVisitsLocation() { $location = new QueryLocation(); $command = new Command('foo', ['foo' => 'bar']); $request = new Request('POST', 'http://httbin.org'); $param = new Parameter(['name' => 'foo']); $request = $location->visit($command, $request, $param); $this->assertEquals('foo=bar', urldecode($request->getUri()->getQuery())); } public function testVisitsMultipleLocations() { $request = new Request('POST', 'http://httbin.org'); // First location $location = new QueryLocation(); $command = new Command('foo', ['foo' => 'bar']); $param = new Parameter(['name' => 'foo']); $request = $location->visit($command, $request, $param); // Second location $location = new QueryLocation(); $command = new Command('baz', ['baz' => [6, 7]]); $param = new Parameter(['name' => 'baz']); $request = $location->visit($command, $request, $param); $this->assertEquals('foo=bar&baz[0]=6&baz[1]=7', urldecode($request->getUri()->getQuery())); } /** * @group RequestLocation */ public function testAddsAdditionalProperties() { $location = new QueryLocation(); $command = new Command('foo', ['foo' => 'bar']); $command['add'] = 'props'; $operation = new Operation([ 'additionalParameters' => [ 'location' => 'query' ] ]); $request = new Request('POST', 'http://httbin.org'); $request = $location->after($command, $request, $operation); $this->assertEquals('props', Psr7\parse_query($request->getUri()->getQuery())['add']); } } tests/RequestLocation/XmlLocationTest.php000064400000045541144760130460014647 0ustar00 'bar']); $command['bar'] = 'test'; $request = new Request('POST', 'http://httbin.org'); $param = new Parameter(['name' => 'foo']); $location->visit($command, $request, $param); $param = new Parameter(['name' => 'bar']); $location->visit($command, $request, $param); $operation = new Operation(); $request = $location->after($command, $request, $operation); $xml = $request->getBody()->getContents(); $this->assertEquals('' . "\n" . 'bartest' . "\n", $xml); $header = $request->getHeader('Content-Type'); $this->assertArraySubset([0 => 'application/xml'], $header); } /** * @group RequestLocation */ public function testCreatesBodyForEmptyDocument() { $location = new XmlLocation(); $command = new Command('foo', ['foo' => 'bar']); $request = new Request('POST', 'http://httbin.org'); $operation = new Operation([ 'data' => ['xmlAllowEmpty' => true] ]); $request = $location->after($command, $request, $operation); $xml = $request->getBody()->getContents(); $this->assertEquals('' . "\n" . '' . "\n", $xml); $header = $request->getHeader('Content-Type'); $this->assertArraySubset([0 => 'application/xml'], $header); } /** * @group RequestLocation */ public function testAddsAdditionalParameters() { $location = new XmlLocation('xml', 'test'); $command = new Command('foo', ['foo' => 'bar']); $request = new Request('POST', 'http://httbin.org'); $param = new Parameter(['name' => 'foo']); $command['foo'] = 'bar'; $location->visit($command, $request, $param); $operation = new Operation([ 'additionalParameters' => [ 'location' => 'xml' ] ]); $command['bam'] = 'boo'; $request = $location->after($command, $request, $operation); $xml = $request->getBody()->getContents(); $this->assertEquals('' . "\n" . 'barbarboo' . "\n", $xml); $header = $request->getHeader('Content-Type'); $this->assertArraySubset([0 => 'test'], $header); } /** * @group RequestLocation */ public function testAllowsXmlEncoding() { $location = new XmlLocation(); $operation = new Operation([ 'data' => ['xmlEncoding' => 'UTF-8'] ]); $command = new Command('foo', ['foo' => 'bar']); $request = new Request('POST', 'http://httbin.org'); $param = new Parameter(['name' => 'foo']); $command['foo'] = 'bar'; $location->visit($command, $request, $param); $request = $location->after($command, $request, $operation); $xml = $request->getBody()->getContents(); $this->assertEquals('' . "\n" . 'bar' . "\n", $xml); } public function xmlProvider() { return [ [ [ 'data' => [ 'xmlRoot' => [ 'name' => 'test', 'namespaces' => 'http://foo.com' ] ], 'parameters' => [ 'Foo' => [ 'location' => 'xml', 'type' => 'string' ], 'Baz' => [ 'location' => 'xml', 'type' => 'string' ] ] ], [ 'Foo' => 'test', 'Baz' => 'bar' ], 'testbar' ], // Ensure that the content-type is not added [ [ 'parameters' => [ 'Foo' => [ 'location' => 'xml', 'type' => 'string' ] ] ], [], '' ], // Test with adding attributes and no namespace [ [ 'data' => [ 'xmlRoot' => [ 'name' => 'test' ] ], 'parameters' => [ 'Foo' => [ 'location' => 'xml', 'type' => 'string', 'data' => ['xmlAttribute' => true] ] ] ], [ 'Foo' => 'test', 'Baz' => 'bar' ], '' ], // Test adding with an array [ [ 'parameters' => [ 'Foo' => [ 'location' => 'xml', 'type' => 'string' ], 'Baz' => [ 'type' => 'array', 'location' => 'xml', 'items' => [ 'type' => 'numeric', 'sentAs' => 'Bar' ] ] ] ], ['Foo' => 'test', 'Baz' => [1, 2]], 'test12' ], // Test adding an object [ [ 'parameters' => [ 'Foo' => ['location' => 'xml', 'type' => 'string'], 'Baz' => [ 'type' => 'object', 'location' => 'xml', 'properties' => [ 'Bar' => ['type' => 'string'], 'Bam' => [] ] ] ] ], [ 'Foo' => 'test', 'Baz' => [ 'Bar' => 'abc', 'Bam' => 'foo' ] ], 'testabcfoo' ], // Add an array that contains an object [ [ 'parameters' => [ 'Baz' => [ 'type' => 'array', 'location' => 'xml', 'items' => [ 'type' => 'object', 'sentAs' => 'Bar', 'properties' => ['A' => [], 'B' => []] ] ] ] ], ['Baz' => [ [ 'A' => '1', 'B' => '2' ], [ 'A' => '3', 'B' => '4' ] ]], '1234' ], // Add an object of attributes [ [ 'parameters' => [ 'Foo' => [ 'location' => 'xml', 'type' => 'string' ], 'Baz' => [ 'type' => 'object', 'location' => 'xml', 'properties' => [ 'Bar' => [ 'type' => 'string', 'data' => [ 'xmlAttribute' => true ] ], 'Bam' => [] ] ] ] ], [ 'Foo' => 'test', 'Baz' => [ 'Bar' => 'abc', 'Bam' => 'foo' ] ], 'testfoo' ], // Check order doesn't matter [ [ 'parameters' => [ 'Foo' => [ 'location' => 'xml', 'type' => 'string' ], 'Baz' => [ 'type' => 'object', 'location' => 'xml', 'properties' => [ 'Bar' => [ 'type' => 'string', 'data' => [ 'xmlAttribute' => true ] ], 'Bam' => [] ] ] ] ], [ 'Foo' => 'test', 'Baz' => [ 'Bam' => 'foo', 'Bar' => 'abc' ] ], 'testfoo' ], // Add values with custom namespaces [ [ 'parameters' => [ 'Foo' => [ 'location' => 'xml', 'type' => 'string', 'data' => [ 'xmlNamespace' => 'http://foo.com' ] ] ] ], ['Foo' => 'test'], 'test' ], // Add attributes with custom namespace prefix [ [ 'parameters' => [ 'Wrap' => [ 'type' => 'object', 'location' => 'xml', 'properties' => [ 'Foo' => [ 'type' => 'string', 'sentAs' => 'xsi:baz', 'data' => [ 'xmlNamespace' => 'http://foo.com', 'xmlAttribute' => true ] ] ] ], ] ], ['Wrap' => [ 'Foo' => 'test' ]], '' ], // Add nodes with custom namespace prefix [ [ 'parameters' => [ 'Wrap' => [ 'type' => 'object', 'location' => 'xml', 'properties' => [ 'Foo' => [ 'type' => 'string', 'sentAs' => 'xsi:Foo', 'data' => [ 'xmlNamespace' => 'http://foobar.com' ] ] ] ], ] ], ['Wrap' => [ 'Foo' => 'test' ]], 'test' ], [ [ 'parameters' => [ 'Foo' => [ 'location' => 'xml', 'type' => 'string', 'data' => [ 'xmlNamespace' => 'http://foo.com' ] ] ] ], ['Foo' => '

This is a title

'], 'This is a title]]>' ], // Flat array at top level [ [ 'parameters' => [ 'Bars' => [ 'type' => 'array', 'data' => ['xmlFlattened' => true], 'location' => 'xml', 'items' => [ 'type' => 'object', 'sentAs' => 'Bar', 'properties' => [ 'A' => [], 'B' => [] ] ] ], 'Boos' => [ 'type' => 'array', 'data' => ['xmlFlattened' => true], 'location' => 'xml', 'items' => [ 'sentAs' => 'Boo', 'type' => 'string' ] ] ] ], [ 'Bars' => [ ['A' => '1', 'B' => '2'], ['A' => '3', 'B' => '4'] ], 'Boos' => ['test', '123'] ], '1234test123' ], // Nested flat arrays [ [ 'parameters' => [ 'Delete' => [ 'type' => 'object', 'location' => 'xml', 'properties' => [ 'Items' => [ 'type' => 'array', 'data' => ['xmlFlattened' => true], 'items' => [ 'type' => 'object', 'sentAs' => 'Item', 'properties' => [ 'A' => [], 'B' => [] ] ] ] ] ] ] ], [ 'Delete' => [ 'Items' => [ ['A' => '1', 'B' => '2'], ['A' => '3', 'B' => '4'] ] ] ], '1234' ], // Test adding root node attributes after nodes [ [ 'data' => [ 'xmlRoot' => [ 'name' => 'test' ] ], 'parameters' => [ 'Foo' => ['location' => 'xml', 'type' => 'string'], 'Baz' => ['location' => 'xml', 'type' => 'string', 'data' => ['xmlAttribute' => true]], ] ], ['Foo' => 'test', 'Baz' => 'bar'], 'test' ], ]; } /** * @param array $operation * @param array $input * @param string $xml * @dataProvider xmlProvider * @group RequestLocation */ public function testSerializesXml(array $operation, array $input, $xml) { $container = []; $history = Middleware::history($container); $mock = new MockHandler([new Response(200)]); $stack = new HandlerStack($mock); $stack->push($history); $operation['uri'] = 'http://httpbin.org'; $client = new GuzzleClient( new Client(['handler' => $stack]), new Description([ 'operations' => [ 'foo' => $operation ] ]) ); $command = $client->getCommand('foo', $input); $client->execute($command); $this->assertCount(1, $container); foreach ($container as $transaction) { /** @var Request $request */ $request = $transaction['request']; if (empty($input)) { if ($request->hasHeader('Content-Type')) { $this->assertArraySubset([0 => ''], $request->getHeader('Content-Type')); } } else { $this->assertArraySubset([0 => 'application/xml'], $request->getHeader('Content-Type')); } $body = str_replace(["\n", ""], '', (string) $request->getBody()); $this->assertEquals($xml, $body); } } } tests/ResponseLocation/BodyLocationTest.php000064400000001611144760130460015140 0ustar00 'val', 'filters' => ['strtoupper'] ]); $response = new Response(200, [], 'foo'); $result = new Result(); $result = $location->visit($result, $response, $parameter); $this->assertEquals('FOO', $result['val']); } } tests/ResponseLocation/HeaderLocationTest.php000064400000001674144760130460015444 0ustar00 'val', 'sentAs' => 'X-Foo', 'filters' => ['strtoupper'] ]); $response = new Response(200, ['X-Foo' => 'bar']); $result = new Result(); $result = $location->visit($result, $response, $parameter); $this->assertEquals('BAR', $result['val']); } } tests/ResponseLocation/JsonLocationTest.php000064400000043607144760130460015167 0ustar00 'val', 'sentAs' => 'vim', 'filters' => ['strtoupper'] ]); $response = new Response(200, [], '{"vim":"bar"}'); $result = new Result(); $result = $location->before($result, $response, $parameter); $result = $location->visit($result, $response, $parameter); $this->assertEquals('BAR', $result['val']); } /** * @group ResponseLocation */ public function testVisitsAdditionalProperties() { $location = new JsonLocation(); $parameter = new Parameter(); $model = new Parameter(['additionalProperties' => ['location' => 'json']]); $response = new Response(200, [], '{"vim":"bar","qux":[1,2]}'); $result = new Result(); $result = $location->before($result, $response, $parameter); $result = $location->visit($result, $response, $parameter); $result = $location->after($result, $response, $model); $this->assertEquals('bar', $result['vim']); $this->assertEquals([1, 2], $result['qux']); } /** * @group ResponseLocation */ public function testVisitsAdditionalPropertiesWithEmptyResponse() { $location = new JsonLocation(); $parameter = new Parameter(); $model = new Parameter(['additionalProperties' => ['location' => 'json']]); $response = new Response(204); $result = new Result(); $result = $location->before($result, $response, $parameter); $result = $location->visit($result, $response, $parameter); $result = $location->after($result, $response, $model); $this->assertEquals([], $result->toArray()); } public function jsonProvider() { return [ [null, [['foo' => 'BAR'], ['baz' => 'BAM']]], ['under_me', ['under_me' => [['foo' => 'BAR'], ['baz' => 'BAM']]]], ]; } /** * @dataProvider jsonProvider * @group ResponseLocation * @param $name * @param $expected */ public function testVisitsTopLevelArrays($name, $expected) { $json = [ ['foo' => 'bar'], ['baz' => 'bam'], ]; $body = \GuzzleHttp\json_encode($json); $response = new Response(200, ['Content-Type' => 'application/json'], $body); $mock = new MockHandler([$response]); $guzzle = new Client(['handler' => $mock]); $description = new Description([ 'operations' => [ 'foo' => [ 'uri' => 'http://httpbin.org', 'httpMethod' => 'GET', 'responseModel' => 'j' ] ], 'models' => [ 'j' => [ 'type' => 'array', 'location' => 'json', 'name' => $name, 'items' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'string', 'filters' => ['strtoupper'] ] ] ] ] ]); $guzzle = new GuzzleClient($guzzle, $description); /** @var ResultInterface $result */ $result = $guzzle->foo(); $this->assertEquals($expected, $result->toArray()); } /** * @group ResponseLocation */ public function testVisitsNestedArrays() { $json = [ 'scalar' => 'foo', 'nested' => [ 'bar', 'baz' ] ]; $body = \GuzzleHttp\json_encode($json); $response = new Response(200, ['Content-Type' => 'application/json'], $body); $mock = new MockHandler([$response]); $httpClient = new Client(['handler' => $mock]); $description = new Description([ 'operations' => [ 'foo' => [ 'uri' => 'http://httpbin.org', 'httpMethod' => 'GET', 'responseModel' => 'j' ] ], 'models' => [ 'j' => [ 'type' => 'object', 'location' => 'json', 'properties' => [ 'scalar' => ['type' => 'string'], 'nested' => [ 'type' => 'array', 'items' => ['type' => 'string'] ] ] ] ] ]); $guzzle = new GuzzleClient($httpClient, $description); /** @var ResultInterface $result */ $result = $guzzle->foo(); $expected = [ 'scalar' => 'foo', 'nested' => [ 'bar', 'baz' ] ]; $this->assertEquals($expected, $result->toArray()); } public function nestedProvider() { return [ [ [ 'operations' => [ 'foo' => [ 'uri' => 'http://httpbin.org', 'httpMethod' => 'GET', 'responseModel' => 'j' ] ], 'models' => [ 'j' => [ 'type' => 'object', 'properties' => [ 'nested' => [ 'location' => 'json', 'type' => 'object', 'properties' => [ 'foo' => ['type' => 'string'], 'bar' => ['type' => 'number'], 'bam' => [ 'type' => 'object', 'properties' => [ 'abc' => [ 'type' => 'number' ] ] ] ] ] ], 'additionalProperties' => [ 'location' => 'json', 'type' => 'string', 'filters' => ['strtoupper'] ] ] ] ] ], [ [ 'operations' => [ 'foo' => [ 'uri' => 'http://httpbin.org', 'httpMethod' => 'GET', 'responseModel' => 'j' ] ], 'models' => [ 'j' => [ 'type' => 'object', 'location' => 'json', 'properties' => [ 'nested' => [ 'type' => 'object', 'properties' => [ 'foo' => ['type' => 'string'], 'bar' => ['type' => 'number'], 'bam' => [ 'type' => 'object', 'properties' => [ 'abc' => [ 'type' => 'number' ] ] ] ] ] ], 'additionalProperties' => [ 'type' => 'string', 'filters' => ['strtoupper'] ] ] ] ] ] ]; } /** * @dataProvider nestedProvider * @group ResponseLocation */ public function testVisitsNestedProperties($desc) { $json = [ 'nested' => [ 'foo' => 'abc', 'bar' => 123, 'bam' => [ 'abc' => 456 ] ], 'baz' => 'boo' ]; $body = \GuzzleHttp\json_encode($json); $response = new Response(200, ['Content-Type' => 'application/json'], $body); $mock = new MockHandler([$response]); $httpClient = new Client(['handler' => $mock]); $description = new Description($desc); $guzzle = new GuzzleClient($httpClient, $description); /** @var ResultInterface $result */ $result = $guzzle->foo(); $expected = [ 'nested' => [ 'foo' => 'abc', 'bar' => 123, 'bam' => [ 'abc' => 456 ] ], 'baz' => 'BOO' ]; $this->assertEquals($expected, $result->toArray()); } /** * @group ResponseLocation */ public function testVisitsNullResponseProperties() { $json = [ 'data' => [ 'link' => null ] ]; $body = \GuzzleHttp\json_encode($json); $response = new Response(200, ['Content-Type' => 'application/json'], $body); $mock = new MockHandler([$response]); $httpClient = new Client(['handler' => $mock]); $description = new Description( [ 'operations' => [ 'foo' => [ 'uri' => 'http://httpbin.org', 'httpMethod' => 'GET', 'responseModel' => 'j' ] ], 'models' => [ 'j' => [ 'type' => 'object', 'location' => 'json', 'properties' => [ 'scalar' => ['type' => 'string'], 'data' => [ 'type' => 'object', 'location' => 'json', 'properties' => [ 'link' => [ 'name' => 'val', 'type' => 'string', 'location' => 'json' ], ], 'additionalProperties' => false ] ] ] ] ] ); $guzzle = new GuzzleClient($httpClient, $description); /** @var ResultInterface $result */ $result = $guzzle->foo(); $expected = [ 'data' => [ 'link' => null ] ]; $this->assertEquals($expected, $result->toArray()); } /** * @group ResponseLocation */ public function testVisitsNestedArrayOfArrays() { $json = [ 'scalar' => 'foo', 'nested' => [ [ 'bar' => 123, 'baz' => false, ], [ 'bar' => 345, 'baz' => true, ], [ 'bar' => 678, 'baz' => true, ], ] ]; $body = \GuzzleHttp\json_encode($json); $response = new Response(200, ['Content-Type' => 'application/json'], $body); $mock = new MockHandler([$response]); $httpClient = new Client(['handler' => $mock]); $description = new Description([ 'operations' => [ 'foo' => [ 'uri' => 'http://httpbin.org', 'httpMethod' => 'GET', 'responseModel' => 'j' ] ], 'models' => [ 'j' => [ 'type' => 'object', 'properties' => [ 'scalar' => [ // for some reason (probably because location is also set on array of arrays) // array of arrays sibling elements must have location set to `json` // otherwise JsonLocation ignores them 'location' => 'json', 'type' => 'string' ], 'nested' => [ // array of arrays type must be set to `array` // without that JsonLocation throws an exception 'type' => 'array', // for array of arrays `location` must be set to `json` // otherwise JsonLocation returns an empty array 'location' => 'json', 'items' => [ // although this is array of arrays, array items type // must be set as `object` 'type' => 'object', 'properties' => [ 'bar' => [ 'type' => 'integer', ], 'baz' => [ 'type' => 'boolean', ], ], ] ] ] ] ] ]); $guzzle = new GuzzleClient($httpClient, $description); /** @var ResultInterface $result */ $result = $guzzle->foo(); $expected = [ 'scalar' => 'foo', 'nested' => [ [ 'bar' => 123, 'baz' => false, ], [ 'bar' => 345, 'baz' => true, ], [ 'bar' => 678, 'baz' => true, ], ] ]; $this->assertEquals($expected, $result->toArray()); } /** * @group ResponseLocation */ public function testVisitsNestedArrayOfObjects() { $json = json_decode('{"scalar":"foo","nested":[{"bar":123,"baz":false},{"bar":345,"baz":true},{"bar":678,"baz":true}]}'); $body = \GuzzleHttp\json_encode($json); $response = new Response(200, ['Content-Type' => 'application/json'], $body); $mock = new MockHandler([$response]); $httpClient = new Client(['handler' => $mock]); $description = new Description([ 'operations' => [ 'foo' => [ 'uri' => 'http://httpbin.org', 'httpMethod' => 'GET', 'responseModel' => 'j' ] ], 'models' => [ 'j' => [ 'type' => 'object', 'location' => 'json', 'properties' => [ 'scalar' => [ 'type' => 'string' ], 'nested' => [ // array of objects type must be set to `array` // without that JsonLocation throws an exception 'type' => 'array', 'items' => [ // array elements type must be set to `object` 'type' => 'object', 'properties' => [ 'bar' => [ 'type' => 'integer', ], 'baz' => [ 'type' => 'boolean', ], ], ] ] ] ] ] ]); $guzzle = new GuzzleClient($httpClient, $description); /** @var ResultInterface $result */ $result = $guzzle->foo(); $expected = [ 'scalar' => 'foo', 'nested' => [ [ 'bar' => 123, 'baz' => false, ], [ 'bar' => 345, 'baz' => true, ], [ 'bar' => 678, 'baz' => true, ], ] ]; $this->assertEquals($expected, $result->toArray()); } } tests/ResponseLocation/ReasonPhraseLocationTest.php000064400000001632144760130460016640 0ustar00 'val', 'filters' => ['strtolower'] ]); $response = new Response(200); $result = new Result(); $result = $location->visit($result, $response, $parameter); $this->assertEquals('ok', $result['val']); } } tests/ResponseLocation/StatusCodeLocationTest.php000064400000001522144760130460016322 0ustar00 'val']); $response = new Response(200); $result = new Result(); $result = $location->visit($result, $response, $parameter); $this->assertEquals(200, $result['val']); } } tests/ResponseLocation/XmlLocationTest.php000064400000062052144760130460015011 0ustar00 'val', 'sentAs' => 'vim', 'filters' => ['strtoupper'] ]); $model = new Parameter(); $response = new Response(200, [], \GuzzleHttp\Psr7\stream_for('bar')); $result = new Result(); $result = $location->before($result, $response, $model); $result = $location->visit($result, $response, $parameter); $result = $location->after($result, $response, $model); $this->assertEquals('BAR', $result['val']); } /** * @group ResponseLocation */ public function testVisitsAdditionalProperties() { $location = new XmlLocation(); $parameter = new Parameter(); $model = new Parameter(['additionalProperties' => ['location' => 'xml']]); $response = new Response(200, [], \GuzzleHttp\Psr7\stream_for('bar')); $result = new Result(); $result = $location->before($result, $response, $parameter); $result = $location->visit($result, $response, $parameter); $result = $location->after($result, $response, $model); $this->assertEquals('bar', $result['vim']); } /** * @group ResponseLocation */ public function testEnsuresFlatArraysAreFlat() { $param = new Parameter([ 'location' => 'xml', 'name' => 'foo', 'type' => 'array', 'items' => ['type' => 'string'], ]); $xml = 'barbaz'; $this->xmlTest($param, $xml, ['foo' => ['bar', 'baz']]); $this->xmlTest($param, 'bar', ['foo' => ['bar']]); } public function xmlDataProvider() { $param = new Parameter([ 'location' => 'xml', 'name' => 'Items', 'type' => 'array', 'items' => [ 'type' => 'object', 'name' => 'Item', 'properties' => [ 'Bar' => ['type' => 'string'], 'Baz' => ['type' => 'string'], ], ], ]); return [ [$param, '12', [ 'Items' => [ ['Bar' => 1], ['Bar' => 2], ], ]], [$param, '1', [ 'Items' => [ ['Bar' => 1], ] ]], [$param, '', [ 'Items' => [], ]] ]; } /** * @dataProvider xmlDataProvider * @group ResponseLocation */ public function testEnsuresWrappedArraysAreInCorrectLocations($param, $xml, $expected) { $location = new XmlLocation(); $model = new Parameter(); $response = new Response(200, [], \GuzzleHttp\Psr7\stream_for($xml)); $result = new Result(); $result = $location->before($result, $response, $param); $result = $location->visit($result, $response, $param); $result = $location->after($result, $response, $model); $this->assertEquals($expected, $result->toArray()); } /** * @group ResponseLocation */ public function testCanRenameValues() { $param = new Parameter([ 'name' => 'TerminatingInstances', 'type' => 'array', 'location' => 'xml', 'sentAs' => 'instancesSet', 'items' => [ 'name' => 'item', 'type' => 'object', 'sentAs' => 'item', 'properties' => [ 'InstanceId' => [ 'type' => 'string', 'sentAs' => 'instanceId', ], 'CurrentState' => [ 'type' => 'object', 'sentAs' => 'currentState', 'properties' => [ 'Code' => [ 'type' => 'numeric', 'sentAs' => 'code', ], 'Name' => [ 'type' => 'string', 'sentAs' => 'name', ], ], ], 'PreviousState' => [ 'type' => 'object', 'sentAs' => 'previousState', 'properties' => [ 'Code' => [ 'type' => 'numeric', 'sentAs' => 'code', ], 'Name' => [ 'type' => 'string', 'sentAs' => 'name', ], ], ], ], ] ]); $xml = ' i-3ea74257 32 shutting-down 16 running '; $this->xmlTest($param, $xml, [ 'TerminatingInstances' => [ [ 'InstanceId' => 'i-3ea74257', 'CurrentState' => [ 'Code' => '32', 'Name' => 'shutting-down', ], 'PreviousState' => [ 'Code' => '16', 'Name' => 'running', ], ], ], ]); } /** * @group ResponseLocation */ public function testCanRenameAttributes() { $param = new Parameter([ 'name' => 'RunningQueues', 'type' => 'array', 'location' => 'xml', 'items' => [ 'type' => 'object', 'sentAs' => 'item', 'properties' => [ 'QueueId' => [ 'type' => 'string', 'sentAs' => 'queue_id', 'data' => [ 'xmlAttribute' => true, ], ], 'CurrentState' => [ 'type' => 'object', 'properties' => [ 'Code' => [ 'type' => 'numeric', 'sentAs' => 'code', 'data' => [ 'xmlAttribute' => true, ], ], 'Name' => [ 'sentAs' => 'name', 'data' => [ 'xmlAttribute' => true, ], ], ], ], 'PreviousState' => [ 'type' => 'object', 'properties' => [ 'Code' => [ 'type' => 'numeric', 'sentAs' => 'code', 'data' => [ 'xmlAttribute' => true, ], ], 'Name' => [ 'sentAs' => 'name', 'data' => [ 'xmlAttribute' => true, ], ], ], ], ], ] ]); $xml = ' '; $this->xmlTest($param, $xml, [ 'RunningQueues' => [ [ 'QueueId' => 'q-3ea74257', 'CurrentState' => [ 'Code' => '32', 'Name' => 'processing', ], 'PreviousState' => [ 'Code' => '16', 'Name' => 'wait', ], ], ], ]); } /** * @group ResponseLocation */ public function testAddsEmptyArraysWhenValueIsMissing() { $param = new Parameter([ 'name' => 'Foo', 'type' => 'array', 'location' => 'xml', 'items' => [ 'type' => 'object', 'properties' => [ 'Baz' => ['type' => 'array'], 'Bar' => [ 'type' => 'object', 'properties' => [ 'Baz' => ['type' => 'array'], ], ], ], ], ]); $xml = ''; $this->xmlTest($param, $xml, [ 'Foo' => [ [ 'Bar' => [], ] ], ]); } /** * @group issue-399, ResponseLocation * @link https://github.com/guzzle/guzzle/issues/399 */ public function testDiscardingUnknownProperties() { $param = new Parameter([ 'name' => 'foo', 'type' => 'object', 'additionalProperties' => false, 'properties' => [ 'bar' => [ 'type' => 'string', 'name' => 'bar', ], ], ]); $xml = ' 15 discard me '; $this->xmlTest($param, $xml, [ 'foo' => [ 'bar' => 15 ] ]); } /** * @group issue-399, ResponseLocation * @link https://github.com/guzzle/guzzle/issues/399 */ public function testDiscardingUnknownPropertiesWithAliasing() { $param = new Parameter([ 'name' => 'foo', 'type' => 'object', 'additionalProperties' => false, 'properties' => [ 'bar' => [ 'name' => 'bar', 'sentAs' => 'baz', ], ], ]); $xml = ' 15 discard me '; $this->xmlTest($param, $xml, [ 'foo' => [ 'bar' => 15, ], ]); } /** * @group ResponseLocation */ public function testProcessingOfNestedAdditionalProperties() { $param = new Parameter([ 'name' => 'foo', 'type' => 'object', 'additionalProperties' => true, 'properties' => [ 'bar' => [ 'name' => 'bar', 'sentAs' => 'baz', ], 'nestedNoAdditional' => [ 'type' => 'object', 'additionalProperties' => false, 'properties' => [ 'id' => [ 'type' => 'integer', ], ], ], 'nestedWithAdditional' => [ 'type' => 'object', 'additionalProperties' => true, ], 'nestedWithAdditionalSchema' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'array', 'items' => [ 'type' => 'string', ], ], ], ], ]); $xml = ' 15 include me 15 discard me 15 include me 1 2 3 A B C '; $this->xmlTest($param, $xml, [ 'foo' => [ 'bar' => '15', 'additional' => 'include me', 'nestedNoAdditional' => [ 'id' => '15', ], 'nestedWithAdditional' => [ 'id' => '15', 'additional' => 'include me', ], 'nestedWithAdditionalSchema' => [ 'arrayA' => ['1', '2', '3'], 'arrayB' => ['A', 'B', 'C'], ], ], ]); } /** * @group ResponseLocation */ public function testConvertsMultipleAssociativeElementsToArray() { $param = new Parameter([ 'name' => 'foo', 'type' => 'object', 'additionalProperties' => true, ]); $xml = ' 15 25 hi test '; $this->xmlTest($param, $xml, [ 'foo' => [ 'baz' => ['15', '25'], 'bar' => 'hi', 'bam' => [ 'test', ['@attributes' => ['attr' => 'hi']] ] ] ]); } /** * @group ResponseLocation */ public function testUnderstandsNamespaces() { $param = new Parameter([ 'name' => 'nstest', 'type' => 'array', 'location' => 'xml', 'items' => [ 'name' => 'item', 'type' => 'object', 'sentAs' => 'item', 'properties' => [ 'id' => [ 'type' => 'string', ], 'isbn:number' => [ 'type' => 'string', ], 'meta' => [ 'type' => 'object', 'sentAs' => 'abstract:meta', 'properties' => [ 'foo' => [ 'type' => 'numeric', ], 'bar' => [ 'type' => 'object', 'properties' =>[ 'attribute' => [ 'type' => 'string', 'data' => [ 'xmlAttribute' => true, 'xmlNs' => 'abstract', ], ], ], ], ], ], 'gamma' => [ 'type' => 'object', 'data' => [ 'xmlNs' => 'abstract', ], 'additionalProperties' => true, ], 'nonExistent' => [ 'type' => 'object', 'data' => [ 'xmlNs' => 'abstract', ], 'additionalProperties' => true, ], 'nonExistent2' => [ 'type' => 'object', 'additionalProperties' => true, ], ], ], ]); $xml = ' 101 1568491379 10 bar 102 1568491999 20 baz '; $this->xmlTest($param, $xml, [ 'nstest' => [ [ 'id' => '101', 'isbn:number' => 1568491379, 'meta' => [ 'foo' => 10, 'bar' => [ 'attribute' => 'foo', ], ], 'gamma' => [ 'foo' => 'bar', ], ], [ 'id' => '102', 'isbn:number' => 1568491999, 'meta' => [ 'foo' => 20, 'bar' => [ 'attribute' => 'bar' ], ], 'gamma' => [ 'foo' => 'baz', ], ], ], ]); } /** * @group ResponseLocation */ public function testCanWalkUndefinedPropertiesWithNamespace() { $param = new Parameter([ 'name' => 'nstest', 'type' => 'array', 'location' => 'xml', 'items' => [ 'name' => 'item', 'type' => 'object', 'sentAs' => 'item', 'additionalProperties' => [ 'type' => 'object', 'data' => [ 'xmlNs' => 'abstract' ], ], 'properties' => [ 'id' => [ 'type' => 'string', ], 'isbn:number' => [ 'type' => 'string', ], ], ], ]); $xml = ' 101 1568491379 10 baz 102 1568491999 20 foo '; $this->xmlTest($param, $xml, [ 'nstest' => [ [ 'id' => '101', 'isbn:number' => 1568491379, 'meta' => [ 'foo' => 10, 'bar' => 'baz', ], ], [ 'id' => '102', 'isbn:number' => 1568491999, 'meta' => [ 'foo' => 20, 'bar' => 'foo', ], ], ] ]); } /** * @group ResponseLocation */ public function testCanWalkSimpleArrayWithNamespace() { $param = new Parameter([ 'name' => 'nstest', 'type' => 'array', 'location' => 'xml', 'items' => [ 'type' => 'string', 'sentAs' => 'number', 'data' => [ 'xmlNs' => 'isbn' ], ], ]); $xml = ' 1568491379 1568491999 1568492999 '; $this->xmlTest($param, $xml, [ 'nstest' => [ 1568491379, 1568491999, 1568492999, ], ]); } /** * @group ResponseLocation */ public function testCanWalkSimpleArrayWithNamespace2() { $param = new Parameter([ 'name' => 'nstest', 'type' => 'array', 'location' => 'xml', 'items' => [ 'type' => 'string', 'sentAs' => 'isbn:number', ] ]); $xml = ' 1568491379 1568491999 1568492999 '; $this->xmlTest($param, $xml, [ 'nstest' => [ 1568491379, 1568491999, 1568492999, ], ]); } private function xmlTest(Parameter $param, $xml, $expected) { $location = new XmlLocation(); $model = new Parameter(); $response = new Response(200, [], \GuzzleHttp\Psr7\stream_for($xml)); $result = new Result(); $result = $location->before($result, $response, $param); $result = $location->visit($result, $response, $param); $result = $location->after($result, $response, $model); $this->assertEquals($expected, $result->toArray()); } } tests/SchemaFormatterTest.php000064400000003725144760130460012357 0ustar00assertEquals($result, (new SchemaFormatter)->format($format, $value)); } /** * @expectedException \InvalidArgumentException */ public function testValidatesDateTimeInput() { (new SchemaFormatter)->format('date-time', false); } public function testEnsuresTimestampsAreIntegers() { $t = time(); $result = (new SchemaFormatter)->format('timestamp', $t); $this->assertSame($t, $result); $this->assertInternalType('int', $result); } } tests/SchemaValidatorTest.php000064400000027061144760130460012340 0ustar00validator = new SchemaValidator(); } public function testValidatesArrayListsAreNumericallyIndexed() { $value = [[1]]; $this->assertFalse($this->validator->validate($this->getComplexParam(), $value)); $this->assertEquals( ['[Foo][0] must be an array of properties. Got a numerically indexed array.'], $this->validator->getErrors() ); } public function testValidatesArrayListsContainProperItems() { $value = [true]; $this->assertFalse($this->validator->validate($this->getComplexParam(), $value)); $this->assertEquals( ['[Foo][0] must be of type object'], $this->validator->getErrors() ); } public function testAddsDefaultValuesInLists() { $value = [[]]; $this->assertTrue($this->validator->validate($this->getComplexParam(), $value)); $this->assertEquals([['Bar' => true]], $value); } public function testMergesDefaultValuesInLists() { $value = [ ['Baz' => 'hello!'], ['Bar' => false], ]; $this->assertTrue($this->validator->validate($this->getComplexParam(), $value)); $this->assertEquals([ [ 'Baz' => 'hello!', 'Bar' => true, ], ['Bar' => false], ], $value); } public function testCorrectlyConvertsParametersToArrayWhenArraysArePresent() { $param = $this->getComplexParam(); $result = $param->toArray(); $this->assertInternalType('array', $result['items']); $this->assertEquals('array', $result['type']); $this->assertInstanceOf('GuzzleHttp\Command\Guzzle\Parameter', $param->getItems()); } public function testEnforcesInstanceOfOnlyWhenObject() { $p = new Parameter([ 'name' => 'foo', 'type' => ['object', 'string'], 'instanceOf' => get_class($this) ]); $this->assertTrue($this->validator->validate($p, $this)); $s = 'test'; $this->assertTrue($this->validator->validate($p, $s)); } public function testConvertsObjectsToArraysWhenToArrayInterface() { $o = $this->getMockBuilder(ToArrayInterface::class) ->setMethods(['toArray']) ->getMockForAbstractClass(); $o->expects($this->once()) ->method('toArray') ->will($this->returnValue(['foo' => 'bar'])); $p = new Parameter([ 'name' => 'test', 'type' => 'object', 'properties' => [ 'foo' => ['required' => 'true'], ], ]); $this->assertTrue($this->validator->validate($p, $o)); } public function testMergesValidationErrorsInPropertiesWithParent() { $p = new Parameter([ 'name' => 'foo', 'type' => 'object', 'properties' => [ 'bar' => ['type' => 'string', 'required' => true, 'description' => 'This is what it does'], 'test' => ['type' => 'string', 'minLength' => 2, 'maxLength' => 5], 'test2' => ['type' => 'string', 'minLength' => 2, 'maxLength' => 2], 'test3' => ['type' => 'integer', 'minimum' => 100], 'test4' => ['type' => 'integer', 'maximum' => 10], 'test5' => ['type' => 'array', 'maxItems' => 2], 'test6' => ['type' => 'string', 'enum' => ['a', 'bc']], 'test7' => ['type' => 'string', 'pattern' => '/[0-9]+/'], 'test8' => ['type' => 'number'], 'baz' => [ 'type' => 'array', 'minItems' => 2, 'required' => true, "items" => ["type" => "string"], ], ], ]); $value = [ 'test' => 'a', 'test2' => 'abc', 'baz' => [false], 'test3' => 10, 'test4' => 100, 'test5' => [1, 3, 4], 'test6' => 'Foo', 'test7' => 'abc', 'test8' => 'abc', ]; $this->assertFalse($this->validator->validate($p, $value)); $this->assertEquals([ '[foo][bar] is a required string: This is what it does', '[foo][baz] must contain 2 or more elements', '[foo][baz][0] must be of type string', '[foo][test2] length must be less than or equal to 2', '[foo][test3] must be greater than or equal to 100', '[foo][test4] must be less than or equal to 10', '[foo][test5] must contain 2 or fewer elements', '[foo][test6] must be one of "a" or "bc"', '[foo][test7] must match the following regular expression: /[0-9]+/', '[foo][test8] must be of type number', '[foo][test] length must be greater than or equal to 2', ], $this->validator->getErrors()); } public function testHandlesNullValuesInArraysWithDefaults() { $p = new Parameter([ 'name' => 'foo', 'type' => 'object', 'required' => true, 'properties' => [ 'bar' => [ 'type' => 'object', 'required' => true, 'properties' => [ 'foo' => ['default' => 'hi'], ], ], ], ]); $value = []; $this->assertTrue($this->validator->validate($p, $value)); $this->assertEquals(['bar' => ['foo' => 'hi']], $value); } public function testFailsWhenNullValuesInArraysWithNoDefaults() { $p = new Parameter([ 'name' => 'foo', 'type' => 'object', 'required' => true, 'properties' => [ 'bar' => [ 'type' => 'object', 'required' => true, 'properties' => [ 'foo' => ['type' => 'string'], ], ], ], ]); $value = []; $this->assertFalse($this->validator->validate($p, $value)); $this->assertEquals(['[foo][bar] is a required object'], $this->validator->getErrors()); } public function testChecksTypes() { $p = new SchemaValidator(); $r = new \ReflectionMethod($p, 'determineType'); $r->setAccessible(true); $this->assertEquals('any', $r->invoke($p, 'any', 'hello')); $this->assertEquals(false, $r->invoke($p, 'foo', 'foo')); $this->assertEquals('string', $r->invoke($p, 'string', 'hello')); $this->assertEquals(false, $r->invoke($p, 'string', false)); $this->assertEquals('integer', $r->invoke($p, 'integer', 1)); $this->assertEquals(false, $r->invoke($p, 'integer', 'abc')); $this->assertEquals('numeric', $r->invoke($p, 'numeric', 1)); $this->assertEquals('numeric', $r->invoke($p, 'numeric', '1')); $this->assertEquals('number', $r->invoke($p, 'number', 1)); $this->assertEquals('number', $r->invoke($p, 'number', '1')); $this->assertEquals(false, $r->invoke($p, 'numeric', 'a')); $this->assertEquals('boolean', $r->invoke($p, 'boolean', true)); $this->assertEquals('boolean', $r->invoke($p, 'boolean', false)); $this->assertEquals(false, $r->invoke($p, 'boolean', 'false')); $this->assertEquals('null', $r->invoke($p, 'null', null)); $this->assertEquals(false, $r->invoke($p, 'null', 'abc')); $this->assertEquals('array', $r->invoke($p, 'array', [])); $this->assertEquals(false, $r->invoke($p, 'array', 'foo')); } public function testValidatesFalseAdditionalProperties() { $param = new Parameter([ 'name' => 'foo', 'type' => 'object', 'properties' => [ 'bar' => ['type' => 'string'], ], 'additionalProperties' => false, ]); $value = ['test' => '123']; $this->assertFalse($this->validator->validate($param, $value)); $this->assertEquals(['[foo][test] is not an allowed property'], $this->validator->getErrors()); $value = ['bar' => '123']; $this->assertTrue($this->validator->validate($param, $value)); } public function testAllowsUndefinedAdditionalProperties() { $param = new Parameter([ 'name' => 'foo', 'type' => 'object', 'properties' => [ 'bar' => ['type' => 'string'], ] ]); $value = ['test' => '123']; $this->assertTrue($this->validator->validate($param, $value)); } public function testValidatesAdditionalProperties() { $param = new Parameter([ 'name' => 'foo', 'type' => 'object', 'properties' => [ 'bar' => ['type' => 'string'], ], 'additionalProperties' => ['type' => 'integer'], ]); $value = ['test' => 'foo']; $this->assertFalse($this->validator->validate($param, $value)); $this->assertEquals(['[foo][test] must be of type integer'], $this->validator->getErrors()); } public function testValidatesAdditionalPropertiesThatArrayArrays() { $param = new Parameter([ 'name' => 'foo', 'type' => 'object', 'additionalProperties' => [ 'type' => 'array', 'items' => ['type' => 'string'], ], ]); $value = ['test' => [true]]; $this->assertFalse($this->validator->validate($param, $value)); $this->assertEquals(['[foo][test][0] must be of type string'], $this->validator->getErrors()); } public function testIntegersCastToStringWhenTypeMismatch() { $param = new Parameter([ 'name' => 'test', 'type' => 'string', ]); $value = 12; $this->assertTrue($this->validator->validate($param, $value)); $this->assertEquals('12', $value); } public function testRequiredMessageIncludesType() { $param = new Parameter([ 'name' => 'test', 'type' => [ 'string', 'boolean', ], 'required' => true, ]); $value = null; $this->assertFalse($this->validator->validate($param, $value)); $this->assertEquals(['[test] is a required string or boolean'], $this->validator->getErrors()); } protected function getComplexParam() { return new Parameter([ 'name' => 'Foo', 'type' => 'array', 'required' => true, 'min' => 1, 'items' => [ 'type' => 'object', 'properties' => [ 'Baz' => [ 'type' => 'string', ], 'Bar' => [ 'required' => true, 'type' => 'boolean', 'default' => true, ], ], ], ]); } } tests/SerializerTest.php000064400000002307144760130460011377 0ustar00 'http://test.com', 'operations' => [ 'test' => [ 'httpMethod' => 'GET', 'uri' => '/api/{key}/foo', 'parameters' => [ 'key' => [ 'required' => true, 'type' => 'string', 'location' => 'uri' ], ] ] ] ]); $command = new Command('test', ['key' => 'bar']); $serializer = new Serializer($description); /** @var Request $request */ $request = $serializer($command); $this->assertEquals('http://test.com/api/bar/foo', $request->getUri()); } }