添加网站文件

This commit is contained in:
2025-12-22 13:59:40 +08:00
commit 117aaf83d1
19468 changed files with 2111999 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
phpunit.xml
composer.lock
vendor/
artifacts/

View File

@@ -0,0 +1,23 @@
sudo: 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

View File

@@ -0,0 +1,351 @@
# Change Log
## [1.1.3](https://github.com/guzzle/guzzle-services/tree/1.1.3) (2017-10-06)
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.1.2...HEAD)
**Closed issues:**
- Parameter type configuration causes issue when filters change input type [\#147](https://github.com/guzzle/guzzle-services/issues/147)
**Merged pull requests:**
- Use wire name when visiting array [\#152](https://github.com/guzzle/guzzle-services/pull/152) ([my2ter](https://github.com/my2ter))
- Adding descriptive error message on parameter failure [\#144](https://github.com/guzzle/guzzle-services/pull/144) ([igorsantos07](https://github.com/igorsantos07))
## [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:**
- Default values ignored in 1.1 [\#146](https://github.com/guzzle/guzzle-services/issues/146)
- Operations extends is broken in 1.1.1 [\#145](https://github.com/guzzle/guzzle-services/issues/145)
## [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))
- Allow filters on response model [\#138](https://github.com/guzzle/guzzle-services/pull/138) ([danizord](https://github.com/danizord))
- Exposing properties to a parent class [\#136](https://github.com/guzzle/guzzle-services/pull/136) ([Napas](https://github.com/Napas))
## [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:**
- 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)
**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)
**Merged pull requests:**
- Fix serialization of query params [\#129](https://github.com/guzzle/guzzle-services/pull/129) ([bakura10](https://github.com/bakura10))
## [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))
- Fix combine base url and command uri [\#108](https://github.com/guzzle/guzzle-services/pull/108) ([vlastv](https://github.com/vlastv))
- Fixing JsonLocation::visit\(\) not returning a request \#106 [\#107](https://github.com/guzzle/guzzle-services/pull/107) ([Pinolo](https://github.com/Pinolo))
- Fix call to undefined method "GuzzleHttp\Psr7\Uri::combine" [\#105](https://github.com/guzzle/guzzle-services/pull/105) ([horrorin](https://github.com/horrorin))
- fix description for get request example [\#87](https://github.com/guzzle/guzzle-services/pull/87) ([snoek09](https://github.com/snoek09))
- Allow raw values \(non array/object\) for root model definitions [\#74](https://github.com/guzzle/guzzle-services/pull/74) ([rfink](https://github.com/rfink))
- Allow shortened definition of properties by assigning them directly to a type [\#72](https://github.com/guzzle/guzzle-services/pull/72) ([rfink](https://github.com/rfink))
## [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))
- Throw Any Exceptions On Process [\#59](https://github.com/guzzle/guzzle-services/pull/59) ([GrahamCampbell](https://github.com/GrahamCampbell))
- Allow extension to work recursively over models [\#34](https://github.com/guzzle/guzzle-services/pull/34) ([jamiehannaford](https://github.com/jamiehannaford))
- A custom class can be configured for command instances. [\#29](https://github.com/guzzle/guzzle-services/pull/29) ([robinvdvleuten](https://github.com/robinvdvleuten))
- \[WIP\] doing some experimentation [\#24](https://github.com/guzzle/guzzle-services/pull/24) ([cordoval](https://github.com/cordoval))
## [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))
- baseUrl can be a string or an uri template [\#16](https://github.com/guzzle/guzzle-services/pull/16) ([robinvdvleuten](https://github.com/robinvdvleuten))
## [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))
- Use latest guzzlehttp/command to fix dependencies [\#10](https://github.com/guzzle/guzzle-services/pull/10) ([sbward](https://github.com/sbward))
- some collaboration using Gush :\) [\#3](https://github.com/guzzle/guzzle-services/pull/3) ([cordoval](https://github.com/cordoval))
## [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)*

View File

@@ -0,0 +1,19 @@
Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
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.

View File

@@ -0,0 +1,15 @@
all: 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

View File

@@ -0,0 +1,129 @@
# 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.

View File

@@ -0,0 +1,49 @@
{
"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"
}
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./vendor/autoload.php"
colors="true">
<testsuites>
<testsuite>
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,265 @@
<?php
namespace GuzzleHttp\Command\Guzzle;
use GuzzleHttp\Psr7\Uri;
/**
* Represents a Guzzle service description
*/
class Description implements DescriptionInterface
{
/** @var array Array of {@see OperationInterface} objects */
private $operations = [];
/** @var array Array of API models */
private $models = [];
/** @var string Name of the API */
private $name;
/** @var string API version */
private $apiVersion;
/** @var string Summary of the API */
private $description;
/** @var array Any extra API data */
private $extraData = [];
/** @var Uri baseUri/basePath */
private $baseUri;
/** @var SchemaFormatter */
private $formatter;
/**
* @param array $config Service description data
* @param array $options Custom options to apply to the description
* - formatter: Can provide a custom SchemaFormatter class
*
* @throws \InvalidArgumentException
*/
public function __construct(array $config, array $options = [])
{
// Keep a list of default keys used in service descriptions that is
// later used to determine extra data keys.
static $defaultKeys = ['name', 'models', 'apiVersion', 'description'];
// Pull in the default configuration values
foreach ($defaultKeys as $key) {
if (isset($config[$key])) {
$this->{$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;
}
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace GuzzleHttp\Command\Guzzle;
use GuzzleHttp\Psr7\Uri;
interface DescriptionInterface
{
/**
* Get the basePath/baseUri of the description
*
* @return Uri
*/
public function getBaseUri();
/**
* Get the API operations of the service
*
* @return Operation[] Returns an array of {@see Operation} objects
*/
public function getOperations();
/**
* Check if the service has an operation by name
*
* @param string $name Name of the operation to check
*
* @return bool
*/
public function hasOperation($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);
/**
* 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);
/**
* Get all models of the service description.
*
* @return array
*/
public function getModels();
/**
* 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);
/**
* Get the API version of the service
*
* @return string
*/
public function getApiVersion();
/**
* Get the name of the API
*
* @return string
*/
public function getName();
/**
* Get a summary of the purpose of the API
*
* @return string
*/
public function getDescription();
/**
* 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);
/**
* 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);
}

View File

@@ -0,0 +1,294 @@
<?php
namespace GuzzleHttp\Command\Guzzle;
use GuzzleHttp\Command\CommandInterface;
use GuzzleHttp\Command\Guzzle\ResponseLocation\BodyLocation;
use GuzzleHttp\Command\Guzzle\ResponseLocation\HeaderLocation;
use GuzzleHttp\Command\Guzzle\ResponseLocation\JsonLocation;
use GuzzleHttp\Command\Guzzle\ResponseLocation\ReasonPhraseLocation;
use GuzzleHttp\Command\Guzzle\ResponseLocation\ResponseLocationInterface;
use GuzzleHttp\Command\Guzzle\ResponseLocation\StatusCodeLocation;
use GuzzleHttp\Command\Guzzle\ResponseLocation\XmlLocation;
use GuzzleHttp\Command\Result;
use GuzzleHttp\Command\ResultInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Handler used to create response models based on an HTTP response and
* a service description.
*
* Response location visitors are registered with this Handler to handle
* locations (e.g., 'xml', 'json', 'header'). All of the locations of a response
* model that will be visited first have their ``before`` method triggered.
* After the before method is called on every visitor that will be walked, each
* visitor is triggered using the ``visit()`` method. After all of the visitors
* are visited, the ``after()`` method is called on each visitor. This is the
* place in which you should handle things like additionalProperties with
* custom locations (i.e., this is how it is handled in the JSON visitor).
*/
class Deserializer
{
/** @var ResponseLocationInterface[] $responseLocations */
private $responseLocations;
/** @var DescriptionInterface $description */
private $description;
/** @var boolean $process */
private $process;
/**
* @param DescriptionInterface $description
* @param bool $process
* @param ResponseLocationInterface[] $responseLocations Extra response locations
*/
public function __construct(
DescriptionInterface $description,
$process,
array $responseLocations = []
) {
static $defaultResponseLocations;
if (!$defaultResponseLocations) {
$defaultResponseLocations = [
'body' => 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.
}
}

View File

@@ -0,0 +1,169 @@
<?php
namespace GuzzleHttp\Command\Guzzle;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Command\CommandInterface;
use GuzzleHttp\Command\Guzzle\Handler\ValidatedDescriptionHandler;
use GuzzleHttp\Command\ServiceClient;
use GuzzleHttp\HandlerStack;
/**
* Default Guzzle web service client implementation.
*/
class GuzzleClient extends ServiceClient
{
/** @var array $config */
private $config;
/** @var DescriptionInterface Guzzle service description */
private $description;
/**
* The client constructor accepts an associative array of configuration
* options:
*
* - defaults: Associative array of default command parameters to add to
* each command created by the client.
* - validate: Specify if command input is validated (defaults to true).
* Changing this setting after the client has been created will have no
* effect.
* - process: Specify if HTTP responses are parsed (defaults to true).
* Changing this setting after the client has been created will have no
* effect.
* - response_locations: Associative array of location types mapping to
* ResponseLocationInterface objects.
*
* @param ClientInterface $client HTTP client to use.
* @param DescriptionInterface $description Guzzle service description
* @param callable $commandToRequestTransformer
* @param callable $responseToResultTransformer
* @param HandlerStack $commandHandlerStack
* @param array $config Configuration options
*/
public function __construct(
ClientInterface $client,
DescriptionInterface $description,
callable $commandToRequestTransformer = null,
callable $responseToResultTransformer = null,
HandlerStack $commandHandlerStack = null,
array $config = []
) {
$this->config = $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.
}
}
}

View File

@@ -0,0 +1,82 @@
<?php namespace GuzzleHttp\Command\Guzzle\Handler;
use GuzzleHttp\Command\CommandInterface;
use GuzzleHttp\Command\Exception\CommandException;
use GuzzleHttp\Command\Guzzle\DescriptionInterface;
use GuzzleHttp\Command\Guzzle\SchemaValidator;
/**
* Handler used to validate command input against a service description.
*
* @author Stefano Kowalke <info@arroba-it.de>
*/
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);
};
}
}

View File

@@ -0,0 +1,312 @@
<?php
namespace GuzzleHttp\Command\Guzzle;
use GuzzleHttp\Command\ToArrayInterface;
/**
* Guzzle operation
*/
class Operation implements ToArrayInterface
{
/** @var array Parameters */
private $parameters = [];
/** @var Parameter Additional parameters schema */
private $additionalParameters;
/** @var DescriptionInterface */
private $description;
/** @var array Config data */
private $config;
/**
* Builds an Operation object using an array of configuration data.
*
* - name: (string) Name of the command
* - httpMethod: (string) HTTP method of the operation
* - uri: (string) URI template that can create a relative or absolute URL
* - parameters: (array) Associative array of parameters for the command.
* Each value must be an array that is used to create {@see Parameter}
* objects.
* - summary: (string) This is a short summary of what the operation does
* - notes: (string) A longer description of the operation.
* - documentationUrl: (string) Reference URL providing more information
* about the operation.
* - responseModel: (string) The model name used for processing response.
* - deprecated: (bool) Set to true if this is a deprecated command
* - errorResponses: (array) Errors that could occur when executing the
* command. Array of hashes, each with a 'code' (the HTTP response code),
* 'phrase' (response reason phrase or description of the error), and
* 'class' (a custom exception class that would be thrown if the error is
* encountered).
* - data: (array) Any extra data that might be used to help build or
* serialize the operation
* - additionalParameters: (null|array) Parameter schema to use when an
* option is passed to the operation that is not in the schema
*
* @param array $config Array of configuration data
* @param DescriptionInterface $description Service description used to resolve models if $ref tags are found
* @throws \InvalidArgumentException
*/
public function __construct(array $config = [], DescriptionInterface $description = null)
{
static $defaults = [
'name' => '',
'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, {$this->config['name']}.$name is ".gettype($param)
);
}
$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'];
}
}
}
}

View File

@@ -0,0 +1,655 @@
<?php
namespace GuzzleHttp\Command\Guzzle;
use GuzzleHttp\Command\ToArrayInterface;
/**
* API parameter object used with service descriptions
*/
class Parameter implements ToArrayInterface
{
private $originalData;
/** @var string $name */
private $name;
/** @var string $description */
private $description;
/** @var string|array $type */
private $type;
/** @var bool $required*/
private $required;
/** @var array|null $enum */
private $enum;
/** @var string $pattern */
private $pattern;
/** @var int $minimum*/
private $minimum;
/** @var int $maximum */
private $maximum;
/** @var int $minLength */
private $minLength;
/** @var int $maxLength */
private $maxLength;
/** @var int $minItems */
private $minItems;
/** @var int $maxItems */
private $maxItems;
/** @var mixed $default */
private $default;
/** @var bool $static */
private $static;
/** @var array $filters */
private $filters;
/** @var string $location */
private $location;
/** @var string $sentAs */
private $sentAs;
/** @var array $data */
private $data;
/** @var array $properties */
private $properties = [];
/** @var array|bool|Parameter $additionalProperties */
private $additionalProperties;
/** @var array|Parameter $items */
private $items;
/** @var string $format */
private $format;
private $propertiesCache = null;
/** @var Description */
private $serviceDescription;
/**
* Create a new Parameter using an associative array of data.
*
* The array can contain the following information:
*
* - name: (string) Unique name of the parameter
*
* - type: (string|array) Type of variable (string, number, integer,
* boolean, object, array, numeric, null, any). Types are used for
* validation and determining the structure of a parameter. You can use a
* union type by providing an array of simple types. If one of the union
* types matches the provided value, then the value is valid.
*
* - required: (bool) Whether or not the parameter is required
*
* - default: (mixed) Default value to use if no value is supplied
*
* - static: (bool) Set to true to specify that the parameter value cannot
* be changed from the default.
*
* - description: (string) Documentation of the parameter
*
* - location: (string) The location of a request used to apply a parameter.
* Custom locations can be registered with a command, but the defaults
* are uri, query, header, body, json, xml, formParam, multipart.
*
* - sentAs: (string) Specifies how the data being modeled is sent over the
* wire. For example, you may wish to include certain headers in a
* response model that have a normalized casing of FooBar, but the actual
* header is x-foo-bar. In this case, sentAs would be set to x-foo-bar.
*
* - filters: (array) Array of static method names to run a parameter
* value through. Each value in the array must be a string containing the
* full class path to a static method or an array of complex filter
* information. You can specify static methods of classes using the full
* namespace class name followed by '::' (e.g. Foo\Bar::baz). Some
* filters require arguments in order to properly filter a value. For
* complex filters, use a hash containing a 'method' key pointing to a
* static method, and an 'args' key containing an array of positional
* arguments to pass to the method. Arguments can contain keywords that
* are replaced when filtering a value: '@value' is replaced with the
* value being validated, '@api' is replaced with the Parameter object.
*
* - properties: When the type is an object, you can specify nested parameters
*
* - additionalProperties: (array) This attribute defines a schema for all
* properties that are not explicitly defined in an object type
* definition. If specified, the value MUST be a schema or a boolean. If
* false is provided, no additional properties are allowed beyond the
* properties defined in the schema. The default value is an empty schema
* which allows any value for additional properties.
*
* - items: This attribute defines the allowed items in an instance array,
* and MUST be a schema or an array of schemas. The default value is an
* empty schema which allows any value for items in the instance array.
* When this attribute value is a schema and the instance value is an
* array, then all the items in the array MUST be valid according to the
* schema.
*
* - pattern: When the type is a string, you can specify the regex pattern
* that a value must match
*
* - enum: When the type is a string, you can specify a list of acceptable
* values.
*
* - minItems: (int) Minimum number of items allowed in an array
*
* - maxItems: (int) Maximum number of items allowed in an array
*
* - minLength: (int) Minimum length of a string
*
* - maxLength: (int) Maximum length of a string
*
* - minimum: (int) Minimum value of an integer
*
* - maximum: (int) Maximum value of an integer
*
* - data: (array) Any additional custom data to use when serializing,
* validating, etc
*
* - format: (string) Format used to coax a value into the correct format
* when serializing or unserializing. You may specify either an array of
* filters OR a format, but not both. Supported values: date-time, date,
* time, timestamp, date-time-http, and boolean-string.
*
* - $ref: (string) String referencing a service description model. The
* parameter is replaced by the schema contained in the model.
*
* @param array $data Array of data as seen in service descriptions
* @param array $options Options used when creating the parameter. You can
* specify a Guzzle service description in the 'description' key.
*
* @throws \InvalidArgumentException
*/
public function __construct(array $data = [], array $options = [])
{
$this->originalData = $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});
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace GuzzleHttp\Command\Guzzle\QuerySerializer;
interface QuerySerializerInterface
{
/**
* Aggregate query params and transform them into a string
*
* @param array $queryParams
* @return string
*/
public function aggregate(array $queryParams);
}

View File

@@ -0,0 +1,33 @@
<?php
namespace GuzzleHttp\Command\Guzzle\QuerySerializer;
class Rfc3986Serializer implements QuerySerializerInterface
{
/**
* @var bool
*/
private $removeNumericIndices;
/**
* @param bool $removeNumericIndices
*/
public function __construct($removeNumericIndices = false)
{
$this->removeNumericIndices = $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;
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
use GuzzleHttp\Command\CommandInterface;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\Guzzle\Parameter;
use Psr\Http\Message\RequestInterface;
abstract class AbstractLocation implements RequestLocationInterface
{
/** @var string */
protected $locationName;
/**
* Set the name of the location
*
* @param $locationName
*/
public function __construct($locationName)
{
$this->locationName = $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);
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
use GuzzleHttp\Command\CommandInterface;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Psr7;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
/**
* Adds a body to a request
*/
class BodyLocation extends AbstractLocation
{
/**
* Set the name of the location
*
* @param string $locationName
*/
public function __construct($locationName = 'body')
{
parent::__construct($locationName);
}
/**
* @param CommandInterface $command
* @param RequestInterface $request
* @param Parameter $param
*
* @return MessageInterface
*/
public function visit(
CommandInterface $command,
RequestInterface $request,
Parameter $param
) {
$oldValue = $request->getBody()->getContents();
$value = $command[$param->getName()];
$value = $param->getName() . '=' . $param->filter($value);
if ($oldValue !== '') {
$value = $oldValue . '&' . $value;
}
return $request->withBody(Psr7\stream_for($value));
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
use GuzzleHttp\Command\CommandInterface;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;
/**
* Add form_params to a request
*/
class FormParamLocation extends AbstractLocation
{
/** @var string $contentType */
protected $contentType = 'application/x-www-form-urlencoded; charset=utf-8';
/** @var array $formParamsData */
protected $formParamsData = [];
/**
* Set the name of the location
*
* @param string $locationName
*/
public function __construct($locationName = 'formParam')
{
parent::__construct($locationName);
}
/**
* @param CommandInterface $command
* @param RequestInterface $request
* @param Parameter $param
*
* @return RequestInterface
*/
public function visit(
CommandInterface $command,
RequestInterface $request,
Parameter $param
) {
$this->formParamsData['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;
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
use GuzzleHttp\Command\CommandInterface;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\Guzzle\Parameter;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
/**
* Request header location
*/
class HeaderLocation extends AbstractLocation
{
/**
* Set the name of the location
*
* @param string $locationName
*/
public function __construct($locationName = 'header')
{
parent::__construct($locationName);
}
/**
* @param CommandInterface $command
* @param RequestInterface $request
* @param Parameter $param
*
* @return MessageInterface
*/
public function visit(
CommandInterface $command,
RequestInterface $request,
Parameter $param
) {
$value = $command[$param->getName()];
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;
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
use GuzzleHttp\Command\CommandInterface;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Psr7;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
/**
* Creates a JSON document
*/
class JsonLocation extends AbstractLocation
{
/** @var string Whether or not to add a Content-Type header when JSON is found */
private $jsonContentType;
/** @var array */
private $jsonData;
/**
* @param string $locationName Name of the location
* @param string $contentType Content-Type header to add to the request if
* JSON is added to the body. Pass an empty string to omit.
*/
public function __construct($locationName = 'json', $contentType = 'application/json')
{
parent::__construct($locationName);
$this->jsonContentType = $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)));
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
use GuzzleHttp\Command\CommandInterface;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;
/**
* Adds POST files to a request
*/
class MultiPartLocation extends AbstractLocation
{
/** @var string $contentType */
protected $contentType = 'multipart/form-data; boundary=';
/** @var array $formParamsData */
protected $multipartData = [];
/**
* Set the name of the location
*
* @param string $locationName
*/
public function __construct($locationName = 'multipart')
{
parent::__construct($locationName);
}
/**
* @param CommandInterface $command
* @param RequestInterface $request
* @param Parameter $param
* @return RequestInterface
*/
public function visit(
CommandInterface $command,
RequestInterface $request,
Parameter $param
) {
$this->multipartData[] = [
'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;
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
use GuzzleHttp\Command\CommandInterface;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\QuerySerializer\QuerySerializerInterface;
use GuzzleHttp\Command\Guzzle\QuerySerializer\Rfc3986Serializer;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;
/**
* Adds query string values to requests
*/
class QueryLocation extends AbstractLocation
{
/**
* @var QuerySerializerInterface
*/
private $querySerializer;
/**
* Set the name of the location
*
* @param string $locationName
* @param QuerySerializerInterface|null $querySerializer
*/
public function __construct($locationName = 'query', QuerySerializerInterface $querySerializer = null)
{
parent::__construct($locationName);
$this->querySerializer = $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;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
use GuzzleHttp\Command\CommandInterface;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\Guzzle\Parameter;
use Psr\Http\Message\RequestInterface;
/**
* Handles locations specified in a service description
*/
interface RequestLocationInterface
{
/**
* Visits a location for each top-level parameter
*
* @param CommandInterface $command Command being prepared
* @param RequestInterface $request Request being modified
* @param Parameter $param Parameter being visited
*
* @return RequestInterface Modified request
*/
public function visit(
CommandInterface $command,
RequestInterface $request,
Parameter $param
);
/**
* Called when all of the parameters of a command have been visited.
*
* @param CommandInterface $command Command being prepared
* @param RequestInterface $request Request being modified
* @param Operation $operation Operation being serialized
*
* @return RequestInterface Modified request
*/
public function after(
CommandInterface $command,
RequestInterface $request,
Operation $operation
);
}

View File

@@ -0,0 +1,328 @@
<?php
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
use GuzzleHttp\Command\CommandInterface;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;
/**
* Creates an XML document
*/
class XmlLocation extends AbstractLocation
{
/** @var \XMLWriter XML writer resource */
private $writer;
/** @var string Content-Type header added when XML is found */
private $contentType;
/** @var Parameter[] Buffered elements to write */
private $buffered = [];
/**
* @param string $locationName Name of the location
* @param string $contentType Set to a non-empty string to add a
* Content-Type header to a request if any XML content is added to the
* body. Pass an empty string to disable the addition of the header.
*/
public function __construct($locationName = 'xml', $contentType = 'application/xml')
{
parent::__construct($locationName);
$this->contentType = $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);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\ResultInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Class AbstractLocation
*
* @package GuzzleHttp\Command\Guzzle\ResponseLocation
*/
abstract class AbstractLocation implements ResponseLocationInterface
{
/** @var string $locationName */
protected $locationName;
/**
* Set the name of the location
*
* @param $locationName
*/
public function __construct($locationName)
{
$this->locationName = $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;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\ResultInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Extracts the body of a response into a result field
*/
class BodyLocation extends AbstractLocation
{
/**
* Set the name of the location
*
* @param string $locationName
*/
public function __construct($locationName = 'body')
{
parent::__construct($locationName);
}
/**
* @param ResultInterface $result
* @param ResponseInterface $response
* @param Parameter $param
* @return ResultInterface
*/
public function visit(
ResultInterface $result,
ResponseInterface $response,
Parameter $param
) {
$result[$param->getName()] = $param->filter($response->getBody());
return $result;
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\ResultInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Extracts headers from the response into a result fields
*/
class HeaderLocation extends AbstractLocation
{
/**
* Set the name of the location
*
* @param string $locationName
*/
public function __construct($locationName = 'header')
{
parent::__construct($locationName);
}
/**
* @param ResultInterface $result
* @param ResponseInterface $response
* @param Parameter $param
*
* @return ResultInterface
*/
public function visit(
ResultInterface $result,
ResponseInterface $response,
Parameter $param
) {
// Retrieving a single header by name
$name = $param->getName();
if ($header = $response->getHeader($param->getWireName())) {
if (is_array($header)) {
$header = array_shift($header);
}
$result[$name] = $param->filter($header);
}
return $result;
}
}

View File

@@ -0,0 +1,176 @@
<?php
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Result;
use GuzzleHttp\Command\ResultInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Extracts elements from a JSON document.
*/
class JsonLocation extends AbstractLocation
{
/** @var array The JSON document being visited */
private $json = [];
/**
* Set the name of the location
*
* @param string $locationName
*/
public function __construct($locationName = 'json')
{
parent::__construct($locationName);
}
/**
* @param \GuzzleHttp\Command\ResultInterface $result
* @param \Psr\Http\Message\ResponseInterface $response
* @param \GuzzleHttp\Command\Guzzle\Parameter $model
*
* @return \GuzzleHttp\Command\ResultInterface
*/
public function before(
ResultInterface $result,
ResponseInterface $response,
Parameter $model
) {
$body = (string) $response->getBody();
$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[$key]) ? $this->json[$key] : 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);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\ResultInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Extracts the reason phrase of a response into a result field
*/
class ReasonPhraseLocation extends AbstractLocation
{
/**
* Set the name of the location
*
* @param string $locationName
*/
public function __construct($locationName = 'reasonPhrase')
{
parent::__construct($locationName);
}
/**
* @param ResultInterface $result
* @param ResponseInterface $response
* @param Parameter $param
* @return ResultInterface
*/
public function visit(
ResultInterface $result,
ResponseInterface $response,
Parameter $param
) {
$result[$param->getName()] = $param->filter(
$response->getReasonPhrase()
);
return $result;
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\ResultInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Location visitor used to parse values out of a response into an associative
* array
*/
interface ResponseLocationInterface
{
/**
* Called before visiting all parameters. This can be used for seeding the
* result of a command with default data (e.g. populating with JSON data in
* the response then adding to the parsed data).
*
* @param ResultInterface $result Result being created
* @param ResponseInterface $response Response being visited
* @param Parameter $model Response model
*
* @return ResultInterface Modified result
*/
public function before(
ResultInterface $result,
ResponseInterface $response,
Parameter $model
);
/**
* Called after visiting all parameters
*
* @param ResultInterface $result Result being created
* @param ResponseInterface $response Response being visited
* @param Parameter $model Response model
*
* @return ResultInterface Modified result
*/
public function after(
ResultInterface $result,
ResponseInterface $response,
Parameter $model
);
/**
* Called once for each parameter being visited that matches the location
* type.
*
* @param ResultInterface $result Result being created
* @param ResponseInterface $response Response being visited
* @param Parameter $param Parameter being visited
*
* @return ResultInterface Modified result
*/
public function visit(
ResultInterface $result,
ResponseInterface $response,
Parameter $param
);
}

View File

@@ -0,0 +1,39 @@
<?php
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\ResultInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Extracts the status code of a response into a result field
*/
class StatusCodeLocation extends AbstractLocation
{
/**
* Set the name of the location
*
* @param string $locationName
*/
public function __construct($locationName = 'statusCode')
{
parent::__construct($locationName);
}
/**
* @param ResultInterface $result
* @param ResponseInterface $response
* @param Parameter $param
* @return ResultInterface
*/
public function visit(
ResultInterface $result,
ResponseInterface $response,
Parameter $param
) {
$result[$param->getName()] = $param->filter($response->getStatusCode());
return $result;
}
}

View File

@@ -0,0 +1,311 @@
<?php
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Result;
use GuzzleHttp\Command\ResultInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Extracts elements from an XML document
*/
class XmlLocation extends AbstractLocation
{
/** @var \SimpleXMLElement XML document being visited */
private $xml;
/**
* Set the name of the location
*
* @param string $locationName
*/
public function __construct($locationName = 'xml')
{
parent::__construct($locationName);
}
/**
* @param ResultInterface $result
* @param ResponseInterface $response
* @param Parameter $model
* @return ResultInterface
*/
public function before(
ResultInterface $result,
ResponseInterface $response,
Parameter $model
) {
$this->xml = 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. <collection><foo></foo><foo></foo></collection>)
$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;
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace GuzzleHttp\Command\Guzzle;
/**
* JSON Schema formatter class
*/
class SchemaFormatter
{
/**
* Format a value by a registered format name
*
* @param string $format Registered format used to format the value
* @param mixed $value Value being formatted
*
* @return mixed
*/
public function format($format, $value)
{
switch ($format) {
case 'date-time':
return $this->formatDateTime($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');
}
}

View File

@@ -0,0 +1,297 @@
<?php
namespace GuzzleHttp\Command\Guzzle;
use GuzzleHttp\Command\ToArrayInterface;
/**
* Default parameter validator
*/
class SchemaValidator
{
/**
* Whether or not integers are converted to strings when an integer is
* received for a string input
*
* @var bool
*/
protected $castIntegerToStringType;
/** @var array Errors encountered while validating */
protected $errors;
/**
* @param bool $castIntegerToStringType Set to true to convert integers
* into strings when a required type is a string and the input value is
* an integer. Defaults to true.
*/
public function __construct($castIntegerToStringType = true)
{
$this->castIntegerToStringType = $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);
}
}

View File

@@ -0,0 +1,164 @@
<?php
namespace GuzzleHttp\Command\Guzzle;
use GuzzleHttp\Command\CommandInterface;
use GuzzleHttp\Command\Guzzle\RequestLocation\BodyLocation;
use GuzzleHttp\Command\Guzzle\RequestLocation\FormParamLocation;
use GuzzleHttp\Command\Guzzle\RequestLocation\HeaderLocation;
use GuzzleHttp\Command\Guzzle\RequestLocation\JsonLocation;
use GuzzleHttp\Command\Guzzle\RequestLocation\MultiPartLocation;
use GuzzleHttp\Command\Guzzle\RequestLocation\QueryLocation;
use GuzzleHttp\Command\Guzzle\RequestLocation\RequestLocationInterface;
use GuzzleHttp\Command\Guzzle\RequestLocation\XmlLocation;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri;
use Psr\Http\Message\RequestInterface;
/**
* Serializes requests for a given command.
*/
class Serializer
{
/** @var RequestLocationInterface[] */
private $locations;
/** @var DescriptionInterface */
private $description;
/**
* @param DescriptionInterface $description
* @param RequestLocationInterface[] $requestLocations Extra request locations
*/
public function __construct(
DescriptionInterface $description,
array $requestLocations = []
) {
static $defaultRequestLocations;
if (!$defaultRequestLocations) {
$defaultRequestLocations = [
'body' => 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)
);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\Asset\Exception;
use GuzzleHttp\Command\Exception\CommandException;
/**
* Class CustomCommandException
*
* @package GuzzleHttp\Tests\Command\Guzzle\Asset\Exception
*/
class CustomCommandException extends CommandException
{
}

View File

@@ -0,0 +1,13 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\Asset\Exception;
use GuzzleHttp\Command\Exception\CommandException;
/**
* Class OtherCustomCommandException
*
* @package GuzzleHttp\Tests\Command\Guzzle\Asset\Exception
*/
class OtherCustomCommandException extends CommandException
{
}

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,184 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle;
use GuzzleHttp\Command\Guzzle\Description;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\SchemaFormatter;
/**
* @covers \GuzzleHttp\Command\Guzzle\Description
*/
class DescriptionTest extends \PHPUnit_Framework_TestCase
{
protected $operations;
public function setup()
{
$this->operations = [
'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');
}
}

View File

@@ -0,0 +1,386 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle;
use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\Command\CommandInterface;
use GuzzleHttp\Command\Guzzle\Description;
use GuzzleHttp\Command\Guzzle\DescriptionInterface;
use GuzzleHttp\Command\Guzzle\GuzzleClient;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\ServiceClientInterface;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Tests\Command\Guzzle\Asset\Exception\CustomCommandException;
use GuzzleHttp\Tests\Command\Guzzle\Asset\Exception\OtherCustomCommandException;
use Predis\Response\ResponseInterface;
/**
* @covers \GuzzleHttp\Command\Guzzle\Deserializer
*/
class DeserializerTest extends \PHPUnit_Framework_TestCase
{
/** @var ServiceClientInterface|\PHPUnit_Framework_MockObject_MockObject */
private $serviceClient;
/** @var CommandInterface|\PHPUnit_Framework_MockObject_MockObject */
private $command;
public function setUp()
{
$this->serviceClient = $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']);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,112 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\Handler;
use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\Command\Guzzle\Description;
use GuzzleHttp\Command\Guzzle\GuzzleClient;
/**
* @covers \GuzzleHttp\Command\Guzzle\Handler\ValidatedDescriptionHandler
*/
class ValidatedDescriptionHandlerTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \GuzzleHttp\Command\Exception\CommandException
* @expectedExceptionMessage Validation errors: [bar] is a required string
*/
public function testValidates()
{
$description = new Description([
'operations' => [
'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
}
}

View File

@@ -0,0 +1,227 @@
<?php
namespace Guzzle\Tests\Service\Description;
use GuzzleHttp\Command\Guzzle\Description;
use GuzzleHttp\Command\Guzzle\Operation;
/**
* @covers \GuzzleHttp\Command\Guzzle\Operation
*/
class OperationTest extends \PHPUnit_Framework_TestCase
{
public static function strtoupper($string)
{
return strtoupper($string);
}
public function testOperationIsDataObject()
{
$c = new Operation([
'name' => '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());
}
}

View File

@@ -0,0 +1,378 @@
<?php
namespace Guzzle\Tests\Service\Description;
use GuzzleHttp\Command\Guzzle\Description;
use GuzzleHttp\Command\Guzzle\Parameter;
/**
* @covers \GuzzleHttp\Command\Guzzle\Parameter
*/
class ParameterTest extends \PHPUnit_Framework_TestCase
{
protected $data = [
'name' => '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'));
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\QuerySerializer;
use GuzzleHttp\Command\Guzzle\QuerySerializer\Rfc3986Serializer;
class Rfc3986SerializerTest extends \PHPUnit_Framework_TestCase
{
public function queryProvider()
{
return [
[['foo' => '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));
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\RequestLocation;
use GuzzleHttp\Command\Command;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\RequestLocation\BodyLocation;
use GuzzleHttp\Psr7\Request;
/**
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\BodyLocation
*/
class BodyLocationTest extends \PHPUnit_Framework_TestCase
{
/**
* @group RequestLocation
*/
public function testVisitsLocation()
{
$location = new BodyLocation('body');
$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', $request->getBody()->getContents());
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\RequestLocation;
use GuzzleHttp\Command\Command;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\RequestLocation\FormParamLocation;
use GuzzleHttp\Command\Guzzle\RequestLocation\PostFieldLocation;
use GuzzleHttp\Psr7\Request;
/**
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\FormParamLocation
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\AbstractLocation
*/
class FormParamLocationTest extends \PHPUnit_Framework_TestCase
{
/**
* @group RequestLocation
*/
public function testVisitsLocation()
{
$location = new FormParamLocation();
$command = new Command('foo', ['foo' => '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());
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\RequestLocation;
use GuzzleHttp\Command\Command;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\RequestLocation\HeaderLocation;
use GuzzleHttp\Psr7\Request;
/**
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\HeaderLocation
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\AbstractLocation
*/
class HeaderLocationTest extends \PHPUnit_Framework_TestCase
{
/**
* @group RequestLocation
*/
public function testVisitsLocation()
{
$location = new HeaderLocation('header');
$command = new Command('foo', ['foo' => '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);
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\RequestLocation;
use GuzzleHttp\Command\Command;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\RequestLocation\JsonLocation;
use GuzzleHttp\Psr7\Request;
/**
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\JsonLocation
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\AbstractLocation
*/
class JsonLocationTest extends \PHPUnit_Framework_TestCase
{
/**
* @group RequestLocation
*/
public function testVisitsLocation()
{
$location = new JsonLocation('json');
$command = new Command('foo', ['foo' => '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'));
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\RequestLocation;
use GuzzleHttp\Command\Command;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\RequestLocation\MultiPartLocation;
use GuzzleHttp\Command\Guzzle\RequestLocation\PostFileLocation;
use GuzzleHttp\Psr7\Request;
/**
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\MultiPartLocation
*/
class MultiPartLocationTest extends \PHPUnit_Framework_TestCase
{
/**
* @group RequestLocation
*/
public function testVisitsLocation()
{
$location = new MultiPartLocation();
$command = new Command('foo', ['foo' => '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'));
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\RequestLocation;
use GuzzleHttp\Command\Command;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\RequestLocation\QueryLocation;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Request;
/**
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\QueryLocation
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\AbstractLocation
*/
class QueryLocationTest extends \PHPUnit_Framework_TestCase
{
public function queryProvider()
{
return [
[['foo' => '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']);
}
}

View File

@@ -0,0 +1,525 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\RequestLocation;
use GuzzleHttp\Client;
use GuzzleHttp\Command\Command;
use GuzzleHttp\Command\Guzzle\Description;
use GuzzleHttp\Command\Guzzle\GuzzleClient;
use GuzzleHttp\Command\Guzzle\Operation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\RequestLocation\XmlLocation;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
/**
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\XmlLocation
*/
class XmlLocationTest extends \PHPUnit_Framework_TestCase
{
/**
* @group RequestLocation
*/
public function testVisitsLocation()
{
$location = new XmlLocation();
$command = new Command('foo', ['foo' => '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('<?xml version="1.0"?>' . "\n"
. '<Request><foo>bar</foo><bar>test</bar></Request>' . "\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('<?xml version="1.0"?>' . "\n"
. '<Request/>' . "\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('<?xml version="1.0"?>' . "\n"
. '<Request><foo>bar</foo><foo>bar</foo><bam>boo</bam></Request>' . "\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('<?xml version="1.0" encoding="UTF-8"?>' . "\n"
. '<Request><foo>bar</foo></Request>' . "\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'
],
'<test xmlns="http://foo.com"><Foo>test</Foo><Baz>bar</Baz></test>'
],
// 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 Foo="test"/>'
],
// 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]],
'<Request><Foo>test</Foo><Baz><Bar>1</Bar><Bar>2</Bar></Baz></Request>'
],
// 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'
]
],
'<Request><Foo>test</Foo><Baz><Bar>abc</Bar><Bam>foo</Bam></Baz></Request>'
],
// 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'
]
]],
'<Request><Baz><Bar><A>1</A><B>2</B></Bar><Bar><A>3</A><B>4</B></Bar></Baz></Request>'
],
// 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'
]
],
'<Request><Foo>test</Foo><Baz Bar="abc"><Bam>foo</Bam></Baz></Request>'
],
// 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'
]
],
'<Request><Foo>test</Foo><Baz Bar="abc"><Bam>foo</Bam></Baz></Request>'
],
// Add values with custom namespaces
[
[
'parameters' => [
'Foo' => [
'location' => 'xml',
'type' => 'string',
'data' => [
'xmlNamespace' => 'http://foo.com'
]
]
]
],
['Foo' => 'test'],
'<Request><Foo xmlns="http://foo.com">test</Foo></Request>'
],
// 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'
]],
'<Request><Wrap xsi:baz="test" xmlns:xsi="http://foo.com"/></Request>'
],
// 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'
]],
'<Request><Wrap><xsi:Foo xmlns:xsi="http://foobar.com">test</xsi:Foo></Wrap></Request>'
],
[
[
'parameters' => [
'Foo' => [
'location' => 'xml',
'type' => 'string',
'data' => [
'xmlNamespace' => 'http://foo.com'
]
]
]
],
['Foo' => '<h1>This is a title</h1>'],
'<Request><Foo xmlns="http://foo.com"><![CDATA[<h1>This is a title</h1>]]></Foo></Request>'
],
// 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']
],
'<Request><Bar><A>1</A><B>2</B></Bar><Bar><A>3</A><B>4</B></Bar><Boo>test</Boo><Boo>123</Boo></Request>'
],
// 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']
]
]
],
'<Request><Delete><Item><A>1</A><B>2</B></Item><Item><A>3</A><B>4</B></Item></Delete></Request>'
],
// 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 Baz="bar"><Foo>test</Foo></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", "<?xml version=\"1.0\"?>"], '', (string) $request->getBody());
$this->assertEquals($xml, $body);
}
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\ResponseLocation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\ResponseLocation\BodyLocation;
use GuzzleHttp\Command\Result;
use GuzzleHttp\Psr7\Response;
/**
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\BodyLocation
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\AbstractLocation
*/
class BodyLocationTest extends \PHPUnit_Framework_TestCase
{
/**
* @group ResponseLocation
*/
public function testVisitsLocation()
{
$location = new BodyLocation();
$parameter = new Parameter([
'name' => 'val',
'filters' => ['strtoupper']
]);
$response = new Response(200, [], 'foo');
$result = new Result();
$result = $location->visit($result, $response, $parameter);
$this->assertEquals('FOO', $result['val']);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\ResponseLocation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\ResponseLocation\HeaderLocation;
use GuzzleHttp\Command\Result;
use GuzzleHttp\Psr7\Response;
/**
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\HeaderLocation
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\AbstractLocation
*/
class HeaderLocationTest extends \PHPUnit_Framework_TestCase
{
/**
* @group ResponseLocation
*/
public function testVisitsLocation()
{
$location = new HeaderLocation();
$parameter = new Parameter([
'name' => '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']);
}
}

View File

@@ -0,0 +1,581 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\ResponseLocation;
use GuzzleHttp\Client;
use GuzzleHttp\Command\Guzzle\Description;
use GuzzleHttp\Command\Guzzle\GuzzleClient;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\ResponseLocation\JsonLocation;
use GuzzleHttp\Command\Result;
use GuzzleHttp\Command\ResultInterface;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\Psr7\Response;
/**
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\JsonLocation
* @covers \GuzzleHttp\Command\Guzzle\Deserializer
*/
class JsonLocationTest extends \PHPUnit_Framework_TestCase
{
/**
* @group ResponseLocation
*/
public function testVisitsLocation()
{
$location = new JsonLocation();
$parameter = new Parameter([
'name' => '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
* @param $name
* @param $expected
*/
public function testVisitsWiredArray()
{
$json = ['car_models' => ['ferrari', 'aston martin']];
$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' => [
'getCars' => [
'uri' => 'http://httpbin.org',
'httpMethod' => 'GET',
'responseModel' => 'Cars'
]
],
'models' => [
'Cars' => [
'type' => 'object',
'location' => 'json',
'properties' => [
'cars' => [
'type' => 'array',
'sentAs' => 'car_models',
'items' => [
'type' => 'object',
]
]
],
]
]
]);
$guzzle = new GuzzleClient($guzzle, $description);
$result = $guzzle->getCars();
$this->assertEquals(['cars' => ['ferrari', 'aston martin']], $result->toArray());
}
/**
* @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());
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\ResponseLocation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\ResponseLocation\ReasonPhraseLocation;
use GuzzleHttp\Command\Result;
use GuzzleHttp\Psr7\Response;
/**
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\ReasonPhraseLocation
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\AbstractLocation
*/
class ReasonPhraseLocationTest extends \PHPUnit_Framework_TestCase
{
/**
* @group ResponseLocation
*/
public function testVisitsLocation()
{
$location = new ReasonPhraseLocation();
$parameter = new Parameter([
'name' => 'val',
'filters' => ['strtolower']
]);
$response = new Response(200);
$result = new Result();
$result = $location->visit($result, $response, $parameter);
$this->assertEquals('ok', $result['val']);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\ResponseLocation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\ResponseLocation\StatusCodeLocation;
use GuzzleHttp\Command\Result;
use GuzzleHttp\Psr7\Response;
/**
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\StatusCodeLocation
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\AbstractLocation
*/
class StatusCodeLocationTest extends \PHPUnit_Framework_TestCase
{
/**
* @group ResponseLocation
*/
public function testVisitsLocation()
{
$location = new StatusCodeLocation();
$parameter = new Parameter(['name' => 'val']);
$response = new Response(200);
$result = new Result();
$result = $location->visit($result, $response, $parameter);
$this->assertEquals(200, $result['val']);
}
}

View File

@@ -0,0 +1,795 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle\ResponseLocation;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\ResponseLocation\XmlLocation;
use GuzzleHttp\Command\Result;
use GuzzleHttp\Psr7\Response;
/**
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\XmlLocation
*/
class XmlLocationTest extends \PHPUnit_Framework_TestCase
{
/**
* @group ResponseLocation
*/
public function testVisitsLocation()
{
$location = new XmlLocation();
$parameter = new Parameter([
'name' => 'val',
'sentAs' => 'vim',
'filters' => ['strtoupper']
]);
$model = new Parameter();
$response = new Response(200, [], \GuzzleHttp\Psr7\stream_for('<w><vim>bar</vim></w>'));
$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('<w><vim>bar</vim></w>'));
$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 = '<xml><foo>bar</foo><foo>baz</foo></xml>';
$this->xmlTest($param, $xml, ['foo' => ['bar', 'baz']]);
$this->xmlTest($param, '<xml><foo>bar</foo></xml>', ['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, '<Test><Items><Item><Bar>1</Bar></Item><Item><Bar>2</Bar></Item></Items></Test>', [
'Items' => [
['Bar' => 1],
['Bar' => 2],
],
]],
[$param, '<Test><Items><Item><Bar>1</Bar></Item></Items></Test>', [
'Items' => [
['Bar' => 1],
]
]],
[$param, '<Test><Items /></Test>', [
'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 = '
<xml>
<instancesSet>
<item>
<instanceId>i-3ea74257</instanceId>
<currentState>
<code>32</code>
<name>shutting-down</name>
</currentState>
<previousState>
<code>16</code>
<name>running</name>
</previousState>
</item>
</instancesSet>
</xml>
';
$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 = '
<wrap>
<RunningQueues>
<item queue_id="q-3ea74257">
<CurrentState code="32" name="processing" />
<PreviousState code="16" name="wait" />
</item>
</RunningQueues>
</wrap>';
$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 = '<xml><Foo><Bar></Bar></Foo></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 = '
<xml>
<foo>
<bar>15</bar>
<unknown>discard me</unknown>
</foo>
</xml>
';
$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 = '
<xml>
<foo>
<baz>15</baz>
<unknown>discard me</unknown>
</foo>
</xml>
';
$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 = '
<xml>
<foo>
<baz>15</baz>
<additional>include me</additional>
<nestedNoAdditional>
<id>15</id>
<unknown>discard me</unknown>
</nestedNoAdditional>
<nestedWithAdditional>
<id>15</id>
<additional>include me</additional>
</nestedWithAdditional>
<nestedWithAdditionalSchema>
<arrayA>
<item>1</item>
<item>2</item>
<item>3</item>
</arrayA>
<arrayB>
<item>A</item>
<item>B</item>
<item>C</item>
</arrayB>
</nestedWithAdditionalSchema>
</foo>
</xml>
';
$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 = '
<xml>
<foo>
<baz>15</baz>
<baz>25</baz>
<bar>hi</bar>
<bam>test</bam>
<bam attr="hi" />
</foo>
</xml>
';
$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 = '
<xml>
<nstest xmlns:isbn="urn:ISBN:0-395-36341-6" xmlns:abstract="urn:my.org:abstract">
<item>
<id>101</id>
<isbn:number>1568491379</isbn:number>
<abstract:meta>
<foo>10</foo>
<bar abstract:attribute="foo"></bar>
</abstract:meta>
<abstract:gamma>
<foo>bar</foo>
</abstract:gamma>
</item>
<item>
<id>102</id>
<isbn:number>1568491999</isbn:number>
<abstract:meta>
<foo>20</foo>
<bar abstract:attribute="bar"></bar>
</abstract:meta>
<abstract:gamma>
<foo>baz</foo>
</abstract:gamma>
</item>
</nstest>
</xml>
';
$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 = '
<xml>
<nstest xmlns:isbn="urn:ISBN:0-395-36341-6" xmlns:abstract="urn:my.org:abstract">
<item>
<id>101</id>
<isbn:number>1568491379</isbn:number>
<abstract:meta>
<foo>10</foo>
<bar>baz</bar>
</abstract:meta>
</item>
<item>
<id>102</id>
<isbn:number>1568491999</isbn:number>
<abstract:meta>
<foo>20</foo>
<bar>foo</bar>
</abstract:meta>
</item>
</nstest>
</xml>
';
$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 = '
<xml>
<nstest xmlns:isbn="urn:ISBN:0-395-36341-6">
<isbn:number>1568491379</isbn:number>
<isbn:number>1568491999</isbn:number>
<isbn:number>1568492999</isbn:number>
</nstest>
</xml>
';
$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 = '
<xml>
<nstest xmlns:isbn="urn:ISBN:0-395-36341-6">
<isbn:number>1568491379</isbn:number>
<isbn:number>1568491999</isbn:number>
<isbn:number>1568492999</isbn:number>
</nstest>
</xml>
';
$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());
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle;
use GuzzleHttp\Command\Guzzle\SchemaFormatter;
/**
* @covers \GuzzleHttp\Command\Guzzle\SchemaFormatter
*/
class SchemaFormatterTest extends \PHPUnit_Framework_TestCase
{
public function dateTimeProvider()
{
$dateUtc = 'October 13, 2012 16:15:46 UTC';
$dateOffset = 'October 13, 2012 10:15:46 -06:00';
$expectedDateTime = '2012-10-13T16:15:46Z';
return [
['foo', 'does-not-exist', 'foo'],
[$dateUtc, 'date-time', $expectedDateTime],
[$dateUtc, 'date-time-http', 'Sat, 13 Oct 2012 16:15:46 GMT'],
[$dateUtc, 'date', '2012-10-13'],
[$dateUtc, 'timestamp', strtotime($dateUtc)],
[new \DateTime($dateUtc), 'timestamp', strtotime($dateUtc)],
[$dateUtc, 'time', '16:15:46'],
[strtotime($dateUtc), 'time', '16:15:46'],
[strtotime($dateUtc), 'timestamp', strtotime($dateUtc)],
['true', 'boolean-string', 'true'],
[true, 'boolean-string', 'true'],
['false', 'boolean-string', 'false'],
[false, 'boolean-string', 'false'],
['1350144946', 'date-time', $expectedDateTime],
[1350144946, 'date-time', $expectedDateTime],
[$dateOffset, 'date-time', $expectedDateTime],
];
}
/**
* @dataProvider dateTimeProvider
*/
public function testFilters($value, $format, $result)
{
$this->assertEquals($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);
}
}

View File

@@ -0,0 +1,330 @@
<?php
namespace Guzzle\Tests\Service\Description;
use GuzzleHttp\Command\Guzzle\Parameter;
use GuzzleHttp\Command\Guzzle\SchemaValidator;
use GuzzleHttp\Command\ToArrayInterface;
/**
* @covers \GuzzleHttp\Command\Guzzle\SchemaValidator
*/
class SchemaValidatorTest extends \PHPUnit_Framework_TestCase
{
/** @var SchemaValidator */
protected $validator;
public function setUp()
{
$this->validator = 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,
],
],
],
]);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace GuzzleHttp\Tests\Command\Guzzle;
use GuzzleHttp\Command\Command;
use GuzzleHttp\Command\Guzzle\Description;
use GuzzleHttp\Command\Guzzle\Serializer;
use GuzzleHttp\Psr7\Request;
/**
* @covers \GuzzleHttp\Command\Guzzle\Serializer
*/
class SerializerTest extends \PHPUnit_Framework_TestCase
{
public function testAllowsUriTemplates()
{
$description = new Description([
'baseUri' => '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());
}
}