[
  {
    "path": ".gitignore",
    "content": "/vendor\n/composer.lock\n/tests/cache\n/build\n/.php_cs.cache\n\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\n\nlanguage: php\nphp:\n  - 7.1\n  - 7.2\n  - 7.3\n  - 7.4\n\nenv:\n  - SF_CACHE=3\n  - SF_CACHE=4\n  - SF_CACHE=4.3\n  - SF_CACHE=5\n\njobs:\n  exclude:\n    - php: 7.1\n      env:\n        - SF_CACHE=5\n\n# remove composer.lock (it should not be published)\nbefore_script:\n  - composer install --no-interaction -o\n  - if [ \"$SF_CACHE\" = \"3\" ]; then composer require symfony/cache:^3.3; fi;\n  - if [ \"$SF_CACHE\" = \"4\" ]; then composer require symfony/cache:\">=4.0 <4.3\"; fi;\n  - if [ \"$SF_CACHE\" = \"4.3\" ]; then composer require symfony/cache:^4.3; fi;\n  - if [ \"$SF_CACHE\" = \"5\" ]; then composer require symfony/cache:5.0; fi;\n\nscript:\n  - mkdir -p build/logs\n  - vendor/bin/phpunit\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "Change Log\n==========\n\nThis document keeps track of important changes between releases of the library.\n\n3.2.0\n-----\n\n* ADDED: Exception handling on response body conversion (Should not be a\nBC break unless applications are checking for specific exceptions)\n\n3.1.0\n-----\n\n* ADDED: Ability to set custom cache adapter\n* CHANGED: Default cache to use php file cache over filesystem cache\n\n3.0.3\n-----\n\n* FIXED: Issue with default parameters and nulls\n\n3.0.2\n-----\n\n* ADDED: Symfony 4 support\n\n3.0.1\n-----\n\n* FIXED: Issue with query encoding\n\n3.0.0\n-----\n\n* See [upgrade guide](docs/upgrade_2_3.md)\n\n2.9.0\n-----\n\n* Dependency updates\n* Added request/response to return event\n\n\n2.8.3\n-----\n\n* Fixed issue with AfterSendEvent\n\n2.8.2\n-----\n\n* Fixed issue with events\n\n2.8.1\n-----\n\n* Updating dependencies\n\n\n2.8.0\n-----\n\n* Added option to return complete response instead of body\n* Removed logging of which events occurred\n* Dependency updates\n* Added multipart support\n\n2.7.0\n-----\n\n* Updated dynamo library\n\n2.6.1\n-----\n\n* Fixed issue with urlencoding parameters\n\n2.6.0\n-----\n\n* Changing client adapter to only accept request interface\n* Updating client dependency\n\n2.5.5\n-----\n\n* Updating Dynamo library\n* Updating minimum dependencies\n* Added ability to set a client adapter\n\n2.5.4\n-----\n\n* Event updates: adding request\n\n2.5.3\n-----\n\n* Fixed bug AfterSendEvent\n\n2.5.2\n-----\n\n* Fixed bug in event system\n\n2.5.1\n-----\n\n* Fixed issue with urlencode\n\n2.5.0\n-----\n\n* Added logging\n* Fixed Dyanmo minimum version dependency\n\n2.4.0\n-----\n\n* Added support for async requests\n* Using http_build_query() to build all query strings\n* Allowing modification of return data through event\n\n2.3.0\n-----\n\n* Fixed events to dispatch objects that can be modified\n\n2.2.0\n-----\n\n* Allowed body object to be form encoded\n* Added support for serializing \\JsonSerializable\n* Fixed issue with booleans getting cast to ints\n* Fixed issue with serializing null body objects\n* Improved generated code to exclude serialization contexts if not applicable\n\n2.1.0\n-----\n\n* Added an event dispatcher\n* Added a library specific exception\n\n2.0.0\n-----\n\n* Removed composer wrapper\n* Removed guzzle dependency (added support for either v5 or v6)\n* Abstracted class generation into tebru/dynamo\n* Fixed issue with query map\n* Fixed issue with interface inheritance \n\n1.2.1\n-----\n\n* Upgraded twig to resolve security vulnerability\n\n1.2.0\n-----\n\n* Added annotation for JMS serialization contexts\n\n\n1.1.0\n-----\n\n* Added support for JMS serialization contexts\n\n1.0.0\n-----\n\n* Mirrored functionality of Square's Retrofit library.\n* PSR autoloading of generated classes\n* Created binary tool for managing cache.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Contributing Guidelines\n=======================\n\nBefore contributing code, please be aware of the following:\n\nCode Style\n----------\n\nThis project uses the [PSR-2] code style. Please follow these formatting \nstandards where possible.\n\n[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md\n"
  },
  {
    "path": "CONTRIBUTORS.md",
    "content": "Contributors\n============\n\nThis is a credits file of people that have contributed Retrofit-PHP Library.\n\n * Nate Brunette\n   * Email: n@tebru.net\n\n * Maxwell Vandervelde\n   * Email: Max@MaxVandervelde.com\n   * PGP: `4096R/2B95C590 C21D 81E7 1F58 6C96 955F 5141 444C 4178 2B95 C590`\n\n * Matthew Loberg\n   * Email: loberg.matt@gmail.com\n   \n * Edward Pfremmer\n   * Email: edward.pfremmer@nerdery.com\n   \n * Tony Nelson\n   * Email: tonynelson19@gmail.com\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Project License\n===============\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 Nate Brunette\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "Retrofit PHP\n============\n\n[![Build Status](https://travis-ci.org/tebru/retrofit-php.svg?branch=master)](https://travis-ci.org/tebru/retrofit-php)\n[![Code Coverage](https://scrutinizer-ci.com/g/tebru/retrofit-php/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/tebru/retrofit-php/?branch=master)\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/tebru/retrofit-php/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/tebru/retrofit-php/?branch=master)\n[![SensioLabsInsight](https://insight.sensiolabs.com/projects/d2188bf8-8248-4df6-8bc5-8150fc0b8898/mini.png)](https://insight.sensiolabs.com/projects/d2188bf8-8248-4df6-8bc5-8150fc0b8898)\n\nRetrofit is a type-safe REST client. It is blatantly stolen from\n[square/retrofit](https://github.com/square/retrofit) and implemented in\nPHP.\n\n❗UPGRADE NOTICE❗\n----------------\n\n**Version 3 introduces many breaking changes. Please review the\n[upgrade guide](docs/upgrade_2_3.md) before upgrading.**\n\nOverview\n--------\n\n*The following is for version 3, please check out the corresponding tag\nfor version 2 documentation*\n\nRetrofit allows you to define your REST API with a simple interface. The\nfollow example will attempt to display a typical use-case, but requires\ntwo additional libraries. The first uses Guzzle to make http requests as\nRetrofit does not ship with any default way to make network requests. The\nsecond uses a serializer (Gson) to hook into Retrofit's Converter\nfunctionality. This allows for automatic serialization of request bodies\nand deserialization of response bodies.\n\n```php\ninterface GitHubService\n{\n    /**\n     * @GET(\"/users/{user}/list\")\n     * @Path(\"user\")\n     * @ResponseBody(\"App\\GithubService\\ListRepo\")\n     * @ErrorBody(\"App\\GitHubService\\ApiError\")\n     */\n    public function listRepos(string $user): Call;\n}\n```\n\nAnnotations are used to configure the endpoint.\nThen, the `Retrofit` class generates a working implementation of the\nservice interface.\n\n```php\n$retrofit = Retrofit::builder()\n    ->setBaseUrl('https://api.github.com')\n    ->setHttpClient(new Guzzle6HttpClient(new Client())) // requires a separate library\n    ->addConverterFactory(new GsonConverterFactory(Gson::builder()->build())) // requies a separate library\n    ->build();\n    \n$gitHubService = $retrofit->create(GitHubService::class);\n```\n\nOur newly created service is capable of making GET requests to\n/users/{user}/list, which returns a `Call` object.\n\n```php\n$call = $gitHubService->listRepos('octocat');\n```\n\nThe `Call` object is then used to execute the request synchronously\nor asynchronously, returning a response.\n\n```php\n$response = $call->execute();\n\n// or\n\n$call->enqueue(\n    function(Response $response) { }, // response callback (optional)\n    function(Throwable $throwable) { } // error callback (optional)\n);\n$call->wait();\n```\n\nYou can then check to see if the request was successful and get the\ndeserialized response body.\n\n```php\nif (!$response->isSuccessful()) {\n    throw new ApiException($response->errorBody());\n}\n\n$responseBody = $response->body();\n```\n\n*Usage examples are referenced from Square's documentation*\n\n\nInstallation & Usage\n--------------------\n\n*Retrofit 3 requires PHP 7.1*\n\n```bash\ncomposer require tebru/retrofit-php\n```\n\nPlease make sure you also install an http client.\n\n```bash\ncomposer require tebru/retrofit-php-http-guzzle6\n```\n\nInstall a converter to handle more advanced request and response body\nconversions.\n\n```bash\ncomposer require tebru/retrofit-php-converter-gson\n```\n\n### Documentation \n\n- [Installation](docs/installation.md)\n- [Getting Started](docs/usage.md)\n- [Advanced Usage](docs/advanced_usage.md)\n- [Annotation Reference](docs/annotations.md)\n\nLicense\n-------\n\nThis project is licensed under the MIT license. Please see the `LICENSE` file\nfor more information.\n"
  },
  {
    "path": "bin/retrofit",
    "content": "#!/usr/bin/env php\n\n<?php\n\nuse Doctrine\\Common\\Annotations\\AnnotationRegistry;\nuse Tebru\\Retrofit\\Command\\CompileCommand;\nuse Symfony\\Component\\Console\\Application;\n\n$dirs = [__DIR__ . '/../vendor', __DIR__ . '/../../..'];\nforeach ($dirs as $dir) {\n    $file = $dir . '/autoload.php';\n    if (file_exists($file)) {\n        $vendorDir = $dir;\n        require $file;\n    }\n}\n\n$loader = require $vendorDir . '/autoload.php';\nAnnotationRegistry::registerLoader([$loader, 'loadClass']);\n\n$application = new Application();\n$application->add(new CompileCommand);\n$application->run();\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"tebru/retrofit-php\",\n    \"description\": \"Retrofit for PHP - A type-safe PHP REST client.\",\n    \"require\": {\n        \"php\": \">= 7.1\",\n        \"guzzlehttp/psr7\": \"^1.0\",\n        \"nikic/php-parser\": \"~3.0.6|^4.0\",\n        \"symfony/cache\": \"^3.3|^4.0|^5.0\",\n        \"symfony/console\": \"^3.0|^4.0|^5.0\",\n        \"tebru/doctrine-annotation-reader\": \"^0.3.0\",\n        \"tebru/php-type\": \"^0.1.1\"\n    },\n    \"require-dev\": {\n        \"phpunit/phpunit\": \"^7.3\",\n        \"mikey179/vfsstream\": \"^1.6\"\n    },\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Nate Brunette\",\n            \"email\": \"n@tebru.net\"\n        }\n    ],\n    \"autoload\": {\n        \"psr-4\": {\n            \"Tebru\\\\Retrofit\\\\\": \"src/\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"Tebru\\\\Retrofit\\\\Test\\\\\": \"tests/\"\n        }\n    },\n    \"suggest\": {\n        \"guzzlehttp/guzzle\": \"Required to make requests\"\n    },\n    \"bin\": [\"bin/retrofit\"]\n}\n"
  },
  {
    "path": "docs/advanced_usage.md",
    "content": "Advanced Usage\n==============\n\nCaching\n-------\n\nBy default, Retrofit doesn't do any filesystem caching. In production,\nyou should enable caching to increase performance.\n\n```php\nRetrofit::builder()\n    ->setCacheDir(__DIR__.'/cache')\n    ->enableCache();\n```\n\nCustom Converters\n-----------------\n\nBy default, Retrofit can convert any type to a string, but only handles\nPSR-7 `StreamInterface` for request and response bodies. Use a custom\nconverter to allow handling different types.\n\n```php\nRetrofit::builder()\n    ->addConverterFactory(new CustomConverterFactory());\n```\n\nThis should implement `Tebru\\Retrofit\\ConverterFactory` with three\nmethods to convert to string, to a stream for a request body, or from a\nstream for a response body. Return null from any of them will cause\nRetrofit to skip that converter and move on to the next one.\n\nEach method will receive a `TypeToken`, which you can use to determine\nthe type of the parameter you're converting.\n\nCurrently only a Gson converter exists to handle more complex\nconversions.\n\n```bash\ncomposer require tebru/retrofit-php-converter-gson\n```\n\n\nCustom Http Client\n------------------\n\nRetrofit doesn't provide any way to make http requests out of the box.\n\nCurrently, Guzzle 6 is the only supported library.\n\n```bash\ncomposer require tebru/retrofit-php-http-guzzle6\n```\n\n\n```php\nRetrofit::builder()\n    ->setHttpClient(new Guzzle6HttpClient(new Client()));\n```\n\nThis implements `Tebru\\Retrofit\\HttpClient` which has methods to make\nrequests synchronously and asynchronously using PSR-7 request objects.\n\nCustom Call Adapters\n--------------------\n\nThe default call adapter doesn't modify the Call at all. Implement a\n`Tebru\\Retrofit\\CallAdapter` and `Tebru\\Retrofit\\CallAdapterFactory`\nif you want to change the return type from service methods.\n\nThis supporting library doesn't exist yet, but if you wanted to use\nRetrofit with Rx-PHP, you could do so like this.\n\n```php\n/**\n * @GET(\"/\")\n */\npublic function test(): Observable;\n```\n\n```php\nRetrofit::builder()\n    ->addCallAdapterFactory(new RxCallAdapterFactory());\n```\n\n```php\ninterface CallAdapterFactory\n{\n    public function supports(TypeToken $type): bool\n    {\n        return $type->isA(Observable::class);\n    }\n\n    public function create(TypeToken $type): CallAdapter\n    {\n        return new RxCallAdapter();\n    }\n}\n```\n\nThe `RxCallAdapter` would then wrap the call in an observable and return\nthat instead of the normal Call.\n\nCustom Proxy\n------------\n\nYou may find it necessary to alter the default behavior of sending http\nrequests. This could be for testing purposes, or perhaps because an\napi is not built yet and you need to fetch the data somewhere else.\n\nThis can be handled by implementing `Tebru\\Retrofit\\ProxyFactory` and\n`\\Tebru\\Retrofit\\Proxy`.\n\n```php\nRetrofit::builder()\n    ->addProxyFactory(new CustomProxyFactory());\n```\n\nYour proxy should implement the service interface. If you need access to\nthe default proxy factory, you can implement\n`\\Tebru\\Retrofit\\DefaultProxyFactoryAware` which will tell Retrofit to\nset the default proxy to your proxy. This is useful if you only need\nto override specific methods and want to delegate the rest to the default.\n"
  },
  {
    "path": "docs/annotations.md",
    "content": "Annotation Reference\n====================\n\nA simple example will be provided to show usage for each annotation,\nbut may not be a completely functioning snippet. All usages may not\nhave an example.\n\n- [Body](#body)\n- [DELETE](#delete)\n- [ErrorBody](#errorbody)\n- [Field](#field)\n- [FieldMap](#fieldmap)\n- [GET](#get)\n- [HEAD](#head)\n- [Header](#header)\n- [HeaderMap](#headermap)\n- [Headers](#headers)\n- [OPTIONS](#options)\n- [Part](#part)\n- [PartMap](#partmap)\n- [PATCH](#patch)\n- [Path](#path)\n- [POST](#post)\n- [PUT](#put)\n- [Query](#query)\n- [QueryMap](#querymap)\n- [QueryName](#queryname)\n- [REQUEST](#request)\n- [ResponseBody](#responsebody)\n- [Url](#url)\n\nBody\n----\n\nDefines a json body for the HTTP request.\n\nThis annotation sets the content type of the request to\n`application/json`. By default, the parameter must be a PSR-7\n`StreamInterface`, but adding a converter will allow additional\ntypes to be provided and converted to json.\n\n```php\n/**\n * @Body(\"myBody\")\n */\npublic function example(Mybody $myBody): Call;\n```\n\nDELETE\n------\n\nPerforms a DELETE request to the provided path.\n\n```php\n/**\n * @DELETE(\"/foo?q=test\")\n */\npublic function example(): Call;\n```\n\nErrorBody\n---------\n\nDefines the type the response body should be converted to on errors.\n\nErrors are defined as any http status code outside of 200-300. By\ndefault, only `StreamInterface` is supported, but a converter\nwill extend the supported types.\n\n```php\n/**\n * @ErrorBody(\"App\\MyClass\")\n */\npublic function example(): Call;\n```\n\nField\n-----\n\nAdds values to a form encoded body.\n\nThis annotation sets the request content type to\n`application/x-www-form-urlencoded`. The value of this annotation\nis both the name of the field and the name of the method parameter.\nThis can be overridden using the `var` key in the annotation. The\nprovided argument will be converted to a string before getting applied\nto the request. Passing in an array will apply each value to the field\nname. The `encoded` key will specify if the data is already encoded,\nand therefore shouldn't be re-encoded. This value defaults to false.\n\n```php\n/**\n * @Field(\"field1\")\n * @Field(\"field2\", encoded=true)\n * @Field(\"field3[]\", var=\"field3\")\n */\npublic function example(int $field1, bool $field2, array $field3): Call;\n```\n\nFieldMap\n--------\n\nThis works much like [@Field](#field), except it must be provided an\niterable map. Each key/value pair will be treated as a `@Field` with\nthe key acting as the field name. The value may be anything that\n`@Field` supports.\n\n```php\n/**\n * @FieldMap(\"fieldMap\")\n */\npublic function example(Traversable $fieldMap): Call;\n```\n\nGET\n---\n\nPerforms a GET request to the provided path.\n\n```php\n/**\n * @GET(\"/foo?q=test\")\n */\npublic function example(): Call;\n```\n\nHEAD\n----\n\nPerforms a HEAD request to the provided path.\n\n```php\n/**\n * @HEAD(\"/foo?q=test\")\n */\npublic function example(): Call;\n```\n\nHeader\n------\n\nAdd a single header to a request.\n\nThe value of this annotation represents both the name of the header\nand the parameter name. Header names are lower-cased before getting\nadded to the request. Headers follow PSR-7 request format, so multiple\nvalues with the same name may be added to the request. Passing an\narray as the header value also allows multiple values to be added\nunder the same header name. Headers will converted to a string before\nbeing applied to the request.\n\n```php\n/**\n * @Header(\"X-Foo\", var=\"header1\")\n * @Header(\"X-Foo\", var=\"header2\")\n * @Header(\"X-Foo\", var=\"header3\")\n */\npublic function example(string $header1, bool $header2, array $header3): Call;\n```\n\nThe above example will add multiple different values to the `x-foo`\nheader.\n\nHeaderMap\n---------\n\nAllows a way to add multiple headers to a request at once. The annotation\nacts much like [FieldMap](#fieldmap), and accepts the same values as\n[Header](#header).\n\n```php\n/**\n * @HeaderMap(\"headerMap\")\n */\npublic function example(Traversable $headerMap): Call;\n```\n\nHeaders\n-------\n\nAdds headers statically supplied in the annotation value.\n\n```php\n/**\n * @Headers({\n *     \"X-Foo: Bar\",\n *     \"X-Ping: Pong\"\n * })\n */\n```\n\nOPTIONS\n-------\n\nPerforms an OPTIONS request to the provided path.\n\n```php\n/**\n * @OPTIONS(\"/foo?q=test\")\n */\npublic function example(): Call;\n```\n\nPart\n----\n\nAdds values to a multipart body.\n\nThis annotation set the request content type to `multipart/form-data`.\nThis annotation can accept any value [@Body](#body) can or a special\n`MultipartBody` class, which you can use to set additional properties\nlike headers or the filename. The annotation value specifies the part\nname. If you're using `MultipartBody` the part name will be pulled from\nthe object and the annotation value will be ignored.\n\n```php\n/**\n * @Part(\"part1\")\n * @Part(\"part2\")\n */\npublic function example(MyBody $part1, MultipartBody $part2): Call;\n```\n\nPartMap\n-------\n\nThis annotation works like previous map annotations. Keys represent\npart names, and values may be anything that a [@Part](#part) can\naccept. The same rules apply.\n\n```php\n/**\n * @PartMap(\"partMap\")\n */\npublic function example(Traversable $partMap): Call;\n```\n\nPATCH\n-----\n\nPerforms a PATCH request to the provided path.\n\n```php\n/**\n * @PATCH(\"/foo?q=test\")\n */\npublic function example(): Call;\n```\n\nPath\n----\n\nThis annotation allows you to create a dynamic url, and map path\nparts to method parameters. By default, the annotation value will\nbe the same as the url placeholder and parameter name, but this is\noverridable by using the `var` annotation key. Path values will get\nconverted to strings before getting added to the url.\n\n```php\n/**\n * @GET(\"/foo/{user}/{foo-bar}\"\n * @Path(\"user\")\n * @Path(\"foo-bar\", var=\"fooBar\")\n */\npublic function example(int $user, string $fooBar): Call;\n```\n\nPOST\n----\n\nPerforms a POST request to the provided path.\n\n```php\n/**\n * @POST(\"/foo?q=test\")\n */\npublic function example(): Call;\n```\n\nPUT\n---\n\nPerforms a PUT request to the provided path.\n\n```php\n/**\n * @PUT(\"/foo?q=test\")\n */\npublic function example(): Call;\n```\n\nQuery\n-----\n\nAdd a query to the url.\n\nThe annotation value represents the query name. The value will be\nconverted to a string before being added to the url. Use the `var`\nkey if the parameter name doesn't match the query name and the `encoded`\nkey to specify if the query data is already encoded. This defaults to\nfalse. Passing an array will apply each value to to the specified name.\n\n```php\n/**\n * @Query(\"query1\")\n * @Query(\"query2\", encoded=true)\n * @Query(\"query3[]\", var=\"field3\")\n */\npublic function example(int $query1, bool $query2, array $query3): Call;\n```\n\nQueryMap\n--------\n\nAdds multiple queries to a url.\n\nThis annotation works the same as other map annotations and adds\nkey/value pairs of queries to a url where keys represent the query name\nand the value may be anything that [@Query](#query) allows.\n\n```php\n/**\n * @QueryMap(\"queryMap\")\n */\npublic function example(Traversable $queryMap): Call;\n```\n\nQueryName\n---------\n\nAdds only the query name part of a query, excluding the `=` and anything\nafter it. The annotation value represents the name (which can be changed\nwith the `var` key), and you can specify that the data is already encoded\nwith the `encoded` key. Data is converted to a string before getting\nadded to the url.\n\n```php\n/**\n * @QueryName(\"queryName1\")\n * @QueryName(\"queryName2\", encoded=true)\n */\npublic function example(float $queryName1, string $queryName2): Call;\n```\n\nREQUEST\n-------\n\nPerforms a custom request method to the provided path.\n\nAdditionally, the method type and whether the request contains a body\nmust be specified.\n\n```php\n/**\n * @REQUEST(\"/foo?q=test\", type=\"FOO\", body=false)\n */\npublic function example(): Call;\n```\n\nResponseBody\n------------\n\nDefines the type the response body should be converted to on success.\n\nSuccessful responses are defined as any http status code with 200-300.\nBy default, only `StreamInterface` is supported, but a converter\nwill extend the supported types.\n\n```php\n/**\n * @ResponseBody(\"App\\MyClass\")\n */\npublic function example(): Call;\n```\n\nUrl\n---\n\nThis annotation allows changing the base url of a request. This will\nallow overriding whatever url is specified on the `RetrofitBuilder`.\n\n```php\n/**\n * @Url(\"baseUrl\")\n */\npublic function example(string $baseUrl): Call;\n```\n"
  },
  {
    "path": "docs/installation.md",
    "content": "Installation\n============\n\nInstallation with Composer\n--------------------------\n\n```bash\ncomposer require tebru/retrofit-php:~3.0\n```\n\nRetrofit does not include an http client, please install an\nimplementation.\n\n```bash\ncomposer require tebru/retrofit-php-http-guzzle6\n```\n\nRetrofit does not support converting request or response bodies outside\nPSR-7 `StreamInterface`. Install a converter to handle custom\nconversions.\n\n```bash\ncomposer require tebru/retrofit-php-converter-gson\n```\n\nSetup\n-----\n\nThe easiest way to get setup is to add the psr-4 autoload location where\nyou include the autoloader.\n\n```php\n$loader = require __DIR__ . '/vendor/autoload.php';\n$loader->addPsr4('Tebru\\\\Retrofit\\\\Proxy\\\\', __DIR__ . '</path/to/cache/dir>/retrofit');\n```\n\nIf you do not have environment specific cache directories, you could\nspecify the psr-4 autoload location in your composer.json instead.\n\n```json\n\"autoload\": {\n    \"psr-4\": {\n        \"Tebru\\\\Retrofit\\\\Proxy\\\\\": \"<path/to/cache/dir>/retrofit\"\n    }\n}\n```\n"
  },
  {
    "path": "docs/upgrade_2_3.md",
    "content": "Upgrading from Retrofit 2 to Retrofit 3\n=======================================\n\nRetrofit 3 is a complete rewrite of Retrofit 2 in an attempt to make\nthe library operate more closely with the Square version. This includes\nchanging how annotations operate. Therefore, creating a friendly upgrade\npath would have been extremely time consuming, and not that useful, as\nnearly everything has been deprecated.\n\n- [Overview](#overview)\n- [Feature Updates](#feature-updates)\n- [Dependency Updates](#dependency-updates)\n- [File Updates](#file-updates)\n- [Annotation Updates](#annotation-updates)\n\nOverview\n--------\n\nThe way that requests are sent and responses are handled has changed\ndramatically. Walking through how the steps have changed is the best\nway to illustrate these differences.\n\n### Retrofit 2\n\n1. Collect all services class names\n2. Read annotations and generate PHP code based on annotations\n3. Cache code on filesystem\n4. Use RestAdapterBuilder to build a RestAdapter instance\n5. Use RestAdapter to load the service class from the filesystem and return instance\n6. Call methods on the service to make http requests\n7. Return expected response based on annotations\n\n### Retrofit 3\n\n1. Use RetrofitBuilder to build a Retrofit instance\n2. Use Retrofit to ask for a Proxy object based on interface class name\n3. Generate proxy object, which is an implementation of the provided interface\n4. Call methods on proxy object, which internally reads annotations and\nreturns a Call object\n5. Use the call object to execute a request synchronously or asynchronously,\nand returns a Response object\n6. Fetch deserialized response body from Response object based on success/failure\nof request\n\nThe new method has several advantages.\n\n- Nothing is written to the filesystem unless caching is enabled. This\nfixes some issues with interfaces changing in development.\n- All requests can be made asynchronously. Previously a Callback needed\nto be defined in the method signature to enable async requests.\n- There is more flexibility in getting different deserialized response\nbodies depending on the success or failure of the request.\n- The code is testable, as the only generated code is tiny and the same\nfor every method as it just delegate to a real class. Previously testing\nwhat got generated was painful and prone to error.\n\nIn addition, the generated class namespace has changed from\n`Tebru\\Retrofit\\Generated` to `Tebru\\Retrofit\\Proxy`.\n\nFeature Updates\n---------------\n\nThe following are high level feature changes\n\n- The event system has been removed\n- All methods require a return type (this must be `Call` if not using a custom adapter)\n- All method parameter require a typehint\n- All types are supported for all annotations mapped to a parameter. This\nis handled through the converter system.\n- Custom annotations may be used to modify the request\n\nDependency Updates\n------------------\n\n### Upgraded\n\n- php: from 5.4 to 7.1\n- nikic/php-parer: from ~1.3 to ~3.0.6\n\n### Added\n\n- symfony/cache (switched from doctrine/cache)\n- tebru/doctrine-annotation-reader\n- tebru/php-type\n\n### Removed\n\n- jms/serializer\n- phpoption/phpption\n- tebru/assert\n- tebru/dynamo\n- tebru/retrofit-http-clients\n- symfony/event-dispatcher\n- symfony/filesystem\n- psr/log\n- doctrine/cache\n- doctrine/collections\n\nAdditionally removed the coveralls binary that was used for pushing\ncode coverage to Coveralls. This unfortunately added Guzzle 3 classes,\nwhich could be confusing if the binary wasn't ignored.\n\nFile Updates\n------------\n\nThere are many new files, but the directory structure has changed. There\nis now a an `Internal` package that represents classes that should only\nbe used internally by the library. They may change between releases,\nbut will not represent a BC break. All classes will be referenced through\nan interface or abstract class with few exceptions.\n\n### Removed Directories\n\nThe following directories and all files have been removed.\n\n- src/Adapter\n- src/Generation\n- src/Event\n- src/Exception\n- src/Subscriber\n\n### Removed Files\n\nThe following files have been removed.\n\n- src/Http/AsyncAware.php\n- src/Http/Callback.php\n- src/Http/Response.php\n\nAnnotation Updates\n------------------\n\nMany annotations have changed behavior. Additionally, annotations have\nbeen added, removed, and renamed.\n\n### Changed Annotations\n\n- @Body now only works with application/json requests\n- @Body may not appear with requests that do not have a body\n- @Body removed jsonSerializable option, use a custom converter instead\n- @GET, @HEAD, @OPTIONS, @DELETE may no longer be used with an request that contains a body\n- @Part now specifically denotes a multipart request\n- @Part now allows specification of encoding type (defaults to binary)\n- @Part must accept a body value that can be converted, or the new MultipartBody\n- @PartMap is an iterable where keys are names as strings and values are the same as @Part\n- @Query added ability to specify whether data is pre-encoded or not\n- Passing an array to a @Query parameter will now create separate\nparameters with the name as the query name and the value as one of the array\n- @QueryMap is now an iterable with supported values the same as @Query\n\n### Added Annotations\n\n- @Field: Used only for form encoded request bodies. An array will add all values to the field name\n- @FieldMap, @HeaderMap: Like corresponding annotation, but for any iterable where string keys are the name\n- @Path is new and required to map url placeholders to parameters\n- @QueryName allows adding a query parameter that doesn’t have a value\n- @REQUEST is a generic http request annotation like @GET or @POST\n\n### Removed Annotations\n\n- @FormUrlEncoded - use @Field/@FieldMap instead\n- @JsonBody - use @Body instead\n- @Multipart - use @Part/@PartMap instead\n- @ResponseType – this was specifically created to handle cases where a response instead of body was returned and is no longer needed\n- @SerializationContext/@DeserializationContext - jms/serializer was removed from project\n\n### Renamed Annotations\n\n- @BaseUrl to @Url\n- @Returns to @ResponseBody\n\n"
  },
  {
    "path": "docs/usage.md",
    "content": "Usage\n=====\n\nTo start, it would be a good idea to get an understanding of what\nRetrofit is doing under the hood.\n\nLet's assume we're using the following service definition\n\n```php\ninterface GitHubService\n{\n    /**\n     * @GET(\"/users/{user}/list\")\n     * @Path(\"user\")\n     * @Query(\"limit\")\n     */\n    public function listRepos(string $user, int $limit): Call;\n}\n```\n\nFirst, you need an instance of `Retrofit`. Here is the easiest way to\ncreate one.\n\n```php\n$retrofit = Retrofit::builder()\n    ->setBaseUrl('http://api.example.com')\n    ->setHttpClient(new Guzzle6HttpClient(new Client()))\n    ->build();\n```\n\nCreating a `GitHubService` implementation is easy\n\n```php\n$githubService = $retrofit->create(GitHubService::class);\n```\n\nThis is going to generate a `Proxy` object that implements the\n`GitHubService` interface. You can use the proxy like you normally would\nuse an instance of `GitHubService`.\n\n```php\n$call = $githubService->listRepos('octocat', 10);\n```\n\nThis will parse the `GitHubService` interface, and store the following\ninformation about the `listRepos` method:\n\n1. It should make a `GET` request to `/users/{user}/list`\n2. `{user}` is provided by the `$user` parameter, which is a string\n3. `limit` is a query parameter and provided by `$limit`, which is an integer\n3. We're returning a `Call` object\n4. We provided two arguments and they're `octocat` and `10`\n\nThis information is available in the `Call` object, so when you're ready\nto make the request, it has enough information to construct the request.\n\nDoing so can be done synchronously or asynchronously. Here's an example\nof a synchronous request\n\n```php\n$response = $call->execute();\n```\n\nAnd asynchronously\n\n```php\n$call->enqueue(\n    function(Response $response) {\n        // handle any response (200, 404, 500)\n    },\n    function(Throwable $throwable) {\n        // handle an exception (network unreachable)\n    }\n);\n$call->wait();\n```\n\nBoth of the callbacks are optional. If you just wanted to make the\nrequest and didn't care about the response, you could do this instead\n\n```php\n$call->enqueue();\n$call->wait();\n```\n\nCalling `->wait()` is necessary to trigger sending the requests. Using\nthe guzzle client, the requests are sent in batches of 5 using a Pool.\nThis allows callbacks to trigger as soon as one response is received.\n\n`Response` here is a Retrofit Response (`Tebru\\Retrofit\\Response`). It\nprovides an easy way to check if the request was successful (if the\nstatus code is between 200 and 300) and methods for getting the success\nor error response bodies.\n\n```php\nif ($response->isSuccessful()) {\n    // success body\n    $response->body(); // StreamInterface\n} else {\n    // error body\n    $response->errorBody(); // StreamInterface\n}\n```\n\nBy default, Retrofit only works with a PSR-7 `StreamInterface` for\nsetting request bodies and handling response bodies. This is somewhat\nlimiting, but can be enhanced by using converters. The converter I'm\ngoing to demonstrate uses [Gson](https://github.com/tebru/gson-php)\nbecause it can handle the conversion from any type to json and back.\n\nAdding it to the builder is simple\n\n```php\nRetrofit::builder()\n    // ...\n    ->addConverterFactory(new GsonConverterFactory(Gson::builder()->build()))\n    // ...\n```\n\nAnd allows using a lot more annotations of the client. Here's an example\nusing the `ResponseBody` and `ErrorBody` annotations to inform Retrofit\nhow to convert success and error responses\n\n```php\ninterface GitHubService\n{\n    /**\n     * @GET(\"/users/{user}/list\")\n     * @Path(\"user\")\n     * @Query(\"limit\")\n     * @ResponseBody(\"App\\GithubService\\ListRepo\")\n     * @ErrorBody(\"App\\GitHubService\\ApiError\")\n     */\n    public function listRepos(string $user, int $limit): Call;\n}\n```\n\nThe annotation value is the class name that the response should be\ndeserialized into. This allows for cleaner handling of responses\n\n```php\nif (!$response->isSuccessful()) {\n    // throw an exception with the deserialized body to be handled elsewhere\n    throw new ApiException($response->errorBody());\n}\n\n$listRepos = $response->body();\nforeach ($listRepos as $repo) {\n    // iterate over the repo list\n}\n```\n\nConverters can also be used when sending json. For example, assume this\nservice definition\n\n```php\n/**\n * @ErrorBody(\"App\\GitHubService\\ApiError\")\n */\ninterface GitHubService\n{\n    /**\n     * @POST(\"/user/repos\")\n     * @Body(\"repo\")\n     * @ResponseBody(\"App\\GitHubService\\Repo\")\n     */\n    public function createRepo(Repo $repo): Call;\n}\n```\n\nBecause we have already registered a converter that can handle any\nobject, Retrofit will make a POST request to `/user/repos`, convert the\n`Repo` object to json, add a `Content-Type` header of `application/json`,\nand deserialize the response into a `App\\GitHubService\\Repo` object.\n\nWe've discussed `RequestBodyConverter`s and `ResponseBodyConverters`s.\nThe third type is a `StringConverter`. Adding query parameters to the\nrequest was introduced earlier and uses a string converter to convert\nvarious types to strings.\n\nAs seen before, we were able to easily convert an integer to a string.\nThe same works for a string, float, and boolean types. Booleans get\nconverted to `\"true\"` or `\"false\"`.\n\nHowever, using an array as a query parameter has a unique effect as it\nwill apply each item in the array to the same query key. Let's look at\nan example.\n\n```php\ninterface GitHubService\n{\n    /**\n     * @GET(\"/user/list\")\n     * @Query(\"affiliation[]\", var=\"affiliations\")\n     */\n    public function listRepos(array $affiliations): Call;\n}\n\n$githubService->listRepos(['owner', 'collaborator']);\n```\n\n*The actual GitHub API passes affiliations as a comma separated string,\nbut for the purposes of this example, we'll pretend it's in array\nsyntax.*\n\nHere we're using the `var` key to tell Retrofit to not look for a\nparameter called `$affiliation[]`—as that's illegal—but `$affiliations`\ninstead. This will create the query string\n\n```\naffiliation[]=owner&affiliation[]=collaborator\n```\n\nThe `[]` at the end is not magic, Retrofit would behave the same way if\nit was missing. It just tells servers that this data is an array.\n\nMultiple `@Query` annotations may be used on the same method\n\n```php\ninterface GitHubService\n{\n    /**\n     * @GET(\"/user/list\")\n     * @Query(\"affiliation[]\", var=\"affiliations\")\n     * @Query(\"sort\")\n     */\n    public function listRepos(array $affiliations, string $sort): Call;\n}\n```\n\nThis can tend to get lengthy, which is where `@QueryMap` becomes useful.\nA QueryMap is just an iterable hash of string keys and values. The\nvalues can be anything that can be passed to a regular `@Query`. Here's\nan example using an array\n\n```php\ninterface GitHubService\n{\n    /**\n     * @GET(\"/user/list\")\n     * @QueryMap(\"queries\")\n     */\n    public function listRepos(array $queries): Call;\n}\n\n$githubService->listRepos([\n    'affiliations[]' => ['owner', 'collaborator'],\n    'sort' => 'created',\n]);\n```\n\nBut an object that implements `Iterable` could also be used\n\n```php\ninterface GitHubService\n{\n    /**\n     * @GET(\"/user/list\")\n     * @QueryMap(\"queries\")\n     */\n    public function listRepos(ListReposQueries $queries): Call;\n}\n\n$githubService->listRepos(new ListReposQueries());\n```\n\nParameters (and all parameters) are optional and accept default values.\nSpecifying default null here will not send any query parameters.\n\n```php\ninterface GitHubService\n{\n    /**\n     * @GET(\"/user/list\")\n     * @QueryMap(\"queries\")\n     */\n    public function listRepos(?ListReposQueries $queries = null): Call;\n}\n\n$githubService->listRepos();\n```\n\nThis behavior is consistent with `@Field`/`@FieldMap` and\n`@Header`/`@HeaderMap`.\n\nPlease see the [Annotation Reference](annotations.md) for a more in-depth\nlook at the different annotations and how they function.\n"
  },
  {
    "path": "phpcs.xml",
    "content": "<?xml version=\"1.0\"?>\n<ruleset>\n    <file>./src</file>\n    <rule ref=\"PSR1\" />\n</ruleset>\n"
  },
  {
    "path": "phpunit.xml",
    "content": "<phpunit colors=\"true\" bootstrap=\"tests/bootstrap.php\">\n    <testsuites>\n        <testsuite name=\"Unit\">\n            <directory>tests/Unit</directory>\n        </testsuite>\n    </testsuites>\n    <filter>\n        <whitelist>\n            <directory>src</directory>\n        </whitelist>\n    </filter>\n    <logging>\n        <log type=\"coverage-clover\" target=\"build/logs/clover.xml\"/>\n        <log type=\"coverage-html\" target=\"build/logs/html\"/>\n    </logging>\n</phpunit>\n"
  },
  {
    "path": "src/Annotation/Body.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\Retrofit\\RequestBodyConverter;\n\n/**\n * Define the body of the HTTP request.\n *\n * This annotation may only be used on requests that are sending json. The parameter\n * this annotation maps to must be able to be converted to json.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass Body extends ParameterAnnotation\n{\n    /**\n     * Return the converter interface class\n     *\n     * Can be one of RequestBodyConverter, ResponseBodyConverter, or StringConverter\n     *\n     * @return string\n     */\n    public function converterType(): ?string\n    {\n        return RequestBodyConverter::class;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/DELETE.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\n\n/**\n * Defines an HTTP DELETE request type to a REST path relative to base URL.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass DELETE extends AbstractAnnotation implements HttpRequest\n{\n    /**\n     * Returns the type of the annotation (get, post, put, etc)\n     *\n     * @return string\n     */\n    public function getType(): string\n    {\n        return 'delete';\n    }\n\n    /**\n     * Returns true if the request type contains a body\n     *\n     * @return bool\n     */\n    public function hasBody(): bool\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/Encodable.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Abstract class that adds an 'encoded' boolean key to annotations to\n * represent data that is already encoded or not.\n *\n * @author Nate Brunette <n@tebru.net>\n */\nabstract class Encodable extends ParameterAnnotation\n{\n    /**\n     * The values are already encoded\n     *\n     * @var bool\n     */\n    private $encoded;\n\n    /**\n     * Initialize annotation data\n     */\n    protected function init(): void\n    {\n        parent::init();\n\n        $this->encoded = $this->data['encoded'] ?? false;\n    }\n\n    /**\n     * Returns true if the values are already encoded\n     *\n     * @return bool\n     */\n    public function isEncoded(): bool\n    {\n        return $this->encoded;\n    }\n\n    /**\n     * Return the converter interface class\n     *\n     * Can be one of RequestBodyConverter, ResponseBodyConverter, or StringConverter\n     *\n     * @return string\n     */\n    public function converterType(): ?string\n    {\n        return StringConverter::class;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/ErrorBody.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\n\n/**\n * Use this annotation to define a class that errors should be deserialized as\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass ErrorBody extends AbstractAnnotation\n{\n}\n"
  },
  {
    "path": "src/Annotation/Field.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\n/**\n * Adds a field to form encoded requests\n *\n * The default value represents the field name. Passing an array will add a mapping between the\n * field name and each value in the array. The array must be 0-indexed.\n *\n * Set 'encoded' to true to specify that the data is already encoded.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass Field extends Encodable\n{\n    /**\n     * Whether or not multiple annotations of this type can\n     * be added to a method\n     *\n     * @return bool\n     */\n    public function allowMultiple(): bool\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/FieldMap.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\n/**\n * Represents a collection of @Field annotations\n *\n * The default value specifies the variable name in the method signature.\n *\n * Any iterable may be passed as an argument. Keys of the map will be the field\n * names, and the values will be handled exactly the same as as @Field.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass FieldMap extends Encodable\n{\n    /**\n     * Returns true if multiple annotations of this type are allowed\n     *\n     * @return bool\n     */\n    public function allowMultiple(): bool\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/GET.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\n\n/**\n * Defines an HTTP GET request type to a REST path relative to base URL.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass GET extends AbstractAnnotation implements HttpRequest\n{\n    /**\n     * Returns the type of the annotation (get, post, put, etc)\n     *\n     * @return string\n     */\n    public function getType(): string\n    {\n        return 'get';\n    }\n\n    /**\n     * Returns true if the request type contains a body\n     *\n     * @return bool\n     */\n    public function hasBody(): bool\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/HEAD.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\n\n/**\n * Defines an HTTP HEAD request type to a REST path relative to base URL.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass HEAD extends AbstractAnnotation implements HttpRequest\n{\n    /**\n     * Returns the type of the annotation (get, post, put, etc)\n     *\n     * @return string\n     */\n    public function getType(): string\n    {\n        return 'head';\n    }\n\n    /**\n     * Returns true if the request type contains a body\n     *\n     * @return bool\n     */\n    public function hasBody(): bool\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/Header.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Adds a header to request\n *\n * The default value represents the header name. Passing an array will add a new value for each\n * header name.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass Header extends ParameterAnnotation\n{\n    /**\n     * Whether or not multiple annotations of this type can\n     * be added to a method\n     *\n     * @return bool\n     */\n    public function allowMultiple(): bool\n    {\n        return true;\n    }\n\n    /**\n     * Return the converter interface class\n     *\n     * Can be one of RequestBodyConverter, ResponseBodyConverter, or StringConverter\n     *\n     * @return string\n     */\n    public function converterType(): ?string\n    {\n        return StringConverter::class;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/HeaderMap.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Represents a collection of @Header annotations\n *\n * The default value specifies the variable name in the method signature.\n *\n * Any iterable may be passed as an argument. Keys of the map will be the header\n * names, and the values will be handled exactly the same as as @Header.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass HeaderMap extends ParameterAnnotation\n{\n    /**\n     * Return the converter interface class\n     *\n     * Can be one of RequestBodyConverter, ResponseBodyConverter, or StringConverter\n     *\n     * @return string\n     */\n    public function converterType(): ?string\n    {\n        return StringConverter::class;\n    }\n\n    /**\n     * Returns true if multiple annotations of this type are allowed\n     *\n     * @return bool\n     */\n    public function allowMultiple(): bool\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/Headers.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse RuntimeException;\nuse Tebru;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\n\n/**\n * Adds headers statically supplied in the value.\n *\n *     @Headers(\"Cache-Control: max-age=640000\")\n *     @Headers({\n *         \"X-Foo: Bar\",\n *         \"X-Ping: Pong\"\n *     })\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass Headers extends AbstractAnnotation\n{\n    /**\n     * Initialize annotation data\n     *\n     * @throws \\RuntimeException\n     */\n    protected function init(): void\n    {\n        // loop through each string and break on ':'\n        foreach ((array)$this->getValue() as $header) {\n            $position = strpos($header, ':');\n\n            if ($position === false) {\n                throw new RuntimeException('Retrofit: Header in an incorrect format.  Expected \"Name: value\"');\n            }\n\n            $name = trim(substr($header, 0, $position));\n            $value = trim(substr($header, $position + 1));\n\n            $this->value[$name] = $value;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Annotation/HttpRequest.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru;\n\n/**\n * Interface for the different http request annotations (e.g. @GET, @POST)\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface HttpRequest\n{\n    /**\n     * Returns the type of the annotation (get, post, put, etc)\n     *\n     * @return string\n     */\n    public function getType(): string;\n\n    /**\n     * Returns true if the request type contains a body\n     *\n     * @return bool\n     */\n    public function hasBody(): bool;\n\n    /**\n     * Get the url value\n     *\n     * @return mixed\n     */\n    public function getValue();\n}\n"
  },
  {
    "path": "src/Annotation/OPTIONS.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\n\n/**\n * Defines an HTTP OPTIONS request type to a REST path relative to base URL.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass OPTIONS extends AbstractAnnotation implements HttpRequest\n{\n    /**\n     * Returns the type of the annotation (get, post, put, etc)\n     *\n     * @return string\n     */\n    public function getType(): string\n    {\n        return 'options';\n    }\n\n    /**\n     * Returns true if the request type contains a body\n     *\n     * @return bool\n     */\n    public function hasBody(): bool\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/PATCH.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\n\n/**\n * Defines an HTTP PATCH request type to a REST path relative to base URL.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass PATCH extends AbstractAnnotation implements HttpRequest\n{\n    /**\n     * Returns the type of the annotation (get, post, put, etc)\n     *\n     * @return string\n     */\n    public function getType(): string\n    {\n        return 'patch';\n    }\n\n    /**\n     * Returns true if the request type contains a body\n     *\n     * @return bool\n     */\n    public function hasBody(): bool\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/POST.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\n\n/**\n * Defines an HTTP POST request type to a REST path relative to base URL.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass POST extends AbstractAnnotation implements HttpRequest\n{\n    /**\n     * Returns the type of the annotation (get, post, put, etc)\n     *\n     * @return string\n     */\n    public function getType(): string\n    {\n        return 'post';\n    }\n\n    /**\n     * Returns true if the request type contains a body\n     *\n     * @return bool\n     */\n    public function hasBody(): bool\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/PUT.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\n\n/**\n * Defines an HTTP PUT request type to a REST path relative to base URL.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass PUT extends AbstractAnnotation implements HttpRequest\n{\n    /**\n     * Returns the type of the annotation (get, post, put, etc)\n     *\n     * @return string\n     */\n    public function getType(): string\n    {\n        return 'put';\n    }\n\n    /**\n     * Returns true if the request type contains a body\n     *\n     * @return bool\n     */\n    public function hasBody(): bool\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/ParameterAnnotation.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\n\n/**\n * Parent class for annotation that have a [parameterName => $parameterName] format\n *\n * @author Nate Brunette <n@tebru.net>\n */\nabstract class ParameterAnnotation extends AbstractAnnotation implements ParameterAwareAnnotation\n{\n    /**\n     * An alias for the variable name\n     *\n     * @var string $var\n     */\n    private $var;\n\n    /**\n     * Initialize annotation data\n     */\n    protected function init(): void\n    {\n        parent::init();\n\n        $this->var = $this->data['var'] ?? null;\n    }\n\n    /**\n     * Get the variable name\n     *\n     * @return string\n     */\n    public function getVariableName(): string\n    {\n        return $this->var ?? $this->getValue();\n    }\n}\n"
  },
  {
    "path": "src/Annotation/ParameterAwareAnnotation.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\n/**\n * Interface ParameterAwareAnnotation\n *\n * Annotations that implement this interface are mapped to method parameters, so they\n * need to know a variable name to reference and how to convert the value.\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface ParameterAwareAnnotation\n{\n    /**\n     * The variable name, which will either be the default value or the value of 'var' if\n     * specified. The variable name excludes the '$'.\n     *\n     * @return string\n     */\n    public function getVariableName(): string;\n\n    /**\n     * Return the converter interface class\n     *\n     * Can be one of RequestBodyConverter, ResponseBodyConverter, or StringConverter\n     *\n     * @return null|string\n     */\n    public function converterType(): ?string;\n}\n"
  },
  {
    "path": "src/Annotation/Part.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\Retrofit\\Http\\MultipartBody;\nuse Tebru\\Retrofit\\RequestBodyConverter;\n\n/**\n * Denotes a single part of a multipart request.\n *\n * The default value represents the part name. Passing a [@see MultipartBody] will use the values from\n * that object, otherwise the value will be converted to a stream and added to the request.\n *\n * Use the 'encoding' key to override the default 'binary' encoding.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass Part extends ParameterAnnotation\n{\n    /**\n     * How the multipart request is encoded\n     *\n     * @var string\n     */\n    private $encoding;\n\n    /**\n     * Initialize annotation data\n     */\n    protected function init(): void\n    {\n        parent::init();\n\n        $this->encoding = $this->data['encoding'] ?? 'binary';\n    }\n\n    /**\n     * Get the encoding type\n     *\n     * @return string\n     */\n    public function getEncoding(): string\n    {\n        return $this->encoding;\n    }\n\n    /**\n     * Whether or not multiple annotations of this type can\n     * be added to a method\n     *\n     * @return bool\n     */\n    public function allowMultiple(): bool\n    {\n        return true;\n    }\n\n    /**\n     * Return the converter interface class\n     *\n     * Can be one of RequestBodyConverter, ResponseBodyConverter, or StringConverter\n     *\n     * @return string\n     */\n    public function converterType(): ?string\n    {\n        return RequestBodyConverter::class;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/PartMap.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\Retrofit\\Http\\MultipartBody;\nuse Tebru\\Retrofit\\RequestBodyConverter;\n\n/**\n * Represents a collection of @Part annotations\n *\n * The default value specifies the variable name in the method signature.\n *\n * Any iterable may be passed as an argument.\n *\n * If the item is not a [@see MultipartBody], keys of the map will be the field\n * names, and the values will be handled exactly the same as as @Part. Otherwise,\n * all values of the MultipartBody will be used.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass PartMap extends ParameterAnnotation\n{\n    /**\n     * How the multipart request is encoded\n     *\n     * @var string\n     */\n    private $encoding;\n\n    /**\n     * Initialize annotation data\n     */\n    protected function init(): void\n    {\n        parent::init();\n\n        $this->encoding = $this->data['encoding'] ?? 'binary';\n    }\n\n    /**\n     * Get the encoding type\n     *\n     * @return string\n     */\n    public function getEncoding(): string\n    {\n        return $this->encoding;\n    }\n\n    /**\n     * Return the converter interface class\n     *\n     * Can be one of RequestBodyConverter, ResponseBodyConverter, or StringConverter\n     *\n     * @return string\n     */\n    public function converterType(): ?string\n    {\n        return RequestBodyConverter::class;\n    }\n\n    /**\n     * Returns true if multiple annotations of this type are allowed\n     *\n     * @return bool\n     */\n    public function allowMultiple(): bool\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/Path.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Specifies replaceable parts in a url path\n *\n * Given this service method\n *\n * /**\n *  * @GET(\"/foo/{my-path}\")\n *  * @Path(\"my-path\", var=\"myPath\")\n *  *\n *  public function foo(string $myPath): Call;\n *\n * Passing in \"bar\" will result in the path \"/foo/bar\". The 'var' key is unnecessary if\n * the path value inside the {} matches the variable name.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass Path extends ParameterAnnotation\n{\n    /**\n     * Returns true if multiple annotations of this type are allowed\n     *\n     * @return bool\n     */\n    public function allowMultiple(): bool\n    {\n        return true;\n    }\n\n    /**\n     * Return the converter interface class\n     *\n     * Can be one of RequestBodyConverter, ResponseBodyConverter, or StringConverter\n     *\n     * @return string\n     */\n    public function converterType(): ?string\n    {\n        return StringConverter::class;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/Query.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\n/**\n * Adds a query to the url path\n *\n * The default value represents the query name. Passing an array will add a mapping between the\n * query name and each value in the array. The array must be 0-indexed.\n *\n * Set 'encoded' to true to specify that the data is already encoded.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass Query extends Encodable\n{\n    /**\n     * Whether or not multiple annotations of this type can\n     * be added to a method\n     *\n     * @return bool\n     */\n    public function allowMultiple(): bool\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/QueryMap.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\n/**\n * Represents a collection of @Query annotations\n *\n * The default value specifies the variable name in the method signature.\n *\n * Any iterable may be passed as an argument. Keys of the map will be the query\n * names, and the values will be handled exactly the same as as @Query.\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass QueryMap extends Encodable\n{\n    /**\n     * Returns true if multiple annotations of this type are allowed\n     *\n     * @return bool\n     */\n    public function allowMultiple(): bool\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/QueryName.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\n/**\n * Query parameter appended to the URL\n *\n * Use this annotation if the query parameter does not have a value\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass QueryName extends Encodable\n{\n    /**\n     * Whether or not multiple annotations of this type can\n     * be added to a method\n     *\n     * @return bool\n     */\n    public function allowMultiple(): bool\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/REQUEST.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\n\n/**\n * Defines an HTTP request type to a REST path relative to base URL.\n *\n * This is a generic annotation that lets you define an http request\n * outside the standard GET, POST, etc\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass REQUEST extends AbstractAnnotation implements HttpRequest\n{\n    /**\n     * The request method\n     *\n     * @var string\n     */\n    private $type;\n\n    /**\n     * If the request contains a body\n     *\n     * @var bool\n     */\n    private $body;\n\n    /**\n     * Initialize annotation data\n     */\n    protected function init(): void\n    {\n        $this->assertKey('type');\n\n        $this->type = $this->data['type'];\n        $this->body = $this->data['body'] ?? false;\n    }\n\n\n    /**\n     * Returns the type of the annotation (get, post, put, etc)\n     *\n     * @return string\n     */\n    public function getType(): string\n    {\n        return $this->type;\n    }\n\n    /**\n     * Returns true if the request type contains a body\n     *\n     * @return bool\n     */\n    public function hasBody(): bool\n    {\n        return $this->body;\n    }\n}\n"
  },
  {
    "path": "src/Annotation/ResponseBody.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\n\n/**\n * Use this annotation to define a class that responses should be deserialized as\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass ResponseBody extends AbstractAnnotation\n{\n}\n"
  },
  {
    "path": "src/Annotation/Url.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Annotation;\n\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Use this annotation to override the base url\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass Url extends ParameterAnnotation\n{\n    /**\n     * Return the converter interface class\n     *\n     * Can be one of RequestBodyConverter, ResponseBodyConverter, or StringConverter\n     *\n     * @return null|string\n     */\n    public function converterType(): ?string\n    {\n        return StringConverter::class;\n    }\n}\n"
  },
  {
    "path": "src/AnnotationHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\n\n/**\n * Interface AnnotationHandler\n *\n * Implementations of this interface accept an annotation and manipulate the [@see ServiceMethodBuilder] based\n * on the value.\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface AnnotationHandler\n{\n    /**\n     * Handle an annotation, mutating the [@see ServiceMethodBuilder] based on the value\n     *\n     * @param AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param Converter|StringConverter|RequestBodyConverter|null $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void;\n}\n"
  },
  {
    "path": "src/Call.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\nuse Psr\\Http\\Message\\RequestInterface;\n\n/**\n * Interface Call\n *\n * Implementations will be able to make requests synchronously or asynchronously and will\n * be able to provide a PSR-7 request object.\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface Call\n{\n    /**\n     * Execute request synchronously\n     *\n     * A [@see Response] will be returned\n     *\n     * @return Response\n     */\n    public function execute(): Response;\n\n    /**\n     * Execute request asynchronously\n     *\n     * This method accepts two optional callbacks.\n     *\n     * onResponse() will be called for any request that gets a response,\n     * whether it was successful or not. It will send a [@see Call] and\n     * a [@see Response] as the first and second parameters.\n     *\n     * onFailure() will be called in the event a network request failed. It\n     * will send the [@see Throwable] that was encountered.\n     *\n     * Example of method signatures:\n     *\n     * $call->enqueue(\n     *     function (\\Tebru\\Retrofit\\Call $call, \\Tebru\\Retrofit\\Response $response) {},\n     *     function (\\Throwable $throwable) {}\n     * );\n     *\n     * @param callable $onResponse On any response\n     * @param callable $onFailure On any network request failure\n     * @return Call\n     */\n    public function enqueue(?callable $onResponse = null, ?callable $onFailure = null): Call;\n\n    /**\n     * When making requests asynchronously, call wait() to execute the requests\n     *\n     * @return void\n     */\n    public function wait(): void;\n\n    /**\n     * Get the PSR-7 request\n     *\n     * @return RequestInterface\n     */\n    public function request(): RequestInterface;\n}\n"
  },
  {
    "path": "src/CallAdapter.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\n/**\n * Interface CallAdapter\n *\n * Implementors can modify a [@see Call] to match the expected service method\n * return type.\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface CallAdapter\n{\n    /**\n     * Accepts a [@see Call] and converts it to the appropriate type\n     *\n     * @param Call $call\n     * @return mixed\n     */\n    public function adapt(Call $call);\n}\n"
  },
  {
    "path": "src/CallAdapterFactory.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\nuse Tebru\\PhpType\\TypeToken;\n\n/**\n * Interface CallAdapterFactory\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface CallAdapterFactory\n{\n    /**\n     * Returns true if the factory supports this type\n     *\n     * @param TypeToken $type\n     * @return bool\n     */\n    public function supports(TypeToken $type): bool;\n\n    /**\n     * Create a new factory from type\n     *\n     * @param TypeToken $type\n     * @return CallAdapter\n     */\n    public function create(TypeToken $type): CallAdapter;\n}\n"
  },
  {
    "path": "src/Command/CompileCommand.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Command;\n\nuse GuzzleHttp\\Psr7\\Response;\nuse Psr\\Http\\Message\\RequestInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Exception\\InvalidArgumentException;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Tebru\\Retrofit\\HttpClient;\nuse Tebru\\Retrofit\\Retrofit;\n\n/**\n * Class CompileCommand\n *\n * @author Nate Brunette <n@tebru.net>\n * @codeCoverageIgnore\n */\nclass CompileCommand extends Command\n{\n    /**\n     * Configure command\n     *\n     * @throws InvalidArgumentException\n     */\n    protected function configure(): void\n    {\n        $this->setName('compile');\n        $this->setDescription('Compiles and caches all services found in the project');\n        $this->addArgument('sourceDirectory', InputArgument::REQUIRED, 'Enter the source directory');\n        $this->addArgument('cacheDirectory', InputArgument::REQUIRED, 'Enter the cache directory');\n    }\n\n    /**\n     * Execute command\n     *\n     * @param InputInterface $input\n     * @param OutputInterface $output\n     * @return int|null|void\n     */\n    protected function execute(InputInterface $input, OutputInterface $output)\n    {\n        $srcDir = $input->getArgument('sourceDirectory');\n        $cacheDir = $input->getArgument('cacheDirectory');\n\n        $clientStub = new class implements HttpClient {\n\n            /**\n             * Send a request synchronously and return a PSR-7 [@see ResponseInterface]\n             *\n             * @param RequestInterface $request\n             * @return ResponseInterface\n             */\n            public function send(RequestInterface $request): ResponseInterface\n            {\n                return new Response();\n            }\n\n            /**\n             * Send a request asynchronously\n             *\n             * The response callback must be called if any response is returned from the request, and the failure\n             * callback should only be executed if a request was not completed.\n             *\n             * The response callback should pass a PSR-7 [@see ResponseInterface] as the one and only argument. The\n             * failure callback should pass a [@see Throwable] as the one and only argument.\n             *\n             * @param RequestInterface $request\n             * @param callable $onResponse\n             * @param callable $onFailure\n             * @return void\n             */\n            public function sendAsync(RequestInterface $request, callable $onResponse, callable $onFailure): void\n            {\n            }\n\n            /**\n             * Calling this method should execute any enqueued requests asynchronously\n             *\n             * @return void\n             */\n            public function wait(): void\n            {\n            }\n        };\n\n        $retrofit = Retrofit::builder()\n            ->setBaseUrl('')\n            ->setHttpClient($clientStub)\n            ->setCacheDir($cacheDir)\n            ->enableCache()\n            ->build();\n        $count = $retrofit->createAll($srcDir);\n\n        $output->writeln(sprintf('<info>Compiled %s %s successfully</info>', $count, ($count === 1) ? 'class' : 'classes'));\n    }\n}\n"
  },
  {
    "path": "src/Converter.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\n/**\n * Interface Converter\n *\n * This base interface exists for very basic type awareness\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface Converter\n{\n}\n"
  },
  {
    "path": "src/ConverterFactory.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\nuse Tebru\\PhpType\\TypeToken;\n\n/**\n * Interface ConverterFactory\n *\n * Implementors should return implementations of converters for the types\n * that are supported, and null if the type is not supported.\n *\n * For example, if a converter does not convert types to strings, just return\n * null from the stringConverter() method.\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface ConverterFactory\n{\n    /**\n     * Return a [@see ResponseBodyConverter] or null\n     *\n     * @param TypeToken $type\n     * @return null|ResponseBodyConverter\n     */\n    public function responseBodyConverter(TypeToken $type): ?ResponseBodyConverter;\n\n    /**\n     * Return a [@see RequestBodyConverter] or null\n     *\n     * @param TypeToken $type\n     * @return null|RequestBodyConverter\n     */\n    public function requestBodyConverter(TypeToken $type): ?RequestBodyConverter;\n\n    /**\n     * Return a [@see StringConverter] or null\n     *\n     * @param TypeToken $type\n     * @return null|StringConverter\n     */\n    public function stringConverter(TypeToken $type): ?StringConverter;\n}\n"
  },
  {
    "path": "src/DefaultProxyFactoryAware.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\n/**\n * Any custom proxy factory that implements this interface will get an instance of the default\n * proxy factory through a setter. This is useful if you need to temporarily override how requests\n * are created for a subset of service methods, but for all other methods, you can delegate to\n * the default behavior.\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface DefaultProxyFactoryAware\n{\n    /**\n     * Set the default proxy factory\n     *\n     * @param ProxyFactory $proxyFactory\n     * @return void\n     */\n    public function setDefaultProxyFactory(ProxyFactory $proxyFactory): void;\n}\n"
  },
  {
    "path": "src/Exception/ResponseHandlingFailedException.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Exception;\n\nuse Psr\\Http\\Message\\RequestInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse RuntimeException;\nuse Throwable;\n\n/**\n * Class ResponseConversionFailedException\n *\n * This exception is thrown if there's an issue handling the response. It exists\n * in order to provide more information about the request/response to the consumer in\n * the event of a failure. It signifies that an HTTP request was successful, but could\n * not be properly handled.\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass ResponseHandlingFailedException extends RuntimeException\n{\n    /**\n     * @var RequestInterface\n     */\n    private $request;\n\n    /**\n     * @var ResponseInterface\n     */\n    private $response;\n\n    /**\n     * Constructor\n     *\n     * @param RequestInterface $request\n     * @param ResponseInterface $response\n     * @param string $message\n     * @param Throwable|null $previous\n     */\n    public function __construct(\n        RequestInterface $request,\n        ResponseInterface $response,\n        string $message = '',\n        Throwable $previous = null\n    ) {\n        parent::__construct($message, 0, $previous);\n\n        $this->request = $request;\n        $this->response = $response;\n    }\n\n    /**\n     * @return RequestInterface\n     */\n    public function getRequest(): RequestInterface\n    {\n        return $this->request;\n    }\n\n    /**\n     * @return ResponseInterface\n     */\n    public function getResponse(): ResponseInterface\n    {\n        return $this->response;\n    }\n}\n"
  },
  {
    "path": "src/Finder/ServiceResolver.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Finder;\n\nuse RecursiveDirectoryIterator;\nuse RecursiveIteratorIterator;\nuse RecursiveRegexIterator;\nuse RegexIterator;\n\n/**\n * Class ServiceResolver\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass ServiceResolver\n{\n    private const ANNOTATION_REGEX = '/Tebru\\\\\\\\Retrofit\\\\\\\\Annotation/';\n    private const FILE_REGEX = '/^.+\\.php$/i';\n    private const INTERFACE_REGEX = '/^interface\\s+([\\w\\\\\\\\]+)[\\s{\\n]?/m';\n    private const NAMESPACE_REGEX = '/^namespace\\s+([\\w\\\\\\\\]+)/m';\n    \n    /**\n     * Find all services given a source directory\n     *\n     * @param string $srcDir\n     * @return string[]\n     */\n    public function findServices(string $srcDir): array\n    {\n        $directory = new RecursiveDirectoryIterator($srcDir);\n        $iterator = new RecursiveIteratorIterator($directory);\n        $files = new RegexIterator($iterator, self::FILE_REGEX, RecursiveRegexIterator::GET_MATCH);\n\n        $services = [];\n        foreach ($files as $file) {\n            $fileString = file_get_contents($file[0]);\n            \n            $annotationMatchesFound = preg_match(self::ANNOTATION_REGEX, $fileString);\n\n            if (!$annotationMatchesFound) {\n                continue;\n            }\n\n            $interfaceMatchesFound = preg_match(self::INTERFACE_REGEX, $fileString, $interfaceMatches);\n\n            if (!$interfaceMatchesFound) {\n                continue;\n            }\n            \n            $namespaceMatchesFound = preg_match(self::NAMESPACE_REGEX, $fileString, $namespaceMatches);\n\n            $className = '';\n\n            if ($namespaceMatchesFound) {\n                $className .= $namespaceMatches[1];\n            }\n\n            $className .= '\\\\' . $interfaceMatches[1];\n\n            $services[] = $className;\n        }\n\n        return $services;\n    }\n}\n"
  },
  {
    "path": "src/Http/MultipartBody.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Http;\n\nuse Psr\\Http\\Message\\StreamInterface;\n\nuse function GuzzleHttp\\Psr7\\stream_for;\n\n/**\n * Class MultipartBody\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class MultipartBody\n{\n    /**\n     * @var string\n     */\n    private $name;\n\n    /**\n     * @var StreamInterface\n     */\n    private $contents;\n\n    /**\n     * @var array\n     */\n    private $headers;\n\n    /**\n     * @var string\n     */\n    private $filename;\n\n    /**\n     * Constructor\n     *\n     * @param string $name\n     * @param mixed $contents\n     * @param array[] $headers\n     * @param null|string $filename\n     */\n    public function __construct(string $name, $contents, array $headers = [], ?string $filename = null)\n    {\n        $this->name = $name;\n        $this->contents = stream_for($contents);\n        $this->headers = $headers;\n        $this->filename = $filename;\n    }\n\n    /**\n     * @return string\n     */\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n    /**\n     * @return StreamInterface\n     */\n    public function getContents(): StreamInterface\n    {\n        return $this->contents;\n    }\n\n    /**\n     * @return array\n     */\n    public function getHeaders(): array\n    {\n        return $this->headers;\n    }\n\n    /**\n     * @return string|null\n     */\n    public function getFilename(): ?string\n    {\n        return $this->filename;\n    }\n}\n"
  },
  {
    "path": "src/HttpClient.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\nuse Psr\\Http\\Message\\RequestInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\n\n/**\n * Interface HttpClient\n *\n * Implementors will make http requests using PSR-7 request and response objects\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface HttpClient\n{\n    /**\n     * Send a request synchronously and return a PSR-7 [@see ResponseInterface]\n     *\n     * @param RequestInterface $request\n     * @return ResponseInterface\n     */\n    public function send(RequestInterface $request): ResponseInterface;\n\n    /**\n     * Send a request asynchronously\n     *\n     * The response callback must be called if any response is returned from the request, and the failure\n     * callback should only be executed if a request was not completed.\n     *\n     * The response callback should pass a PSR-7 [@see ResponseInterface] as the one and only argument. The\n     * failure callback should pass a [@see Throwable] as the one and only argument.\n     *\n     * @param RequestInterface $request\n     * @param callable $onResponse\n     * @param callable $onFailure\n     * @return void\n     */\n    public function sendAsync(RequestInterface $request, callable $onResponse, callable $onFailure): void;\n\n    /**\n     * Calling this method should execute any enqueued requests asynchronously\n     *\n     * @return void\n     */\n    public function wait(): void;\n}\n"
  },
  {
    "path": "src/Internal/AnnotationHandler/BodyAnnotHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\AnnotationHandler;\n\nuse InvalidArgumentException;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\BodyParamHandler;\nuse Tebru\\Retrofit\\RequestBodyConverter;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\n\n/**\n * Class BodyAnnotHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class BodyAnnotHandler implements AnnotationHandler\n{\n    /**\n     * Sets the request method as 'json' and adds a parameter handler for body json data\n     *\n     * @param AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param Converter|RequestBodyConverter $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     * @throws \\InvalidArgumentException\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void {\n        if (!$converter instanceof RequestBodyConverter) {\n            throw new InvalidArgumentException(sprintf(\n                'Retrofit: Converter must be a RequestBodyConverter, %s found',\n                \\gettype($converter)\n            ));\n        }\n\n        $serviceMethodBuilder->setIsJson();\n        $serviceMethodBuilder->addParameterHandler($index, new BodyParamHandler($converter));\n    }\n}\n"
  },
  {
    "path": "src/Internal/AnnotationHandler/FieldAnnotHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\AnnotationHandler;\n\nuse InvalidArgumentException;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\Annotation\\Encodable;\nuse Tebru\\Retrofit\\Annotation\\Field;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\FieldParamHandler;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class FieldAnnotHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class FieldAnnotHandler implements AnnotationHandler\n{\n    /**\n     * Set the content type to form encoded and adds a parameter handler for individual fields\n     *\n     * @param Field|AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param Converter|StringConverter $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     * @throws \\InvalidArgumentException\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void {\n        if (!$annotation instanceof Encodable) {\n            throw new InvalidArgumentException('Retrofit: Annotation must be encodable');\n        }\n\n        if (!$converter instanceof StringConverter) {\n            throw new InvalidArgumentException(sprintf(\n                'Retrofit: Converter must be a StringConverter, %s found',\n                \\gettype($converter)\n            ));\n        }\n\n        $serviceMethodBuilder->setIsFormUrlEncoded();\n        $serviceMethodBuilder->addParameterHandler(\n            $index,\n            new FieldParamHandler($converter, $annotation->getValue(), $annotation->isEncoded())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Internal/AnnotationHandler/FieldMapAnnotHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\AnnotationHandler;\n\nuse InvalidArgumentException;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\Annotation\\Encodable;\nuse Tebru\\Retrofit\\Annotation\\FieldMap;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\FieldMapParamHandler;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class FieldMapAnnotHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class FieldMapAnnotHandler implements AnnotationHandler\n{\n    /**\n     * Set the content type to form encoded and adds a parameter handler for a field map\n     *\n     * @param FieldMap|AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param Converter|StringConverter $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     * @throws \\InvalidArgumentException\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void {\n        if (!$annotation instanceof Encodable) {\n            throw new InvalidArgumentException('Retrofit: Annotation must be encodable');\n        }\n\n        if (!$converter instanceof StringConverter) {\n            throw new InvalidArgumentException(sprintf(\n                'Retrofit: Converter must be a StringConverter, %s found',\n                \\gettype($converter)\n            ));\n        }\n\n        $serviceMethodBuilder->setIsFormUrlEncoded();\n        $serviceMethodBuilder->addParameterHandler(\n            $index,\n            new FieldMapParamHandler($converter, $annotation->isEncoded())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Internal/AnnotationHandler/HeaderAnnotHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\AnnotationHandler;\n\nuse InvalidArgumentException;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\HeaderParamHandler;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class HeaderAnnotHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class HeaderAnnotHandler implements AnnotationHandler\n{\n    /**\n     * Adds header param handler\n     *\n     * @param AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param Converter|StringConverter $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     * @throws \\InvalidArgumentException\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void {\n        if (!$converter instanceof StringConverter) {\n            throw new InvalidArgumentException(sprintf(\n                'Retrofit: Converter must be a StringConverter, %s found',\n                \\gettype($converter)\n            ));\n        }\n\n        $serviceMethodBuilder->addParameterHandler(\n            $index,\n            new HeaderParamHandler($converter, $annotation->getValue())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Internal/AnnotationHandler/HeaderMapAnnotHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\AnnotationHandler;\n\nuse InvalidArgumentException;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\HeaderMapParamHandler;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class HeaderMapAnnotHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class HeaderMapAnnotHandler implements AnnotationHandler\n{\n    /**\n     * Adds header map param handler\n     *\n     * @param AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param null|Converter|StringConverter $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     * @throws \\InvalidArgumentException\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void {\n        if (!$converter instanceof StringConverter) {\n            throw new InvalidArgumentException(sprintf(\n                'Retrofit: Converter must be a StringConverter, %s found',\n                \\gettype($converter)\n            ));\n        }\n\n        $serviceMethodBuilder->addParameterHandler($index, new HeaderMapParamHandler($converter));\n    }\n}\n"
  },
  {
    "path": "src/Internal/AnnotationHandler/HeadersAnnotHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\AnnotationHandler;\n\nuse InvalidArgumentException;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\n\n/**\n * Class HeadersAnnotHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class HeadersAnnotHandler implements AnnotationHandler\n{\n    /**\n     * Set each header to request\n     *\n     * @param AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param Converter|null $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     * @throws \\InvalidArgumentException\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void {\n        if ($converter !== null) {\n            throw new InvalidArgumentException(sprintf(\n                'Retrofit: Converter must be null, %s found',\n                \\gettype($converter)\n            ));\n        }\n\n        /** @var string[] $headerList */\n        $headerList = $annotation->getValue();\n        foreach ($headerList as $name => $header) {\n            $serviceMethodBuilder->addHeader($name, $header);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Internal/AnnotationHandler/HttpRequestAnnotHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\AnnotationHandler;\n\nuse InvalidArgumentException;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\Annotation\\HttpRequest;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\n\n/**\n * Class HttpRequestAnnotHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class HttpRequestAnnotHandler implements AnnotationHandler\n{\n    /**\n     * Sets the request method, uri, and whether or not the request contains a body\n     *\n     * @param HttpRequest|AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param Converter|null $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     * @throws \\LogicException\n     * @throws \\InvalidArgumentException\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void {\n        if (!$annotation instanceof HttpRequest) {\n            throw new InvalidArgumentException('Retrofit: Annotation must be an HttpRequest');\n        }\n\n        if ($converter !== null) {\n            throw new InvalidArgumentException(sprintf(\n                'Retrofit: Converter must be null, %s found',\n                \\gettype($converter)\n            ));\n        }\n\n        $uri = $annotation->getValue();\n\n        $serviceMethodBuilder->setMethod($annotation->getType());\n        $serviceMethodBuilder->setPath($uri);\n\n        if (!$annotation->hasBody()) {\n            $serviceMethodBuilder->setHasBody($annotation->hasBody());\n        }\n    }\n}\n"
  },
  {
    "path": "src/Internal/AnnotationHandler/PartAnnotHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\AnnotationHandler;\n\nuse InvalidArgumentException;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\Annotation\\Part;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\PartParamHandler;\nuse Tebru\\Retrofit\\RequestBodyConverter;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\n\n/**\n * Class PartAnnotHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class PartAnnotHandler implements AnnotationHandler\n{\n    /**\n     * Handle an annotation, mutating the [@see ServiceMethodBuilder] based on the value\n     *\n     * @param Part|AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param Converter|RequestBodyConverter $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     * @throws \\InvalidArgumentException\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void {\n        if (!$annotation instanceof Part) {\n            throw new InvalidArgumentException('Retrofit: Annotation must be a Part');\n        }\n\n        if (!$converter instanceof RequestBodyConverter) {\n            throw new InvalidArgumentException(sprintf(\n                'Retrofit: Converter must be a RequestBodyConverter, %s found',\n                \\gettype($converter)\n            ));\n        }\n\n        $serviceMethodBuilder->setIsMultipart();\n        $serviceMethodBuilder->addParameterHandler(\n            $index,\n            new PartParamHandler($converter, $annotation->getValue(), $annotation->getEncoding())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Internal/AnnotationHandler/PartMapAnnotHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\AnnotationHandler;\n\nuse InvalidArgumentException;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\Annotation\\PartMap;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\PartMapParamHandler;\nuse Tebru\\Retrofit\\RequestBodyConverter;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\n\n/**\n * Class PartMapAnnotHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class PartMapAnnotHandler implements AnnotationHandler\n{\n    /**\n     * Add part map param handler\n     *\n     * @param PartMap|AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param Converter|RequestBodyConverter $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     * @throws \\InvalidArgumentException\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void {\n        if (!$annotation instanceof PartMap) {\n            throw new InvalidArgumentException('Retrofit: Annotation must be a PartMap');\n        }\n\n        if (!$converter instanceof RequestBodyConverter) {\n            throw new InvalidArgumentException(sprintf(\n                'Retrofit: Converter must be a RequestBodyConverter, %s found',\n                \\gettype($converter)\n            ));\n        }\n\n        $serviceMethodBuilder->setIsMultipart();\n        $serviceMethodBuilder->addParameterHandler(\n            $index,\n            new PartMapParamHandler($converter, $annotation->getEncoding())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Internal/AnnotationHandler/PathAnnotHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\AnnotationHandler;\n\nuse InvalidArgumentException;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\PathParamHandler;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class PathAnnotHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class PathAnnotHandler implements AnnotationHandler\n{\n    /**\n     * Add a path param handler\n     *\n     * @param AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param Converter|StringConverter $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     * @throws \\InvalidArgumentException\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void {\n        if (!$converter instanceof StringConverter) {\n            throw new InvalidArgumentException(sprintf(\n                'Retrofit: Converter must be a StringConverter, %s found',\n                \\gettype($converter)\n            ));\n        }\n\n        $serviceMethodBuilder->addParameterHandler($index, new PathParamHandler($converter, $annotation->getValue()));\n    }\n}\n"
  },
  {
    "path": "src/Internal/AnnotationHandler/QueryAnnotHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\AnnotationHandler;\n\nuse InvalidArgumentException;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\Annotation\\Encodable;\nuse Tebru\\Retrofit\\Annotation\\Query;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\QueryParamHandler;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class QueryAnnotHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class QueryAnnotHandler implements AnnotationHandler\n{\n    /**\n     * Add a query param handler\n     *\n     * @param Query|AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param Converter|StringConverter $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     * @throws \\InvalidArgumentException\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void {\n        if (!$annotation instanceof Encodable) {\n            throw new InvalidArgumentException('Retrofit: Annotation must be encodable');\n        }\n\n        if (!$converter instanceof StringConverter) {\n            throw new InvalidArgumentException(sprintf(\n                'Retrofit: Converter must be a StringConverter, %s found',\n                \\gettype($converter)\n            ));\n        }\n\n        $serviceMethodBuilder->addParameterHandler(\n            $index,\n            new QueryParamHandler($converter, $annotation->getValue(), $annotation->isEncoded())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Internal/AnnotationHandler/QueryMapAnnotHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\AnnotationHandler;\n\nuse InvalidArgumentException;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\Annotation\\Encodable;\nuse Tebru\\Retrofit\\Annotation\\QueryMap;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\QueryMapParamHandler;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class QueryMapAnnotHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class QueryMapAnnotHandler implements AnnotationHandler\n{\n    /**\n     * Add query map param handler\n     *\n     * @param QueryMap|AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param Converter|StringConverter $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     * @throws \\InvalidArgumentException\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void {\n        if (!$annotation instanceof Encodable) {\n            throw new InvalidArgumentException('Retrofit: Annotation must be encodable');\n        }\n\n        if (!$converter instanceof StringConverter) {\n            throw new InvalidArgumentException(sprintf(\n                'Retrofit: Converter must be a StringConverter, %s found',\n                \\gettype($converter)\n            ));\n        }\n\n        $serviceMethodBuilder->addParameterHandler(\n            $index,\n            new QueryMapParamHandler($converter, $annotation->isEncoded())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Internal/AnnotationHandler/QueryNameAnnotHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\AnnotationHandler;\n\nuse InvalidArgumentException;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\Annotation\\Encodable;\nuse Tebru\\Retrofit\\Annotation\\QueryName;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\QueryNameParamHandler;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class QueryNameAnnotHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class QueryNameAnnotHandler implements AnnotationHandler\n{\n    /**\n     * Add a query name param handler\n     *\n     * @param QueryName|AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param Converter|StringConverter $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     * @throws \\InvalidArgumentException\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void {\n        if (!$annotation instanceof Encodable) {\n            throw new InvalidArgumentException('Retrofit: Annotation must be encodable');\n        }\n\n        if (!$converter instanceof StringConverter) {\n            throw new InvalidArgumentException(sprintf(\n                'Retrofit: Converter must be a StringConverter, %s found',\n                \\gettype($converter)\n            ));\n        }\n\n        $serviceMethodBuilder->addParameterHandler(\n            $index,\n            new QueryNameParamHandler($converter, $annotation->isEncoded())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Internal/AnnotationHandler/UrlAnnotHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\AnnotationHandler;\n\nuse InvalidArgumentException;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\UrlParamHandler;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class UrlAnnotHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class UrlAnnotHandler implements AnnotationHandler\n{\n    /**\n     * Add url param handler\n     *\n     * @param AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param Converter|StringConverter $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     * @throws \\InvalidArgumentException\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void {\n        if (!$converter instanceof StringConverter) {\n            throw new InvalidArgumentException(sprintf(\n                'Retrofit: Converter must be a StringConverter, %s found',\n                \\gettype($converter)\n            ));\n        }\n\n        $serviceMethodBuilder->addParameterHandler($index, new UrlParamHandler($converter));\n    }\n}\n"
  },
  {
    "path": "src/Internal/AnnotationProcessor.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal;\n\nuse LogicException;\nuse ReflectionMethod;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\PhpType\\TypeToken;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\Internal\\Converter\\ConverterProvider;\nuse Tebru\\Retrofit\\Annotation\\ParameterAwareAnnotation;\nuse Tebru\\Retrofit\\RequestBodyConverter;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class AnnotationProcessor\n *\n * Given an array of handlers, process an [@see AbstractAnnotation]\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class AnnotationProcessor\n{\n    /**\n     * An array of annotation handlers\n     *\n     * @var AnnotationHandler[]\n     */\n    private $handlers;\n\n    /**\n     * Constructor\n     *\n     * @param AnnotationHandler[] $handlers\n     */\n    public function __construct(array $handlers)\n    {\n        $this->handlers = $handlers;\n    }\n\n    /**\n     * Accepts an annotation and delegates to an [@see AnnotationHandler]\n     *\n     * @param AbstractAnnotation $annotation\n     * @param ServiceMethodBuilder $serviceMethodBuilder\n     * @param ConverterProvider $converterProvider\n     * @param ReflectionMethod $reflectionMethod\n     * @throws \\LogicException\n     */\n    public function process(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ConverterProvider $converterProvider,\n        ReflectionMethod $reflectionMethod\n    ): void {\n        $name = $annotation->getName();\n\n        if (!isset($this->handlers[$name])) {\n            return;\n        }\n\n        $handler = $this->handlers[$name];\n        $converter = null;\n        $index = null;\n\n        if ($annotation instanceof ParameterAwareAnnotation) {\n            $index = $this->findMethodParameterIndex($reflectionMethod, $annotation->getVariableName());\n            $type = $this->getParameterType($reflectionMethod, $index);\n            $converter = $this->getConverter($annotation, $converterProvider, $type);\n        }\n\n        $handler->handle($annotation, $serviceMethodBuilder, $converter, $index);\n    }\n\n    /**\n     * Find the position of the method parameter\n     *\n     * @param ReflectionMethod $reflectionMethod\n     * @param string $name\n     * @return int\n     * @throws \\LogicException\n     */\n    private function findMethodParameterIndex(ReflectionMethod $reflectionMethod, string $name): int\n    {\n        $reflectionParameters = $reflectionMethod->getParameters();\n        foreach ($reflectionParameters as $index => $reflectionParameter) {\n            if ($reflectionParameter->name === $name) {\n                return $index;\n            }\n        }\n\n        throw new LogicException(sprintf(\n            'Retrofit: Could not find parameter named %s in %s::%s. Please double check that annotations are properly ' .\n            'referencing method parameters.',\n            $name,\n            $reflectionMethod->getDeclaringClass()->name,\n            $reflectionMethod->name\n        ));\n    }\n\n    /**\n     * Get the parameter type\n     *\n     * @param ReflectionMethod $reflectionMethod\n     * @param int $index\n     * @return TypeToken\n     * @throws \\LogicException\n     */\n    private function getParameterType(ReflectionMethod $reflectionMethod, int $index): TypeToken\n    {\n        $reflectionParameter = $reflectionMethod->getParameters()[$index];\n        $reflectionType = $reflectionParameter->getType();\n\n        if ($reflectionType === null) {\n            throw new LogicException(sprintf(\n                'Retrofit: Parameter type was not found for method %s::%s',\n                $reflectionMethod->getDeclaringClass()->name,\n                $reflectionMethod->name\n            ));\n        }\n\n        /** @noinspection ExceptionsAnnotatingAndHandlingInspection */\n        return new TypeToken($reflectionType->getName());\n    }\n\n    /**\n     * Get the converter from annotation converter class\n     *\n     * @param ParameterAwareAnnotation $annotation\n     * @param ConverterProvider $converterProvider\n     * @param TypeToken $type\n     * @return Converter\n     * @throws \\LogicException\n     */\n    private function getConverter(\n        ParameterAwareAnnotation $annotation,\n        ConverterProvider $converterProvider,\n        TypeToken $type\n    ): Converter {\n        switch ($annotation->converterType()) {\n            case RequestBodyConverter::class:\n                return $converterProvider->getRequestBodyConverter($type);\n            case StringConverter::class:\n                return $converterProvider->getStringConverter($type);\n        }\n\n        throw new LogicException(sprintf(\n            'Retrofit: Unable to handle converter of type %s. Please use RequestBodyConverter or StringConverter',\n            $annotation->converterType()\n        ));\n    }\n}\n"
  },
  {
    "path": "src/Internal/CacheProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal;\n\nuse Psr\\SimpleCache\\CacheInterface;\nuse Symfony\\Component\\Cache\\Adapter\\ArrayAdapter;\nuse Symfony\\Component\\Cache\\Adapter\\ChainAdapter;\nuse Symfony\\Component\\Cache\\Adapter\\NullAdapter;\nuse Symfony\\Component\\Cache\\Adapter\\PhpFilesAdapter;\nuse Symfony\\Component\\Cache\\Adapter\\Psr16Adapter;\nuse Symfony\\Component\\Cache\\Exception\\CacheException;\nuse Symfony\\Component\\Cache\\Psr16Cache;\nuse Symfony\\Component\\Cache\\Simple\\ArrayCache;\nuse Symfony\\Component\\Cache\\Simple\\ChainCache;\nuse Symfony\\Component\\Cache\\Simple\\NullCache;\nuse Symfony\\Component\\Cache\\Simple\\PhpFilesCache;\n\n/**\n * @codeCoverageIgnore\n */\nfinal class CacheProvider\n{\n    /**\n     * Create a \"file cache\", chained to a \"memory cache\" depending on symfony/cache version\n     *\n     * @param string $cacheDir\n     * @return CacheInterface\n     * @throws CacheException\n     */\n    public static function createFileCache(string $cacheDir): CacheInterface\n    {\n        // >= Symfony 4.3\n        if (class_exists('Symfony\\Component\\Cache\\Psr16Cache')) {\n            return new Psr16Cache(new ChainAdapter([\n                new Psr16Adapter(self::createMemoryCache()),\n                new PhpFilesAdapter('', 0, $cacheDir),\n            ]));\n        }\n\n        return new ChainCache([\n            self::createMemoryCache(),\n            new PhpFilesCache('', 0, $cacheDir)\n        ]);\n    }\n\n    /**\n     * Create a \"memory cache\" depending on symfony/cache version\n     * @return CacheInterface\n     */\n    public static function createMemoryCache(): CacheInterface\n    {\n        // >= Symfony 4.3\n        if (class_exists('Symfony\\Component\\Cache\\Psr16Cache')) {\n            return new Psr16Cache(new ArrayAdapter(0, false));\n        }\n\n        return new ArrayCache(0, false);\n    }\n\n    /**\n     * Create a \"null\" cache (for annotations) depending on symfony/cache version\n     *\n     * @return CacheInterface\n     */\n    public static function createNullCache(): CacheInterface\n    {\n        // >= Symfony 4.3\n        if (class_exists('Symfony\\Component\\Cache\\Psr16Cache')) {\n            return new Psr16Cache(new NullAdapter());\n        }\n\n        return new NullCache();\n    }\n}\n"
  },
  {
    "path": "src/Internal/CallAdapter/CallAdapterProvider.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\CallAdapter;\n\nuse LogicException;\nuse Tebru\\PhpType\\TypeToken;\nuse Tebru\\Retrofit\\CallAdapter;\nuse Tebru\\Retrofit\\CallAdapterFactory;\n\n/**\n * Class CallAdapterProvider\n *\n * Returns a [@see CallAdapterFactory] that can handle a given type\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class CallAdapterProvider\n{\n    /**\n     * @var CallAdapterFactory[]\n     */\n    private $callAdapterFactories;\n\n    /**\n     * Constructor\n     *\n     * @param CallAdapterFactory[] $callAdapterFactories\n     */\n    public function __construct(array $callAdapterFactories)\n    {\n        $this->callAdapterFactories = $callAdapterFactories;\n    }\n\n    /**\n     * Given a type, find the first available [@see CallAdapterFactory] and return it\n     *\n     * @param TypeToken $type\n     * @return CallAdapter\n     * @throws \\LogicException\n     */\n    public function get(TypeToken $type): CallAdapter\n    {\n        foreach ($this->callAdapterFactories as $callAdapterFactory) {\n            if ($callAdapterFactory->supports($type)) {\n                return $callAdapterFactory->create($type);\n            }\n        }\n\n        throw new LogicException(sprintf('Retrofit: Could not get call adapter for type %s', $type));\n    }\n}\n"
  },
  {
    "path": "src/Internal/CallAdapter/DefaultCallAdapter.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\CallAdapter;\n\nuse Tebru\\Retrofit\\Call;\nuse Tebru\\Retrofit\\CallAdapter;\n\n/**\n * Class DefaultCallAdapter\n *\n * By default, do not alter a [@see Call]\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class DefaultCallAdapter implements CallAdapter\n{\n    /**\n     * Accepts a [@see Call] and converts it to the appropriate type\n     *\n     * @param Call $call\n     * @return Call\n     */\n    public function adapt(Call $call): Call\n    {\n        return $call;\n    }\n}\n"
  },
  {
    "path": "src/Internal/CallAdapter/DefaultCallAdapterFactory.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\CallAdapter;\n\nuse Tebru\\PhpType\\TypeToken;\nuse Tebru\\Retrofit\\Call;\nuse Tebru\\Retrofit\\CallAdapter;\nuse Tebru\\Retrofit\\CallAdapterFactory;\n\n/**\n * Class DefaultCallAdapterFactory\n *\n * Only supports [@see Call] instances\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class DefaultCallAdapterFactory implements CallAdapterFactory\n{\n    /**\n     * Returns true if the factory supports this type\n     *\n     * @param TypeToken $type\n     * @return bool\n     */\n    public function supports(TypeToken $type): bool\n    {\n        return $type->isA(Call::class);\n    }\n\n    /**\n     * Create a new factory from type\n     *\n     * @param TypeToken $type\n     * @return CallAdapter\n     */\n    public function create(TypeToken $type): CallAdapter\n    {\n        return new DefaultCallAdapter();\n    }\n}\n"
  },
  {
    "path": "src/Internal/Converter/ConverterProvider.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\Converter;\n\nuse LogicException;\nuse Tebru\\PhpType\\TypeToken;\nuse Tebru\\Retrofit\\ConverterFactory;\nuse Tebru\\Retrofit\\RequestBodyConverter;\nuse Tebru\\Retrofit\\ResponseBodyConverter;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class ConverterProvider\n *\n * Returns a [@see ConverterFactory] that matches the provided type\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class ConverterProvider\n{\n    /**\n     * A cache of [@see ResponseBodyConverter]'s\n     *\n     * @var ResponseBodyConverter[]\n     */\n    private $responseBodyConverters = [];\n\n    /**\n     * A cache of [@see RequestBodyConverter]'s\n     *\n     * @var RequestBodyConverter[]\n     */\n    private $requestBodyConverters = [];\n\n    /**\n     * A cache of [@see StringConverter]'s\n     *\n     * @var StringConverter[]\n     */\n    private $stringConverters = [];\n\n    /**\n     * An array of [@see ConverterFactory]'s\n     *\n     * @var ConverterFactory[]\n     */\n    private $converterFactories;\n\n    /**\n     * Constructor\n     *\n     * @param ConverterFactory[] $factories\n     */\n    public function __construct(array $factories)\n    {\n        $this->converterFactories = array_values($factories);\n    }\n\n    /**\n     * Get a response body converter for type\n     *\n     * @param TypeToken $type\n     * @return ResponseBodyConverter\n     * @throws LogicException\n     */\n    public function getResponseBodyConverter(TypeToken $type): ResponseBodyConverter\n    {\n        $key = (string)$type;\n        if (isset($this->responseBodyConverters[$key])) {\n            return $this->responseBodyConverters[$key];\n        }\n\n        foreach ($this->converterFactories as $converterFactory) {\n            $converter = $converterFactory->responseBodyConverter($type);\n            if ($converter === null) {\n                continue;\n            }\n\n            $this->responseBodyConverters[$key] = $converter;\n\n            return $converter;\n        }\n\n        throw new LogicException(sprintf(\n            'Retrofit: Could not get response body converter for type %s',\n            $type\n        ));\n    }\n\n    /**\n     * Get a request body converter for type\n     *\n     * @param TypeToken $type\n     * @return RequestBodyConverter\n     * @throws \\LogicException\n     */\n    public function getRequestBodyConverter(TypeToken $type): RequestBodyConverter\n    {\n        $key = (string)$type;\n        if (isset($this->requestBodyConverters[$key])) {\n            return $this->requestBodyConverters[$key];\n        }\n\n        foreach ($this->converterFactories as $converterFactory) {\n            $converter = $converterFactory->requestBodyConverter($type);\n            if ($converter === null) {\n                continue;\n            }\n\n            $this->requestBodyConverters[$key] = $converter;\n\n            return $converter;\n        }\n\n        throw new LogicException(sprintf(\n            'Retrofit: Could not get request body converter for type %s',\n            $type\n        ));\n    }\n\n    /**\n     * Get a string converter for type\n     *\n     * @param TypeToken $type\n     * @return StringConverter\n     * @throws \\LogicException\n     */\n    public function getStringConverter(TypeToken $type): StringConverter\n    {\n        $key = (string)$type;\n        if (isset($this->stringConverters[$key])) {\n            return $this->stringConverters[$key];\n        }\n\n        foreach ($this->converterFactories as $converterFactory) {\n            $converter = $converterFactory->stringConverter($type);\n            if ($converter === null) {\n                continue;\n            }\n\n            $this->stringConverters[$key] = $converter;\n\n            return $converter;\n        }\n\n        throw new LogicException(sprintf(\n            'Retrofit: Could not get string converter for type %s',\n            $type\n        ));\n    }\n}\n"
  },
  {
    "path": "src/Internal/Converter/DefaultConverterFactory.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\Converter;\n\nuse Psr\\Http\\Message\\StreamInterface;\nuse Tebru\\PhpType\\TypeToken;\nuse Tebru\\Retrofit\\ConverterFactory;\nuse Tebru\\Retrofit\\RequestBodyConverter;\nuse Tebru\\Retrofit\\ResponseBodyConverter;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class DefaultConverterFactory\n *\n * Creates default converters\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class DefaultConverterFactory implements ConverterFactory\n{\n    /**\n     * Return default converter if type is a stream\n     *\n     * @param TypeToken $type\n     * @return null|ResponseBodyConverter\n     */\n    public function responseBodyConverter(TypeToken $type): ?ResponseBodyConverter\n    {\n        if (!$type->isA(StreamInterface::class)) {\n            return null;\n        }\n\n        return new DefaultResponseBodyConverter();\n    }\n\n    /**\n     * Return default converter if type is a stream\n     *\n     * @param TypeToken $type\n     * @return null|RequestBodyConverter\n     */\n    public function requestBodyConverter(TypeToken $type): ?RequestBodyConverter\n    {\n        if (!$type->isA(StreamInterface::class)) {\n            return null;\n        }\n\n        return new DefaultRequestBodyConverter();\n    }\n\n    /**\n     * Return default string converter for any type\n     *\n     * If the type is a string already, use a converter that doesn't do\n     * any type checking.\n     *\n     * @param TypeToken $type\n     * @return null|StringConverter\n     */\n    public function stringConverter(TypeToken $type): ?StringConverter\n    {\n        if ($type->isString()) {\n            return new NoopStringConverter();\n        }\n\n        return new DefaultStringConverter();\n    }\n}\n"
  },
  {
    "path": "src/Internal/Converter/DefaultRequestBodyConverter.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\Converter;\n\nuse Psr\\Http\\Message\\StreamInterface;\nuse Tebru\\Retrofit\\RequestBodyConverter;\n\n/**\n * Class DefaultRequestBodyConverter\n *\n * Noop, defaults to returning stream\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class DefaultRequestBodyConverter implements RequestBodyConverter\n{\n    /**\n     * The value here should already be a stream, so we can return it\n     *\n     * @param mixed $value\n     * @return StreamInterface\n     */\n    public function convert($value): StreamInterface\n    {\n        return $value;\n    }\n}\n"
  },
  {
    "path": "src/Internal/Converter/DefaultResponseBodyConverter.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\Converter;\n\nuse Psr\\Http\\Message\\StreamInterface;\nuse Tebru\\Retrofit\\ResponseBodyConverter;\n\n/**\n * Class DefaultResponseBodyConverter\n *\n * Noop, returns response body stream\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class DefaultResponseBodyConverter implements ResponseBodyConverter\n{\n    /**\n     * By default, returns the stream\n     *\n     * @param StreamInterface $value\n     * @return StreamInterface\n     */\n    public function convert(StreamInterface $value): StreamInterface\n    {\n        return $value;\n    }\n}\n"
  },
  {
    "path": "src/Internal/Converter/DefaultStringConverter.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\Converter;\n\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class DefaultStringConverter\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class DefaultStringConverter implements StringConverter\n{\n    /**\n     * Convert any supported value to a string\n     *\n     * @param mixed $value\n     * @return string\n     */\n    public function convert($value): string\n    {\n        // if it's an array or object, just serialize it\n        if (\\is_array($value) || \\is_object($value)) {\n            return serialize($value);\n        }\n\n        if ($value === true) {\n            return 'true';\n        }\n\n        if ($value === false) {\n            return 'false';\n        }\n\n        return (string)$value;\n    }\n}\n"
  },
  {
    "path": "src/Internal/Converter/NoopStringConverter.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\Converter;\n\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class NoopStringConverter\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass NoopStringConverter implements StringConverter\n{\n    /**\n     * Only types that are known to be strings should be passed to this converter,\n     * so we can just return the value.\n     *\n     * @param string $value\n     * @return string\n     */\n    public function convert($value): string\n    {\n        return $value;\n    }\n}\n"
  },
  {
    "path": "src/Internal/DefaultProxyFactory.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal;\n\nuse InvalidArgumentException;\nuse LogicException;\nuse PhpParser\\BuilderFactory;\nuse PhpParser\\Node\\Expr;\nuse PhpParser\\Node\\Expr\\Array_;\nuse PhpParser\\Node\\Expr\\ConstFetch;\nuse PhpParser\\Node\\Expr\\FuncCall;\nuse PhpParser\\Node\\Expr\\MethodCall;\nuse PhpParser\\Node\\Expr\\Variable;\nuse PhpParser\\Node\\Name;\nuse PhpParser\\Node\\NullableType;\nuse PhpParser\\Node\\Scalar\\DNumber;\nuse PhpParser\\Node\\Scalar\\LNumber;\nuse PhpParser\\Node\\Scalar\\String_;\nuse PhpParser\\Node\\Stmt\\Return_;\nuse PhpParser\\PrettyPrinterAbstract;\nuse ReflectionClass;\nuse RuntimeException;\nuse Tebru\\PhpType\\TypeToken;\nuse Tebru\\Retrofit\\HttpClient;\nuse Tebru\\Retrofit\\Internal\\ServiceMethod\\ServiceMethodFactory;\nuse Tebru\\Retrofit\\Proxy;\nuse Tebru\\Retrofit\\Proxy\\AbstractProxy;\nuse Tebru\\Retrofit\\ProxyFactory;\n\n/**\n * Class DefaultProxyFactory\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class DefaultProxyFactory implements ProxyFactory\n{\n    public const PROXY_PREFIX = 'Tebru\\Retrofit\\Proxy\\\\';\n\n    /**\n     * @var BuilderFactory\n     */\n    private $builderFactory;\n\n    /**\n     * @var PrettyPrinterAbstract\n     */\n    private $printer;\n\n    /**\n     * @var ServiceMethodFactory\n     */\n    private $serviceMethodFactory;\n\n    /**\n     * @var HttpClient\n     */\n    private $httpClient;\n\n    /**\n     * @var Filesystem\n     */\n    private $filesystem;\n\n    /**\n     * @var bool\n     */\n    private $enableCache;\n\n    /**\n     * @var string\n     */\n    private $cacheDir;\n\n    /**\n     * Constructor\n     *\n     * @param BuilderFactory $builderFactory\n     * @param PrettyPrinterAbstract $printer\n     * @param ServiceMethodFactory $serviceMethodFactory\n     * @param HttpClient $httpClient\n     * @param Filesystem $filesystem\n     * @param bool $enableCache\n     * @param string $cacheDir\n     */\n    public function __construct(\n        BuilderFactory $builderFactory,\n        PrettyPrinterAbstract $printer,\n        ServiceMethodFactory $serviceMethodFactory,\n        HttpClient $httpClient,\n        Filesystem $filesystem,\n        bool $enableCache,\n        string $cacheDir\n    ) {\n        $this->builderFactory = $builderFactory;\n        $this->printer = $printer;\n        $this->serviceMethodFactory = $serviceMethodFactory;\n        $this->httpClient = $httpClient;\n        $this->filesystem = $filesystem;\n        $this->enableCache = $enableCache;\n        $this->cacheDir = $cacheDir;\n    }\n\n    /**\n     * Create a new proxy class given an interface name. This returns a class\n     * in a string to be cached.\n     *\n     * @param string $service\n     * @return Proxy\n     * @throws \\RuntimeException\n     * @throws \\LogicException\n     * @throws \\Tebru\\PhpType\\Exception\\MalformedTypeException\n     * @throws \\InvalidArgumentException\n     */\n    public function create(string $service): ?Proxy\n    {\n        $className = self::PROXY_PREFIX.$service;\n        if ($this->enableCache && class_exists($className)) {\n            return new $className($this->serviceMethodFactory, $this->httpClient);\n        }\n\n        if (!$this->enableCache && class_exists($className, false)) {\n            return new $className($this->serviceMethodFactory, $this->httpClient);\n        }\n\n        if (!interface_exists($service)) {\n            throw new InvalidArgumentException(sprintf('Retrofit: %s is expected to be an interface', $service));\n        }\n\n        /** @noinspection ExceptionsAnnotatingAndHandlingInspection */\n        $reflectionClass = new ReflectionClass($service);\n        $builder = $this->builderFactory\n            ->class($reflectionClass->getShortName())\n            ->extend('\\\\'.AbstractProxy::class)\n            ->implement('\\\\'.$reflectionClass->name);\n\n        foreach ($reflectionClass->getMethods() as $reflectionMethod) {\n            $methodBuilder = $this->builderFactory\n                ->method($reflectionMethod->name)\n                ->makePublic();\n\n            if ($reflectionMethod->isStatic()) {\n                $methodBuilder->makeStatic();\n            }\n\n            $defaultValues = [];\n\n            foreach ($reflectionMethod->getParameters() as $reflectionParameter) {\n                $paramBuilder = $this->builderFactory->param($reflectionParameter->name);\n\n                if ($reflectionParameter->isDefaultValueAvailable()) {\n                    $paramBuilder->setDefault($reflectionParameter->getDefaultValue());\n                }\n\n                if ($reflectionParameter->getType() === null) {\n                    throw new LogicException(sprintf(\n                        'Retrofit: Parameter types are required. None found for parameter %s in %s::%s()',\n                        $reflectionParameter->name,\n                        $reflectionClass->name,\n                        $reflectionMethod->name\n                    ));\n                }\n\n                $reflectionTypeName = $reflectionParameter->getType()->getName();\n                if ((new TypeToken($reflectionTypeName))->isObject()) {\n                    $reflectionTypeName = '\\\\'.$reflectionTypeName;\n                }\n\n                $type = $reflectionParameter->getType()->allowsNull() ? new NullableType($reflectionTypeName): $reflectionTypeName;\n                $paramBuilder->setTypeHint($type);\n\n                if ($reflectionParameter->isPassedByReference()) {\n                    $paramBuilder->makeByRef();\n                }\n\n                if ($reflectionParameter->isVariadic()) {\n                    $paramBuilder->makeVariadic();\n                }\n\n                $methodBuilder->addParam($paramBuilder->getNode());\n\n                // set all default values\n                // if a method is called with two few arguments, a native exception will be thrown\n                // so we can safely use null as a placeholder here.\n                $defaultValues[] = $reflectionParameter->isDefaultValueAvailable()\n                    ? $reflectionParameter->getDefaultValue()\n                    : null;\n            }\n\n            if (!$reflectionMethod->hasReturnType()) {\n                throw new LogicException(sprintf(\n                    'Retrofit: Method return types are required. None found for %s::%s()',\n                    $reflectionClass->name,\n                    $reflectionMethod->name\n                ));\n            }\n\n            /** @noinspection NullPointerExceptionInspection */\n            $methodBuilder->setReturnType('\\\\'.$reflectionMethod->getReturnType()->getName());\n\n            $defaultNodes = $this->mapArray($defaultValues);\n\n            $methodBuilder->addStmt(\n                new Return_(\n                    new MethodCall(\n                        new Variable('this'),\n                        '__handleRetrofitRequest',\n                        [\n                            new String_($reflectionClass->name),\n                            new ConstFetch(new Name('__FUNCTION__')),\n                            new FuncCall(new Name('func_get_args')),\n                            new Array_($defaultNodes)\n                        ]\n                    )\n                )\n            );\n\n            $builder->addStmt($methodBuilder->getNode());\n        }\n\n        $namespaceBuilder = $this->builderFactory\n            ->namespace(self::PROXY_PREFIX.$reflectionClass->getNamespaceName())\n            ->addStmt($builder);\n\n        $source = $this->printer->prettyPrint([$namespaceBuilder->getNode()]);\n\n        eval($source);\n\n        if (!$this->enableCache) {\n            return new $className($this->serviceMethodFactory, $this->httpClient);\n        }\n\n        $directory = $this->cacheDir.DIRECTORY_SEPARATOR.$reflectionClass->getNamespaceName();\n        $directory = str_replace('\\\\', DIRECTORY_SEPARATOR, $directory);\n        $filename = $directory.DIRECTORY_SEPARATOR.$reflectionClass->getShortName().'.php';\n\n        $class = '<?php'.PHP_EOL.PHP_EOL.$source;\n        if (!$this->filesystem->makeDirectory($directory)) {\n            throw new RuntimeException(\n                sprintf(\n                    'Retrofit: There was an issue creating the cache directory: %s',\n                    $directory\n                )\n            );\n        }\n\n        if (!$this->filesystem->put($filename, $class)) {\n            throw new RuntimeException(sprintf('Retrofit: There was an issue writing proxy class to: %s', $filename));\n        }\n\n        return new $className($this->serviceMethodFactory, $this->httpClient);\n    }\n\n    /**\n     * Convert array to an array of [@see Expr] to add to builder\n     *\n     * @param array $array\n     * @return Expr[]\n     */\n    private function mapArray(array $array): array\n    {\n        // for each element in the array, create an Expr object\n        $values = array_values(array_map(function ($value) {\n            $type = TypeToken::createFromVariable($value);\n            switch ($type) {\n                case TypeToken::STRING:\n                    return new String_($value);\n                case TypeToken::INTEGER:\n                    return new LNumber($value);\n                case TypeToken::FLOAT:\n                    return new DNumber($value);\n                case TypeToken::BOOLEAN:\n                    return $value === true ? new ConstFetch(new Name('true')) : new ConstFetch(new Name('false'));\n                case TypeToken::HASH:\n                    // recurse if array contains an array\n                    return new Array_($this->mapArray($value));\n                case TypeToken::NULL:\n                    return new ConstFetch(new Name('null'));\n            }\n        }, $array));\n\n        $keys = \\array_keys($array);\n        $isNumericKeys = \\count(\\array_filter($keys, '\\is_string')) === 0;\n\n        // a 0-indexed array can be returned as-is\n        if ($isNumericKeys) {\n            return $values;\n        }\n\n        // if we're dealing with an associative array, run the keys through the mapper\n        $keys = $this->mapArray($keys);\n\n        // create an array of ArrayItem objects for an associative array\n        $items = [];\n        foreach ($values as $index => $value) {\n            $items[] = new Expr\\ArrayItem($value, $keys[$index]);\n        }\n\n        return $items;\n    }\n}\n"
  },
  {
    "path": "src/Internal/Filesystem.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal;\n\n/**\n * A light wrapper around filesystem commands\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass Filesystem\n{\n    /**\n     * Wraps the php mkdir() function, but defaults to recursive directory creation\n     *\n     * @param string $pathname\n     * @param int $mode\n     * @param bool $recursive\n     * @return bool\n     */\n    public function makeDirectory(string $pathname, int $mode = 0777, bool $recursive = true): bool\n    {\n        return !(!@mkdir($pathname, $mode, $recursive) && !is_dir($pathname));\n    }\n\n    /**\n     * Write contents to file\n     *\n     * @param string $filename\n     * @param string $contents\n     * @return bool\n     */\n    public function put(string $filename, string $contents): bool\n    {\n        $written = file_put_contents($filename, $contents);\n\n        return !($written === 0);\n    }\n}\n"
  },
  {
    "path": "src/Internal/HttpClientCall.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal;\n\nuse Psr\\Http\\Message\\RequestInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Tebru\\Retrofit\\Call;\nuse Tebru\\Retrofit\\Exception\\ResponseHandlingFailedException;\nuse Tebru\\Retrofit\\HttpClient;\nuse Tebru\\Retrofit\\Response;\nuse Throwable;\n\n/**\n * Class HttpClientCall\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class HttpClientCall implements Call\n{\n    /**\n     * A retrofit http client implementation\n     *\n     * @var HttpClient\n     */\n    private $client;\n\n    /**\n     * A web service resource as a method model\n     *\n     * @var ServiceMethod\n     */\n    private $serviceMethod;\n\n    /**\n     * The runtime arguments that a request should be constructed with\n     *\n     * @var array\n     */\n    private $args;\n\n    /**\n     * The constructed request\n     *\n     * @var RequestInterface\n     */\n    private $request;\n\n    /**\n     * Constructor\n     *\n     * @param HttpClient $client\n     * @param ServiceMethod $serviceMethod\n     * @param array $args\n     */\n    public function __construct(HttpClient $client, ServiceMethod $serviceMethod, array $args)\n    {\n        $this->client = $client;\n        $this->serviceMethod = $serviceMethod;\n        $this->args = $args;\n    }\n\n    /**\n     * Execute request synchronously\n     *\n     * A [@see Response] will be returned\n     *\n     * @return Response\n     */\n    public function execute(): Response\n    {\n        $response = $this->client->send($this->request());\n\n        return $this->createResponse($response);\n    }\n\n    /**\n     * Execute request asynchronously\n     *\n     * This method accepts two optional callbacks.\n     *\n     * onResponse() will be called for any request that gets a response,\n     * whether it was successful or not. It will send a [@see Response] as\n     * the parameter.\n     *\n     * onFailure() will be called in the event a network request failed. It\n     * will send the [@see Throwable] that was encountered.\n     *\n     * Example of method signatures:\n     *\n     * $call->enqueue(\n     *     function (\\Tebru\\Retrofit\\Call $call, \\Tebru\\Retrofit\\Response $response) {},\n     *     function (\\Throwable $throwable) {}\n     * );\n     *\n     * @param callable $onResponse On any response\n     * @param callable $onFailure On any network request failure\n     * @return Call\n     * @throws \\LogicException\n     */\n    public function enqueue(?callable $onResponse = null, ?callable $onFailure = null): Call\n    {\n        $this->client->sendAsync(\n            $this->request(),\n            function (ResponseInterface $response) use ($onResponse) {\n                if ($onResponse !== null) {\n                    $onResponse($this->createResponse($response));\n                }\n            },\n            function (Throwable $throwable) use ($onFailure) {\n                if ($onFailure === null) {\n                    throw $throwable;\n                }\n\n                $onFailure($throwable);\n            }\n        );\n\n        return $this;\n    }\n\n    /**\n     * When making requests asynchronously, call wait() to execute the requests\n     *\n     * @return void\n     */\n    public function wait(): void\n    {\n        $this->client->wait();\n    }\n\n    /**\n     * Get the PSR-7 request\n     *\n     * @return RequestInterface\n     */\n    public function request(): RequestInterface\n    {\n        if ($this->request === null) {\n            $this->request = $this->serviceMethod->toRequest($this->args);\n        }\n\n        return $this->request;\n    }\n\n    /**\n     * Create a [@see Response] from a PSR-7 response\n     *\n     * @param ResponseInterface $response\n     * @return RetrofitResponse\n     */\n    private function createResponse(ResponseInterface $response): RetrofitResponse\n    {\n        $code = $response->getStatusCode();\n        if ($code >= 200 && $code < 300) {\n            try {\n                $responseBody = $this->serviceMethod->toResponseBody($response);\n            } catch (Throwable $throwable) {\n                throw new ResponseHandlingFailedException(\n                    $this->request(),\n                    $response,\n                    'Retrofit: Could not convert response body',\n                    $throwable\n                );\n            }\n\n            return new RetrofitResponse($response, $responseBody, null);\n        }\n\n        try {\n            $errorBody = $this->serviceMethod->toErrorBody($response);\n        } catch (Throwable $throwable) {\n            throw new ResponseHandlingFailedException(\n                $this->request(),\n                $response,\n                'Retrofit: Could not convert error body',\n                $throwable\n            );\n        }\n\n        return new RetrofitResponse($response, null, $errorBody);\n    }\n}\n"
  },
  {
    "path": "src/Internal/ParameterHandler/AbstractParameterHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ParameterHandler;\n\nuse Generator;\nuse RuntimeException;\nuse Tebru\\Retrofit\\Http\\MultipartBody;\nuse Tebru\\Retrofit\\ParameterHandler;\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse Tebru\\Retrofit\\RequestBodyConverter;\n\n/**\n * Class AbstractFieldHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nabstract class AbstractParameterHandler implements ParameterHandler\n{\n    private const HEADER_CON_TRANS_ENC = 'Content-Transfer-Encoding';\n\n    /**\n     * Convert a value to a generator\n     *\n     * This method is used when a value can optionally be an array and each element in the\n     * array should be processed the same way.\n     *\n     * @param array|mixed $list\n     * @return Generator\n     * @throws \\RuntimeException\n     */\n    protected function getListValues($list): Generator\n    {\n        foreach ((array)$list as $key => $element) {\n            if (!\\is_int($key)) {\n                throw new RuntimeException('Retrofit: Array value must use numeric keys');\n            }\n\n            yield $element;\n        }\n    }\n\n    /**\n     * Handle Part or PartMap annotations\n     *\n     * This could use a simple method using name and value, or if a [@see MultipartBody] is passed in as the\n     * value, then a filename and additional headers could be set as well.\n     *\n     * @param RequestBuilder $requestBuilder\n     * @param RequestBodyConverter $converter\n     * @param string $name\n     * @param mixed $value\n     * @param string $encoding\n     * @return void\n     */\n    protected function handlePart(\n        RequestBuilder $requestBuilder,\n        RequestBodyConverter $converter,\n        string $name,\n        $value,\n        string $encoding\n    ): void {\n        if ($value === null) {\n            return;\n        }\n\n        // if not a MultipartBody, only set name, contents, and content header\n        if (!$value instanceof MultipartBody) {\n            $requestBuilder->addPart($name, $converter->convert($value), [self::HEADER_CON_TRANS_ENC => $encoding]);\n            return;\n        }\n\n        $headers = $value->getHeaders();\n        if (!isset($headers[self::HEADER_CON_TRANS_ENC])) {\n            $headers[self::HEADER_CON_TRANS_ENC] = $encoding;\n        }\n\n        $requestBuilder->addPart($value->getName(), $value->getContents(), $headers, $value->getFilename());\n    }\n}\n"
  },
  {
    "path": "src/Internal/ParameterHandler/BodyParamHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ParameterHandler;\n\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse Tebru\\Retrofit\\RequestBodyConverter;\n\n/**\n * Class BodyParamHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class BodyParamHandler extends AbstractParameterHandler\n{\n    /**\n     * @var RequestBodyConverter\n     */\n    private $converter;\n\n    /**\n     * Constructor\n     *\n     * @param RequestBodyConverter $converter\n     */\n    public function __construct(RequestBodyConverter $converter)\n    {\n        $this->converter = $converter;\n    }\n\n    /**\n     * Converts the value to a stream, then sets the body to the request builder\n     *\n     * @param RequestBuilder $requestBuilder\n     * @param mixed $value\n     * @return void\n     */\n    public function apply(RequestBuilder $requestBuilder, $value): void\n    {\n        if ($value === null) {\n            return;\n        }\n\n        $requestBuilder->setBody($this->converter->convert($value));\n    }\n}\n"
  },
  {
    "path": "src/Internal/ParameterHandler/FieldMapParamHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ParameterHandler;\n\nuse Iterator;\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class FieldMapParamHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class FieldMapParamHandler extends AbstractParameterHandler\n{\n    /**\n     * @var StringConverter\n     */\n    private $converter;\n\n    /**\n     * @var bool\n     */\n    private $encoded;\n\n    /**\n     * Constructor\n     *\n     * @param StringConverter $converter\n     * @param bool $encoded\n     */\n    public function __construct(StringConverter $converter, bool $encoded)\n    {\n        $this->converter = $converter;\n        $this->encoded = $encoded;\n    }\n\n    /**\n     * Set a value to the [@see RequestBuilder] for parameter type\n     *\n     * @param RequestBuilder $requestBuilder\n     * @param array|Iterator $map\n     * @throws \\RuntimeException\n     */\n    public function apply(RequestBuilder $requestBuilder, $map): void\n    {\n        if ($map === null) {\n            return;\n        }\n\n        foreach ($map as $name => $value) {\n            foreach ($this->getListValues($value) as $element) {\n                $requestBuilder->addField($name, $this->converter->convert($element), $this->encoded);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Internal/ParameterHandler/FieldParamHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ParameterHandler;\n\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class FieldParamHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class FieldParamHandler extends AbstractParameterHandler\n{\n    /**\n     * @var StringConverter\n     */\n    private $converter;\n\n    /**\n     * @var string\n     */\n    private $name;\n\n    /**\n     * @var bool\n     */\n    private $encoded;\n\n    /**\n     * Constructor\n     *\n     * @param string $name\n     * @param StringConverter $converter\n     * @param bool $encoded\n     */\n    public function __construct(StringConverter $converter, string $name, bool $encoded)\n    {\n        $this->converter = $converter;\n        $this->name = $name;\n        $this->encoded = $encoded;\n    }\n\n    /**\n     * Set a value to the [@see RequestBuilder] for parameter type\n     *\n     * @param RequestBuilder $requestBuilder\n     * @param mixed $value\n     * @return void\n     * @throws \\RuntimeException\n     */\n    public function apply(RequestBuilder $requestBuilder, $value): void\n    {\n        if ($value === null) {\n            return;\n        }\n\n        foreach ($this->getListValues($value) as $element) {\n            $requestBuilder->addField($this->name, $this->converter->convert($element), $this->encoded);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Internal/ParameterHandler/HeaderMapParamHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ParameterHandler;\n\nuse Iterator;\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class HeaderMapParamHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class HeaderMapParamHandler extends AbstractParameterHandler\n{\n    /**\n     * @var StringConverter\n     */\n    private $converter;\n\n    /**\n     * Constructor\n     *\n     * @param StringConverter $converter\n     */\n    public function __construct(StringConverter $converter)\n    {\n        $this->converter = $converter;\n    }\n\n    /**\n     * Set a value to the [@see RequestBuilder] for parameter type\n     *\n     * @param RequestBuilder $requestBuilder\n     * @param array|Iterator $map\n     * @return void\n     * @throws \\RuntimeException\n     */\n    public function apply(RequestBuilder $requestBuilder, $map): void\n    {\n        if ($map === null) {\n            return;\n        }\n\n        foreach ($map as $name => $value) {\n            foreach ($this->getListValues($value) as $element) {\n                $requestBuilder->addHeader($name, $this->converter->convert($element));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Internal/ParameterHandler/HeaderParamHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ParameterHandler;\n\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class HeaderParamHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class HeaderParamHandler extends AbstractParameterHandler\n{\n    /**\n     * @var StringConverter\n     */\n    private $converter;\n\n    /**\n     * @var string\n     */\n    private $name;\n\n    /**\n     * Constructor\n     *\n     * @param StringConverter $converter\n     * @param string $name\n     */\n    public function __construct(StringConverter $converter, string $name)\n    {\n        $this->converter = $converter;\n        $this->name = $name;\n    }\n\n    /**\n     * Set a value to the [@see RequestBuilder] for parameter type\n     *\n     * @param RequestBuilder $requestBuilder\n     * @param mixed $value\n     * @return void\n     * @throws \\RuntimeException\n     */\n    public function apply(RequestBuilder $requestBuilder, $value): void\n    {\n        if ($value === null) {\n            return;\n        }\n\n        foreach ($this->getListValues($value) as $element) {\n            $requestBuilder->addHeader($this->name, $this->converter->convert($element));\n        }\n    }\n}\n"
  },
  {
    "path": "src/Internal/ParameterHandler/PartMapParamHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ParameterHandler;\n\nuse Iterator;\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse Tebru\\Retrofit\\RequestBodyConverter;\n\n/**\n * Class PartMapParamHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class PartMapParamHandler extends AbstractParameterHandler\n{\n    /**\n     * @var RequestBodyConverter\n     */\n    private $converter;\n\n    /**\n     * @var string\n     */\n    private $encoding;\n\n    /**\n     * Constructor\n     *\n     * @param RequestBodyConverter $converter\n     * @param string $encoding\n     */\n    public function __construct(RequestBodyConverter $converter, string $encoding)\n    {\n        $this->converter = $converter;\n        $this->encoding = $encoding;\n    }\n\n    /**\n     * Set a value to the [@see RequestBuilder] for parameter type\n     *\n     * @param RequestBuilder $requestBuilder\n     * @param array|Iterator $map\n     * @return void\n     */\n    public function apply(RequestBuilder $requestBuilder, $map): void\n    {\n        if ($map === null) {\n            return;\n        }\n\n        foreach ($map as $name => $value) {\n            $this->handlePart($requestBuilder, $this->converter, $name, $value, $this->encoding);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Internal/ParameterHandler/PartParamHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ParameterHandler;\n\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse Tebru\\Retrofit\\RequestBodyConverter;\n\n/**\n * Class PartParamHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class PartParamHandler extends AbstractParameterHandler\n{\n    /**\n     * @var RequestBodyConverter\n     */\n    private $converter;\n\n    /**\n     * @var string\n     */\n    private $name;\n\n    /**\n     * @var string\n     */\n    private $encoding;\n\n    /**\n     * Constructor\n     *\n     * @param RequestBodyConverter $converter\n     * @param string $name\n     * @param string $encoding\n     */\n    public function __construct(RequestBodyConverter $converter, string $name, string $encoding)\n    {\n        $this->converter = $converter;\n        $this->name = $name;\n        $this->encoding = $encoding;\n    }\n\n    /**\n     * Set a value to the [@see RequestBuilder] for parameter type\n     *\n     * @param RequestBuilder $requestBuilder\n     * @param mixed $value\n     * @return void\n     */\n    public function apply(RequestBuilder $requestBuilder, $value): void\n    {\n        if ($value === null) {\n            return;\n        }\n\n        $this->handlePart($requestBuilder, $this->converter, $this->name, $value, $this->encoding);\n    }\n}\n"
  },
  {
    "path": "src/Internal/ParameterHandler/PathParamHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ParameterHandler;\n\nuse RuntimeException;\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class PathParamHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class PathParamHandler extends AbstractParameterHandler\n{\n    /**\n     * @var StringConverter\n     */\n    private $converter;\n\n    /**\n     * @var string\n     */\n    private $name;\n\n    /**\n     * Constructor\n     *\n     * @param StringConverter $converter\n     * @param string $name\n     */\n    public function __construct(StringConverter $converter, string $name)\n    {\n        $this->converter = $converter;\n        $this->name = $name;\n    }\n\n    /**\n     * Set a value to the [@see RequestBuilder] for parameter type\n     *\n     * @param RequestBuilder $requestBuilder\n     * @param mixed $value\n     * @return void\n     * @throws \\RuntimeException\n     */\n    public function apply(RequestBuilder $requestBuilder, $value): void\n    {\n        if ($value === null) {\n            throw new RuntimeException('Path parameters cannot be null');\n        }\n\n        $requestBuilder->replacePath($this->name, $this->converter->convert($value));\n    }\n}\n"
  },
  {
    "path": "src/Internal/ParameterHandler/QueryMapParamHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ParameterHandler;\n\nuse Iterator;\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class QueryMapParamHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class QueryMapParamHandler extends AbstractParameterHandler\n{\n    /**\n     * @var StringConverter\n     */\n    private $converter;\n\n    /**\n     * @var bool\n     */\n    private $encoded;\n\n    /**\n     * Constructor\n     *\n     * @param StringConverter $converter\n     * @param bool $encoded\n     */\n    public function __construct(StringConverter $converter, bool $encoded)\n    {\n        $this->converter = $converter;\n        $this->encoded = $encoded;\n    }\n\n    /**\n     * Set a value to the [@see RequestBuilder] for parameter type\n     *\n     * @param RequestBuilder $requestBuilder\n     * @param array|Iterator $map\n     * @return void\n     * @throws \\RuntimeException\n     */\n    public function apply(RequestBuilder $requestBuilder, $map): void\n    {\n        if ($map === null) {\n            return;\n        }\n\n        foreach ($map as $name => $value) {\n            foreach ($this->getListValues($value) as $element) {\n                $requestBuilder->addQuery($name, $this->converter->convert($element), $this->encoded);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Internal/ParameterHandler/QueryNameParamHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ParameterHandler;\n\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class QueryNameParamHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class QueryNameParamHandler extends AbstractParameterHandler\n{\n    /**\n     * @var StringConverter\n     */\n    private $converter;\n\n    /**\n     * @var bool\n     */\n    private $encoded;\n\n    /**\n     * Constructor\n     *\n     * @param StringConverter $converter\n     * @param bool $encoded\n     */\n    public function __construct(StringConverter $converter, bool $encoded)\n    {\n        $this->converter = $converter;\n        $this->encoded = $encoded;\n    }\n\n    /**\n     * Set a value to the [@see RequestBuilder] for parameter type\n     *\n     * @param RequestBuilder $requestBuilder\n     * @param mixed $value\n     * @return void\n     * @throws \\RuntimeException\n     */\n    public function apply(RequestBuilder $requestBuilder, $value): void\n    {\n        if ($value === null) {\n            return;\n        }\n\n        foreach ($this->getListValues($value) as $element) {\n            $requestBuilder->addQueryName($this->converter->convert($element), $this->encoded);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Internal/ParameterHandler/QueryParamHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ParameterHandler;\n\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class QueryParamHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class QueryParamHandler extends AbstractParameterHandler\n{\n    /**\n     * @var StringConverter\n     */\n    private $converter;\n\n    /**\n     * @var string\n     */\n    private $name;\n\n    /**\n     * @var bool\n     */\n    private $encoded;\n\n    /**\n     * Constructor\n     *\n     * @param string $name\n     * @param StringConverter $converter\n     * @param bool $encoded\n     */\n    public function __construct(StringConverter $converter, string $name, bool $encoded)\n    {\n        $this->converter = $converter;\n        $this->name = $name;\n        $this->encoded = $encoded;\n    }\n\n    /**\n     * Set a value to the [@see RequestBuilder] for parameter type\n     *\n     * @param RequestBuilder $requestBuilder\n     * @param mixed $value\n     * @return void\n     * @throws \\RuntimeException\n     */\n    public function apply(RequestBuilder $requestBuilder, $value): void\n    {\n        if ($value === null) {\n            return;\n        }\n\n        foreach ($this->getListValues($value) as $element) {\n            $requestBuilder->addQuery($this->name, $this->converter->convert($element), $this->encoded);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Internal/ParameterHandler/UrlParamHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ParameterHandler;\n\nuse RuntimeException;\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class UrlParamHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class UrlParamHandler extends AbstractParameterHandler\n{\n    /**\n     * @var StringConverter\n     */\n    private $converter;\n\n    /**\n     * Constructor\n     *\n     * @param StringConverter $converter\n     */\n    public function __construct(StringConverter $converter)\n    {\n        $this->converter = $converter;\n    }\n\n    /**\n     * Set a value to the [@see RequestBuilder] for parameter type\n     *\n     * @param RequestBuilder $requestBuilder\n     * @param mixed $value\n     * @return void\n     * @throws \\RuntimeException\n     */\n    public function apply(RequestBuilder $requestBuilder, $value): void\n    {\n        if ($value === null) {\n            throw new RuntimeException('Url parameters cannot be null');\n        }\n\n        $requestBuilder->setBaseUrl($this->converter->convert($value));\n    }\n}\n"
  },
  {
    "path": "src/Internal/RequestBuilder.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal;\n\nuse GuzzleHttp\\Psr7;\nuse GuzzleHttp\\Psr7\\MultipartStream;\nuse GuzzleHttp\\Psr7\\Request;\nuse GuzzleHttp\\Psr7\\Uri;\nuse LogicException;\nuse Psr\\Http\\Message\\StreamInterface;\n\n/**\n * Class RequestBuilder\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class RequestBuilder\n{\n    /**\n     * The request method\n     *\n     * @var string\n     */\n    private $method;\n\n    /**\n     * The request [@see Uri] object\n     *\n     * @var Uri\n     */\n    private $uri;\n\n    /**\n     * An array of query strings that can be appended together\n     *\n     * @var array\n     */\n    private $queries = [];\n\n    /**\n     * An array of headers in PSR-7 format\n     *\n     * @var array\n     */\n    private $headers;\n\n    /**\n     * The request body\n     *\n     * @var StreamInterface\n     */\n    private $body;\n\n    /**\n     * An array of request body fields that can be appended together\n     *\n     * @var array\n     */\n    private $fields = [];\n\n    /**\n     * An array of arrays of multipart parts, each with name, content, headers, and filename\n     *\n     * @var array[]\n     */\n    private $parts = [];\n\n    /**\n     * Constructor\n     *\n     * @param string $method\n     * @param string $baseUrl\n     * @param string $uri\n     * @param array $headers\n     */\n    public function __construct(string $method, string $baseUrl, string $uri, array $headers)\n    {\n        $this->method = $method;\n        $this->uri = new Uri($baseUrl.$uri);\n        $this->headers = $headers;\n    }\n\n    /**\n     * Set the uri base url\n     *\n     * @param string $value\n     */\n    public function setBaseUrl(string $value): void\n    {\n        $uri = new Uri($value);\n        $this->uri = $this->uri\n            ->withScheme($uri->getScheme())\n            ->withHost($uri->getHost())\n            ->withPort($uri->getPort());\n    }\n\n    /**\n     * Replace a url path placeholder with a value\n     *\n     * @param string $name\n     * @param string $value\n     */\n    public function replacePath(string $name, string $value): void\n    {\n        $path = rawurldecode($this->uri->getPath());\n        $path = str_replace(sprintf('{%s}', $name), $value, $path);\n        $this->uri = $this->uri->withPath($path);\n    }\n\n    /**\n     * Add a query string; if encoded, decodes to be encoded later\n     *\n     * @param string $name\n     * @param string $value\n     * @param bool $encoded\n     */\n    public function addQuery(string $name, string $value, bool $encoded): void\n    {\n        $name = rawurlencode($name);\n        if ($encoded === false) {\n            $value = rawurlencode($value);\n        }\n\n        $this->queries[] = $name.'='.$value;\n    }\n\n    /**\n     * Adds a query string without value; if encoded, decodes to be encoded later\n     *\n     * @param string $value\n     * @param bool $encoded\n     */\n    public function addQueryName(string $value, bool $encoded): void\n    {\n        if ($encoded === false) {\n            $value = rawurlencode($value);\n        }\n\n        $this->queries[] = $value;\n    }\n\n    /**\n     * Add a header in PSR-7 format\n     *\n     * @param string $name\n     * @param string $value\n     */\n    public function addHeader(string $name, string $value): void\n    {\n        $name = strtolower($name);\n        $this->headers[$name][] = $value;\n    }\n\n    /**\n     * Set the request body\n     *\n     * @param StreamInterface $body\n     */\n    public function setBody(StreamInterface $body): void\n    {\n        $this->body = $body;\n    }\n\n    /**\n     * Add a field; if not encoded, encodes first\n     *\n     * @param string $name\n     * @param string $value\n     * @param bool $encoded\n     */\n    public function addField(string $name, string $value, bool $encoded): void\n    {\n        $name = rawurlencode($name);\n        if ($encoded === false) {\n            $value = rawurlencode($value);\n        }\n\n        $this->fields[] = $name.'='.$value;\n    }\n\n    /**\n     * Add a multipart part\n     *\n     * @param string $name\n     * @param StreamInterface $contents\n     * @param array $headers\n     * @param null|string $filename\n     */\n    public function addPart(string $name, StreamInterface $contents, array $headers = [], ?string $filename = null): void\n    {\n        $this->parts[] = [\n            'name' => $name,\n            'contents' => $contents,\n            'headers' => $headers,\n            'filename' => $filename,\n        ];\n    }\n\n    /**\n     * Create a PSR-7 request\n     *\n     * @return Request\n     * @throws \\LogicException\n     */\n    public function build(): Request\n    {\n        $uri = $this->uri;\n        if ($this->queries !== []) {\n            $query = implode('&', $this->queries);\n            $uri = $this->uri->getQuery() === ''\n                ? $this->uri->withQuery($query)\n                : $this->uri->withQuery($query.'&'.$this->uri->getQuery());\n        }\n\n        if ($this->fields !== []) {\n            if ($this->body !== null) {\n                throw new LogicException('Retrofit: Cannot mix @Field and @Body annotations.');\n            }\n\n            $this->body = Psr7\\stream_for(implode('&', $this->fields));\n        }\n\n        if ($this->parts !== []) {\n            if ($this->body !== null) {\n                throw new LogicException('Retrofit: Cannot mix @Part and @Body annotations.');\n            }\n\n            $this->body = new MultipartStream($this->parts);\n        }\n\n        return new Request($this->method, $uri, $this->headers, $this->body);\n    }\n}\n"
  },
  {
    "path": "src/Internal/RetrofitResponse.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal;\n\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Tebru\\Retrofit\\Response;\n\n/**\n * Wraps a PSR-7 [@see ResponseInterface] and provides convenience methods for getting\n * a converted success or error body.\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class RetrofitResponse implements Response\n{\n    /**\n     * The PSR-7 response\n     *\n     * @var ResponseInterface\n     */\n    private $response;\n\n    /**\n     * Converted body on success\n     *\n     * @var mixed\n     */\n    private $body;\n\n    /**\n     * Converted body on failure\n     *\n     * @var mixed\n     */\n    private $errorBody;\n\n    /**\n     * Constructor\n     *\n     * @param ResponseInterface $response\n     * @param mixed $body\n     * @param mixed $errorBody\n     */\n    public function __construct(ResponseInterface $response, $body, $errorBody)\n    {\n        $this->response = $response;\n        $this->body = $body;\n        $this->errorBody = $errorBody;\n    }\n\n    /**\n     * Get the raw PSR-7 response\n     *\n     * @return ResponseInterface\n     */\n    public function raw(): ResponseInterface\n    {\n        return $this->response;\n    }\n\n    /**\n     * Get the response status code\n     *\n     * @return int\n     */\n    public function code(): int\n    {\n        return $this->response->getStatusCode();\n    }\n\n    /**\n     * Get the response message\n     *\n     * @return string\n     */\n    public function message(): string\n    {\n        return $this->response->getReasonPhrase();\n    }\n\n    /**\n     * Get response headers\n     *\n     * @return array\n     */\n    public function headers(): array\n    {\n        return $this->response->getHeaders();\n    }\n\n    /**\n     * Returns true if the response was successful\n     *\n     * @return bool\n     */\n    public function isSuccessful(): bool\n    {\n        return $this->response->getStatusCode() >= 200 && $this->response->getStatusCode() < 300;\n    }\n\n    /**\n     * Get converted body\n     *\n     * @return mixed\n     */\n    public function body()\n    {\n        return $this->body;\n    }\n\n    /**\n     * Get converted body on errors\n     *\n     * @return mixed\n     */\n    public function errorBody()\n    {\n        return $this->errorBody;\n    }\n}\n"
  },
  {
    "path": "src/Internal/ServiceMethod/DefaultServiceMethod.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ServiceMethod;\n\nuse LogicException;\nuse Psr\\Http\\Message\\RequestInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Tebru\\Retrofit\\Call;\nuse Tebru\\Retrofit\\CallAdapter;\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse Tebru\\Retrofit\\ParameterHandler;\nuse Tebru\\Retrofit\\ResponseBodyConverter;\nuse Tebru\\Retrofit\\Internal\\ServiceMethod;\n\n/**\n * Class DefaultServiceMethod\n *\n * This is the class that [@see Call]s will interact with. Its main responsibility is taking\n * known request parameters and applying arguments supplied at runtime to build a PSR-7 request\n * object. Additionally, it converts responses and adapts [@Call]s.\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class DefaultServiceMethod implements ServiceMethod\n{\n    /**\n     * Request method\n     *\n     * @var string\n     */\n    private $method;\n\n    /**\n     * Request base url\n     *\n     * @var string\n     */\n    private $baseUrl;\n\n    /**\n     * Request path\n     *\n     * @var string\n     */\n    private $path;\n\n    /**\n     * @var array\n     */\n    private $headers;\n\n    /**\n     * Array of parameter handlers\n     *\n     * @var ParameterHandler[]\n     */\n    private $parameterHandlers;\n\n    /**\n     * The call adapter to use\n     *\n     * @var CallAdapter\n     */\n    private $callAdapter;\n\n    /**\n     * Response body converter\n     *\n     * @var ResponseBodyConverter\n     */\n    private $responseBodyConverter;\n\n    /**\n     * Error body converter\n     *\n     * @var ResponseBodyConverter\n     */\n    private $errorBodyConverter;\n\n    /**\n     * Constructor\n     *\n     * @param string $method\n     * @param string $baseUrl\n     * @param string $uri\n     * @param array $headers\n     * @param array $parameterHandlers\n     * @param CallAdapter $callAdapter\n     * @param ResponseBodyConverter $responseBodyConverter\n     * @param ResponseBodyConverter $errorBodyConverter\n     */\n    public function __construct(\n        string $method,\n        string $baseUrl,\n        string $uri,\n        array $headers,\n        array $parameterHandlers,\n        CallAdapter $callAdapter,\n        ResponseBodyConverter $responseBodyConverter,\n        ResponseBodyConverter $errorBodyConverter\n    ) {\n        $this->method = $method;\n        $this->baseUrl = $baseUrl;\n        $this->path = $uri;\n        $this->headers = $headers;\n        $this->parameterHandlers = $parameterHandlers;\n        $this->callAdapter = $callAdapter;\n        $this->responseBodyConverter = $responseBodyConverter;\n        $this->errorBodyConverter = $errorBodyConverter;\n    }\n\n    /**\n     * Apply runtime arguments and build request\n     *\n     * @param array $args\n     * @return RequestInterface\n     * @throws \\LogicException\n     */\n    public function toRequest(array $args): RequestInterface\n    {\n        if (\\count($this->parameterHandlers) !== \\count($args)) {\n            throw new LogicException(sprintf(\n                'Retrofit: Incompatible number of arguments. Expected %d and got %s. This either ' .\n                'means that the service method was not called with the correct number of parameters, ' .\n                'or there is not an annotation for every parameter.',\n                \\count($this->parameterHandlers),\n                \\count($args)\n            ));\n        }\n\n        $requestBuilder = new RequestBuilder(\n            $this->method,\n            $this->baseUrl,\n            $this->path,\n            $this->headers\n        );\n\n        foreach ($this->parameterHandlers as $index => $parameterHandler) {\n            $parameterHandler->apply($requestBuilder, $args[$index]);\n        }\n\n        return $requestBuilder->build();\n    }\n\n    /**\n     * Take a response and convert it to expected value\n     *\n     * @param ResponseInterface $response\n     * @return mixed\n     */\n    public function toResponseBody(ResponseInterface $response)\n    {\n        return $this->responseBodyConverter->convert($response->getBody());\n    }\n\n    /**\n     * Take a response and convert it to expected value\n     *\n     * @param ResponseInterface $response\n     * @return mixed\n     */\n    public function toErrorBody(ResponseInterface $response)\n    {\n        return $this->errorBodyConverter->convert($response->getBody());\n    }\n\n    /**\n     * Take a [@see Call] and adapt to expected value\n     *\n     * @param Call $call\n     * @return mixed\n     */\n    public function adapt(Call $call)\n    {\n        return $this->callAdapter->adapt($call);\n    }\n}\n"
  },
  {
    "path": "src/Internal/ServiceMethod/DefaultServiceMethodBuilder.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ServiceMethod;\n\nuse LogicException;\nuse Tebru\\Retrofit\\CallAdapter;\nuse Tebru\\Retrofit\\ParameterHandler;\nuse Tebru\\Retrofit\\ResponseBodyConverter;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\n\n/**\n * Class DefaultServiceMethodBuilder\n *\n * Constructs a [@ServiceMethod]\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class DefaultServiceMethodBuilder implements ServiceMethodBuilder\n{\n    /**\n     * The request method\n     *\n     * @var string\n     */\n    private $method;\n\n    /**\n     * The request base url\n     *\n     * @var string\n     */\n    private $baseUrl;\n\n    /**\n     * The request path\n     *\n     * @var string\n     */\n    private $path;\n\n    /**\n     * True if the request has a body\n     *\n     * @var bool\n     */\n    private $hasBody;\n\n    /**\n     * The request body content type\n     *\n     * @var string\n     */\n    private $contentType;\n\n    /**\n     * Array of headers\n     *\n     * @var array\n     */\n    private $headers = [];\n\n    /**\n     * Array of Parameter handlers, indexed to match the position of the parameters\n     *\n     * @var ParameterHandler[]\n     */\n    private $parameterHandlers = [];\n\n    /**\n     * Converts successful response bodies to expected value\n     *\n     * @var ResponseBodyConverter\n     */\n    private $responseBodyConverter;\n\n    /**\n     * Converts error response bodies to expected value\n     *\n     * @var ResponseBodyConverter\n     */\n    private $errorBodyConverter;\n\n    /**\n     * Adapts a [@see Call] to expected value\n     *\n     * @var CallAdapter\n     */\n    private $callAdapter;\n\n    /**\n     * Set the request method (e.g. GET, POST)\n     *\n     * @param string $method\n     * @return ServiceMethodBuilder\n     * @throws \\LogicException\n     */\n    public function setMethod(string $method): ServiceMethodBuilder\n    {\n        if ($this->method !== null) {\n            throw new LogicException(sprintf(\n                'Retrofit: Only one http method is allowed. Trying to set %s, but %s already exists',\n                strtoupper($method),\n                $this->method\n            ));\n        }\n\n        $this->method = strtoupper($method);\n\n        return $this;\n    }\n\n    /**\n     * Set the request base url (e.g. http://example.com)\n     *\n     * @param string $baseUrl\n     * @return ServiceMethodBuilder\n     */\n    public function setBaseUrl(string $baseUrl): ServiceMethodBuilder\n    {\n        $this->baseUrl = $baseUrl;\n\n        return $this;\n    }\n\n    /**\n     * Set the request path\n     *\n     * @param string $path\n     * @return ServiceMethodBuilder\n     */\n    public function setPath(string $path): ServiceMethodBuilder\n    {\n        $this->path = $path;\n\n        return $this;\n    }\n\n    /**\n     * Set to true if an annotation exists that denotes a request body. This should also set\n     * the request content type.\n     *\n     * @param bool $hasBody\n     * @return ServiceMethodBuilder\n     * @throws \\LogicException\n     */\n    public function setHasBody(bool $hasBody): ServiceMethodBuilder\n    {\n        if ($this->hasBody !== null && $this->hasBody !== $hasBody) {\n            throw new LogicException(\n                'Retrofit: Body cannot be changed after it has been set. This indicates a conflict between ' .\n                'HTTP Request annotations, body annotations, and request type annotations. For example, ' .\n                '@GET cannot be used with @Body, @Field, or @Part annotations'\n            );\n        }\n\n        $this->hasBody = $hasBody;\n\n        return $this;\n    }\n\n    /**\n     * Set the content type of the request. A content type should not be set if there\n     * isn't a request body.\n     *\n     * @param string $contentType\n     * @return ServiceMethodBuilder\n     * @throws \\LogicException\n     */\n    public function setContentType(string $contentType): ServiceMethodBuilder\n    {\n        if ($this->contentType !== null && $this->contentType !== $contentType) {\n            throw new LogicException(\n                'Retrofit: Content type cannot be changed after it has been set. This indicates a conflict between ' .\n                'HTTP Request annotations, body annotations, and request type annotations. For example, ' .\n                '@GET cannot be used with @Body, @Field, or @Part annotations'\n            );\n        }\n\n        $this->contentType = $contentType;\n\n        return $this;\n    }\n\n    /**\n     * Convenience method to declare that the request has content and is json\n     *\n     * @return ServiceMethodBuilder\n     * @throws \\LogicException\n     */\n    public function setIsJson(): ServiceMethodBuilder\n    {\n        $this->setHasBody(true);\n        $this->setContentType('application/json');\n\n        return $this;\n    }\n\n\n    /**\n     * Convenience method to declare that the request has content and is form encoded\n     *\n     * @return ServiceMethodBuilder\n     * @throws \\LogicException\n     */\n    public function setIsFormUrlEncoded(): ServiceMethodBuilder\n    {\n        $this->setHasBody(true);\n        $this->setContentType('application/x-www-form-urlencoded');\n\n        return $this;\n    }\n\n    /**\n     * Convenience method to declare that the request has content and is multipart\n     *\n     * @return ServiceMethodBuilder\n     * @throws \\LogicException\n     */\n    public function setIsMultipart(): ServiceMethodBuilder\n    {\n        $this->setHasBody(true);\n        $this->setContentType('multipart/form-data');\n\n        return $this;\n    }\n\n    /**\n     * Add a request header. Header name should be normalized.\n     *\n     * @param string $name\n     * @param string $header\n     * @return ServiceMethodBuilder\n     */\n    public function addHeader(string $name, string $header): ServiceMethodBuilder\n    {\n        $this->headers[strtolower($name)][] = $header;\n\n        return $this;\n    }\n\n    /**\n     * Add a [@see ParameterHandler] at the position the parameter exists\n     *\n     * @param int $index\n     * @param ParameterHandler $parameterHandler\n     * @return ServiceMethodBuilder\n     */\n    public function addParameterHandler(int $index, ParameterHandler $parameterHandler): ServiceMethodBuilder\n    {\n        $this->parameterHandlers[$index] = $parameterHandler;\n\n        return $this;\n    }\n\n    /**\n     * Set the [@see CallAdapter]\n     *\n     * @param CallAdapter $callAdapter\n     * @return ServiceMethodBuilder\n     */\n    public function setCallAdapter(CallAdapter $callAdapter): ServiceMethodBuilder\n    {\n        $this->callAdapter = $callAdapter;\n\n        return $this;\n    }\n\n    /**\n     * Set the response body converter to convert successful responses\n     *\n     * @param ResponseBodyConverter $responseBodyConverter\n     * @return ServiceMethodBuilder\n     */\n    public function setResponseBodyConverter(ResponseBodyConverter $responseBodyConverter): ServiceMethodBuilder\n    {\n        $this->responseBodyConverter = $responseBodyConverter;\n\n        return $this;\n    }\n\n    /**\n     * Set the response body converter to convert error responses\n     *\n     * @param ResponseBodyConverter $errorBodyConverter\n     * @return ServiceMethodBuilder\n     */\n    public function setErrorBodyConverter(ResponseBodyConverter $errorBodyConverter): ServiceMethodBuilder\n    {\n        $this->errorBodyConverter = $errorBodyConverter;\n\n        return $this;\n    }\n\n    /**\n     * Create a new [@see DefaultServiceMethod] from previously set parameters\n     *\n     * @return DefaultServiceMethod\n     * @throws \\LogicException\n     */\n    public function build(): DefaultServiceMethod\n    {\n        if ($this->method === null) {\n            throw new LogicException(\n                'Retrofit: Cannot build service method without HTTP method. Please specify @GET, @POST, etc'\n            );\n        }\n\n        if ($this->baseUrl === null) {\n            throw new LogicException(\n                'Retrofit: Cannot build service method without base url. Please specify on RetrofitBuilder'\n            );\n        }\n\n        if ($this->path === null) {\n            throw new LogicException(\n                'Retrofit: Cannot build service method without HTTP method. Please specify @GET, @POST, etc'\n            );\n        }\n\n        if ($this->hasBody === true && $this->contentType === null) {\n            throw new LogicException(\n                'Retrofit: Cannot build service method with body and no content type. Set one using @Body, ' .\n                '@Field, or @Part'\n            );\n        }\n\n        if ($this->hasBody !== true && $this->contentType !== null) {\n            throw new LogicException(\n                'Retrofit: Cannot set a content-type without a body. This indicates a conflict between ' .\n                'HTTP Request annotations, body annotations, and request type annotations. For example, ' .\n                '@GET cannot be used with @Body, @Field, or @Part annotations'\n            );\n        }\n\n        if ($this->responseBodyConverter === null) {\n            throw new LogicException(\n                'Retrofit: Cannot build service method without response body converter'\n            );\n        }\n\n        if ($this->errorBodyConverter === null) {\n            throw new LogicException(\n                'Retrofit: Cannot build service method without error body converter'\n            );\n        }\n\n        if ($this->callAdapter === null) {\n            throw new LogicException(\n                'Retrofit: Cannot build service method without call adapter'\n            );\n        }\n\n        if ($this->contentType !== null && !isset($this->headers['content-type'])) {\n            $this->addHeader('content-type', $this->contentType);\n        }\n\n        if ($this->hasBody === null) {\n            $this->hasBody = false;\n        }\n\n        ksort($this->parameterHandlers);\n\n        return new DefaultServiceMethod(\n            $this->method,\n            $this->baseUrl,\n            $this->path,\n            $this->headers,\n            $this->parameterHandlers,\n            $this->callAdapter,\n            $this->responseBodyConverter,\n            $this->errorBodyConverter\n        );\n    }\n}\n"
  },
  {
    "path": "src/Internal/ServiceMethod/ServiceMethodFactory.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Internal\\ServiceMethod;\n\nuse LogicException;\nuse Psr\\Http\\Message\\StreamInterface;\nuse ReflectionMethod;\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\AnnotationReader\\AnnotationCollection;\nuse Tebru\\AnnotationReader\\AnnotationReaderAdapter;\nuse Tebru\\PhpType\\TypeToken;\nuse Tebru\\Retrofit\\Annotation as Annot;\nuse Tebru\\Retrofit\\CallAdapter;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\Internal\\AnnotationProcessor;\nuse Tebru\\Retrofit\\Internal\\CallAdapter\\CallAdapterProvider;\nuse Tebru\\Retrofit\\Internal\\Converter\\ConverterProvider;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\n\n/**\n * Class ServiceMethodFactory\n *\n * Creates and sets up values for [@see ServiceMethodBuilder], then delegates final setup\n * to annotation handles.\n *\n * @author Nate Brunette <n@tebru.net>\n */\nfinal class ServiceMethodFactory\n{\n    /**\n     * Handles an [@see AbstractAnnotation]\n     *\n     * @var AnnotationProcessor\n     */\n    private $annotationProcessor;\n\n    /**\n     * Fetches a [@see CallAdapter]\n     *\n     * @var CallAdapterProvider\n     */\n    private $callAdapterProvider;\n\n    /**\n     * Fetches a [@see Converter]\n     *\n     * @var ConverterProvider\n     */\n    private $converterProvider;\n\n    /**\n     * Reads annotations from service interface\n     *\n     * @var AnnotationReaderAdapter\n     */\n    private $annotationReader;\n\n    /**\n     * The request base url\n     *\n     * @var string\n     */\n    private $baseUrl;\n\n    /**\n     * Constructor\n     *\n     * @param AnnotationProcessor $annotationProcessor\n     * @param CallAdapterProvider $callAdapterProvider\n     * @param ConverterProvider $converterProvider\n     * @param AnnotationReaderAdapter $annotationReader\n     * @param string $baseUrl\n     */\n    public function __construct(\n        AnnotationProcessor $annotationProcessor,\n        CallAdapterProvider $callAdapterProvider,\n        ConverterProvider $converterProvider,\n        AnnotationReaderAdapter $annotationReader,\n        string $baseUrl\n    ) {\n        $this->annotationProcessor = $annotationProcessor;\n        $this->callAdapterProvider = $callAdapterProvider;\n        $this->converterProvider = $converterProvider;\n        $this->annotationReader = $annotationReader;\n        $this->baseUrl = $baseUrl;\n    }\n\n    /**\n     * Creates a [@see DefaultServiceMethod]\n     *\n     * @param string $interfaceName\n     * @param string $methodName\n     * @return DefaultServiceMethod\n     * @throws \\LogicException\n     */\n    public function create(string $interfaceName, string $methodName): DefaultServiceMethod\n    {\n        $serviceMethodBuilder = new DefaultServiceMethodBuilder();\n        $annotations = $this->annotationReader->readMethod($methodName, $interfaceName, true, true);\n\n        $reflectionMethod = new ReflectionMethod($interfaceName, $methodName);\n        $returnType = $reflectionMethod->getReturnType();\n        if ($returnType === null) {\n            throw new LogicException(sprintf(\n                'Retrofit: All service methods must contain a return type. None found for %s::%s()',\n                $reflectionMethod->getDeclaringClass()->name,\n                $reflectionMethod->name\n            ));\n        }\n\n        /** @noinspection ExceptionsAnnotatingAndHandlingInspection */\n        $returnTypeToken = new TypeToken($returnType->getName());\n\n        $serviceMethodBuilder->setBaseUrl($this->baseUrl);\n        $serviceMethodBuilder->setCallAdapter($this->callAdapterProvider->get($returnTypeToken));\n\n        foreach ($annotations as $annotationArray) {\n            if (!\\is_array($annotationArray)) {\n                $annotationArray = [$annotationArray];\n            }\n\n            foreach ($annotationArray as $annotation) {\n                try {\n                    $this->annotationProcessor->process(\n                        $annotation,\n                        $serviceMethodBuilder,\n                        $this->converterProvider,\n                        $reflectionMethod\n                    );\n                } catch (LogicException $exception) {\n                    throw new LogicException(\n                        $exception->getMessage() .\n                        sprintf(\n                            ' for %s::%s()',\n                            $reflectionMethod->getDeclaringClass()->name,\n                            $reflectionMethod->name\n                        )\n                    );\n                }\n            }\n        }\n\n        $this->applyConverters($annotations, $serviceMethodBuilder);\n\n        return $serviceMethodBuilder->build();\n    }\n\n    /**\n     * @param AnnotationCollection $annotations\n     * @param DefaultServiceMethodBuilder $builder\n     * @throws \\LogicException\n     */\n    private function applyConverters(AnnotationCollection $annotations, DefaultServiceMethodBuilder $builder): void\n    {\n        $responseBody = $annotations->get(Annot\\ResponseBody::class);\n        if ($responseBody !== null) {\n            /** @noinspection ExceptionsAnnotatingAndHandlingInspection */\n            $builder->setResponseBodyConverter(\n                $this->converterProvider->getResponseBodyConverter(new TypeToken($responseBody->getValue()))\n            );\n        } else {\n            /** @noinspection ExceptionsAnnotatingAndHandlingInspection */\n            $builder->setResponseBodyConverter(\n                $this->converterProvider->getResponseBodyConverter(new TypeToken(StreamInterface::class))\n            );\n        }\n\n        $errorBody = $annotations->get(Annot\\ErrorBody::class);\n        if ($errorBody !== null) {\n            /** @noinspection ExceptionsAnnotatingAndHandlingInspection */\n            $builder->setErrorBodyConverter(\n                $this->converterProvider->getResponseBodyConverter(new TypeToken($errorBody->getValue()))\n            );\n        } else {\n            /** @noinspection ExceptionsAnnotatingAndHandlingInspection */\n            $builder->setErrorBodyConverter(\n                $this->converterProvider->getResponseBodyConverter(new TypeToken(StreamInterface::class))\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "src/Internal/ServiceMethod.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\nnamespace Tebru\\Retrofit\\Internal;\n\nuse Psr\\Http\\Message\\RequestInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Tebru\\Retrofit\\Call;\n\n/**\n * Class ServiceMethod\n *\n * This is the class that [@see Call]s will interact with. Its main responsibility is taking\n * known request parameters and applying arguments supplied at runtime to build a PSR-7 request\n * object. Additionally, it converts responses and adapts [@Call]s.\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface ServiceMethod\n{\n    /**\n     * Apply runtime arguments and build request\n     *\n     * @param array $args\n     * @return RequestInterface\n     */\n    public function toRequest(array $args): RequestInterface;\n\n    /**\n     * Take a response and convert it to expected value\n     *\n     * @param ResponseInterface $response\n     * @return mixed\n     */\n    public function toResponseBody(ResponseInterface $response);\n\n    /**\n     * Take a response and convert it to expected value\n     *\n     * @param ResponseInterface $response\n     * @return mixed\n     */\n    public function toErrorBody(ResponseInterface $response);\n\n    /**\n     * Take a [@see Call] and adapt to expected value\n     *\n     * @param Call $call\n     * @return mixed\n     */\n    public function adapt(Call $call);\n}\n"
  },
  {
    "path": "src/ParameterHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\n\n/**\n * Interface ParameterHandler\n *\n * Implementors of this interface will be able to handle different method parameters\n * provided during runtime.\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface ParameterHandler\n{\n    /**\n     * Set a value to the [@see RequestBuilder] for parameter type\n     *\n     * @param RequestBuilder $requestBuilder\n     * @param mixed $value\n     * @return void\n     */\n    public function apply(RequestBuilder $requestBuilder, $value): void;\n}\n"
  },
  {
    "path": "src/Proxy/AbstractProxy.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Proxy;\n\nuse Tebru\\Retrofit\\HttpClient;\nuse Tebru\\Retrofit\\Internal\\HttpClientCall;\nuse Tebru\\Retrofit\\Internal\\ServiceMethod\\ServiceMethodFactory;\nuse Tebru\\Retrofit\\Proxy;\n\n/**\n * Class Proxy\n *\n * @author Nate Brunette <n@tebru.net>\n */\nabstract class AbstractProxy implements Proxy\n{\n    public const RETROFIT_NO_DEFAULT_VALUE = '__retrofit_no_default_value__';\n\n    /**\n     * Creates a [@see DefaultServiceMethod]\n     *\n     * @var ServiceMethodFactory\n     */\n    private $serviceMethodFactory;\n\n    /**\n     * A retrofit http client\n     *\n     * @var HttpClient\n     */\n    private $client;\n\n    /**\n     * Constructor\n     *\n     * @param ServiceMethodFactory $serviceMethodFactory\n     * @param HttpClient $client\n     */\n    public function __construct(ServiceMethodFactory $serviceMethodFactory, HttpClient $client)\n    {\n        $this->serviceMethodFactory = $serviceMethodFactory;\n        $this->client = $client;\n    }\n\n    /** @noinspection MagicMethodsValidityInspection */\n    /**\n     * Constructs a [@see Call] object based on an interface method and arguments, then passes it through a\n     * [@see CallAdapter] before returning.\n     *\n     * @param string $interfaceName\n     * @param string $methodName\n     * @param array $args\n     * @param array $defaultArgs\n     * @return mixed\n     */\n    public function __handleRetrofitRequest(string $interfaceName, string $methodName, array $args, array $defaultArgs)\n    {\n        $args = $this->createArgs($args, $defaultArgs);\n        $serviceMethod = $this->serviceMethodFactory->create($interfaceName, $methodName);\n\n        return $serviceMethod->adapt(new HttpClientCall($this->client, $serviceMethod, $args));\n    }\n\n    /**\n     * Append any default args to argument array\n     *\n     * @param array $args\n     * @param array $defaultArgs\n     * @return array\n     */\n    private function createArgs(array $args, array $defaultArgs): array\n    {\n        $numProvidedArgs = \\count($args);\n        $numArgs = \\count($defaultArgs);\n\n        if ($numArgs === $numProvidedArgs) {\n            return $args;\n        }\n\n        // get arguments from end that were not provided\n        $appendedArgs = \\array_slice($defaultArgs, $numProvidedArgs);\n\n        return \\array_merge($args, $appendedArgs);\n    }\n}\n"
  },
  {
    "path": "src/Proxy.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\n/**\n * Interface Proxy\n *\n * This represents an implementation of a service interface and should be able to handle\n * method calls on that interface.\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface Proxy\n{\n    /**\n     * Constructs a [@see Call] object based on an interface method and arguments, then passes it through a\n     * [@see CallAdapter] before returning.\n     *\n     * @param string $interfaceName\n     * @param string $methodName\n     * @param array $args\n     * @param array $defaultArgs\n     * @return mixed\n     */\n    public function __handleRetrofitRequest(string $interfaceName, string $methodName, array $args, array $defaultArgs);\n}\n"
  },
  {
    "path": "src/ProxyFactory.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\n/**\n * Interface ProxyFactory\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface ProxyFactory\n{\n    /**\n     * Create a new [@see Proxy] from given service name\n     *\n     * Returns null if the factory cannot handle the service\n     *\n     * @param string $service\n     * @return null|Proxy\n     */\n    public function create(string $service): ?Proxy;\n}\n"
  },
  {
    "path": "src/RequestBodyConverter.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\nuse Psr\\Http\\Message\\StreamInterface;\n\n/**\n * Interface RequestBodyConverter\n *\n * Convert various values to [@see StreamInterface] to be used as\n * as request body\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface RequestBodyConverter extends Converter\n{\n    /**\n     * Convert to stream\n     *\n     * @param mixed $value\n     * @return StreamInterface\n     */\n    public function convert($value): StreamInterface;\n}\n"
  },
  {
    "path": "src/Response.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\nuse Psr\\Http\\Message\\ResponseInterface;\n\n/**\n * Interface Response\n *\n * Wraps a PSR-7 [@see ResponseInterface] and provides convenience methods for getting\n * a converted success or error body.\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface Response\n{\n    /**\n     * Get the raw PSR-7 response\n     *\n     * @return ResponseInterface\n     */\n    public function raw(): ResponseInterface;\n\n    /**\n     * Get the response status code\n     *\n     * @return int\n     */\n    public function code(): int;\n\n    /**\n     * Get the response message\n     *\n     * @return string\n     */\n    public function message(): string;\n\n    /**\n     * Get response headers\n     *\n     * @return array\n     */\n    public function headers(): array;\n\n    /**\n     * Returns true if the response was successful\n     *\n     * @return bool\n     */\n    public function isSuccessful(): bool;\n\n    /**\n     * Get converted body\n     *\n     * @return mixed\n     */\n    public function body();\n\n    /**\n     * Get converted body on errors\n     *\n     * @return mixed\n     */\n    public function errorBody();\n}\n"
  },
  {
    "path": "src/ResponseBodyConverter.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\nuse Psr\\Http\\Message\\StreamInterface;\n\n/**\n * Interface ResponseBodyConverter\n *\n * Convert various values to [@see StreamInterface] to be used as\n * as response body\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface ResponseBodyConverter extends Converter\n{\n    /**\n     * Convert from stream to any type\n     *\n     * @param StreamInterface $value\n     * @return mixed\n     */\n    public function convert(StreamInterface $value);\n}\n"
  },
  {
    "path": "src/Retrofit.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\nuse LogicException;\nuse Tebru\\Retrofit\\Finder\\ServiceResolver;\n\n/**\n * Class Retrofit\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass Retrofit\n{\n    /**\n     * Registered services\n     *\n     * @var array $services\n     */\n    private $services = [];\n\n    /**\n     * Finds all services in a given source directory\n     *\n     * @var ServiceResolver $serviceResolver\n     */\n    private $serviceResolver;\n\n    /**\n     * An array of proxy factories\n     *\n     * @var ProxyFactory[]\n     */\n    private $proxyFactories;\n\n    /**\n     * Constructor\n     *\n     * @param ServiceResolver $serviceResolver Finds service classes\n     * @param ProxyFactory[] $proxyFactories\n     */\n    public function __construct(ServiceResolver $serviceResolver, array $proxyFactories)\n    {\n        $this->serviceResolver = $serviceResolver;\n        $this->proxyFactories = $proxyFactories;\n    }\n\n    /**\n     * Create a new builder\n     *\n     * @return RetrofitBuilder\n     */\n    public static function builder(): RetrofitBuilder\n    {\n        return new RetrofitBuilder();\n    }\n\n    /**\n     * Register an array of classes\n     *\n     * @param array $services\n     * @return void\n     */\n    public function registerServices(array $services): void\n    {\n        foreach ($services as $service) {\n            $this->registerService($service);\n        }\n    }\n\n    /**\n     * Register a single class\n     *\n     * @param string $service\n     * @return void\n     */\n    public function registerService(string $service): void\n    {\n        $this->services[] = $service;\n    }\n\n    /**\n     * Use the service resolver to find all the services dynamically\n     *\n     * @param string $srcDir\n     * @return int Number of services cached\n     * @throws \\RuntimeException\n     * @throws \\BadMethodCallException\n     */\n    public function createAll(string $srcDir): int\n    {\n        $this->services = $this->serviceResolver->findServices($srcDir);\n\n        return $this->createServices();\n    }\n\n    /**\n     * Creates cache files based on registered services\n     *\n     * @return int Number of services cached\n     * @throws \\RuntimeException\n     * @throws \\BadMethodCallException\n     */\n    public function createServices(): int\n    {\n        foreach ($this->services as $service) {\n            $this->create($service);\n        }\n\n        return \\count($this->services);\n    }\n\n    /**\n     * Create a new service proxy given an interface name\n     *\n     * The returned proxy object should be used as if it's an\n     * instance of the service provided.\n     *\n     * @param string $service\n     * @return Proxy\n     */\n    public function create(string $service): Proxy\n    {\n        foreach ($this->proxyFactories as $proxyFactory) {\n            $object = $proxyFactory->create($service);\n            if ($object !== null) {\n                return $object;\n            }\n        }\n\n        /** @noinspection ExceptionsAnnotatingAndHandlingInspection */\n        throw new LogicException(sprintf('Retrofit: Could not find a proxy factory for %s', $service));\n    }\n}\n"
  },
  {
    "path": "src/RetrofitBuilder.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\nuse Doctrine\\Common\\Annotations\\AnnotationReader;\nuse LogicException;\nuse PhpParser\\BuilderFactory;\nuse PhpParser\\PrettyPrinter\\Standard;\nuse Psr\\SimpleCache\\CacheInterface;\nuse Tebru\\AnnotationReader\\AnnotationReaderAdapter;\nuse Tebru\\Retrofit\\Annotation as Annot;\nuse Tebru\\Retrofit\\Finder\\ServiceResolver;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler as AnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationProcessor;\nuse Tebru\\Retrofit\\Internal\\CacheProvider;\nuse Tebru\\Retrofit\\Internal\\CallAdapter\\CallAdapterProvider;\nuse Tebru\\Retrofit\\Internal\\CallAdapter\\DefaultCallAdapterFactory;\nuse Tebru\\Retrofit\\Internal\\Converter\\ConverterProvider;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultConverterFactory;\nuse Tebru\\Retrofit\\Internal\\DefaultProxyFactory;\nuse Tebru\\Retrofit\\Internal\\Filesystem;\nuse Tebru\\Retrofit\\Internal\\ServiceMethod\\ServiceMethodFactory;\n\n/**\n * Class RetrofitBuilder\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass RetrofitBuilder\n{\n    /**\n     * A cache interface to be used in place of defaults\n     *\n     * If this is set, [@see RetrofitBuilder::$shouldCache] will be ignored for internal\n     * caches, however, [@see RetrofitBuilder::$shouldCache] and\n     * [@see RetrofitBuilder::$cacheDir] will still be used to cache proxy clients.\n     *\n     * @var CacheInterface\n     */\n    private $cache;\n\n    /**\n     * Directory to store generated proxy clients\n     *\n     * @var string\n     */\n    private $cacheDir;\n\n    /**\n     * A Retrofit http client used to make requests\n     *\n     * @var HttpClient\n     */\n    private $httpClient;\n\n    /**\n     * The service's base url\n     *\n     * @var string\n     */\n    private $baseUrl;\n\n    /**\n     * An array of factories used to create [@see CallAdapter]s\n     *\n     * @var CallAdapterFactory[]\n     */\n    private $callAdapterFactories = [];\n\n    /**\n     * An array of factories used to convert types\n     *\n     * @var ConverterFactory[]\n     */\n    private $converterFactories = [];\n\n    /**\n     * An array of factories used to create [@see Proxy] objects\n     *\n     * @var ProxyFactory[]\n     */\n    private $proxyFactories = [];\n\n    /**\n     * An array of handlers used to modify the request based on an annotation\n     *\n     * @var AnnotationHandler[]\n     */\n    private $annotationHandlers = [];\n\n    /**\n     * If we should cache the proxies\n     *\n     * @var bool\n     */\n    private $shouldCache = false;\n\n    /**\n     * Override default cache adapters\n     *\n     * @param CacheInterface $cache\n     * @return RetrofitBuilder\n     */\n    public function setCache(CacheInterface $cache): RetrofitBuilder\n    {\n        $this->cache = $cache;\n\n        return $this;\n    }\n\n    /**\n     * Set the cache directory\n     *\n     * @param string $cacheDir\n     * @return RetrofitBuilder\n     */\n    public function setCacheDir(string $cacheDir): RetrofitBuilder\n    {\n        $this->cacheDir = $cacheDir;\n\n        return $this;\n    }\n\n    /**\n     * Set the Retrofit http client\n     *\n     * @param HttpClient $client\n     * @return RetrofitBuilder\n     */\n    public function setHttpClient(HttpClient $client): RetrofitBuilder\n    {\n        $this->httpClient = $client;\n\n        return $this;\n    }\n\n    /**\n     * Set the base url\n     *\n     * @param string $baseUrl\n     * @return RetrofitBuilder\n     */\n    public function setBaseUrl(string $baseUrl): RetrofitBuilder\n    {\n        $this->baseUrl = $baseUrl;\n\n        return $this;\n    }\n\n    /**\n     * Add a [@see CallAdapterFactory]\n     *\n     * @param CallAdapterFactory $callAdapterFactory\n     * @return RetrofitBuilder\n     */\n    public function addCallAdapterFactory(CallAdapterFactory $callAdapterFactory): RetrofitBuilder\n    {\n        $this->callAdapterFactories[] = $callAdapterFactory;\n\n        return $this;\n    }\n\n    /**\n     * Add a [@see ConverterFactory]\n     *\n     * @param ConverterFactory $converterFactory\n     * @return RetrofitBuilder\n     */\n    public function addConverterFactory(ConverterFactory $converterFactory): RetrofitBuilder\n    {\n        $this->converterFactories[] = $converterFactory;\n\n        return $this;\n    }\n\n    /**\n     * Add a [@see ProxyFactory]\n     *\n     * @param ProxyFactory $proxyFactory\n     * @return RetrofitBuilder\n     */\n    public function addProxyFactory(ProxyFactory $proxyFactory): RetrofitBuilder\n    {\n        $this->proxyFactories[] = $proxyFactory;\n\n        return $this;\n    }\n\n    /**\n     * Add an [@see AnnotationHandler]\n     *\n     * @param string $annotationName\n     * @param AnnotationHandler $annotationHandler\n     * @return RetrofitBuilder\n     */\n    public function addAnnotationHandler(string $annotationName, AnnotationHandler $annotationHandler): RetrofitBuilder\n    {\n        $this->annotationHandlers[$annotationName] = $annotationHandler;\n\n        return $this;\n    }\n\n    /**\n     * Enable caching proxies\n     *\n     * @param bool $enable\n     * @return RetrofitBuilder\n     */\n    public function enableCache(bool $enable = true): RetrofitBuilder\n    {\n        $this->shouldCache = $enable;\n\n        return $this;\n    }\n\n    /**\n     * Build a retrofit instance\n     *\n     * @return Retrofit\n     * @throws \\LogicException\n     */\n    public function build(): Retrofit\n    {\n        $defaultProxyFactory = $this->createDefaultProxyFactory();\n        foreach ($this->proxyFactories as $proxyFactory) {\n            if ($proxyFactory instanceof DefaultProxyFactoryAware) {\n                $proxyFactory->setDefaultProxyFactory($defaultProxyFactory);\n            }\n        }\n        $this->proxyFactories[] = $defaultProxyFactory;\n\n        return new Retrofit(new ServiceResolver(), $this->proxyFactories);\n    }\n\n    /**\n     * Creates the default proxy factory and all necessary dependencies\n     *\n     * @return ProxyFactory\n     * @throws \\LogicException\n     */\n    private function createDefaultProxyFactory(): ProxyFactory\n    {\n        if ($this->baseUrl === null) {\n            throw new LogicException('Retrofit: Base URL must be provided');\n        }\n\n        if ($this->httpClient === null) {\n            throw new LogicException('Retrofit: Must set http client to make requests');\n        }\n\n        if ($this->shouldCache && $this->cacheDir === null) {\n            throw new LogicException('Retrofit: If caching is enabled, must specify cache directory');\n        }\n\n        $this->cacheDir .= '/retrofit';\n\n        // add defaults to any user registered\n        $this->callAdapterFactories[] = new DefaultCallAdapterFactory();\n        $this->converterFactories[] = new DefaultConverterFactory();\n\n        if ($this->cache === null) {\n            $this->cache = $this->shouldCache === true\n                ? CacheProvider::createFileCache($this->cacheDir)\n                : CacheProvider::createMemoryCache();\n        }\n\n        $httpRequestHandler = new AnnotHandler\\HttpRequestAnnotHandler();\n\n        /** @noinspection ClassConstantUsageCorrectnessInspection */\n        $annotationHandlers = array_merge(\n            [\n                Annot\\Body::class => new AnnotHandler\\BodyAnnotHandler(),\n                Annot\\DELETE::class => $httpRequestHandler,\n                Annot\\Field::class => new AnnotHandler\\FieldAnnotHandler(),\n                Annot\\FieldMap::class => new AnnotHandler\\FieldMapAnnotHandler(),\n                Annot\\GET::class => $httpRequestHandler,\n                Annot\\HEAD::class => $httpRequestHandler,\n                Annot\\Header::class => new AnnotHandler\\HeaderAnnotHandler(),\n                Annot\\HeaderMap::class => new AnnotHandler\\HeaderMapAnnotHandler(),\n                Annot\\Headers::class => new AnnotHandler\\HeadersAnnotHandler(),\n                Annot\\OPTIONS::class => $httpRequestHandler,\n                Annot\\Part::class => new AnnotHandler\\PartAnnotHandler(),\n                Annot\\PartMap::class => new AnnotHandler\\PartMapAnnotHandler(),\n                Annot\\PATCH::class => $httpRequestHandler,\n                Annot\\Path::class => new AnnotHandler\\PathAnnotHandler(),\n                Annot\\POST::class => $httpRequestHandler,\n                Annot\\PUT::class => $httpRequestHandler,\n                Annot\\Query::class => new AnnotHandler\\QueryAnnotHandler(),\n                Annot\\QueryMap::class => new AnnotHandler\\QueryMapAnnotHandler(),\n                Annot\\QueryName::class => new AnnotHandler\\QueryNameAnnotHandler(),\n                Annot\\REQUEST::class => $httpRequestHandler,\n                Annot\\Url::class => new AnnotHandler\\UrlAnnotHandler(),\n            ],\n            $this->annotationHandlers\n        );\n        $serviceMethodFactory = new ServiceMethodFactory(\n            new AnnotationProcessor($annotationHandlers),\n            new CallAdapterProvider($this->callAdapterFactories),\n            new ConverterProvider($this->converterFactories),\n            new AnnotationReaderAdapter(new AnnotationReader(), $this->cache),\n            $this->baseUrl\n        );\n\n        return new DefaultProxyFactory(\n            new BuilderFactory(),\n            new Standard(),\n            $serviceMethodFactory,\n            $this->httpClient,\n            new Filesystem(),\n            $this->shouldCache,\n            $this->cacheDir\n        );\n    }\n}\n"
  },
  {
    "path": "src/ServiceMethodBuilder.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\n/**\n * Class ServiceMethodBuilder\n *\n * Implementations will allow for constructing a [@see DefaultServiceMethod] iteratively\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface ServiceMethodBuilder\n{\n    /**\n     * Set the request method (e.g. GET, POST)\n     *\n     * @param string $method\n     * @return ServiceMethodBuilder\n     */\n    public function setMethod(string $method): ServiceMethodBuilder;\n\n    /**\n     * Set the request base url (e.g. http://example.com)\n     *\n     * @param string $baseUrl\n     * @return ServiceMethodBuilder\n     */\n    public function setBaseUrl(string $baseUrl): ServiceMethodBuilder;\n\n    /**\n     * Set the request path\n     *\n     * @param string $path\n     * @return ServiceMethodBuilder\n     */\n    public function setPath(string $path): ServiceMethodBuilder;\n\n    /**\n     * Set to true if an annotation exists that denotes a request body. This should also set\n     * the request content type.\n     *\n     * @param bool $hasBody\n     * @return ServiceMethodBuilder\n     */\n    public function setHasBody(bool $hasBody): ServiceMethodBuilder;\n\n    /**\n     * Set the content type of the request. A content type should not be set if there\n     * isn't a request body.\n     *\n     * @param string $contentType\n     * @return ServiceMethodBuilder\n     */\n    public function setContentType(string $contentType): ServiceMethodBuilder;\n\n    /**\n     * Convenience method to declare that the request has content and is json\n     *\n     * @return ServiceMethodBuilder\n     */\n    public function setIsJson(): ServiceMethodBuilder;\n\n    /**\n     * Convenience method to declare that the request has content and is form encoded\n     *\n     * @return ServiceMethodBuilder\n     */\n    public function setIsFormUrlEncoded(): ServiceMethodBuilder;\n\n    /**\n     * Convenience method to declare that the request has content and is multipart\n     *\n     * @return ServiceMethodBuilder\n     */\n    public function setIsMultipart(): ServiceMethodBuilder;\n\n    /**\n     * Add a request header. Header name should be normalized.\n     *\n     * @param string $name\n     * @param string $header\n     * @return ServiceMethodBuilder\n     */\n    public function addHeader(string $name, string $header): ServiceMethodBuilder;\n\n    /**\n     * Add a [@see ParameterHandler] at the position the parameter exists\n     *\n     * @param int $index\n     * @param ParameterHandler $parameterHandler\n     * @return ServiceMethodBuilder\n     */\n    public function addParameterHandler(int $index, ParameterHandler $parameterHandler): ServiceMethodBuilder;\n\n    /**\n     * Set the [@see CallAdapter]\n     *\n     * @param CallAdapter $callAdapter\n     * @return ServiceMethodBuilder\n     */\n    public function setCallAdapter(CallAdapter $callAdapter): ServiceMethodBuilder;\n}\n"
  },
  {
    "path": "src/StringConverter.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit;\n\n/**\n * Interface StringConverter\n *\n * Converts a value to a string\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface StringConverter extends Converter\n{\n    /**\n     * Convert any supported value to a string\n     *\n     * @param mixed $value\n     * @return string\n     */\n    public function convert($value): string;\n}\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/AnnotationProcessorTest/AnnotationProcessorTestMock.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\AnnotationProcessorTest;\n\nuse Psr\\Http\\Message\\StreamInterface;\nuse Tebru\\Retrofit\\Call;\n\n/**\n * Interface AnnotationProcessorTestMock\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface AnnotationProcessorTestMock\n{\n    public function foo(int $bar): Call;\n    public function body(StreamInterface $bar): Call;\n    public function noType($bar): Call;\n}\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/AnnotationProcessorTest/BadConverterAnnotation.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\AnnotationProcessorTest;\n\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\Annotation\\ParameterAwareAnnotation;\n\n/**\n * Class AnnotationProcessorTestAnnotation\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n */\nclass BadConverterAnnotation extends AbstractAnnotation implements ParameterAwareAnnotation\n{\n    /**\n     * The variable name, which will either be the default value or the value of 'var' if\n     * specified. The variable name excludes the '$'.\n     *\n     * @return string\n     */\n    public function getVariableName(): string\n    {\n        return $this->getValue();\n    }\n\n    /**\n     * Return the converter interface class\n     *\n     * Can be one of RequestBodyConverter, ResponseBodyConverter, or StringConverter\n     *\n     * @return null|string\n     */\n    public function converterType(): ?string\n    {\n        return 'Foo';\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/HttpClientCallTest/HttpClientCallTestClientMock.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\HttpClientCallTest;\n\nuse Psr\\Http\\Message\\RequestInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse RuntimeException;\nuse Tebru\\Retrofit\\HttpClient;\n\n/**\n * Class HttpClientCallTestClientMock\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass HttpClientCallTestClientMock implements HttpClient\n{\n    /**\n     * @var ResponseInterface\n     */\n    private $response;\n\n    /**\n     * @var callable\n     */\n    private $onResponse;\n\n    /**\n     * @var callable\n     */\n    private $onFailure;\n\n    /**\n     * Constructor\n     *\n     * @param ResponseInterface $response\n     */\n    public function __construct(?ResponseInterface $response = null)\n    {\n        $this->response = $response;\n    }\n\n\n    /**\n     * Send a request synchronously and return a PSR-7 [@see ResponseInterface]\n     *\n     * @param RequestInterface $request\n     * @return ResponseInterface\n     */\n    public function send(RequestInterface $request): ResponseInterface\n    {\n        return $this->response;\n    }\n\n    /**\n     * Send a request asynchronously\n     *\n     * The response callback must be called if any response is returned from the request, and the failure\n     * callback should only be executed if a request was not completed.\n     *\n     * The response callback should pass a PSR-7 [@see ResponseInterface] as the one and only argument. The\n     * failure callback should pass a [@see Throwable] as the one and only argument.\n     *\n     * @param RequestInterface $request\n     * @param callable $onResponse\n     * @param callable $onFailure\n     * @return void\n     */\n    public function sendAsync(RequestInterface $request, callable $onResponse, callable $onFailure): void\n    {\n        $this->onResponse = $onResponse;\n        $this->onFailure = $onFailure;\n    }\n\n    /**\n     * Calling this method should execute any enqueued requests asynchronously\n     *\n     * @return void\n     */\n    public function wait(): void\n    {\n        if ($this->response !== null) {\n            $onResponse = $this->onResponse;\n            $onResponse($this->response);\n            return;\n        }\n\n        $onFailure = $this->onFailure;\n        $onFailure(new RuntimeException());\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/HttpClientCallTest/HttpClientCallTestErrorBodyMock.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\HttpClientCallTest;\n\n/**\n * Class HttpClientCallTestErrorBodyMock\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass HttpClientCallTestErrorBodyMock\n{\n\n}\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/HttpClientCallTest/HttpClientCallTestResponseBodyMock.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\HttpClientCallTest;\n\n/**\n * Class HttpClientCallTestResponseBodyMock\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass HttpClientCallTestResponseBodyMock\n{\n\n}\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/HttpClientCallTest/HttpClientCallTestServiceMethodMock.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\HttpClientCallTest;\n\nuse Psr\\Http\\Message\\RequestInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Tebru\\Retrofit\\Call;\nuse Tebru\\Retrofit\\Internal\\ServiceMethod;\n\n/**\n * Class HttpClientCallTestServiceMethodMock\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass HttpClientCallTestServiceMethodMock implements ServiceMethod\n{\n    /**\n     * @var RequestInterface\n     */\n    private $request;\n\n    /**\n     * @var HttpClientCallTestResponseBodyMock\n     */\n    private $response;\n\n    /**\n     * @var HttpClientCallTestErrorBodyMock\n     */\n    private $error;\n\n    /**\n     * Constructor\n     *\n     * @param RequestInterface $request\n     * @param HttpClientCallTestResponseBodyMock $response\n     * @param HttpClientCallTestErrorBodyMock $error\n     */\n    public function __construct(\n        RequestInterface $request,\n        ?HttpClientCallTestResponseBodyMock $response = null,\n        ?HttpClientCallTestErrorBodyMock $error = null\n    ) {\n        $this->request = $request;\n        $this->response = $response;\n        $this->error = $error;\n    }\n\n\n    /**\n     * Apply runtime arguments and build request\n     *\n     * @param array $args\n     * @return RequestInterface\n     */\n    public function toRequest(array $args): RequestInterface\n    {\n        return $this->request;\n    }\n\n    /**\n     * Take a response and convert it to expected value\n     *\n     * @param ResponseInterface $response\n     * @return mixed\n     */\n    public function toResponseBody(ResponseInterface $response)\n    {\n        $this->checkResponse($response);\n\n        return $this->response;\n    }\n\n    /**\n     * Take a response and convert it to expected value\n     *\n     * @param ResponseInterface $response\n     * @return mixed\n     */\n    public function toErrorBody(ResponseInterface $response)\n    {\n        $this->checkResponse($response);\n\n        return $this->error;\n    }\n\n    /**\n     * Take a [@see Call] and adapt to expected value\n     *\n     * @param Call $call\n     * @return mixed\n     */\n    public function adapt(Call $call)\n    {\n        return $call;\n    }\n\n    private function checkResponse(ResponseInterface $response): void\n    {\n        $body = (string)$response->getBody();\n        if ($body === '') {\n            return;\n        }\n\n        json_decode($body);\n\n        if (json_last_error() !== JSON_ERROR_NONE) {\n            throw new \\RuntimeException();\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/ProxyFactoryTest/PFTCTestCreate.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest;\n\nuse stdClass;\nuse Tebru\\Retrofit\\Annotation\\Body;\nuse Tebru\\Retrofit\\Annotation\\Field;\nuse Tebru\\Retrofit\\Annotation\\GET;\nuse Tebru\\Retrofit\\Annotation\\Path;\nuse Tebru\\Retrofit\\Annotation\\POST;\nuse Tebru\\Retrofit\\Annotation\\Query;\nuse Tebru\\Retrofit\\Call;\n\n/**\n * Interface ProxyFactoryTestClient\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface PFTCTestCreate\n{\n    /**\n     * @POST(\"/foo/path\")\n     * @Path(\"path\")\n     * @Body(\"body\")\n     * @Query(\"query\")\n     * @Field(\"field\", var=\"fields\")\n     *\n     * @param string $path\n     * @param stdClass $body\n     * @param string $query\n     * @param string[] $fields\n     * @return Call\n     */\n    public function simple(string $path, stdClass $body, string &$query = 'foo', string ...$fields): Call;\n\n    /**\n     * @GET(\"/\")\n     *\n     * @return Call\n     */\n    public static function static(): Call;\n}\n\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/ProxyFactoryTest/PFTCTestCreateCacheDirectoryFail.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest;\n\nuse stdClass;\nuse Tebru\\Retrofit\\Annotation\\Body;\nuse Tebru\\Retrofit\\Annotation\\Field;\nuse Tebru\\Retrofit\\Annotation\\GET;\nuse Tebru\\Retrofit\\Annotation\\Path;\nuse Tebru\\Retrofit\\Annotation\\POST;\nuse Tebru\\Retrofit\\Annotation\\Query;\nuse Tebru\\Retrofit\\Call;\n\n/**\n * Interface PFTCTestCreateCacheDirectoryFail\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface PFTCTestCreateCacheDirectoryFail\n{\n    /**\n     * @POST(\"/foo/path\")\n     * @Path(\"path\")\n     * @Body(\"body\")\n     * @Query(\"query\")\n     * @Field(\"field\", var=\"fields\")\n     *\n     * @param string $path\n     * @param stdClass $body\n     * @param string $query\n     * @param string[] $fields\n     * @return Call\n     */\n    public function simple(string $path, stdClass $body, string &$query = 'foo', string ...$fields): Call;\n\n    /**\n     * @GET(\"/\")\n     *\n     * @return Call\n     */\n    public static function static(): Call;\n}\n\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/ProxyFactoryTest/PFTCTestCreateClientFail.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest;\n\nuse stdClass;\nuse Tebru\\Retrofit\\Annotation\\Body;\nuse Tebru\\Retrofit\\Annotation\\Field;\nuse Tebru\\Retrofit\\Annotation\\GET;\nuse Tebru\\Retrofit\\Annotation\\Path;\nuse Tebru\\Retrofit\\Annotation\\POST;\nuse Tebru\\Retrofit\\Annotation\\Query;\nuse Tebru\\Retrofit\\Call;\n\n/**\n * Interface PFTCTestCreateClientFail\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface PFTCTestCreateClientFail\n{\n    /**\n     * @POST(\"/foo/path\")\n     * @Path(\"path\")\n     * @Body(\"body\")\n     * @Query(\"query\")\n     * @Field(\"field\", var=\"fields\")\n     *\n     * @param string $path\n     * @param stdClass $body\n     * @param string $query\n     * @param string[] $fields\n     * @return Call\n     */\n    public function simple(string $path, stdClass $body, string &$query = 'foo', string ...$fields): Call;\n\n    /**\n     * @GET(\"/\")\n     *\n     * @return Call\n     */\n    public static function static(): Call;\n}\n\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/ProxyFactoryTest/PFTCTestCreateTwice.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest;\n\nuse stdClass;\nuse Tebru\\Retrofit\\Annotation\\Body;\nuse Tebru\\Retrofit\\Annotation\\Field;\nuse Tebru\\Retrofit\\Annotation\\GET;\nuse Tebru\\Retrofit\\Annotation\\Path;\nuse Tebru\\Retrofit\\Annotation\\POST;\nuse Tebru\\Retrofit\\Annotation\\Query;\nuse Tebru\\Retrofit\\Call;\n\n/**\n * Interface PFTCTestCreateTwice\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface PFTCTestCreateTwice\n{\n    /**\n     * @POST(\"/foo/path\")\n     * @Path(\"path\")\n     * @Body(\"body\")\n     * @Query(\"query\")\n     * @Field(\"field\", var=\"fields\")\n     *\n     * @param string $path\n     * @param stdClass $body\n     * @param string $query\n     * @param string[] $fields\n     * @return Call\n     */\n    public function simple(string $path, stdClass $body, string &$query = 'foo', string ...$fields): Call;\n\n    /**\n     * @GET(\"/\")\n     *\n     * @return Call\n     */\n    public static function static(): Call;\n}\n\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/ProxyFactoryTest/PFTCTestCreateWithoutCache.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest;\n\nuse stdClass;\nuse Tebru\\Retrofit\\Annotation\\Body;\nuse Tebru\\Retrofit\\Annotation\\Field;\nuse Tebru\\Retrofit\\Annotation\\GET;\nuse Tebru\\Retrofit\\Annotation\\Path;\nuse Tebru\\Retrofit\\Annotation\\POST;\nuse Tebru\\Retrofit\\Annotation\\Query;\nuse Tebru\\Retrofit\\Call;\n\n/**\n * Interface PFTCTestCreateWithoutCache\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface PFTCTestCreateWithoutCache\n{\n    /**\n     * @POST(\"/foo/path\")\n     * @Path(\"path\")\n     * @Body(\"body\")\n     * @Query(\"query\")\n     * @Field(\"field\", var=\"fields\")\n     *\n     * @param string $path\n     * @param stdClass $body\n     * @param string $query\n     * @param string[] $fields\n     * @return Call\n     */\n    public function simple(string $path, stdClass $body, string &$query = 'foo', string ...$fields): Call;\n\n    /**\n     * @GET(\"/\")\n     *\n     * @return Call\n     */\n    public static function static(): Call;\n}\n\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/ProxyFactoryTest/ProxyFactoryTestClientNoReturnType.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest;\n\nuse stdClass;\n\n/**\n * Interface ProxyFactoryTestClientNoReturnType\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface ProxyFactoryTestClientNoReturnType\n{\n    public function foo(stdClass $foo);\n}\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/ProxyFactoryTest/ProxyFactoryTestClientNoTypehint.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest;\n\nuse Tebru\\Retrofit\\Call;\n\n/**\n * Interface ProxyFactoryTestClientNoTypehint\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface ProxyFactoryTestClientNoTypehint\n{\n    public function foo($foo): Call;\n}\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/ProxyFactoryTest/ProxyFactoryTestFilesystem.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest;\n\nuse Tebru\\Retrofit\\Internal\\Filesystem;\n\n/**\n * Class ProxyFactoryTestFilesystem\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass ProxyFactoryTestFilesystem extends Filesystem\n{\n    public $makeDirectory = true;\n    public $put = true;\n\n    public $directory;\n    public $filename;\n    public $contents;\n\n    public function makeDirectory(string $pathname, int $mode = 0777, bool $recursive = false, $context = null): bool\n    {\n        $this->directory = $pathname;\n        return $this->makeDirectory;\n    }\n\n    public function put(string $filename, string $contents): bool\n    {\n        $this->filename = $filename;\n        $this->contents = $contents;\n        return $this->put;\n    }\n\n}\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/ProxyFactoryTest/ProxyFactoryTestHttpClient.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest;\n\nuse Psr\\Http\\Message\\RequestInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Tebru\\Retrofit\\HttpClient;\n\n/**\n * Class ProxyFactoryTestHttpClient\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass ProxyFactoryTestHttpClient implements HttpClient\n{\n    /**\n     * Send a request synchronously and return a PSR-7 [@see ResponseInterface]\n     *\n     * @param RequestInterface $request\n     * @return ResponseInterface\n     */\n    public function send(RequestInterface $request): ResponseInterface\n    {\n        // TODO: Implement send() method.\n    }\n\n    /**\n     * Send a request asynchronously\n     *\n     * The response callback must be called if any response is returned from the request, and the failure\n     * callback should only be executed if a request was not completed.\n     *\n     * The response callback should pass a PSR-7 [@see ResponseInterface] as the one and only argument. The\n     * failure callback should pass a [@see Throwable] as the one and only argument.\n     *\n     * @param RequestInterface $request\n     * @param callable $onResponse\n     * @param callable $onFailure\n     * @return void\n     */\n    public function sendAsync(RequestInterface $request, callable $onResponse, callable $onFailure): void\n    {\n        // TODO: Implement sendAsync() method.\n    }\n\n    /**\n     * Calling this method should execute any enqueued requests asynchronously\n     *\n     * @return void\n     */\n    public function wait(): void\n    {\n        // TODO: Implement wait() method.\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/ServiceMethod/ServiceMethodFactoryTest/ServiceMethodFactoryTestClient.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ServiceMethod\\ServiceMethodFactoryTest;\n\nuse Tebru\\Retrofit\\Annotation\\Body;\nuse Tebru\\Retrofit\\Annotation\\ErrorBody;\nuse Tebru\\Retrofit\\Annotation\\GET;\nuse Tebru\\Retrofit\\Annotation\\POST;\nuse Tebru\\Retrofit\\Annotation\\ResponseBody;\nuse Tebru\\Retrofit\\Call;\n\n/**\n * Interface ServiceMethodFactoryTestClient\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface ServiceMethodFactoryTestClient\n{\n    /**\n     * @GET(\"/foo\")\n     */\n    public function foo(): Call;\n\n    /**\n     * @GET(\"/bar\")\n     */\n    public function bar();\n\n    /**\n     * @GET(\"/\")\n     * @Body(\"body\")\n     */\n    public function baz(Body $body): Call;\n\n    /**\n     * @GET(\"/\")\n     * @ResponseBody(\"Foo\")\n     * @ErrorBody(\"Bar\")\n     */\n    public function qux(): Call;\n}\n"
  },
  {
    "path": "tests/Mock/Unit/Internal/ServiceMethod/ServiceMethodFactoryTest/ServiceMethodFactoryTestConverterFactory.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ServiceMethod\\ServiceMethodFactoryTest;\n\nuse Psr\\Http\\Message\\StreamInterface;\nuse Tebru\\PhpType\\TypeToken;\nuse Tebru\\Retrofit\\ConverterFactory;\nuse Tebru\\Retrofit\\RequestBodyConverter;\nuse Tebru\\Retrofit\\ResponseBodyConverter;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class ServiceMethodFactorTestConverter\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass ServiceMethodFactoryTestConverterFactory implements ConverterFactory\n{\n    /**\n     * Return a [@see ResponseBodyConverter] or null\n     *\n     * @param TypeToken $type\n     * @return null|ResponseBodyConverter\n     */\n    public function responseBodyConverter(TypeToken $type): ?ResponseBodyConverter\n    {\n        return new class implements ResponseBodyConverter {\n            public function convert(StreamInterface $value)\n            {\n                return $value;\n            }\n        };\n    }\n\n    /**\n     * Return a [@see RequestBodyConverter] or null\n     *\n     * @param TypeToken $type\n     * @return null|RequestBodyConverter\n     */\n    public function requestBodyConverter(TypeToken $type): ?RequestBodyConverter\n    {\n        return new class implements RequestBodyConverter {\n            public function convert($value): StreamInterface\n            {\n                return $value;\n            }\n        };\n    }\n\n    /**\n     * Return a [@see StringConverter] or null\n     *\n     * @param TypeToken $type\n     * @return null|StringConverter\n     */\n    public function stringConverter(TypeToken $type): ?StringConverter\n    {\n        return new class implements StringConverter {\n            public function convert($value): string\n            {\n                return (string)$value;\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/MockCall.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit;\n\nuse Psr\\Http\\Message\\RequestInterface;\nuse Tebru\\Retrofit\\Call;\nuse Tebru\\Retrofit\\Response;\n\n/**\n * Class MockCall\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass MockCall implements Call\n{\n    /**\n     * @return Response\n     */\n    public function execute(): Response\n    {\n    }\n\n    /**\n     * @param callable $onResponse\n     * @param callable $onFailure\n     * @return Call\n     */\n    public function enqueue(?callable $onResponse = null, ?callable $onFailure = null): Call\n    {\n    }\n\n    /**\n     * @return void\n     */\n    public function wait(): void\n    {\n    }\n\n    /**\n     * @return RequestInterface\n     */\n    public function request(): RequestInterface\n    {\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/MockConverterFactory.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit;\n\nuse Tebru\\PhpType\\TypeToken;\nuse Tebru\\Retrofit\\ConverterFactory;\nuse Tebru\\Retrofit\\RequestBodyConverter;\nuse Tebru\\Retrofit\\ResponseBodyConverter;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class MockConverterFactory\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass MockConverterFactory implements ConverterFactory\n{\n    /**\n     * Return a [@see ResponseBodyConverter] or null\n     *\n     * @param TypeToken $type\n     * @return null|ResponseBodyConverter\n     */\n    public function responseBodyConverter(TypeToken $type): ?ResponseBodyConverter\n    {\n        return null;\n    }\n\n    /**\n     * Return a [@see RequestBodyConverter] or null\n     *\n     * @param TypeToken $type\n     * @return null|RequestBodyConverter\n     */\n    public function requestBodyConverter(TypeToken $type): ?RequestBodyConverter\n    {\n        return null;\n    }\n\n    /**\n     * Return a [@see StringConverter] or null\n     *\n     * @param TypeToken $type\n     * @return null|StringConverter\n     */\n    public function stringConverter(TypeToken $type): ?StringConverter\n    {\n        return null;\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/ApiClient.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\nuse Tebru\\Retrofit\\Annotation\\Body;\nuse Tebru\\Retrofit\\Annotation\\DELETE;\nuse Tebru\\Retrofit\\Annotation\\Field;\nuse Tebru\\Retrofit\\Annotation\\FieldMap;\nuse Tebru\\Retrofit\\Annotation\\GET;\nuse Tebru\\Retrofit\\Annotation\\HEAD;\nuse Tebru\\Retrofit\\Annotation\\Header;\nuse Tebru\\Retrofit\\Annotation\\HeaderMap;\nuse Tebru\\Retrofit\\Annotation\\Headers;\nuse Tebru\\Retrofit\\Annotation\\OPTIONS;\nuse Tebru\\Retrofit\\Annotation\\Part;\nuse Tebru\\Retrofit\\Annotation\\PartMap;\nuse Tebru\\Retrofit\\Annotation\\PATCH;\nuse Tebru\\Retrofit\\Annotation\\Path;\nuse Tebru\\Retrofit\\Annotation\\POST;\nuse Tebru\\Retrofit\\Annotation\\PUT;\nuse Tebru\\Retrofit\\Annotation\\Query;\nuse Tebru\\Retrofit\\Annotation\\QueryMap;\nuse Tebru\\Retrofit\\Annotation\\QueryName;\nuse Tebru\\Retrofit\\Annotation\\REQUEST;\nuse Tebru\\Retrofit\\Annotation\\ResponseBody;\nuse Tebru\\Retrofit\\Annotation\\Url;\nuse Tebru\\Retrofit\\Call;\nuse Tebru\\Retrofit\\Http\\MultipartBody;\n\n/**\n * Interface ApiClient\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface ApiClient\n{\n    /**\n     * @GET(\"/\")\n     */\n    public function get(): Call;\n\n    /**\n     * @OPTIONS(\"/{my-path}?q=test\")\n     * @Url(\"newUrl\")\n     * @Path(\"my-path\", var=\"path\")\n     * @Query(\"query[]\", var=\"query1\")\n     * @QueryName(\"query2\")\n     * @QueryMap(\"queryMap\")\n     */\n    public function uri(string $newUrl, array $queryMap, array $query1, bool $query2, string $path): Call;\n\n    /**\n     * @HEAD(\"/\")\n     * @Headers({\n     *     \"X-Foo: bar\",\n     *     \"X-Baz: qux\",\n     *     \"X-Header[]: first\"\n     * })\n     * @Header(\"X-Header[]\", var=\"header1\")\n     * @Header(\"header2\")\n     * @HeaderMap(\"headerMap\")\n     */\n    public function headers(array $headerMap, array $header1, int $header2): Call;\n\n    /**\n     * @POST(\"/\")\n     */\n    public function postWithoutBody(): Call;\n\n    /**\n     * @PUT(\"/\")\n     * @Body(\"requestBody\")\n     * @ResponseBody(\"Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest\\RetrofitTestResponseBodyMock\")\n     */\n    public function body(RetrofitTestRequestBodyMock $requestBody): Call;\n\n    /**\n     * @PATCH(\"/\")\n     * @Field(\"field1\")\n     * @Field(\"field2\")\n     * @Field(\"field3\", encoded=true)\n     * @FieldMap(\"fieldMap\")\n     */\n    public function field(float $field1, bool $field2, string $field3, array $fieldMap): Call;\n\n    /**\n     * @REQUEST(\"/\", type=\"FOO\", body=true)\n     * @Part(\"part1\")\n     * @Part(\"part2\")\n     * @PartMap(\"partMap\")\n     */\n    public function part(RetrofitTestRequestBodyMock $part1, MultipartBody $part2, array $partMap): Call;\n\n    /**\n     * @GET(\"/\")\n     */\n    public function callAdapter(): RetrofitTestAdaptedCallMock;\n\n    /**\n     * @DELETE(\"/\")\n     * @RetrofitTestCustomAnnotation\n     */\n    public function customAnnotation(): Call;\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/CacheableApiClient.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\nuse Tebru\\Retrofit\\Annotation\\GET;\n\n/**\n * Interface CacheableApiClient\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface CacheableApiClient extends ApiClient\n{\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/DefaultParamsApiClient.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\nuse Tebru\\Retrofit\\Annotation\\GET;\nuse Tebru\\Retrofit\\Annotation\\HeaderMap;\nuse Tebru\\Retrofit\\Annotation\\Query;\nuse Tebru\\Retrofit\\Annotation\\QueryMap;\nuse Tebru\\Retrofit\\Call;\n\n/**\n * Interface DefaultParamsApiClient\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface DefaultParamsApiClient\n{\n    /**\n     * @GET(\"/\")\n     * @Query(\"string\")\n     * @Query(\"bool\")\n     * @Query(\"int\")\n     * @Query(\"float\")\n     * @QueryMap(\"client\")\n     * @HeaderMap(\"array\")\n     *\n     * @param string $string\n     * @param bool $bool\n     * @param int $int\n     * @param float $float\n     * @param array $array\n     * @param ApiClient|null $client\n     * @return Call\n     */\n    public function getWithDefaults(\n        ?string $string = 'test',\n        ?bool $bool = true,\n        ?int $int = 1,\n        ?float $float = 3.2,\n        ?array $array = ['test' => ['value']],\n        ?ApiClient $client = null\n    ): Call;\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/InvalidSyntaxApiClient.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\nuse Tebru\\Retrofit\\Annotation\\GET;\nuse Tebru\\Retrofit\\Annotation\\Headers;\nuse Tebru\\Retrofit\\Call;\n\n/**\n * Interface InvalidSyntaxApiClient\n *\n * @author Nate Brunette <n@tebru.net>\n */\ninterface InvalidSyntaxApiClient\n{\n    /**\n     * @GET(\"/\")\n     * @Headers({\"asdf\": \"asdf\"})\n     */\n    public function get(): Call;\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/RetrofitTestAdaptedCallMock.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\n/**\n * Class RetrofitTestAdaptedCallMock\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass RetrofitTestAdaptedCallMock\n{\n\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/RetrofitTestCallAdapterFactory.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\nuse Tebru\\PhpType\\TypeToken;\nuse Tebru\\Retrofit\\CallAdapter;\nuse Tebru\\Retrofit\\CallAdapterFactory;\n\n/**\n * Class RetrofitTestCallAdapterFactory\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass RetrofitTestCallAdapterFactory implements CallAdapterFactory\n{\n    /**\n     * Returns true if the factory supports this type\n     *\n     * @param TypeToken $type\n     * @return bool\n     */\n    public function supports(TypeToken $type): bool\n    {\n        return $type->isA(RetrofitTestAdaptedCallMock::class);\n    }\n\n    /**\n     * Create a new factory from type\n     *\n     * @param TypeToken $type\n     * @return CallAdapter\n     */\n    public function create(TypeToken $type): CallAdapter\n    {\n        return new RetrofitTestCallAdapterMock();\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/RetrofitTestCallAdapterMock.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\nuse Tebru\\Retrofit\\Call;\nuse Tebru\\Retrofit\\CallAdapter;\n\n/**\n * Class RetrofitTestCallAdapterMock\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass RetrofitTestCallAdapterMock implements CallAdapter\n{\n    /**\n     * Accepts a [@see Call] and converts it to the appropriate type\n     *\n     * @param Call $call\n     * @return mixed\n     */\n    public function adapt(Call $call)\n    {\n        return new RetrofitTestAdaptedCallMock();\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/RetrofitTestConverterFactory.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\nuse Tebru\\PhpType\\TypeToken;\nuse Tebru\\Retrofit\\ConverterFactory;\nuse Tebru\\Retrofit\\RequestBodyConverter;\nuse Tebru\\Retrofit\\ResponseBodyConverter;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class RetrofitTestConverterFactory\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass RetrofitTestConverterFactory implements ConverterFactory\n{\n    /**\n     * Return a [@see ResponseBodyConverter] or null\n     *\n     * @param TypeToken $type\n     * @return null|ResponseBodyConverter\n     */\n    public function responseBodyConverter(TypeToken $type): ?ResponseBodyConverter\n    {\n        return new RetrofitTestResponseBodyConverter();\n    }\n\n    /**\n     * Return a [@see RequestBodyConverter] or null\n     *\n     * @param TypeToken $type\n     * @return null|RequestBodyConverter\n     */\n    public function requestBodyConverter(TypeToken $type): ?RequestBodyConverter\n    {\n        return new RetrofitTestRequestBodyConverter();\n    }\n\n    /**\n     * Return a [@see StringConverter] or null\n     *\n     * @param TypeToken $type\n     * @return null|StringConverter\n     */\n    public function stringConverter(TypeToken $type): ?StringConverter\n    {\n        return null;\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/RetrofitTestCustomAnnotation.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\n\n/**\n * Class RetrofitTestCustomAnnotation\n *\n * @author Nate Brunette <n@tebru.net>\n *\n * @Annotation\n * @Target({\"CLASS\", \"METHOD\"})\n */\nclass RetrofitTestCustomAnnotation extends AbstractAnnotation\n{\n    protected function init(): void\n    {\n    }\n\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/RetrofitTestCustomAnnotationHandler.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\nuse Tebru\\AnnotationReader\\AbstractAnnotation;\nuse Tebru\\Retrofit\\AnnotationHandler;\nuse Tebru\\Retrofit\\Converter;\nuse Tebru\\Retrofit\\RequestBodyConverter;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\n/**\n * Class RetrofitTestCustomAnnotationHandler\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass RetrofitTestCustomAnnotationHandler implements AnnotationHandler\n{\n    /**\n     * Handle an annotation, mutating the [@see ServiceMethodBuilder] based on the value\n     *\n     * @param AbstractAnnotation $annotation The annotation to handle\n     * @param ServiceMethodBuilder $serviceMethodBuilder Used to construct a [@see ServiceMethod]\n     * @param Converter|StringConverter|RequestBodyConverter|null $converter Converter used to convert types before sending to service method\n     * @param int|null $index The position of the parameter or null if annotation does not reference parameter\n     * @return void\n     */\n    public function handle(\n        AbstractAnnotation $annotation,\n        ServiceMethodBuilder $serviceMethodBuilder,\n        ?Converter $converter,\n        ?int $index\n    ): void {\n        $serviceMethodBuilder->addHeader('foo', 'bar');\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/RetrofitTestDelegateProxy.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\nuse Tebru\\Retrofit\\Annotation\\POST;\nuse Tebru\\Retrofit\\Call;\nuse Tebru\\Retrofit\\Http\\MultipartBody;\nuse Tebru\\Retrofit\\Proxy;\n\n/**\n * Class RetrofitTestProxy\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass RetrofitTestDelegateProxy implements ApiClient, DefaultParamsApiClient, Proxy\n{\n    /**\n     * @var Proxy\n     */\n    private $proxy;\n\n    /**\n     * Constructor\n     *\n     */\n    public function __construct(Proxy $proxy)\n    {\n        $this->proxy = $proxy;\n    }\n\n    public function get(): Call\n    {\n        return $this->__handleRetrofitRequest(ApiClient::class, __FUNCTION__, func_get_args(), []);\n    }\n\n    public function uri(string $newUrl, array $queryMap, array $query1, bool $query2, string $path): Call\n    {\n        return $this->__handleRetrofitRequest(ApiClient::class, __FUNCTION__, func_get_args(), []);\n    }\n\n    public function headers(array $headerMap, array $header1, int $header2): Call\n    {\n        return $this->__handleRetrofitRequest(ApiClient::class, __FUNCTION__, func_get_args(), []);\n    }\n\n    /**\n     * @POST(\"/\")\n     */\n    public function postWithoutBody(): Call\n    {\n        return $this->__handleRetrofitRequest(ApiClient::class, __FUNCTION__, func_get_args(), []);\n    }\n\n    public function body(RetrofitTestRequestBodyMock $requestBody): Call\n    {\n        return $this->__handleRetrofitRequest(ApiClient::class, __FUNCTION__, func_get_args(), []);\n    }\n\n    public function field(float $field1, bool $field2, string $field3, array $fieldMap): Call\n    {\n        return $this->__handleRetrofitRequest(ApiClient::class, __FUNCTION__, func_get_args(), []);\n    }\n\n    public function part(RetrofitTestRequestBodyMock $part1, MultipartBody $part2, array $partMap): Call\n    {\n        return $this->__handleRetrofitRequest(ApiClient::class, __FUNCTION__, func_get_args(), []);\n    }\n\n    public function callAdapter(): RetrofitTestAdaptedCallMock\n    {\n        return $this->__handleRetrofitRequest(ApiClient::class, __FUNCTION__, func_get_args(), []);\n    }\n\n    public function customAnnotation(): Call\n    {\n        return $this->__handleRetrofitRequest(ApiClient::class, __FUNCTION__, func_get_args(), []);\n    }\n\n    /**\n     * @param string $string\n     * @param bool $bool\n     * @param int $int\n     * @param float $float\n     * @param array $array\n     * @param ApiClient|null $client\n     * @return Call\n     */\n    public function getWithDefaults(\n        ?string $string = 'test',\n        ?bool $bool = true,\n        ?int $int = 1,\n        ?float $float = 3.2,\n        ?array $array = [],\n        ?ApiClient $client = null\n    ): Call {\n        return $this->__handleRetrofitRequest(\n            DefaultParamsApiClient::class,\n            __FUNCTION__,\n            func_get_args(),\n            ['test', true, 1, 3.2, [], null]\n        );\n    }\n\n    /**\n     * Constructs a [@see Call] object based on an interface method and arguments, then passes it through a\n     * [@see CallAdapter] before returning.\n     *\n     * @param string $interfaceName\n     * @param string $methodName\n     * @param array $args\n     * @param array $defaultArgs\n     * @return mixed\n     */\n    public function __handleRetrofitRequest(string $interfaceName, string $methodName, array $args, array $defaultArgs)\n    {\n        return $this->proxy->__handleRetrofitRequest($interfaceName, $methodName, $args, []);\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/RetrofitTestHttpClient.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\nuse GuzzleHttp\\Psr7\\Response;\nuse Psr\\Http\\Message\\RequestInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Tebru\\Retrofit\\HttpClient;\n\n/**\n * Class RetrofitTestHttpClient\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass RetrofitTestHttpClient implements HttpClient\n{\n    /**\n     * @var RequestInterface[]\n     */\n    public $requests = [];\n\n    /**\n     * Send a request synchronously and return a PSR-7 [@see ResponseInterface]\n     *\n     * @param RequestInterface $request\n     * @return ResponseInterface\n     */\n    public function send(RequestInterface $request): ResponseInterface\n    {\n        $this->requests[] = $request;\n\n        return new Response();\n    }\n\n    /**\n     * Send a request asynchronously\n     *\n     * The response callback must be called if any response is returned from the request, and the failure\n     * callback should only be executed if a request was not completed.\n     *\n     * The response callback should pass a PSR-7 [@see ResponseInterface] as the one and only argument. The\n     * failure callback should pass a [@see Throwable] as the one and only argument.\n     *\n     * @param RequestInterface $request\n     * @param callable $onResponse\n     * @param callable $onFailure\n     * @return void\n     */\n    public function sendAsync(RequestInterface $request, callable $onResponse, callable $onFailure): void\n    {\n        $this->requests[] = $request;\n    }\n\n    /**\n     * Calling this method should execute any enqueued requests asynchronously\n     *\n     * @return void\n     */\n    public function wait(): void\n    {\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/RetrofitTestProxyFactory.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\nuse Tebru\\Retrofit\\DefaultProxyFactoryAware;\nuse Tebru\\Retrofit\\Proxy;\nuse Tebru\\Retrofit\\ProxyFactory;\n\n/**\n * Class RetrofitTestProxyFactory\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass RetrofitTestProxyFactory implements ProxyFactory, DefaultProxyFactoryAware\n{\n    /**\n     * @var ProxyFactory\n     */\n    private $proxyFactory;\n\n    /**\n     * Create a new [@see Proxy] from given service name\n     *\n     * Returns null if the factory cannot handle the service\n     *\n     * @param string $service\n     * @return null|Proxy\n     */\n    public function create(string $service): ?Proxy\n    {\n        return new RetrofitTestDelegateProxy($this->proxyFactory->create(ApiClient::class));\n    }\n\n    /**\n     * Set the default proxy factory\n     *\n     * @param ProxyFactory $proxyFactory\n     * @return void\n     */\n    public function setDefaultProxyFactory(ProxyFactory $proxyFactory): void\n    {\n        $this->proxyFactory = $proxyFactory;\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/RetrofitTestRequestBodyConverter.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\nuse function GuzzleHttp\\Psr7\\stream_for;\nuse Psr\\Http\\Message\\StreamInterface;\nuse Tebru\\Retrofit\\RequestBodyConverter;\n\n/**\n * Class RetrofitTestRequestBodyConverter\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass RetrofitTestRequestBodyConverter implements RequestBodyConverter\n{\n    /**\n     * Convert to stream\n     *\n     * @param RetrofitTestRequestBodyMock $value\n     * @return StreamInterface\n     */\n    public function convert($value): StreamInterface\n    {\n        return stream_for(json_encode(['id' => $value->id, 'name' => $value->name]));\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/RetrofitTestRequestBodyMock.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\n/**\n * Class RetrofitTestRequestBodyMock\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass RetrofitTestRequestBodyMock\n{\n    public $id;\n    public $name;\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/RetrofitTestResponseBodyConverter.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\nuse Psr\\Http\\Message\\StreamInterface;\nuse Tebru\\Retrofit\\ResponseBodyConverter;\n\n/**\n * Class RetrofitTestResponseBodyConverter\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass RetrofitTestResponseBodyConverter implements ResponseBodyConverter\n{\n    /**\n     * Convert from stream to any type\n     *\n     * @param StreamInterface $value\n     * @return mixed\n     */\n    public function convert(StreamInterface $value)\n    {\n        return new RetrofitTestResponseBodyMock();\n    }\n}\n"
  },
  {
    "path": "tests/Mock/Unit/RetrofitTest/RetrofitTestResponseBodyMock.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest;\n\n/**\n * Class RetrofitTestResponseBodyMock\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass RetrofitTestResponseBodyMock\n{\n\n\n}\n"
  },
  {
    "path": "tests/Unit/Internal/AnnotationHandlersTest.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Unit\\Internal;\n\nuse InvalidArgumentException;\nuse PHPUnit\\Framework\\TestCase;\nuse Tebru\\Retrofit\\Annotation\\Body;\nuse Tebru\\Retrofit\\Annotation\\Field;\nuse Tebru\\Retrofit\\Annotation\\FieldMap;\nuse Tebru\\Retrofit\\Annotation\\GET;\nuse Tebru\\Retrofit\\Annotation\\Header;\nuse Tebru\\Retrofit\\Annotation\\HeaderMap;\nuse Tebru\\Retrofit\\Annotation\\Headers;\nuse Tebru\\Retrofit\\Annotation\\Part;\nuse Tebru\\Retrofit\\Annotation\\PartMap;\nuse Tebru\\Retrofit\\Annotation\\Path;\nuse Tebru\\Retrofit\\Annotation\\POST;\nuse Tebru\\Retrofit\\Annotation\\Query;\nuse Tebru\\Retrofit\\Annotation\\QueryMap;\nuse Tebru\\Retrofit\\Annotation\\QueryName;\nuse Tebru\\Retrofit\\Annotation\\Url;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\BodyAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\FieldAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\FieldMapAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\HeaderAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\HeaderMapAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\HeadersAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\HttpRequestAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\PartAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\PartMapAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\PathAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\QueryAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\QueryMapAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\QueryNameAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\UrlAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultRequestBodyConverter;\nuse Tebru\\Retrofit\\Internal\\ServiceMethod\\DefaultServiceMethodBuilder;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultStringConverter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\BodyParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\FieldMapParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\FieldParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\HeaderMapParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\HeaderParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\PartMapParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\PartParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\PathParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\QueryMapParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\QueryNameParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\QueryParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\UrlParamHandler;\nuse Tebru\\Retrofit\\RequestBodyConverter;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\nuse Tebru\\Retrofit\\StringConverter;\n\nclass AnnotationHandlersTest extends TestCase\n{\n    /**\n     * @var ServiceMethodBuilder\n     */\n    private $serviceMethodBuilder;\n\n    /**\n     * @var RequestBodyConverter\n     */\n    private $requestBodyConverter;\n\n    /**\n     * @var StringConverter\n     */\n    private $stringConverter;\n\n    public function setUp()\n    {\n        $this->serviceMethodBuilder = new DefaultServiceMethodBuilder();\n        $this->requestBodyConverter = new DefaultRequestBodyConverter();\n        $this->stringConverter = new DefaultStringConverter();\n\n    }\n\n    public function testHandleBodyAnnotation()\n    {\n        (new BodyAnnotHandler())->handle(\n            new Body(['value' => 'foo']),\n            $this->serviceMethodBuilder,\n            $this->requestBodyConverter,\n            1\n        );\n\n        self::assertAttributeSame(true, 'hasBody', $this->serviceMethodBuilder);\n        self::assertAttributeCount(1, 'parameterHandlers', $this->serviceMethodBuilder);\n        self::assertAttributeEquals([1 => new BodyParamHandler($this->requestBodyConverter)], 'parameterHandlers', $this->serviceMethodBuilder);\n    }\n\n    public function testHandleBodyAnnotationWrongConverter()\n    {\n        try {\n            (new BodyAnnotHandler())->handle(\n                new Body(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                null,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Converter must be a RequestBodyConverter, NULL found', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandleFieldAnnotation()\n    {\n        (new FieldAnnotHandler())->handle(\n            new Field(['value' => 'foo', 'encoded' => true]),\n            $this->serviceMethodBuilder,\n            $this->stringConverter,\n            1\n        );\n\n        self::assertAttributeSame(true, 'hasBody', $this->serviceMethodBuilder);\n        self::assertAttributeSame('application/x-www-form-urlencoded', 'contentType', $this->serviceMethodBuilder);\n        self::assertAttributeCount(1, 'parameterHandlers', $this->serviceMethodBuilder);\n        self::assertAttributeEquals([1 => new FieldParamHandler($this->stringConverter, 'foo', true)], 'parameterHandlers', $this->serviceMethodBuilder);\n    }\n\n    public function testHandleFieldAnnotationWrongAnnotation()\n    {\n        try {\n            (new FieldAnnotHandler())->handle(\n                new Body(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                $this->stringConverter,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Annotation must be encodable', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandleFieldAnnotationWrongConverter()\n    {\n        try {\n            (new FieldAnnotHandler())->handle(\n                new Field(['value' => 'foo', 'encoded' => true]),\n                $this->serviceMethodBuilder,\n                null,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Converter must be a StringConverter, NULL found', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandleFieldMapAnnotation()\n    {\n        (new FieldMapAnnotHandler())->handle(\n            new FieldMap(['value' => 'foo', 'encoded' => true]),\n            $this->serviceMethodBuilder,\n            $this->stringConverter,\n            1\n        );\n\n        self::assertAttributeSame(true, 'hasBody', $this->serviceMethodBuilder);\n        self::assertAttributeSame('application/x-www-form-urlencoded', 'contentType', $this->serviceMethodBuilder);\n        self::assertAttributeCount(1, 'parameterHandlers', $this->serviceMethodBuilder);\n        self::assertAttributeEquals([1 => new FieldMapParamHandler($this->stringConverter, true)], 'parameterHandlers', $this->serviceMethodBuilder);\n    }\n\n    public function testHandleFieldMapAnnotationWrongAnnotation()\n    {\n        try {\n            (new FieldMapAnnotHandler())->handle(\n                new Body(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                $this->stringConverter,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Annotation must be encodable', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandleFieldMapAnnotationWrongConverter()\n    {\n        try {\n            (new FieldMapAnnotHandler())->handle(\n                new FieldMap(['value' => 'foo', 'encoded' => true]),\n                $this->serviceMethodBuilder,\n                null,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Converter must be a StringConverter, NULL found', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandleHeaderAnnotation()\n    {\n        (new HeaderAnnotHandler())->handle(\n            new Header(['value' => 'foo']),\n            $this->serviceMethodBuilder,\n            $this->stringConverter,\n            1\n        );\n\n        self::assertAttributeCount(1, 'parameterHandlers', $this->serviceMethodBuilder);\n        self::assertAttributeEquals([1 => new HeaderParamHandler($this->stringConverter, 'foo')], 'parameterHandlers', $this->serviceMethodBuilder);\n    }\n\n    public function testHandleHeaderAnnotationWrongConverter()\n    {\n        try {\n            (new HeaderAnnotHandler())->handle(\n                new Header(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                null,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Converter must be a StringConverter, NULL found', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandleHeaderMapAnnotation()\n    {\n        (new HeaderMapAnnotHandler())->handle(\n            new HeaderMap(['value' => 'foo']),\n            $this->serviceMethodBuilder,\n            $this->stringConverter,\n            1\n        );\n\n        self::assertAttributeCount(1, 'parameterHandlers', $this->serviceMethodBuilder);\n        self::assertAttributeEquals([1 => new HeaderMapParamHandler($this->stringConverter)], 'parameterHandlers', $this->serviceMethodBuilder);\n    }\n\n    public function testHandleHeaderMapAnnotationWrongConverter()\n    {\n        try {\n            (new HeaderMapAnnotHandler())->handle(\n                new HeaderMap(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                null,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Converter must be a StringConverter, NULL found', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandleHeadersAnnotation()\n    {\n        (new HeadersAnnotHandler())->handle(\n            new Headers(['value' => ['Foo: bar', 'Baz: true']]),\n            $this->serviceMethodBuilder,\n            null,\n            1\n        );\n\n        self::assertAttributeSame(['foo' => ['bar'], 'baz' => ['true']], 'headers', $this->serviceMethodBuilder);\n    }\n\n    public function testHandleHeadersAnnotationWrongConverter()\n    {\n        try {\n            (new HeadersAnnotHandler())->handle(\n                new Headers(['value' => ['Foo: bar', 'Baz: true']]),\n                $this->serviceMethodBuilder,\n                $this->stringConverter,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Converter must be null, object found', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandleGETAnnotation()\n    {\n        (new HttpRequestAnnotHandler())->handle(\n            new GET(['value' => '/my/path?q=test']),\n            $this->serviceMethodBuilder,\n            null,\n            1\n        );\n\n        self::assertAttributeSame('GET', 'method', $this->serviceMethodBuilder);\n        self::assertAttributeSame('/my/path?q=test', 'path', $this->serviceMethodBuilder);\n        self::assertAttributeSame(false, 'hasBody', $this->serviceMethodBuilder);\n    }\n\n    public function testHandleGETAnnotationWrongConverter()\n    {\n        try {\n            (new HttpRequestAnnotHandler())->handle(\n                new GET(['value' => '/my/path?q=test']),\n                $this->serviceMethodBuilder,\n                $this->stringConverter,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Converter must be null, object found', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandlePOSTAnnotation()\n    {\n        (new HttpRequestAnnotHandler())->handle(\n            new POST(['value' => '/my/path?q=test']),\n            $this->serviceMethodBuilder,\n            null,\n            1\n        );\n\n        self::assertAttributeSame('POST', 'method', $this->serviceMethodBuilder);\n        self::assertAttributeSame('/my/path?q=test', 'path', $this->serviceMethodBuilder);\n    }\n\n    public function testHandlePOSTAnnotationWrongAnnotation()\n    {\n        try {\n            (new HttpRequestAnnotHandler())->handle(\n                new Body(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                $this->stringConverter,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Annotation must be an HttpRequest', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandlePartAnnotation()\n    {\n        (new PartAnnotHandler())->handle(\n            new Part(['value' => 'foo']),\n            $this->serviceMethodBuilder,\n            $this->requestBodyConverter,\n            1\n        );\n\n        self::assertAttributeSame(true, 'hasBody', $this->serviceMethodBuilder);\n        self::assertAttributeSame('multipart/form-data', 'contentType', $this->serviceMethodBuilder);\n        self::assertAttributeCount(1, 'parameterHandlers', $this->serviceMethodBuilder);\n        self::assertAttributeEquals([1 => new PartParamHandler($this->requestBodyConverter, 'foo', 'binary')], 'parameterHandlers', $this->serviceMethodBuilder);\n    }\n\n    public function testHandlePartAnnotationWrongAnnotation()\n    {\n        try {\n            (new PartAnnotHandler())->handle(\n                new Body(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                $this->stringConverter,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Annotation must be a Part', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandlePartAnnotationWrongConverter()\n    {\n        try {\n            (new PartAnnotHandler())->handle(\n                new Part(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                null,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Converter must be a RequestBodyConverter, NULL found', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandlePartMapAnnotation()\n    {\n        (new PartMapAnnotHandler())->handle(\n            new PartMap(['value' => 'foo']),\n            $this->serviceMethodBuilder,\n            $this->requestBodyConverter,\n            1\n        );\n\n        self::assertAttributeSame(true, 'hasBody', $this->serviceMethodBuilder);\n        self::assertAttributeSame('multipart/form-data', 'contentType', $this->serviceMethodBuilder);\n        self::assertAttributeCount(1, 'parameterHandlers', $this->serviceMethodBuilder);\n        self::assertAttributeEquals([1 => new PartMapParamHandler($this->requestBodyConverter, 'binary')], 'parameterHandlers', $this->serviceMethodBuilder);\n    }\n\n    public function testHandlePartMapAnnotationWrongAnnotation()\n    {\n        try {\n            (new PartMapAnnotHandler())->handle(\n                new Body(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                $this->stringConverter,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Annotation must be a PartMap', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandlePartMapAnnotationWrongConverter()\n    {\n        try {\n            (new PartMapAnnotHandler())->handle(\n                new PartMap(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                null,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Converter must be a RequestBodyConverter, NULL found', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandlePathAnnotation()\n    {\n        (new PathAnnotHandler())->handle(\n            new Path(['value' => 'foo']),\n            $this->serviceMethodBuilder,\n            $this->stringConverter,\n            1\n        );\n\n        self::assertAttributeCount(1, 'parameterHandlers', $this->serviceMethodBuilder);\n        self::assertAttributeEquals([1 => new PathParamHandler($this->stringConverter, 'foo')], 'parameterHandlers', $this->serviceMethodBuilder);\n    }\n\n    public function testHandlePathAnnotationWrongConverter()\n    {\n        try {\n            (new PathAnnotHandler())->handle(\n                new Path(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                null,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Converter must be a StringConverter, NULL found', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandleQueryAnnotation()\n    {\n        (new QueryAnnotHandler())->handle(\n            new Query(['value' => 'foo']),\n            $this->serviceMethodBuilder,\n            $this->stringConverter,\n            1\n        );\n\n        self::assertAttributeCount(1, 'parameterHandlers', $this->serviceMethodBuilder);\n        self::assertAttributeEquals([1 => new QueryParamHandler($this->stringConverter, 'foo', false)], 'parameterHandlers', $this->serviceMethodBuilder);\n    }\n\n    public function testHandleQueryAnnotationWrongAnnotation()\n    {\n        try {\n            (new QueryAnnotHandler())->handle(\n                new Body(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                $this->stringConverter,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Annotation must be encodable', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandleQueryAnnotationWrongConverter()\n    {\n        try {\n            (new QueryAnnotHandler())->handle(\n                new Query(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                null,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Converter must be a StringConverter, NULL found', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandleQueryMapAnnotation()\n    {\n        (new QueryMapAnnotHandler())->handle(\n            new QueryMap(['value' => 'foo', 'encoded' => true]),\n            $this->serviceMethodBuilder,\n            $this->stringConverter,\n            1\n        );\n\n        self::assertAttributeCount(1, 'parameterHandlers', $this->serviceMethodBuilder);\n        self::assertAttributeEquals([1 => new QueryMapParamHandler($this->stringConverter, true)], 'parameterHandlers', $this->serviceMethodBuilder);\n    }\n\n    public function testHandleQueryMapAnnotationWrongAnnotation()\n    {\n        try {\n            (new QueryMapAnnotHandler())->handle(\n                new Body(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                $this->stringConverter,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Annotation must be encodable', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandleQueryMapAnnotationWrongConverter()\n    {\n        try {\n            (new QueryMapAnnotHandler())->handle(\n                new QueryMap(['value' => 'foo', 'encoded' => true]),\n                $this->serviceMethodBuilder,\n                null,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Converter must be a StringConverter, NULL found', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandleQueryNameAnnotation()\n    {\n        (new QueryNameAnnotHandler())->handle(\n            new QueryName(['value' => 'foo']),\n            $this->serviceMethodBuilder,\n            $this->stringConverter,\n            1\n        );\n\n        self::assertAttributeCount(1, 'parameterHandlers', $this->serviceMethodBuilder);\n        self::assertAttributeEquals([1 => new QueryNameParamHandler($this->stringConverter, false)], 'parameterHandlers', $this->serviceMethodBuilder);\n    }\n\n    public function testHandleQueryNameAnnotationWrongAnnotation()\n    {\n        try {\n            (new QueryNameAnnotHandler())->handle(\n                new Body(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                $this->stringConverter,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Annotation must be encodable', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandleQueryNameAnnotationWrongConverter()\n    {\n        try {\n            (new QueryNameAnnotHandler())->handle(\n                new QueryName(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                null,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Converter must be a StringConverter, NULL found', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testHandleUrlAnnotation()\n    {\n        (new UrlAnnotHandler())->handle(\n            new Url(['value' => 'foo']),\n            $this->serviceMethodBuilder,\n            $this->stringConverter,\n            1\n        );\n\n        self::assertAttributeCount(1, 'parameterHandlers', $this->serviceMethodBuilder);\n        self::assertAttributeEquals([1 => new UrlParamHandler($this->stringConverter)], 'parameterHandlers', $this->serviceMethodBuilder);\n    }\n\n    public function testHandleUrlAnnotationWrongConverter()\n    {\n        try {\n            (new UrlAnnotHandler())->handle(\n                new Url(['value' => 'foo']),\n                $this->serviceMethodBuilder,\n                null,\n                1\n            );\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Converter must be a StringConverter, NULL found', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/AnnotationProcessorTest.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\nnamespace Tebru\\Retrofit\\Test\\Unit\\Unit\\Internal;\n\nuse LogicException;\nuse ReflectionMethod;\nuse Tebru\\Retrofit\\Annotation\\Body;\nuse Tebru\\Retrofit\\Annotation\\Header;\nuse Tebru\\Retrofit\\Annotation\\Headers;\nuse Tebru\\Retrofit\\Annotation\\Path;\nuse Tebru\\Retrofit\\Annotation\\Query;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\BodyAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\HeadersAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\QueryAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationProcessor;\nuse PHPUnit\\Framework\\TestCase;\nuse Tebru\\Retrofit\\Internal\\Converter\\ConverterProvider;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultConverterFactory;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultRequestBodyConverter;\nuse Tebru\\Retrofit\\Internal\\ServiceMethod\\DefaultServiceMethodBuilder;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultStringConverter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\BodyParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\QueryParamHandler;\nuse Tebru\\Retrofit\\ServiceMethodBuilder;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\AnnotationProcessorTest\\AnnotationProcessorTestMock;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\AnnotationProcessorTest\\BadConverterAnnotation;\n\nclass AnnotationProcessorTest extends TestCase\n{\n    /**\n     * @var AnnotationProcessor\n     */\n    private $annotationProcessor;\n\n    /**\n     * @var ServiceMethodBuilder\n     */\n    private $serviceMethodBuilder;\n\n    /**\n     * @var ConverterProvider\n     */\n    private $converterProvider;\n\n    public function setUp()\n    {\n        $this->annotationProcessor = new AnnotationProcessor([\n            Header::class => new HeadersAnnotHandler(),\n            Body::class => new BodyAnnotHandler(),\n            Query::class => new QueryAnnotHandler(),\n            Headers::class => new HeadersAnnotHandler(),\n            BadConverterAnnotation::class => new HeadersAnnotHandler(),\n        ]);\n        $this->serviceMethodBuilder = new DefaultServiceMethodBuilder();\n        $this->converterProvider = new ConverterProvider([new DefaultConverterFactory()]);\n    }\n\n    public function testParameterAwareAnnotation()\n    {\n        $this->annotationProcessor->process(\n            new Query(['value' => 'bar']),\n            $this->serviceMethodBuilder,\n            $this->converterProvider,\n            new ReflectionMethod(AnnotationProcessorTestMock::class, 'foo')\n        );\n\n        self::assertAttributeEquals(\n            [new QueryParamHandler(new DefaultStringConverter(), 'bar', false)],\n            'parameterHandlers',\n            $this->serviceMethodBuilder\n        );\n    }\n\n    public function testNonParameterAwareAnnotation()\n    {\n        $this->annotationProcessor->process(\n            new Headers(['value' => ['foo:bar']]),\n            $this->serviceMethodBuilder,\n            $this->converterProvider,\n            new ReflectionMethod(AnnotationProcessorTestMock::class, 'foo')\n        );\n\n        self::assertAttributeSame(\n            ['foo' => ['bar']],\n            'headers',\n            $this->serviceMethodBuilder\n        );\n    }\n\n    public function testRequestBodyAnnotation()\n    {\n        $this->annotationProcessor->process(\n            new Body(['value' => 'bar']),\n            $this->serviceMethodBuilder,\n            $this->converterProvider,\n            new ReflectionMethod(AnnotationProcessorTestMock::class, 'body')\n        );\n\n        self::assertAttributeEquals(\n            [new BodyParamHandler(new DefaultRequestBodyConverter())],\n            'parameterHandlers',\n            $this->serviceMethodBuilder\n        );\n    }\n\n    public function testNoHandler()\n    {\n        $this->annotationProcessor->process(\n            new Path(['value' => 'bar']),\n            $this->serviceMethodBuilder,\n            $this->converterProvider,\n            new ReflectionMethod(AnnotationProcessorTestMock::class, 'body')\n        );\n\n        self::assertAttributeEquals(\n            [],\n            'parameterHandlers',\n            $this->serviceMethodBuilder\n        );\n    }\n\n    public function testParameterNotFound()\n    {\n        try {\n            $this->annotationProcessor->process(\n                new Query(['value' => 'bar2']),\n                $this->serviceMethodBuilder,\n                $this->converterProvider,\n                new ReflectionMethod(AnnotationProcessorTestMock::class, 'foo')\n            );\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Could not find parameter named bar2 in ' .\n                'Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\AnnotationProcessorTest\\AnnotationProcessorTestMock::foo. ' .\n                'Please double check that annotations are properly referencing method parameters.',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception was not thrown');\n    }\n\n    public function testParameterTypeNotFound()\n    {\n        try {\n            $this->annotationProcessor->process(\n                new Query(['value' => 'bar']),\n                $this->serviceMethodBuilder,\n                $this->converterProvider,\n                new ReflectionMethod(AnnotationProcessorTestMock::class, 'noType')\n            );\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Parameter type was not found for method ' .\n                'Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\AnnotationProcessorTest\\AnnotationProcessorTestMock::noType',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception was not thrown');\n    }\n\n    public function testConverterNotFound()\n    {\n        try {\n            $this->annotationProcessor->process(\n                new BadConverterAnnotation(['value' => 'bar']),\n                $this->serviceMethodBuilder,\n                $this->converterProvider,\n                new ReflectionMethod(AnnotationProcessorTestMock::class, 'foo')\n            );\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Unable to handle converter of type Foo. Please use RequestBodyConverter or StringConverter',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception was not thrown');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/CallAdapterTest.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Unit\\Internal;\n\nuse LogicException;\nuse PHPUnit\\Framework\\TestCase;\nuse Tebru\\PhpType\\TypeToken;\nuse Tebru\\Retrofit\\Internal\\CallAdapter\\CallAdapterProvider;\nuse Tebru\\Retrofit\\Internal\\CallAdapter\\DefaultCallAdapterFactory;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\MockCall;\n\n/**\n * Class CallAdapterTest\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass CallAdapterTest extends TestCase\n{\n    /**\n     * @var CallAdapterProvider\n     */\n    private $callAdapterProvider;\n\n    public function setUp()\n    {\n        $this->callAdapterProvider = new CallAdapterProvider([new DefaultCallAdapterFactory()]);\n    }\n\n    public function testAdaptDefaultCall()\n    {\n        $call = new MockCall();\n        $adaptedCall = $this->callAdapterProvider->get(new TypeToken(MockCall::class))->adapt($call);\n\n        self::assertSame($adaptedCall, $call);\n    }\n\n    public function testCallAdapterNotFound()\n    {\n        try {\n            $this->callAdapterProvider->get(new TypeToken(self::class));\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Could not get call adapter for type ' .\n                'Tebru\\Retrofit\\Test\\Unit\\Internal\\CallAdapterTest',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/ConverterTest.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Unit\\Internal;\n\nuse GuzzleHttp\\Psr7\\AppendStream;\nuse LogicException;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Http\\Message\\StreamInterface;\nuse Tebru\\PhpType\\TypeToken;\nuse Tebru\\Retrofit\\Internal\\Converter\\ConverterProvider;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultConverterFactory;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\MockConverterFactory;\n\n/**\n * Class ConverterTest\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass ConverterTest extends TestCase\n{\n    /**\n     * @var ConverterProvider\n     */\n    private $converterProvider;\n\n    public function setUp()\n    {\n        $this->converterProvider = new ConverterProvider([new DefaultConverterFactory()]);\n    }\n\n    public function testRequestBodyConverter()\n    {\n        $stream = new AppendStream();\n        $converted = $this->converterProvider->getRequestBodyConverter(new TypeToken(StreamInterface::class))->convert($stream);\n\n        self::assertSame($stream, $converted);\n    }\n\n    public function testRequestBodyConverterProviderCache()\n    {\n        $converter = $this->converterProvider->getRequestBodyConverter(new TypeToken(StreamInterface::class));\n        $converter2 = $this->converterProvider->getRequestBodyConverter(new TypeToken(StreamInterface::class));\n\n        self::assertSame($converter, $converter2);\n    }\n\n    public function testRequestBodyConverterProviderException()\n    {\n        try {\n            $this->converterProvider->getRequestBodyConverter(new TypeToken('Foo'));\n        } catch (LogicException $exception) {\n            self::assertSame('Retrofit: Could not get request body converter for type Foo', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n\n    public function testResponseBodyConverter()\n    {\n        $stream = new AppendStream();\n        $converted = $this->converterProvider->getResponseBodyConverter(new TypeToken(StreamInterface::class))->convert($stream);\n\n        self::assertSame($stream, $converted);\n    }\n\n    public function testResponseBodyConverterProviderCache()\n    {\n        $converter = $this->converterProvider->getResponseBodyConverter(new TypeToken(StreamInterface::class));\n        $converter2 = $this->converterProvider->getResponseBodyConverter(new TypeToken(StreamInterface::class));\n\n        self::assertSame($converter, $converter2);\n    }\n\n    public function testResponseBodyConverterProviderException()\n    {\n        try {\n            $this->converterProvider->getResponseBodyConverter(new TypeToken('Foo'));\n        } catch (LogicException $exception) {\n            self::assertSame('Retrofit: Could not get response body converter for type Foo', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testStringConverter()\n    {\n        $converted = $this->converterProvider->getStringConverter(new TypeToken('string'))->convert('foo');\n\n        self::assertSame('foo', $converted);\n    }\n\n    public function testStringConverterInt()\n    {\n        $converted = $this->converterProvider->getStringConverter(new TypeToken('int'))->convert(1);\n\n        self::assertSame('1', $converted);\n    }\n\n    public function testStringConverterFloat()\n    {\n        $converted = $this->converterProvider->getStringConverter(new TypeToken('float'))->convert(1.5);\n\n        self::assertSame('1.5', $converted);\n    }\n\n    public function testStringConverterTrue()\n    {\n        $converted = $this->converterProvider->getStringConverter(new TypeToken('boolean'))->convert(true);\n\n        self::assertSame('true', $converted);\n    }\n\n    public function testStringConverterFalse()\n    {\n        $converted = $this->converterProvider->getStringConverter(new TypeToken('boolean'))->convert(false);\n\n        self::assertSame('false', $converted);\n    }\n\n    public function testStringConverterArray()\n    {\n        $converted = $this->converterProvider->getStringConverter(new TypeToken('array'))->convert([1]);\n\n        self::assertSame('a:1:{i:0;i:1;}', $converted);\n    }\n\n    public function testStringConverterObject()\n    {\n        $converted = $this->converterProvider->getStringConverter(new TypeToken('object'))->convert(new \\stdClass());\n\n        self::assertSame('O:8:\"stdClass\":0:{}', $converted);\n    }\n\n    public function testStringConverterProviderCache()\n    {\n        $converter = $this->converterProvider->getStringConverter(new TypeToken('string'));\n        $converter2 = $this->converterProvider->getStringConverter(new TypeToken('string'));\n\n        self::assertSame($converter, $converter2);\n    }\n\n    public function testStringConverterProviderException()\n    {\n        $converterProvider = new ConverterProvider([new MockConverterFactory()]);\n        try {\n            $converterProvider->getStringConverter(new TypeToken('Foo'));\n        } catch (LogicException $exception) {\n            self::assertSame('Retrofit: Could not get string converter for type Foo', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/DefaultProxyFactoryTest.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\nnamespace Tebru\\Retrofit\\Test\\Unit\\Internal;\n\nuse Doctrine\\Common\\Annotations\\AnnotationReader;\nuse InvalidArgumentException;\nuse LogicException;\nuse PhpParser\\BuilderFactory;\nuse PhpParser\\PrettyPrinter\\Standard;\nuse RuntimeException;\nuse Tebru\\AnnotationReader\\AnnotationReaderAdapter;\nuse Tebru\\Retrofit\\Internal\\AnnotationProcessor;\nuse Tebru\\Retrofit\\Internal\\CacheProvider;\nuse Tebru\\Retrofit\\Internal\\CallAdapter\\CallAdapterProvider;\nuse Tebru\\Retrofit\\Internal\\CallAdapter\\DefaultCallAdapterFactory;\nuse Tebru\\Retrofit\\Internal\\Converter\\ConverterProvider;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultConverterFactory;\nuse Tebru\\Retrofit\\Internal\\DefaultProxyFactory;\nuse PHPUnit\\Framework\\TestCase;\nuse Tebru\\Retrofit\\Internal\\ServiceMethod\\ServiceMethodFactory;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest\\PFTCTestCreate;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest\\PFTCTestCreateCacheDirectoryFail;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest\\PFTCTestCreateClientFail;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest\\PFTCTestCreateTwice;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest\\PFTCTestCreateWithoutCache;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest\\ProxyFactoryTestClientNoReturnType;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest\\ProxyFactoryTestClientNoTypehint;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest\\ProxyFactoryTestFilesystem;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest\\ProxyFactoryTestHttpClient;\n\nclass DefaultProxyFactoryTest extends TestCase\n{\n    /**\n     * @var ProxyFactoryTestFilesystem\n     */\n    private $filesystem;\n\n    public function setUp()\n    {\n        $this->filesystem = new ProxyFactoryTestFilesystem();\n    }\n\n    public function testCreate()\n    {\n        $client = $this->createFactory()->create(PFTCTestCreate::class);\n\n        self::assertInstanceOf(PFTCTestCreate::class, $client);\n        self::assertSame($this->getExpectedClass(), $this->filesystem->contents);\n        self::assertSame(\n            '/tmp/cache/retrofit/Tebru/Retrofit/Test/Mock/Unit/Internal/ProxyFactoryTest',\n            $this->filesystem->directory\n        );\n        self::assertSame(\n            '/tmp/cache/retrofit/Tebru/Retrofit/Test/Mock/Unit/Internal/ProxyFactoryTest/PFTCTestCreate.php',\n            $this->filesystem->filename\n        );\n    }\n\n    public function testCreateTwice()\n    {\n        $this->createFactory()->create(PFTCTestCreateTwice::class);\n\n        $this->filesystem->directory = null;\n        $this->filesystem->filename = null;\n        $this->filesystem->contents = null;\n\n        $client = $this->createFactory()->create(PFTCTestCreateTwice::class);\n\n        self::assertInstanceOf(PFTCTestCreateTwice::class, $client);\n        self::assertNull($this->filesystem->contents);\n        self::assertNull($this->filesystem->directory);\n        self::assertNull($this->filesystem->filename);\n    }\n\n    public function testCreateWithoutCache()\n    {\n        $this->createFactory(false)->create(PFTCTestCreateWithoutCache::class);\n        $client = $this->createFactory(false)->create(PFTCTestCreateWithoutCache::class);\n\n        self::assertInstanceOf(PFTCTestCreateWithoutCache::class, $client);\n        self::assertNull($this->filesystem->contents);\n        self::assertNull($this->filesystem->directory);\n        self::assertNull($this->filesystem->filename);\n    }\n\n    public function testCreateInvalidInterface()\n    {\n        try {\n            $this->createFactory()->create('Foo');\n        } catch (InvalidArgumentException $exception) {\n            self::assertSame('Retrofit: Foo is expected to be an interface', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testCreateNoTypehint()\n    {\n        try {\n            $this->createFactory()->create(ProxyFactoryTestClientNoTypehint::class);\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Parameter types are required. None found for parameter foo in ' .\n                'Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest\\ProxyFactoryTestClientNoTypehint::foo()',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testCreateNoReturnType()\n    {\n        try {\n            $this->createFactory()->create(ProxyFactoryTestClientNoReturnType::class);\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Method return types are required. None found for ' .\n                'Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest\\ProxyFactoryTestClientNoReturnType::foo()',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testCreateCacheDirectoryFail()\n    {\n        $this->filesystem->makeDirectory = false;\n        try {\n            $this->createFactory()->create(PFTCTestCreateCacheDirectoryFail::class);\n        } catch (RuntimeException $exception) {\n            self::assertSame(\n                'Retrofit: There was an issue creating the cache directory: ' .\n                '/tmp/cache/retrofit/Tebru/Retrofit/Test/Mock/Unit/Internal/ProxyFactoryTest',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testCreateClientFail()\n    {\n        $this->filesystem->put = false;\n        try {\n            $this->createFactory()->create(PFTCTestCreateClientFail::class);\n        } catch (RuntimeException $exception) {\n            self::assertSame(\n                'Retrofit: There was an issue writing proxy class to: ' .\n                '/tmp/cache/retrofit/Tebru/Retrofit/Test/Mock/Unit/Internal/ProxyFactoryTest/PFTCTestCreateClientFail.php',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    private function createFactory(bool $enableCache = true): DefaultProxyFactory\n    {\n        return new DefaultProxyFactory(\n            new BuilderFactory(),\n            new Standard(),\n            new ServiceMethodFactory(\n                new AnnotationProcessor([\n\n                ]),\n                new CallAdapterProvider([new DefaultCallAdapterFactory()]),\n                new ConverterProvider([new DefaultConverterFactory()]),\n                new AnnotationReaderAdapter(new AnnotationReader(), CacheProvider::createNullCache()),\n                'http://example.com'\n            ),\n            new ProxyFactoryTestHttpClient(),\n            $this->filesystem,\n            $enableCache,\n            '/tmp/cache/retrofit'\n        );\n    }\n\n    private function getExpectedClass(): string\n    {\n        return <<< 'EOT'\n<?php\n\nnamespace Tebru\\Retrofit\\Proxy\\Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest;\n\nclass PFTCTestCreate extends \\Tebru\\Retrofit\\Proxy\\AbstractProxy implements \\Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ProxyFactoryTest\\PFTCTestCreate\n{\n    public function simple(string $path, \\stdClass $body, string &$query = 'foo', string ...$fields) : \\Tebru\\Retrofit\\Call\n    {\n        return $this->__handleRetrofitRequest('Tebru\\\\Retrofit\\\\Test\\\\Mock\\\\Unit\\\\Internal\\\\ProxyFactoryTest\\\\PFTCTestCreate', __FUNCTION__, func_get_args(), array(null, null, 'foo', null));\n    }\n    public static function static() : \\Tebru\\Retrofit\\Call\n    {\n        return $this->__handleRetrofitRequest('Tebru\\\\Retrofit\\\\Test\\\\Mock\\\\Unit\\\\Internal\\\\ProxyFactoryTest\\\\PFTCTestCreate', __FUNCTION__, func_get_args(), array());\n    }\n}\nEOT;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/FilesystemTest.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\nnamespace Tebru\\Retrofit\\Test\\Unit\\Internal;\n\nuse org\\bovigo\\vfs\\vfsStream;\nuse Tebru\\Retrofit\\Internal\\Filesystem;\nuse PHPUnit\\Framework\\TestCase;\n\nclass FilesystemTest extends TestCase\n{\n    /**\n     * @var Filesystem\n     */\n    private $filesystem;\n\n    public function setUp()\n    {\n        vfsStream::setup('cache');\n        $this->filesystem = new Filesystem();\n    }\n\n    public function testCreateDirectory()\n    {\n        $directory = vfsStream::url('cache/retrofit/Test');\n\n        self::assertTrue($this->filesystem->makeDirectory($directory));\n    }\n\n    public function testCreateFile()\n    {\n        $directory = vfsStream::url('cache/retrofit/Test');\n        $this->filesystem->makeDirectory($directory);\n        $file = vfsStream::url('cache/retrofit/Test/Service.php');\n\n        self::assertTrue($this->filesystem->put($file, 'foo'));\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/HttpClientCallTest.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\nnamespace Tebru\\Retrofit\\Test\\Unit\\Internal;\n\nuse GuzzleHttp\\Psr7\\Request;\nuse GuzzleHttp\\Psr7\\Response;\nuse RuntimeException;\nuse Tebru\\Retrofit\\Exception\\ResponseHandlingFailedException;\nuse Tebru\\Retrofit\\Internal\\HttpClientCall;\nuse Tebru\\Retrofit\\Response as RetrofitResponse;\nuse PHPUnit\\Framework\\TestCase;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\HttpClientCallTest\\HttpClientCallTestClientMock;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\HttpClientCallTest\\HttpClientCallTestErrorBodyMock;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\HttpClientCallTest\\HttpClientCallTestResponseBodyMock;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\HttpClientCallTest\\HttpClientCallTestServiceMethodMock;\nuse Throwable;\n\nclass HttpClientCallTest extends TestCase\n{\n    public function testSync()\n    {\n        $request = new Request('GET', 'http://example.com/');\n        $response = new Response(204);\n        $responseBody = new HttpClientCallTestResponseBodyMock();\n\n        $call = new HttpClientCall(\n            new HttpClientCallTestClientMock($response),\n            new HttpClientCallTestServiceMethodMock($request, $responseBody),\n            []\n        );\n\n        $response = $call->execute();\n\n        self::assertInstanceOf(RetrofitResponse::class, $response);\n        self::assertSame(204, $response->raw()->getStatusCode());\n        self::assertInstanceOf(HttpClientCallTestResponseBodyMock::class, $response->body());\n        self::assertNull($response->errorBody());\n    }\n\n    public function testSyncError()\n    {\n        $request = new Request('GET', 'http://example.com/');\n        $response = new Response(500);\n        $responseBody = new HttpClientCallTestErrorBodyMock();\n\n        $call = new HttpClientCall(\n            new HttpClientCallTestClientMock($response),\n            new HttpClientCallTestServiceMethodMock($request, null, $responseBody),\n            []\n        );\n\n        $response = $call->execute();\n\n        self::assertInstanceOf(RetrofitResponse::class, $response);\n        self::assertSame(500, $response->raw()->getStatusCode());\n        self::assertNull($response->body());\n        self::assertInstanceOf(HttpClientCallTestErrorBodyMock::class, $response->errorBody());\n    }\n\n    public function testAsync()\n    {\n        $request = new Request('GET', 'http://example.com/');\n        $response = new Response(204);\n        $responseBody = new HttpClientCallTestResponseBodyMock();\n\n        $call = new HttpClientCall(\n            new HttpClientCallTestClientMock($response),\n            new HttpClientCallTestServiceMethodMock($request, $responseBody),\n            []\n        );\n\n        $call->enqueue(\n            function (RetrofitResponse $response) {\n                self::assertInstanceOf(RetrofitResponse::class, $response);\n                self::assertSame(204, $response->raw()->getStatusCode());\n                self::assertInstanceOf(HttpClientCallTestResponseBodyMock::class, $response->body());\n                self::assertNull($response->errorBody());\n            },\n            function (Throwable $throwable) {\n                self::fail('Error callback should not be called');\n            }\n        );\n\n        $call->wait();\n    }\n\n    public function testAsyncError()\n    {\n        $request = new Request('GET', 'http://example.com/');\n        $response = new Response(500);\n        $responseBody = new HttpClientCallTestErrorBodyMock();\n\n        $call = new HttpClientCall(\n            new HttpClientCallTestClientMock($response),\n            new HttpClientCallTestServiceMethodMock($request, null, $responseBody),\n            []\n        );\n\n        $call->enqueue(\n            function (RetrofitResponse $response) {\n                self::assertInstanceOf(RetrofitResponse::class, $response);\n                self::assertSame(500, $response->raw()->getStatusCode());\n                self::assertNull($response->body());\n                self::assertInstanceOf(HttpClientCallTestErrorBodyMock::class, $response->errorBody());\n            },\n            function (Throwable $throwable) {\n                self::fail('Error callback should not be called');\n            }\n        );\n\n        $call->wait();\n    }\n\n    public function testAsyncFailure()\n    {\n        $request = new Request('GET', 'http://example.com/');\n\n        $call = new HttpClientCall(\n            new HttpClientCallTestClientMock(),\n            new HttpClientCallTestServiceMethodMock($request),\n            []\n        );\n\n        $call->enqueue(\n            function (RetrofitResponse $response) {\n                self::fail('Response callback should not be called');\n            },\n            function (Throwable $throwable) {\n                self::assertInstanceOf(RuntimeException::class, $throwable);\n            }\n        );\n\n        $call->wait();\n    }\n\n    public function testAsyncFailureThrowsException()\n    {\n        $request = new Request('GET', 'http://example.com/');\n\n        $call = new HttpClientCall(\n            new HttpClientCallTestClientMock(),\n            new HttpClientCallTestServiceMethodMock($request),\n            []\n        );\n\n        $call->enqueue(\n            function (RetrofitResponse $response) {\n                self::fail('Response callback should not be called');\n            }\n        );\n\n        try {\n            $call->wait();\n        } catch (RuntimeException $exception) {\n            self::assertTrue(true);\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testSyncInvalidJsonResponseThrowsException()\n    {\n        $request = new Request('GET', 'http://example.com/');\n        $response = new Response(204, [], '{');\n\n        $responseBody = new HttpClientCallTestResponseBodyMock();\n\n        $call = new HttpClientCall(\n            new HttpClientCallTestClientMock($response),\n            new HttpClientCallTestServiceMethodMock($request, $responseBody),\n            []\n        );\n\n        try {\n            $call->execute();\n        } catch (ResponseHandlingFailedException $exception) {\n            self::assertSame('GET', $exception->getRequest()->getMethod());\n            self::assertSame('{', (string)$exception->getResponse()->getBody());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testSyncInvalidJsonErrorThrowsException()\n    {\n        $request = new Request('GET', 'http://example.com/');\n        $response = new Response(400, [], '{');\n\n        $responseBody = new HttpClientCallTestResponseBodyMock();\n\n        $call = new HttpClientCall(\n            new HttpClientCallTestClientMock($response),\n            new HttpClientCallTestServiceMethodMock($request, $responseBody),\n            []\n        );\n\n        try {\n            $call->execute();\n        } catch (ResponseHandlingFailedException $exception) {\n            self::assertSame('GET', $exception->getRequest()->getMethod());\n            self::assertSame('{', (string)$exception->getResponse()->getBody());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testAsyncInvalidJsonResponseThrowsExceptions()\n    {\n        $request = new Request('GET', 'http://example.com/');\n        $response = new Response(204, [], '{');\n        $responseBody = new HttpClientCallTestResponseBodyMock();\n\n        $call = new HttpClientCall(\n            new HttpClientCallTestClientMock($response),\n            new HttpClientCallTestServiceMethodMock($request, $responseBody),\n            []\n        );\n\n        $call->enqueue(function () {});\n\n        try {\n            $call->wait();\n        } catch (ResponseHandlingFailedException $exception) {\n            self::assertSame('GET', $exception->getRequest()->getMethod());\n            self::assertSame('{', (string)$exception->getResponse()->getBody());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testAsyncInvalidJsonerrorThrowsExceptions()\n    {\n        $request = new Request('GET', 'http://example.com/');\n        $response = new Response(400, [], '{');\n        $responseBody = new HttpClientCallTestResponseBodyMock();\n\n        $call = new HttpClientCall(\n            new HttpClientCallTestClientMock($response),\n            new HttpClientCallTestServiceMethodMock($request, $responseBody),\n            []\n        );\n\n        $call->enqueue(function () {});\n\n        try {\n            $call->wait();\n        } catch (ResponseHandlingFailedException $exception) {\n            self::assertSame('GET', $exception->getRequest()->getMethod());\n            self::assertSame('{', (string)$exception->getResponse()->getBody());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/ParameterHandlersTest.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\ndeclare(strict_types=1);\n\nnamespace Tebru\\Retrofit\\Test\\Unit\\Unit\\Internal;\n\nuse ArrayIterator;\nuse GuzzleHttp\\Psr7\\AppendStream;\nuse GuzzleHttp\\Psr7\\Uri;\nuse PHPUnit\\Framework\\TestCase;\nuse RuntimeException;\nuse Tebru\\Retrofit\\Http\\MultipartBody;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultRequestBodyConverter;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultStringConverter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\BodyParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\FieldMapParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\FieldParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\HeaderMapParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\HeaderParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\PartMapParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\PartParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\PathParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\QueryMapParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\QueryNameParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\QueryParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\UrlParamHandler;\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse function GuzzleHttp\\Psr7\\stream_for;\n\n/**\n * Class ParameterHandlersTest\n *\n * @author Nate Brunette <n@tebru.net>\n */\nclass ParameterHandlersTest extends TestCase\n{\n    /**\n     * @var RequestBuilder\n     */\n    private $requestBuilder;\n\n    public function setUp()\n    {\n        $this->requestBuilder = new RequestBuilder('GET', 'http://example.com', '/test/{path}?q=test', []);\n    }\n\n    public function testBodyHandler()\n    {\n        $stream = new AppendStream();\n        (new BodyParamHandler(new DefaultRequestBodyConverter()))->apply($this->requestBuilder, $stream);\n\n        self::assertAttributeSame($stream, 'body', $this->requestBuilder);\n    }\n\n    public function testBodyHandlerNull()\n    {\n        (new BodyParamHandler(new DefaultRequestBodyConverter()))->apply($this->requestBuilder, null);\n\n        self::assertAttributeSame(null, 'body', $this->requestBuilder);\n    }\n\n    public function testFieldMapHandler()\n    {\n        $map = [\n            'foo' => 'bar',\n            'afoo[]' => ['baz', 'qux'],\n            'nfoo' => null,\n        ];\n        $expected = [\n            'foo=bar',\n            'afoo%5B%5D=baz',\n            'afoo%5B%5D=qux',\n        ];\n\n        (new FieldMapParamHandler(new DefaultStringConverter(), false))->apply($this->requestBuilder, $map);\n\n        self::assertAttributeSame($expected, 'fields', $this->requestBuilder);\n    }\n\n    public function testFieldMapHandlerIterator()\n    {\n        $map = new ArrayIterator([\n            'foo' => 'bar',\n            'afoo[]' => ['baz', 'qux'],\n            'nfoo' => null,\n        ]);\n        $expected = [\n            'foo=bar',\n            'afoo%5B%5D=baz',\n            'afoo%5B%5D=qux',\n        ];\n\n        (new FieldMapParamHandler(new DefaultStringConverter(), false))->apply($this->requestBuilder, $map);\n\n        self::assertAttributeSame($expected, 'fields', $this->requestBuilder);\n    }\n\n    public function testFieldMapHandlerEmpty()\n    {\n        (new FieldMapParamHandler(new DefaultStringConverter(), false))->apply($this->requestBuilder, []);\n\n        self::assertAttributeSame([], 'fields', $this->requestBuilder);\n    }\n\n    public function testFieldMapHandlerNull()\n    {\n        (new FieldMapParamHandler(new DefaultStringConverter(), false))->apply($this->requestBuilder, null);\n\n        self::assertAttributeSame([], 'fields', $this->requestBuilder);\n    }\n\n    public function testFieldHandler()\n    {\n        (new FieldParamHandler(new DefaultStringConverter(), 'foo', false))->apply($this->requestBuilder, 'bar');\n\n        self::assertAttributeSame(['foo=bar'], 'fields', $this->requestBuilder);\n    }\n\n    public function testFieldHandlerArray()\n    {\n        (new FieldParamHandler(new DefaultStringConverter(), 'foo', false))->apply($this->requestBuilder, ['bar', 'baz']);\n\n        self::assertAttributeSame(['foo=bar', 'foo=baz'], 'fields', $this->requestBuilder);\n    }\n\n    public function testFieldHandlerNull()\n    {\n        (new FieldParamHandler(new DefaultStringConverter(), 'foo', false))->apply($this->requestBuilder, null);\n\n        self::assertAttributeSame([], 'fields', $this->requestBuilder);\n    }\n\n    public function testHeaderMapHandler()\n    {\n        $map = [\n            'foo' => 'bar',\n            'afoo' => ['baz', 'qux'],\n            'nfoo' => null,\n        ];\n        $expected = [\n            'foo' => ['bar'],\n            'afoo' => ['baz', 'qux'],\n        ];\n\n        (new HeaderMapParamHandler(new DefaultStringConverter()))->apply($this->requestBuilder, $map);\n\n        self::assertAttributeSame($expected, 'headers', $this->requestBuilder);\n    }\n\n    public function testHeaderMapHandlerIterator()\n    {\n        $map = new ArrayIterator([\n            'foo' => 'bar',\n            'afoo' => ['baz', 'qux'],\n            'nfoo' => null,\n        ]);\n        $expected = [\n            'foo' => ['bar'],\n            'afoo' => ['baz', 'qux'],\n        ];\n\n        (new HeaderMapParamHandler(new DefaultStringConverter()))->apply($this->requestBuilder, $map);\n\n        self::assertAttributeSame($expected, 'headers', $this->requestBuilder);\n    }\n\n    public function testHeaderMapHandlerEmpty()\n    {\n        (new HeaderMapParamHandler(new DefaultStringConverter()))->apply($this->requestBuilder, []);\n\n        self::assertAttributeSame([], 'headers', $this->requestBuilder);\n    }\n\n    public function testHeaderMapHandlerNull()\n    {\n        (new HeaderMapParamHandler(new DefaultStringConverter()))->apply($this->requestBuilder, null);\n\n        self::assertAttributeSame([], 'headers', $this->requestBuilder);\n    }\n\n    public function testHeaderHandler()\n    {\n        (new HeaderParamHandler(new DefaultStringConverter(), 'foo'))->apply($this->requestBuilder, 'bar');\n\n        self::assertAttributeSame(['foo' => ['bar']], 'headers', $this->requestBuilder);\n    }\n\n    public function testHeaderHandlerArray()\n    {\n        (new HeaderParamHandler(new DefaultStringConverter(), 'foo'))->apply($this->requestBuilder, ['bar', 'baz']);\n\n        self::assertAttributeSame(['foo' => ['bar', 'baz']], 'headers', $this->requestBuilder);\n    }\n\n    public function testHeaderHandlerNull()\n    {\n        (new HeaderParamHandler(new DefaultStringConverter(), 'foo'))->apply($this->requestBuilder, null);\n\n        self::assertAttributeSame([], 'headers', $this->requestBuilder);\n    }\n\n    public function testPartMapHandler()\n    {\n        $stream1 = stream_for('bar');\n        $stream2 = stream_for('bar');\n        $stream3 = new AppendStream();\n        $simpleMultipart = new MultipartBody('mfoo1', $stream2);\n        $streamMultipart = new MultipartBody('mfoo2', $stream3, ['a' => 'b'], 'ParameterHandlersTest.php');\n\n        $map = [\n            'foo' => $stream1,\n            'simple' => $simpleMultipart,\n            'stream' => $streamMultipart,\n            'nfoo' => null,\n        ];\n\n        $expected = [\n            [\n                'name' => 'foo',\n                'contents' => $stream1,\n                'headers' => ['Content-Transfer-Encoding' => 'binary'],\n                'filename' => null,\n            ],\n            [\n                'name' => 'mfoo1',\n                'contents' => $stream2,\n                'headers' => ['Content-Transfer-Encoding' => 'binary'],\n                'filename' => null,\n            ],\n            [\n                'name' => 'mfoo2',\n                'contents' => new $stream3,\n                'headers' => ['a' => 'b', 'Content-Transfer-Encoding' => 'binary'],\n                'filename' => 'ParameterHandlersTest.php',\n            ],\n        ];\n\n        (new PartMapParamHandler(new DefaultRequestBodyConverter(), 'binary'))->apply($this->requestBuilder, $map);\n\n        self::assertAttributeEquals($expected, 'parts', $this->requestBuilder);\n    }\n\n    public function testPartMapHandlerIterator()\n    {\n        $stream1 = stream_for('bar');\n        $stream2 = stream_for('bar');\n        $stream3 = new AppendStream();\n        $simpleMultipart = new MultipartBody('mfoo1', $stream2);\n        $streamMultipart = new MultipartBody('mfoo2', $stream3, ['a' => 'b'], 'ParameterHandlersTest.php');\n\n        $map = new ArrayIterator([\n            'foo' => $stream1,\n            'simple' => $simpleMultipart,\n            'stream' => $streamMultipart,\n            'nfoo' => null,\n        ]);\n\n        $expected = [\n            [\n                'name' => 'foo',\n                'contents' => $stream1,\n                'headers' => ['Content-Transfer-Encoding' => 'binary'],\n                'filename' => null,\n            ],\n            [\n                'name' => 'mfoo1',\n                'contents' => $stream2,\n                'headers' => ['Content-Transfer-Encoding' => 'binary'],\n                'filename' => null,\n            ],\n            [\n                'name' => 'mfoo2',\n                'contents' => new $stream3,\n                'headers' => ['a' => 'b', 'Content-Transfer-Encoding' => 'binary'],\n                'filename' => 'ParameterHandlersTest.php',\n            ],\n        ];\n\n        (new PartMapParamHandler(new DefaultRequestBodyConverter(), 'binary'))->apply($this->requestBuilder, $map);\n\n        self::assertAttributeEquals($expected, 'parts', $this->requestBuilder);\n    }\n\n    public function testPartMapHandlerEmpty()\n    {\n        (new PartMapParamHandler(new DefaultRequestBodyConverter(), 'binary'))->apply($this->requestBuilder, []);\n\n        self::assertAttributeSame([], 'parts', $this->requestBuilder);\n    }\n\n    public function testPartMapHandlerNull()\n    {\n        (new PartMapParamHandler(new DefaultRequestBodyConverter(), 'binary'))->apply($this->requestBuilder, null);\n\n        self::assertAttributeSame([], 'parts', $this->requestBuilder);\n    }\n\n    public function testPartHandler()\n    {\n        $stream = stream_for('bar');\n        $expected = [[\n            'name' => 'foo',\n            'contents' => $stream,\n            'headers' => ['Content-Transfer-Encoding' => 'binary'],\n            'filename' => null,\n        ]];\n        (new PartParamHandler(new DefaultRequestBodyConverter(), 'foo', 'binary'))->apply($this->requestBuilder, $stream);\n\n        self::assertAttributeSame($expected, 'parts', $this->requestBuilder);\n    }\n\n    public function testPartHandlerMultipart()\n    {\n        $stream = stream_for('bar');\n        $multipart = new MultipartBody('foo', $stream);\n        $expected = [[\n            'name' => 'foo',\n            'contents' => $stream,\n            'headers' => ['Content-Transfer-Encoding' => 'binary'],\n            'filename' => null,\n        ]];\n        (new PartParamHandler(new DefaultRequestBodyConverter(), 'foo', 'binary'))->apply($this->requestBuilder, $multipart);\n\n        self::assertAttributeSame($expected, 'parts', $this->requestBuilder);\n    }\n\n    public function testPartHandlerMultipartHeadersAndFilename()\n    {\n        $stream = stream_for('bar');\n        $multipart = new MultipartBody('foo', $stream, ['a' => 'b'], 'Test.php');\n        $expected = [[\n            'name' => 'foo',\n            'contents' => $stream,\n            'headers' => ['a' => 'b', 'Content-Transfer-Encoding' => 'binary'],\n            'filename' => 'Test.php',\n        ]];\n        (new PartParamHandler(new DefaultRequestBodyConverter(), 'foo', 'binary'))->apply($this->requestBuilder, $multipart);\n\n        self::assertAttributeSame($expected, 'parts', $this->requestBuilder);\n    }\n\n    public function testPartHandlerNull()\n    {\n        (new PartParamHandler(new DefaultRequestBodyConverter(), 'foo', 'binary'))->apply($this->requestBuilder, null);\n\n        self::assertAttributeSame([], 'parts', $this->requestBuilder);\n    }\n\n    public function testPathHandler()\n    {\n        (new PathParamHandler(new DefaultStringConverter(), 'path'))->apply($this->requestBuilder, 'bar');\n\n        self::assertAttributeEquals(new Uri('http://example.com/test/bar?q=test'), 'uri', $this->requestBuilder);\n    }\n\n    public function testPathHandlerNull()\n    {\n        try {\n            (new PathParamHandler(new DefaultStringConverter(), 'path'))->apply($this->requestBuilder, null);\n        } catch (RuntimeException $exception) {\n            self::assertSame('Path parameters cannot be null', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception was not thrown');\n    }\n\n    public function testQueryMapHandler()\n    {\n        $map = [\n            'foo' => 'bar',\n            'afoo[]' => ['baz', 'qux'],\n            'nfoo' => null,\n        ];\n        $expected = [\n            'foo=bar',\n            'afoo%5B%5D=baz',\n            'afoo%5B%5D=qux',\n        ];\n        (new QueryMapParamHandler(new DefaultStringConverter(), false))->apply($this->requestBuilder, $map);\n\n        self::assertAttributeSame($expected, 'queries', $this->requestBuilder);\n    }\n\n    public function testQueryMapHandlerIterator()\n    {\n        $map = new ArrayIterator([\n            'foo' => 'bar',\n            'afoo[]' => ['baz', 'qux'],\n            'nfoo' => null,\n        ]);\n        $expected = [\n            'foo=bar',\n            'afoo%5B%5D=baz',\n            'afoo%5B%5D=qux',\n        ];\n        (new QueryMapParamHandler(new DefaultStringConverter(), false))->apply($this->requestBuilder, $map);\n\n        self::assertAttributeSame($expected, 'queries', $this->requestBuilder);\n    }\n\n    public function testQueryMapHandlerEmpty()\n    {\n        (new QueryMapParamHandler(new DefaultStringConverter(), false))->apply($this->requestBuilder, []);\n\n        self::assertAttributeSame([], 'queries', $this->requestBuilder);\n    }\n\n    public function testQueryMapHandlerNull()\n    {\n        (new QueryMapParamHandler(new DefaultStringConverter(), false))->apply($this->requestBuilder, null);\n\n        self::assertAttributeSame([], 'queries', $this->requestBuilder);\n    }\n\n    public function testQueryNameHandler()\n    {\n        (new QueryNameParamHandler(new DefaultStringConverter(), false))->apply($this->requestBuilder, 'bar');\n\n        self::assertAttributeSame(['bar'], 'queries', $this->requestBuilder);\n    }\n\n    public function testQueryNameHandlerArray()\n    {\n        (new QueryNameParamHandler(new DefaultStringConverter(), false))->apply($this->requestBuilder, ['bar', 'baz']);\n\n        self::assertAttributeSame(['bar', 'baz'], 'queries', $this->requestBuilder);\n    }\n\n    public function testQueryNameHandlerNull()\n    {\n        (new QueryNameParamHandler(new DefaultStringConverter(), false))->apply($this->requestBuilder, null);\n\n        self::assertAttributeSame([], 'queries', $this->requestBuilder);\n    }\n\n    public function testQueryHandler()\n    {\n        (new QueryParamHandler(new DefaultStringConverter(), 'foo', false))->apply($this->requestBuilder, 'bar');\n\n        self::assertAttributeSame(['foo=bar'], 'queries', $this->requestBuilder);\n    }\n\n    public function testQueryHandlerArray()\n    {\n        (new QueryParamHandler(new DefaultStringConverter(), 'foo', false))->apply($this->requestBuilder, ['bar', 'baz']);\n\n        self::assertAttributeSame(['foo=bar', 'foo=baz'], 'queries', $this->requestBuilder);\n    }\n\n    public function testQueryHandlerNull()\n    {\n        (new QueryParamHandler(new DefaultStringConverter(), 'foo', false))->apply($this->requestBuilder, null);\n\n        self::assertAttributeSame([], 'queries', $this->requestBuilder);\n    }\n\n    public function testUrlHandler()\n    {\n        (new UrlParamHandler(new DefaultStringConverter()))->apply($this->requestBuilder, 'http://example2.com');\n\n        self::assertAttributeEquals(new Uri('http://example2.com/test/{path}?q=test'), 'uri', $this->requestBuilder);\n    }\n\n    public function testUrlHandlerNull()\n    {\n        try {\n            (new UrlParamHandler(new DefaultStringConverter()))->apply($this->requestBuilder, null);\n        } catch (RuntimeException $exception) {\n            self::assertSame('Url parameters cannot be null', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception was not thrown');\n    }\n\n    public function testUsingMapAsListThrowsException()\n    {\n        $map = ['foo' => ['foo' => 'bar']];\n\n        try {\n            (new FieldMapParamHandler(new DefaultStringConverter(), false))->apply($this->requestBuilder, $map);\n        } catch (RunTimeException $exception) {\n            self::assertSame('Retrofit: Array value must use numeric keys', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception was not thrown');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/RequestBuilderTest.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\nnamespace Tebru\\Retrofit\\Test\\Unit\\Internal;\n\nuse GuzzleHttp\\Psr7\\AppendStream;\nuse LogicException;\nuse Tebru\\Retrofit\\Internal\\RequestBuilder;\nuse PHPUnit\\Framework\\TestCase;\nuse function GuzzleHttp\\Psr7\\stream_for;\n\nclass RequestBuilderTest extends TestCase\n{\n    public function testSimple()\n    {\n        $requestBuilder = new RequestBuilder('GET', 'http://example.com', '/test?q=test', []);\n        $request = $requestBuilder->build();\n\n        self::assertSame('GET', $request->getMethod());\n        self::assertSame('http://example.com/test?q=test', (string)$request->getUri());\n        self::assertSame(['Host' => ['example.com']], $request->getHeaders());\n        self::assertSame('', (string)$request->getBody());\n    }\n\n    public function testSetBody()\n    {\n        $stream = new AppendStream();\n        $requestBuilder = new RequestBuilder('POST', 'http://example.com', '/test?q=test', []);\n        $requestBuilder->setBody($stream);\n        $request = $requestBuilder->build();\n\n        self::assertSame('POST', $request->getMethod());\n        self::assertSame('http://example.com/test?q=test', (string)$request->getUri());\n        self::assertSame(['Host' => ['example.com']], $request->getHeaders());\n        self::assertSame($stream, $request->getBody());\n    }\n\n    public function testUrlSetters()\n    {\n        $requestBuilder = new RequestBuilder('GET', 'http://example.com', '/{part}?q=test', []);\n        $requestBuilder->setBaseUrl('https://example2.com');\n        $requestBuilder->replacePath('part', 'test');\n        $requestBuilder->addQuery('q2', 'test2', false);\n        $requestBuilder->addQueryName('contains(foo)', false);\n        $request = $requestBuilder->build();\n\n        self::assertSame('GET', $request->getMethod());\n        self::assertSame('https://example2.com/test?q2=test2&contains%28foo%29&q=test', (string)$request->getUri());\n        self::assertSame(['Host' => ['example2.com']], $request->getHeaders());\n        self::assertSame('', (string)$request->getBody());\n    }\n\n    public function testUrlSettersEncoded()\n    {\n        $requestBuilder = new RequestBuilder('GET', 'http://example.com', '/{part}', []);\n        $requestBuilder->setBaseUrl('https://example2.com');\n        $requestBuilder->replacePath('part', 'test');\n        $requestBuilder->addQuery('q2', 'test2', true);\n        $requestBuilder->addQueryName('contains%28foo%29', true);\n        $request = $requestBuilder->build();\n\n        self::assertSame('GET', $request->getMethod());\n        self::assertSame('https://example2.com/test?q2=test2&contains%28foo%29', (string)$request->getUri());\n        self::assertSame(['Host' => ['example2.com']], $request->getHeaders());\n        self::assertSame('', (string)$request->getBody());\n    }\n\n    public function testAddHeader()\n    {\n        $requestBuilder = new RequestBuilder('GET', 'http://example.com', '/test?q=test', []);\n        $requestBuilder->addHeader('foo', 'bar');\n        $request = $requestBuilder->build();\n\n        self::assertSame('GET', $request->getMethod());\n        self::assertSame('http://example.com/test?q=test', (string)$request->getUri());\n        self::assertSame(['Host' => ['example.com'], 'foo' => ['bar']], $request->getHeaders());\n        self::assertSame('', (string)$request->getBody());\n    }\n\n    public function testFields()\n    {\n        $requestBuilder = new RequestBuilder('POST', 'http://example.com', '/', []);\n        $requestBuilder->addField('foo', 'bar', false);\n        $requestBuilder->addField('foo()', 'bar()', false);\n        $request = $requestBuilder->build();\n\n        self::assertSame('POST', $request->getMethod());\n        self::assertSame('http://example.com/', (string)$request->getUri());\n        self::assertSame(['Host' => ['example.com']], $request->getHeaders());\n        self::assertSame('foo=bar&foo%28%29=bar%28%29', (string)$request->getBody());\n    }\n\n    public function testFieldsEncoded()\n    {\n        $requestBuilder = new RequestBuilder('POST', 'http://example.com', '/', []);\n        $requestBuilder->addField('foo', 'bar', true);\n        $requestBuilder->addField('foo()', 'bar%28%29', true);\n        $request = $requestBuilder->build();\n\n        self::assertSame('POST', $request->getMethod());\n        self::assertSame('http://example.com/', (string)$request->getUri());\n        self::assertSame(['Host' => ['example.com']], $request->getHeaders());\n        self::assertSame('foo=bar&foo%28%29=bar%28%29', (string)$request->getBody());\n    }\n\n    public function testParts()\n    {\n        $stream = stream_for('foo');\n        $requestBuilder = new RequestBuilder('POST', 'http://example.com', '/', []);\n        $requestBuilder->addPart('foo', $stream);\n        $request = $requestBuilder->build();\n\n        self::assertSame('POST', $request->getMethod());\n        self::assertSame('http://example.com/', (string)$request->getUri());\n        self::assertSame(['Host' => ['example.com']], $request->getHeaders());\n        self::assertNotFalse(strpos((string)$request->getBody(), 'Content-Disposition: form-data; name=\"foo\"'));\n        self::assertNotFalse(strpos((string)$request->getBody(), 'Content-Length: 3'));\n    }\n\n    public function testPartsFilenameAndHeader()\n    {\n        $stream = stream_for('foo');\n        $requestBuilder = new RequestBuilder('POST', 'http://example.com', '/', []);\n        $requestBuilder->addPart('foo', $stream, ['foo' => 'bar'], 'Test.php');\n        $request = $requestBuilder->build();\n\n        self::assertSame('POST', $request->getMethod());\n        self::assertSame('http://example.com/', (string)$request->getUri());\n        self::assertSame(['Host' => ['example.com']], $request->getHeaders());\n        self::assertNotFalse(strpos((string)$request->getBody(), 'foo: bar'));\n        self::assertNotFalse(strpos((string)$request->getBody(), 'Content-Disposition: form-data; name=\"foo\"; filename=\"Test.php\"'));\n        self::assertNotFalse(strpos((string)$request->getBody(), 'Content-Length: 3'));\n    }\n\n    public function testFieldAndBody()\n    {\n        $requestBuilder = new RequestBuilder('POST', 'http://example.com', '/test?q=test', []);\n        $requestBuilder->setBody(new AppendStream());\n        $requestBuilder->addField('foo', 'bar', false);\n\n        try {\n            $requestBuilder->build();\n        } catch (LogicException $exception) {\n            self::assertSame('Retrofit: Cannot mix @Field and @Body annotations.', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testPartAndBody()\n    {\n        $requestBuilder = new RequestBuilder('POST', 'http://example.com', '/test?q=test', []);\n        $requestBuilder->setBody(new AppendStream());\n        $requestBuilder->addPart('foo', new AppendStream());\n\n        try {\n            $requestBuilder->build();\n        } catch (LogicException $exception) {\n            self::assertSame('Retrofit: Cannot mix @Part and @Body annotations.', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/RetrofitResponseTest.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\nnamespace Tebru\\Retrofit\\Test\\Unit\\Internal;\n\nuse GuzzleHttp\\Psr7\\AppendStream;\nuse GuzzleHttp\\Psr7\\Response;\nuse Tebru\\Retrofit\\Internal\\RetrofitResponse;\nuse PHPUnit\\Framework\\TestCase;\n\nclass RetrofitResponseTest extends TestCase\n{\n    public function testGettersSuccess()\n    {\n        $response = new Response();\n        $responseBody = new AppendStream();\n        $retrofitResponse = new RetrofitResponse($response, $responseBody, null);\n\n        self::assertSame($response, $retrofitResponse->raw());\n        self::assertSame(200, $retrofitResponse->code());\n        self::assertSame('OK', $retrofitResponse->message());\n        self::assertSame([], $retrofitResponse->headers());\n        self::assertTrue($retrofitResponse->isSuccessful());\n        self::assertSame($responseBody, $retrofitResponse->body());\n        self::assertNull($retrofitResponse->errorBody());\n    }\n\n    public function testGettersFailure()\n    {\n        $response = new Response(500);\n        $responseBody = new AppendStream();\n        $retrofitResponse = new RetrofitResponse($response, null, $responseBody);\n\n        self::assertSame($response, $retrofitResponse->raw());\n        self::assertSame(500, $retrofitResponse->code());\n        self::assertSame('Internal Server Error', $retrofitResponse->message());\n        self::assertSame([], $retrofitResponse->headers());\n        self::assertFalse($retrofitResponse->isSuccessful());\n        self::assertNull($retrofitResponse->body());\n        self::assertSame($responseBody, $retrofitResponse->errorBody());\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/ServiceMethod/DefaultServiceMethodBuilderTest.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\nnamespace Tebru\\Retrofit\\Test\\Unit\\Internal\\ServiceMethod;\n\nuse LogicException;\nuse Tebru\\Retrofit\\Internal\\CallAdapter\\DefaultCallAdapter;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultRequestBodyConverter;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultResponseBodyConverter;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultStringConverter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\BodyParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\FieldParamHandler;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\PartParamHandler;\nuse Tebru\\Retrofit\\Internal\\ServiceMethod\\DefaultServiceMethodBuilder;\nuse PHPUnit\\Framework\\TestCase;\n\nclass DefaultServiceMethodBuilderTest extends TestCase\n{\n    /**\n     * @var DefaultServiceMethodBuilder\n     */\n    private $serviceMethodBuilder;\n\n    public function setUp()\n    {\n        $this->serviceMethodBuilder = new DefaultServiceMethodBuilder();\n    }\n\n    public function testCreateServiceMethodGet()\n    {\n        $this->serviceMethodBuilder->setMethod('get');\n        $this->serviceMethodBuilder->setBaseUrl('http://example.com');\n        $this->serviceMethodBuilder->setPath('/foo/bar?q=test');\n        $this->serviceMethodBuilder->setCallAdapter(new DefaultCallAdapter());\n        $this->serviceMethodBuilder->setResponseBodyConverter(new DefaultResponseBodyConverter());\n        $this->serviceMethodBuilder->setErrorBodyConverter(new DefaultResponseBodyConverter());\n\n        $serviceMethod = $this->serviceMethodBuilder->build();\n\n        self::assertAttributeSame('GET', 'method', $serviceMethod);\n        self::assertAttributeSame('http://example.com', 'baseUrl', $serviceMethod);\n        self::assertAttributeSame('/foo/bar?q=test', 'path', $serviceMethod);\n    }\n\n    public function testCreateServiceMethodPost()\n    {\n        $paramHandler = new BodyParamHandler(new DefaultRequestBodyConverter());\n        $this->serviceMethodBuilder->setMethod('post');\n        $this->serviceMethodBuilder->setBaseUrl('http://example.com');\n        $this->serviceMethodBuilder->setPath('/foo/bar?q=test');\n        $this->serviceMethodBuilder->setIsJson();\n        $this->serviceMethodBuilder->addParameterHandler(0, $paramHandler);\n        $this->serviceMethodBuilder->setCallAdapter(new DefaultCallAdapter());\n        $this->serviceMethodBuilder->setResponseBodyConverter(new DefaultResponseBodyConverter());\n        $this->serviceMethodBuilder->setErrorBodyConverter(new DefaultResponseBodyConverter());\n\n        $serviceMethod = $this->serviceMethodBuilder->build();\n\n        self::assertAttributeSame('POST', 'method', $serviceMethod);\n        self::assertAttributeSame('http://example.com', 'baseUrl', $serviceMethod);\n        self::assertAttributeSame('/foo/bar?q=test', 'path', $serviceMethod);\n        self::assertAttributeSame(['content-type' => ['application/json']], 'headers', $serviceMethod);\n        self::assertAttributeSame([$paramHandler], 'parameterHandlers', $serviceMethod);\n    }\n\n    public function testCreateServiceMethodForm()\n    {\n        $paramHandler = new FieldParamHandler(new DefaultStringConverter(), 'foo', false);\n        $this->serviceMethodBuilder->setMethod('post');\n        $this->serviceMethodBuilder->setBaseUrl('http://example.com');\n        $this->serviceMethodBuilder->setPath('/foo/bar?q=test');\n        $this->serviceMethodBuilder->setIsFormUrlEncoded();\n        $this->serviceMethodBuilder->addParameterHandler(0, $paramHandler);\n        $this->serviceMethodBuilder->setCallAdapter(new DefaultCallAdapter());\n        $this->serviceMethodBuilder->setResponseBodyConverter(new DefaultResponseBodyConverter());\n        $this->serviceMethodBuilder->setErrorBodyConverter(new DefaultResponseBodyConverter());\n\n        $serviceMethod = $this->serviceMethodBuilder->build();\n\n        self::assertAttributeSame('POST', 'method', $serviceMethod);\n        self::assertAttributeSame('http://example.com', 'baseUrl', $serviceMethod);\n        self::assertAttributeSame('/foo/bar?q=test', 'path', $serviceMethod);\n        self::assertAttributeSame(['content-type' => ['application/x-www-form-urlencoded']], 'headers', $serviceMethod);\n        self::assertAttributeSame([$paramHandler], 'parameterHandlers', $serviceMethod);\n    }\n\n    public function testCreateServiceMethodMultipart()\n    {\n        $paramHandler = new PartParamHandler(new DefaultRequestBodyConverter(), 'foo', 'binary');\n        $this->serviceMethodBuilder->setMethod('post');\n        $this->serviceMethodBuilder->setBaseUrl('http://example.com');\n        $this->serviceMethodBuilder->setPath('/foo/bar?q=test');\n        $this->serviceMethodBuilder->setIsMultipart();\n        $this->serviceMethodBuilder->addParameterHandler(0, $paramHandler);\n        $this->serviceMethodBuilder->setCallAdapter(new DefaultCallAdapter());\n        $this->serviceMethodBuilder->setResponseBodyConverter(new DefaultResponseBodyConverter());\n        $this->serviceMethodBuilder->setErrorBodyConverter(new DefaultResponseBodyConverter());\n\n        $serviceMethod = $this->serviceMethodBuilder->build();\n\n        self::assertAttributeSame('POST', 'method', $serviceMethod);\n        self::assertAttributeSame('http://example.com', 'baseUrl', $serviceMethod);\n        self::assertAttributeSame('/foo/bar?q=test', 'path', $serviceMethod);\n        self::assertAttributeSame(['content-type' => ['multipart/form-data']], 'headers', $serviceMethod);\n        self::assertAttributeSame([$paramHandler], 'parameterHandlers', $serviceMethod);\n    }\n\n    public function testSetMethodTwice()\n    {\n        $this->serviceMethodBuilder->setMethod('get');\n        try {\n            $this->serviceMethodBuilder->setMethod('post');\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Only one http method is allowed. Trying to set POST, but GET already exists',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testSetHasBodyAfterSet()\n    {\n        $this->serviceMethodBuilder->setIsJson();\n        try {\n            $this->serviceMethodBuilder->setHasBody(false);\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Body cannot be changed after it has been set. This indicates a conflict between ' .\n                'HTTP Request annotations, body annotations, and request type annotations. For example, ' .\n                '@GET cannot be used with @Body, @Field, or @Part annotations',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testChangeContentType()\n    {\n        $this->serviceMethodBuilder->setIsJson();\n        try {\n            $this->serviceMethodBuilder->setIsFormUrlEncoded();\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Content type cannot be changed after it has been set. This indicates a conflict between ' .\n                'HTTP Request annotations, body annotations, and request type annotations. For example, ' .\n                '@GET cannot be used with @Body, @Field, or @Part annotations',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testBuildWithoutMethod()\n    {\n        try {\n            $this->serviceMethodBuilder->build();\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Cannot build service method without HTTP method. Please specify @GET, @POST, etc',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testBuildWithoutBaseUrl()\n    {\n        $this->serviceMethodBuilder->setMethod('GET');\n        try {\n            $this->serviceMethodBuilder->build();\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Cannot build service method without base url. Please specify on RetrofitBuilder',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testBuildWithoutPath()\n    {\n        $this->serviceMethodBuilder->setMethod('GET');\n        $this->serviceMethodBuilder->setBaseUrl('http://example.com');\n        try {\n            $this->serviceMethodBuilder->build();\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Cannot build service method without HTTP method. Please specify @GET, @POST, etc',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testBuildWithoutContentType()\n    {\n        $this->serviceMethodBuilder->setMethod('POST');\n        $this->serviceMethodBuilder->setBaseUrl('http://example.com');\n        $this->serviceMethodBuilder->setPath('/foo/bar');\n        $this->serviceMethodBuilder->setHasBody(true);\n        try {\n            $this->serviceMethodBuilder->build();\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Cannot build service method with body and no content type. Set one using @Body, ' .\n                '@Field, or @Part',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testBuildWithoutBody()\n    {\n        $this->serviceMethodBuilder->setMethod('POST');\n        $this->serviceMethodBuilder->setBaseUrl('http://example.com');\n        $this->serviceMethodBuilder->setPath('/foo/bar');\n        $this->serviceMethodBuilder->setContentType('application/json');\n        try {\n            $this->serviceMethodBuilder->build();\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Cannot set a content-type without a body. This indicates a conflict between ' .\n                'HTTP Request annotations, body annotations, and request type annotations. For example, ' .\n                '@GET cannot be used with @Body, @Field, or @Part annotations',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testBuildWithoutResponseConverter()\n    {\n        $this->serviceMethodBuilder->setMethod('POST');\n        $this->serviceMethodBuilder->setBaseUrl('http://example.com');\n        $this->serviceMethodBuilder->setPath('/foo/bar');\n        $this->serviceMethodBuilder->setIsJson();\n        try {\n            $this->serviceMethodBuilder->build();\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Cannot build service method without response body converter',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testBuildWithoutErrorConverter()\n    {\n        $this->serviceMethodBuilder->setMethod('POST');\n        $this->serviceMethodBuilder->setBaseUrl('http://example.com');\n        $this->serviceMethodBuilder->setPath('/foo/bar');\n        $this->serviceMethodBuilder->setIsJson();\n        $this->serviceMethodBuilder->setResponseBodyConverter(new DefaultResponseBodyConverter());\n        try {\n            $this->serviceMethodBuilder->build();\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Cannot build service method without error body converter',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testBuildWithoutCallAdapter()\n    {\n        $this->serviceMethodBuilder->setMethod('POST');\n        $this->serviceMethodBuilder->setBaseUrl('http://example.com');\n        $this->serviceMethodBuilder->setPath('/foo/bar');\n        $this->serviceMethodBuilder->setIsJson();\n        $this->serviceMethodBuilder->setResponseBodyConverter(new DefaultResponseBodyConverter());\n        $this->serviceMethodBuilder->setErrorBodyConverter(new DefaultResponseBodyConverter());\n        try {\n            $this->serviceMethodBuilder->build();\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Cannot build service method without call adapter',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testBuildWillNotOverrideContentType()\n    {\n        $this->serviceMethodBuilder->setMethod('POST');\n        $this->serviceMethodBuilder->setBaseUrl('http://example.com');\n        $this->serviceMethodBuilder->setPath('/foo/bar');\n        $this->serviceMethodBuilder->setHasBody(true);\n        $this->serviceMethodBuilder->setContentType('foo');\n        $this->serviceMethodBuilder->setResponseBodyConverter(new DefaultResponseBodyConverter());\n        $this->serviceMethodBuilder->setErrorBodyConverter(new DefaultResponseBodyConverter());\n        $this->serviceMethodBuilder->setCallAdapter(new DefaultCallAdapter());\n\n        $serviceMethod = $this->serviceMethodBuilder->build();\n\n        self::assertAttributeSame(['content-type' => ['foo']], 'headers', $serviceMethod);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/ServiceMethod/ServiceMethodFactoryTest.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\nnamespace Tebru\\Retrofit\\Test\\Unit\\Internal\\ServiceMethod;\n\nuse Doctrine\\Common\\Annotations\\AnnotationReader;\nuse LogicException;\nuse Tebru\\AnnotationReader\\AnnotationReaderAdapter;\nuse Tebru\\Retrofit\\Annotation\\Body;\nuse Tebru\\Retrofit\\Annotation\\GET;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\BodyAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationHandler\\HttpRequestAnnotHandler;\nuse Tebru\\Retrofit\\Internal\\AnnotationProcessor;\nuse Tebru\\Retrofit\\Internal\\CacheProvider;\nuse Tebru\\Retrofit\\Internal\\CallAdapter\\CallAdapterProvider;\nuse Tebru\\Retrofit\\Internal\\CallAdapter\\DefaultCallAdapterFactory;\nuse Tebru\\Retrofit\\Internal\\Converter\\ConverterProvider;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultConverterFactory;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultResponseBodyConverter;\nuse Tebru\\Retrofit\\Internal\\ServiceMethod\\ServiceMethodFactory;\nuse PHPUnit\\Framework\\TestCase;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ServiceMethod\\ServiceMethodFactoryTest\\ServiceMethodFactoryTestConverterFactory;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ServiceMethod\\ServiceMethodFactoryTest\\ServiceMethodFactoryTestClient;\n\nclass ServiceMethodFactoryTest extends TestCase\n{\n    /**\n     * @var ServiceMethodFactory\n     */\n    private $serviceMethodFactory;\n\n    public function setUp()\n    {\n        $this->serviceMethodFactory = new ServiceMethodFactory(\n            new AnnotationProcessor([\n                GET::class => new HttpRequestAnnotHandler(),\n                Body::class => new BodyAnnotHandler(),\n            ]),\n            new CallAdapterProvider([new DefaultCallAdapterFactory()]),\n            new ConverterProvider([new DefaultConverterFactory(), new ServiceMethodFactoryTestConverterFactory()]),\n            new AnnotationReaderAdapter(new AnnotationReader(), CacheProvider::createNullCache()),\n            'http://example.com'\n        );\n    }\n\n    public function testCreateServiceMethod()\n    {\n        $serviceMethod = $this->serviceMethodFactory->create(ServiceMethodFactoryTestClient::class, 'foo');\n\n        self::assertAttributeSame('GET', 'method', $serviceMethod);\n        self::assertAttributeSame('http://example.com', 'baseUrl', $serviceMethod);\n        self::assertAttributeSame('/foo', 'path', $serviceMethod);\n        self::assertAttributeSame([], 'headers', $serviceMethod);\n        self::assertAttributeSame([], 'parameterHandlers', $serviceMethod);\n        self::assertAttributeEquals(new DefaultResponseBodyConverter(), 'responseBodyConverter', $serviceMethod);\n        self::assertAttributeEquals(new DefaultResponseBodyConverter(), 'errorBodyConverter', $serviceMethod);\n    }\n\n    public function testCreateServiceMethodCustomConverters()\n    {\n        $serviceMethod = $this->serviceMethodFactory->create(ServiceMethodFactoryTestClient::class, 'qux');\n\n        self::assertAttributeSame('GET', 'method', $serviceMethod);\n        self::assertAttributeSame('http://example.com', 'baseUrl', $serviceMethod);\n        self::assertAttributeSame('/', 'path', $serviceMethod);\n        self::assertAttributeSame([], 'headers', $serviceMethod);\n        self::assertAttributeSame([], 'parameterHandlers', $serviceMethod);\n        self::assertAttributeNotEquals(new DefaultResponseBodyConverter(), 'responseBodyConverter', $serviceMethod);\n        self::assertAttributeNotEquals(new DefaultResponseBodyConverter(), 'errorBodyConverter', $serviceMethod);\n    }\n\n    public function testCreateServiceMethodNoReturnType()\n    {\n        try {\n            $this->serviceMethodFactory->create(ServiceMethodFactoryTestClient::class, 'bar');\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: All service methods must contain a return type. None found for ' .\n                'Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ServiceMethod\\ServiceMethodFactoryTest\\ServiceMethodFactoryTestClient::bar()',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testCreateServiceMethodAnnotationException()\n    {\n        try {\n            $this->serviceMethodFactory->create(ServiceMethodFactoryTestClient::class, 'baz');\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Body cannot be changed after it has been set. This indicates a conflict between HTTP Request ' .\n                'annotations, body annotations, and request type annotations. For example, @GET cannot be used with ' .\n                '@Body, @Field, or @Part annotations for ' .\n                'Tebru\\Retrofit\\Test\\Mock\\Unit\\Internal\\ServiceMethod\\ServiceMethodFactoryTest\\ServiceMethodFactoryTestClient::baz()',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Internal/ServiceMethod/ServiceMethodTest.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\nnamespace Tebru\\Retrofit\\Test\\Unit\\Internal\\ServiceMethod;\n\nuse GuzzleHttp\\Psr7\\AppendStream;\nuse GuzzleHttp\\Psr7\\Request;\nuse GuzzleHttp\\Psr7\\Response;\nuse LogicException;\nuse Tebru\\Retrofit\\Internal\\CallAdapter\\DefaultCallAdapter;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultRequestBodyConverter;\nuse Tebru\\Retrofit\\Internal\\Converter\\DefaultResponseBodyConverter;\nuse Tebru\\Retrofit\\Internal\\ParameterHandler\\BodyParamHandler;\nuse Tebru\\Retrofit\\Internal\\ServiceMethod\\DefaultServiceMethod;\nuse PHPUnit\\Framework\\TestCase;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\MockCall;\n\nclass ServiceMethodTest extends TestCase\n{\n    /**\n     * @var DefaultServiceMethod\n     */\n    private $serviceMethod;\n\n    public function setUp()\n    {\n        $this->serviceMethod = new DefaultServiceMethod(\n            'POST',\n            'http://example.com',\n            '/foo/bar?q=test',\n            ['content-type' => ['application/json']],\n            [new BodyParamHandler(new DefaultRequestBodyConverter())],\n            new DefaultCallAdapter(),\n            new DefaultResponseBodyConverter(),\n            new DefaultResponseBodyConverter()\n        );\n    }\n\n    public function testCreateRequest()\n    {\n        $body = new AppendStream();\n        $request = $this->serviceMethod->toRequest([$body]);\n\n        $expected = new Request(\n            'POST',\n            'http://example.com/foo/bar?q=test',\n            ['content-type' => ['application/json']],\n            $body\n        );\n\n        self::assertEquals($expected, $request);\n    }\n\n    public function testCreateRequestDifferentParameters()\n    {\n        try {\n            $this->serviceMethod->toRequest([]);\n        } catch (LogicException $exception) {\n            self::assertSame(\n                'Retrofit: Incompatible number of arguments. Expected 1 and got 0. This either ' .\n                'means that the service method was not called with the correct number of parameters, ' .\n                'or there is not an annotation for every parameter.',\n                $exception->getMessage()\n            );\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testGetResponseBody()\n    {\n        $body = new AppendStream();\n        $response = new Response(200, [], $body);\n        $result = $this->serviceMethod->toResponseBody($response);\n\n        self::assertSame($body, $result);\n    }\n\n    public function testGetErrorBody()\n    {\n        $body = new AppendStream();\n        $response = new Response(200, [], $body);\n        $result = $this->serviceMethod->toErrorBody($response);\n\n        self::assertSame($body, $result);\n    }\n\n    public function testAdaptCall()\n    {\n        $call = new MockCall();\n        $result = $this->serviceMethod->adapt($call);\n\n        self::assertSame($call, $result);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/RetrofitTest.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\nnamespace Tebru\\Retrofit\\Test\\Unit;\n\nuse LogicException;\nuse RuntimeException;\nuse Tebru\\Retrofit\\Annotation\\GET;\nuse Tebru\\Retrofit\\Finder\\ServiceResolver;\nuse Tebru\\Retrofit\\Http\\MultipartBody;\nuse Tebru\\Retrofit\\Internal\\CacheProvider;\nuse Tebru\\Retrofit\\Retrofit;\nuse PHPUnit\\Framework\\TestCase;\nuse Tebru\\Retrofit\\RetrofitBuilder;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest\\ApiClient;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest\\CacheableApiClient;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest\\DefaultParamsApiClient;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest\\InvalidSyntaxApiClient;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest\\RetrofitTestAdaptedCallMock;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest\\RetrofitTestCallAdapterFactory;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest\\RetrofitTestConverterFactory;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest\\RetrofitTestCustomAnnotation;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest\\RetrofitTestCustomAnnotationHandler;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest\\RetrofitTestHttpClient;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest\\RetrofitTestProxyFactory;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest\\RetrofitTestRequestBodyMock;\nuse Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest\\RetrofitTestResponseBodyMock;\n\nclass RetrofitTest extends TestCase\n{\n    /**\n     * @var RetrofitTestHttpClient\n     */\n    private $httpClient;\n\n    /**\n     * @var RetrofitBuilder\n     */\n    private $retrofitBuilder;\n\n    public function setUp()\n    {\n        $this->httpClient = new RetrofitTestHttpClient();\n        $this->retrofitBuilder = Retrofit::builder()\n            ->setBaseUrl('http://example.com')\n            ->setHttpClient($this->httpClient);\n    }\n\n    public function testSimple()\n    {\n        $retrofit = $this->retrofitBuilder->build();\n\n        /** @var ApiClient $service */\n        $service = $retrofit->create(ApiClient::class);\n\n        $service->get()->execute();\n\n        self::assertCount(1, $this->httpClient->requests);\n\n        $request = $this->httpClient->requests[0];\n\n        self::assertSame('GET', $request->getMethod());\n        self::assertSame('http://example.com/', (string)$request->getUri());\n        self::assertSame('', (string)$request->getBody());\n    }\n\n    public function testUri()\n    {\n        $retrofit = $this->retrofitBuilder->build();\n\n        /** @var ApiClient $service */\n        $service = $retrofit->create(ApiClient::class);\n\n        $service\n            ->uri(\n                'https://example2.com',\n                ['foo' => 'bar'],\n                ['one', 2, true],\n                false,\n                'testpart'\n            )\n            ->execute();\n\n        self::assertCount(1, $this->httpClient->requests);\n\n        $request = $this->httpClient->requests[0];\n\n        self::assertSame('OPTIONS', $request->getMethod());\n        self::assertSame('https://example2.com/testpart?foo=bar&query[]=one&query[]=2&query[]=true&false&q=test', rawurldecode((string)$request->getUri()));\n        self::assertSame('', (string)$request->getBody());\n    }\n\n    public function testHeaders()\n    {\n        $retrofit = $this->retrofitBuilder->build();\n\n        /** @var ApiClient $service */\n        $service = $retrofit->create(ApiClient::class);\n\n        $service\n            ->headers(\n                ['X-Header[]' => ['one', 2, false]],\n                [true, 3.14],\n                5\n            )\n            ->execute();\n\n        self::assertCount(1, $this->httpClient->requests);\n\n        $request = $this->httpClient->requests[0];\n\n        self::assertSame('HEAD', $request->getMethod());\n        self::assertSame('http://example.com/', (string)$request->getUri());\n        self::assertSame('', (string)$request->getBody());\n        self::assertSame(\n            [\n                'Host' => ['example.com'],\n                'x-foo' => ['bar'],\n                'x-baz' => ['qux'],\n                'x-header[]' => ['first', 'one', '2', 'false', 'true', '3.14'],\n                'header2' => ['5']\n            ],\n            $request->getHeaders()\n        );\n    }\n\n    public function testPostWithoutBody()\n    {\n        $retrofit = $this->retrofitBuilder->build();\n\n        /** @var ApiClient $service */\n        $service = $retrofit->create(ApiClient::class);\n\n        $service->postWithoutBody()->execute();\n\n        self::assertCount(1, $this->httpClient->requests);\n\n        $request = $this->httpClient->requests[0];\n\n        self::assertSame('POST', $request->getMethod());\n        self::assertSame('http://example.com/', (string)$request->getUri());\n        self::assertSame('', (string)$request->getBody());\n    }\n\n    public function testBody()\n    {\n        $retrofit = $this->retrofitBuilder\n            ->addConverterFactory(new RetrofitTestConverterFactory())\n            ->build();\n\n        /** @var ApiClient $service */\n        $service = $retrofit->create(ApiClient::class);\n\n        $body = new RetrofitTestRequestBodyMock();\n        $body->id = 1;\n        $body->name = 'Nate';\n\n        $responseBody = $service->body($body)->execute()->body();\n\n        self::assertCount(1, $this->httpClient->requests);\n\n        $request = $this->httpClient->requests[0];\n\n        self::assertSame('PUT', $request->getMethod());\n        self::assertSame('http://example.com/', (string)$request->getUri());\n        self::assertSame('{\"id\":1,\"name\":\"Nate\"}', (string)$request->getBody());\n        self::assertSame(['Host' => ['example.com'], 'content-type' => ['application/json']], $request->getHeaders());\n        self::assertInstanceOf(RetrofitTestResponseBodyMock::class, $responseBody);\n    }\n\n    public function testField()\n    {\n        $retrofit = $this->retrofitBuilder->build();\n\n        /** @var ApiClient $service */\n        $service = $retrofit->create(ApiClient::class);\n\n        $service->field(5.3, false, 'foo%28%29', ['foo' => 'bar'])->execute();\n\n        self::assertCount(1, $this->httpClient->requests);\n\n        $request = $this->httpClient->requests[0];\n\n        self::assertSame('PATCH', $request->getMethod());\n        self::assertSame('http://example.com/', (string)$request->getUri());\n        self::assertSame('field1=5.3&field2=false&field3=foo%28%29&foo=bar', (string)$request->getBody());\n        self::assertSame(['Host' => ['example.com'], 'content-type' => ['application/x-www-form-urlencoded']], $request->getHeaders());\n    }\n\n    public function testPart()\n    {\n        $retrofit = $this->retrofitBuilder\n            ->addConverterFactory(new RetrofitTestConverterFactory())\n            ->build();\n\n        /** @var ApiClient $service */\n        $service = $retrofit->create(ApiClient::class);\n\n        $body = new RetrofitTestRequestBodyMock();\n        $body->id = 1;\n        $body->name = 'Nate';\n\n        $multipartRequestBody = new RetrofitTestRequestBodyMock();\n        $multipartRequestBody->id = 2;\n        $multipartRequestBody->name = 'Mike';\n\n        $multipartBody = new MultipartBody('foo', 'bar');\n\n        $service->part($body, $multipartBody, ['baz' => $multipartRequestBody])->execute();\n\n        self::assertCount(1, $this->httpClient->requests);\n\n        $request = $this->httpClient->requests[0];\n\n        self::assertSame('FOO', $request->getMethod());\n        self::assertSame('http://example.com/', (string)$request->getUri());\n        self::assertSame(['Host' => ['example.com'], 'content-type' => ['multipart/form-data']], $request->getHeaders());\n        self::assertNotFalse(strpos(\n            (string)$request->getBody(),\n            'Content-Disposition: form-data; name=\"part1\"'\n        ));\n        self::assertNotFalse(strpos(\n            (string)$request->getBody(),\n            'Content-Disposition: form-data; name=\"foo\"'\n        ));\n        self::assertNotFalse(strpos(\n            (string)$request->getBody(),\n            'Content-Disposition: form-data; name=\"baz\"'\n        ));\n        self::assertNotFalse(strpos(\n            (string)$request->getBody(),\n            '{\"id\":1,\"name\":\"Nate\"}'\n        ));\n        self::assertNotFalse(strpos(\n            (string)$request->getBody(),\n            '{\"id\":2,\"name\":\"Mike\"}'\n        ));\n    }\n\n    public function testCallAdapter()\n    {\n        $retrofit = $this->retrofitBuilder\n            ->addCallAdapterFactory(new RetrofitTestCallAdapterFactory())\n            ->build();\n\n        /** @var ApiClient $service */\n        $service = $retrofit->create(ApiClient::class);\n\n        $adaptedCall = $service->callAdapter();\n\n        self::assertInstanceOf(RetrofitTestAdaptedCallMock::class, $adaptedCall);\n    }\n\n    public function testCustomProxy()\n    {\n        $retrofit = $this->retrofitBuilder\n            ->addProxyFactory(new RetrofitTestProxyFactory())\n            ->build();\n\n        /** @var ApiClient $service */\n        $service = $retrofit->create(ApiClient::class);\n\n        $service->get()->execute();\n\n        self::assertCount(1, $this->httpClient->requests);\n\n        $request = $this->httpClient->requests[0];\n\n        self::assertSame('GET', $request->getMethod());\n        self::assertSame('http://example.com/', (string)$request->getUri());\n        self::assertSame('', (string)$request->getBody());\n    }\n\n    public function testCustomAnnotation()\n    {\n        $retrofit = $this->retrofitBuilder\n            ->addAnnotationHandler(RetrofitTestCustomAnnotation::class, new RetrofitTestCustomAnnotationHandler())\n            ->build();\n\n        /** @var ApiClient $service */\n        $service = $retrofit->create(ApiClient::class);\n\n        $service->customAnnotation()->execute();\n\n        self::assertCount(1, $this->httpClient->requests);\n\n        $request = $this->httpClient->requests[0];\n\n        self::assertSame('DELETE', $request->getMethod());\n        self::assertSame('http://example.com/', (string)$request->getUri());\n        self::assertSame('', (string)$request->getBody());\n        self::assertSame(['Host' => ['example.com'], 'foo' => ['bar']], $request->getHeaders());\n    }\n\n    public function testGetWithDefaults()\n    {\n        $retrofit = $this->retrofitBuilder->build();\n\n        /** @var DefaultParamsApiClient $service */\n        $service = $retrofit->create(DefaultParamsApiClient::class);\n\n        $service->getWithDefaults()->execute();\n\n        self::assertCount(1, $this->httpClient->requests);\n\n        $request = $this->httpClient->requests[0];\n\n        self::assertSame('GET', $request->getMethod());\n        self::assertSame('http://example.com/?string=test&bool=true&int=1&float=3.2', (string)$request->getUri());\n        self::assertSame('', (string)$request->getBody());\n        self::assertSame(['Host' => ['example.com'], 'test' => ['value']], $request->getHeaders());\n    }\n\n    public function testGetWithSomeDefaults()\n    {\n        $retrofit = $this->retrofitBuilder->build();\n\n        /** @var DefaultParamsApiClient $service */\n        $service = $retrofit->create(DefaultParamsApiClient::class);\n\n        $service->getWithDefaults('test2', false)->execute();\n\n        self::assertCount(1, $this->httpClient->requests);\n\n        $request = $this->httpClient->requests[0];\n\n        self::assertSame('GET', $request->getMethod());\n        self::assertSame('http://example.com/?string=test2&bool=false&int=1&float=3.2', (string)$request->getUri());\n        self::assertSame('', (string)$request->getBody());\n        self::assertSame(['Host' => ['example.com'], 'test' => ['value']], $request->getHeaders());\n    }\n\n    public function testGetWithNullDefaults()\n    {\n        $retrofit = $this->retrofitBuilder->build();\n\n        /** @var DefaultParamsApiClient $service */\n        $service = $retrofit->create(DefaultParamsApiClient::class);\n\n        $service->getWithDefaults(null, null, null, null, null, null)->execute();\n\n        self::assertCount(1, $this->httpClient->requests);\n\n        $request = $this->httpClient->requests[0];\n\n        self::assertSame('GET', $request->getMethod());\n        self::assertSame('http://example.com/', (string)$request->getUri());\n        self::assertSame('', (string)$request->getBody());\n        self::assertSame(['Host' => ['example.com']], $request->getHeaders());\n    }\n\n    public function testCache()\n    {\n        $cacheDir = __DIR__.'/../cache';\n        $file = $cacheDir.'/retrofit/Tebru/Retrofit/Test/Mock/Unit/RetrofitTest/CacheableApiClient.php';\n\n        if (file_exists($file)) {\n            $success = unlink($file);\n\n            if (!$success) {\n                throw new RuntimeException('Could not cleanup test');\n            }\n        }\n\n        $retrofit = $this->retrofitBuilder\n            ->enableCache()\n            ->setCacheDir($cacheDir)\n            ->build();\n\n        /** @var CacheableApiClient $service */\n        $service = $retrofit->create(CacheableApiClient::class);\n\n        $service->get()->execute();\n\n        self::assertFileExists($file);\n        unlink($file);\n    }\n\n    public function testCustomCache()\n    {\n        $cache = CacheProvider::createMemoryCache();\n\n        $retrofit = $this->retrofitBuilder\n            ->setCache($cache)\n            ->build();\n\n        /** @var CacheableApiClient $service */\n        $service = $retrofit->create(CacheableApiClient::class);\n\n        $service->get()->execute();\n\n        $annotation = new GET(['value' => '/']);\n        self::assertEquals([GET::class => $annotation], $cache->get('annotationreader.TebruRetrofitTestMockUnitRetrofitTestCacheableApiClientget'));\n    }\n\n    public function testBuilderThrowsExceptionWithoutBaseUrl()\n    {\n        try {\n            Retrofit::builder()->build();\n        } catch (LogicException $exception) {\n            self::assertSame('Retrofit: Base URL must be provided', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testBuilderThrowsExceptionWithoutHttpClient()\n    {\n        try {\n            Retrofit::builder()\n                ->setBaseUrl('http://example.com')\n                ->build();\n        } catch (LogicException $exception) {\n            self::assertSame('Retrofit: Must set http client to make requests', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testBuilderThrowsExceptionWithoutCacheDir()\n    {\n        try {\n            $this->retrofitBuilder->enableCache()->build();\n        } catch (LogicException $exception) {\n            self::assertSame('Retrofit: If caching is enabled, must specify cache directory', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testCreateServices()\n    {\n        $retrofit = $this->retrofitBuilder->build();\n        $retrofit->registerServices([ApiClient::class]);\n\n        self::assertSame(1, $retrofit->createServices());\n    }\n\n    public function testCreateAll()\n    {\n        $retrofit = $this->retrofitBuilder->build();\n\n        self::assertSame(4, $retrofit->createAll(__DIR__.'/../Mock/Unit/RetrofitTest/'));\n    }\n\n    public function testCreateThrowsExceptionWithoutFactory()\n    {\n        $retrofit = new Retrofit(new ServiceResolver(), []);\n        try {\n            $retrofit->create(ApiClient::class);\n        } catch (LogicException $exception) {\n            self::assertSame('Retrofit: Could not find a proxy factory for Tebru\\Retrofit\\Test\\Mock\\Unit\\RetrofitTest\\ApiClient', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n\n    public function testCreateThrowsExceptionWithInvalidHeaderSyntax()\n    {\n        $retrofit = $this->retrofitBuilder->build();\n\n        /** @var InvalidSyntaxApiClient $service */\n        $service = $retrofit->create(InvalidSyntaxApiClient::class);\n\n        try {\n            $service->get();\n        } catch (RuntimeException $exception) {\n            self::assertSame('Retrofit: Header in an incorrect format.  Expected \"Name: value\"', $exception->getMessage());\n            return;\n        }\n\n        self::fail('Exception not thrown');\n    }\n}\n"
  },
  {
    "path": "tests/bootstrap.php",
    "content": "<?php\n/*\n * Copyright (c) Nate Brunette.\n * Distributed under the MIT License (http://opensource.org/licenses/MIT)\n */\n\nuse Doctrine\\Common\\Annotations\\AnnotationRegistry;\n\n$loader = require __DIR__ . '/../vendor/autoload.php';\nAnnotationRegistry::registerLoader([$loader, 'loadClass']);\n\ndefine('TEST_DIR', __DIR__);\n"
  }
]