PostmanCollectionWriter.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <?php
  2. namespace Mpociot\ApiDoc\Writing;
  3. use Illuminate\Support\Collection;
  4. use Illuminate\Support\Facades\URL;
  5. use Illuminate\Support\Str;
  6. use Ramsey\Uuid\Uuid;
  7. use ReflectionMethod;
  8. class PostmanCollectionWriter
  9. {
  10. /**
  11. * @var Collection
  12. */
  13. private $routeGroups;
  14. /**
  15. * @var string
  16. */
  17. private $baseUrl;
  18. /**
  19. * @var string
  20. */
  21. private $protocol;
  22. /**
  23. * @var array|null
  24. */
  25. private $auth;
  26. /**
  27. * CollectionWriter constructor.
  28. *
  29. * @param Collection $routeGroups
  30. */
  31. public function __construct(Collection $routeGroups, $baseUrl)
  32. {
  33. $this->routeGroups = $routeGroups;
  34. $this->protocol = Str::startsWith($baseUrl, 'https') ? 'https' : 'http';
  35. $this->baseUrl = $this->getBaseUrl($baseUrl);
  36. $this->auth = config('apidoc.postman.auth');
  37. }
  38. public function getCollection()
  39. {
  40. $collection = [
  41. 'variables' => [],
  42. 'info' => [
  43. 'name' => config('apidoc.postman.name') ?: config('app.name').' API',
  44. '_postman_id' => Uuid::uuid4()->toString(),
  45. 'description' => config('apidoc.postman.description') ?: '',
  46. 'schema' => 'https://schema.getpostman.com/json/collection/v2.0.0/collection.json',
  47. ],
  48. 'item' => $this->routeGroups->map(function (Collection $routes, $groupName) {
  49. return [
  50. 'name' => $groupName,
  51. 'description' => $routes->first()['metadata']['groupDescription'],
  52. 'item' => $routes->map(\Closure::fromCallable([$this, 'generateEndpointItem']))->toArray(),
  53. ];
  54. })->values()->toArray(),
  55. ];
  56. if (! empty($this->auth)) {
  57. $collection['auth'] = $this->auth;
  58. }
  59. return json_encode($collection, JSON_PRETTY_PRINT);
  60. }
  61. protected function generateEndpointItem($route)
  62. {
  63. $mode = 'raw';
  64. $method = $route['methods'][0];
  65. return [
  66. 'name' => $route['metadata']['title'] != '' ? $route['metadata']['title'] : $route['uri'],
  67. 'request' => [
  68. 'url' => $this->makeUrlData($route),
  69. 'method' => $method,
  70. 'header' => $this->resolveHeadersForRoute($route),
  71. 'body' => [
  72. 'mode' => $mode,
  73. $mode => json_encode($route['cleanBodyParameters'], JSON_PRETTY_PRINT),
  74. ],
  75. 'description' => $route['metadata']['description'] ?? null,
  76. 'response' => [],
  77. ],
  78. ];
  79. }
  80. protected function resolveHeadersForRoute($route)
  81. {
  82. $headers = collect($route['headers']);
  83. // Exclude authentication headers if they're handled by Postman auth
  84. $authHeader = $this->getAuthHeader();
  85. if (! empty($authHeader)) {
  86. $headers = $headers->except($authHeader);
  87. }
  88. return $headers
  89. ->union([
  90. 'Accept' => 'application/json',
  91. ])
  92. ->map(function ($value, $header) {
  93. return [
  94. 'key' => $header,
  95. 'value' => $value,
  96. ];
  97. })
  98. ->values()
  99. ->all();
  100. }
  101. protected function makeUrlData($route)
  102. {
  103. // URL Parameters are collected by the `UrlParameters` strategies, but only make sense if they're in the route
  104. // definition. Filter out any URL parameters that don't appear in the URL.
  105. $urlParams = collect($route['urlParameters'])->filter(function ($_, $key) use ($route) {
  106. return Str::contains($route['uri'], '{'.$key.'}');
  107. });
  108. /** @var Collection $queryParams */
  109. $base = [
  110. 'protocol' => $this->protocol,
  111. 'host' => $this->baseUrl,
  112. // Substitute laravel/symfony query params ({example}) to Postman style, prefixed with a colon
  113. 'path' => preg_replace_callback('/\/{(\w+)\??}(?=\/|$)/', function ($matches) {
  114. return '/:'.$matches[1];
  115. }, $route['uri']),
  116. 'query' => collect($route['queryParameters'])->map(function ($parameter, $key) {
  117. return [
  118. 'key' => $key,
  119. 'value' => urlencode($parameter['value']),
  120. 'description' => $parameter['description'],
  121. // Default query params to disabled if they aren't required and have empty values
  122. 'disabled' => ! $parameter['required'] && empty($parameter['value']),
  123. ];
  124. })->values()->toArray(),
  125. ];
  126. // If there aren't any url parameters described then return what we've got
  127. /** @var $urlParams Collection */
  128. if ($urlParams->isEmpty()) {
  129. return $base;
  130. }
  131. $base['variable'] = $urlParams->map(function ($parameter, $key) {
  132. return [
  133. 'id' => $key,
  134. 'key' => $key,
  135. 'value' => urlencode($parameter['value']),
  136. 'description' => $parameter['description'],
  137. ];
  138. })->values()->toArray();
  139. return $base;
  140. }
  141. protected function getAuthHeader()
  142. {
  143. $auth = $this->auth;
  144. if (empty($auth) || ! is_string($auth['type'] ?? null)) {
  145. return null;
  146. }
  147. switch ($auth['type']) {
  148. case 'bearer':
  149. return 'Authorization';
  150. case 'apikey':
  151. $spec = $auth['apikey'];
  152. if (isset($spec['in']) && $spec['in'] !== 'header') {
  153. return null;
  154. }
  155. return $spec['key'];
  156. default:
  157. return null;
  158. }
  159. }
  160. protected function getBaseUrl($baseUrl)
  161. {
  162. if (Str::contains(app()->version(), 'Lumen')) { //Is Lumen
  163. $reflectionMethod = new ReflectionMethod(\Laravel\Lumen\Routing\UrlGenerator::class, 'getRootUrl');
  164. $reflectionMethod->setAccessible(true);
  165. $url = app('url');
  166. return $reflectionMethod->invokeArgs($url, ['', $baseUrl]);
  167. }
  168. return URL::formatRoot('', $baseUrl);
  169. }
  170. }