PostmanCollectionWriter.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. <?php
  2. namespace Knuckles\Scribe\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('scribe.postman.auth');
  37. }
  38. public function getCollection()
  39. {
  40. $collection = [
  41. 'variables' => [],
  42. 'info' => [
  43. 'name' => config('scribe.title') ?: config('app.name') . ' API',
  44. '_postman_id' => Uuid::uuid4()->toString(),
  45. 'description' => config('scribe.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. // Allow users to write ['header' => '@{{value}}'] in config
  94. // and have it rendered properly as {{value}} in the Postman collection.
  95. $value = str_replace('@{{', '{{', $value);
  96. return [
  97. 'key' => $header,
  98. 'value' => $value,
  99. ];
  100. })
  101. ->values()
  102. ->all();
  103. }
  104. protected function makeUrlData($route)
  105. {
  106. // URL Parameters are collected by the `UrlParameters` strategies, but only make sense if they're in the route
  107. // definition. Filter out any URL parameters that don't appear in the URL.
  108. $urlParams = collect($route['urlParameters'])->filter(function ($_, $key) use ($route) {
  109. return Str::contains($route['uri'], '{' . $key . '}');
  110. });
  111. $base = [
  112. 'protocol' => $this->protocol,
  113. 'host' => $this->baseUrl,
  114. // Substitute laravel/symfony query params ({example}) to Postman style, prefixed with a colon
  115. 'path' => preg_replace_callback('/\/{(\w+)\??}(?=\/|$)/', function ($matches) {
  116. return '/:' . $matches[1];
  117. }, $route['uri']),
  118. 'query' => collect($route['queryParameters'] ?? [])->map(function ($parameterData, $key) {
  119. $key = rtrim($key,".*");
  120. return [
  121. 'key' => $key,
  122. 'value' => urlencode($parameterData['value']),
  123. 'description' => strip_tags($parameterData['description']),
  124. // Default query params to disabled if they aren't required and have empty values
  125. 'disabled' => !($parameterData['required'] ?? false) && empty($parameterData['value']),
  126. ];
  127. })->values()->toArray(),
  128. ];
  129. // If there aren't any url parameters described then return what we've got
  130. /** @var $urlParams Collection */
  131. if ($urlParams->isEmpty()) {
  132. return $base;
  133. }
  134. $base['variable'] = $urlParams->map(function ($parameter, $key) {
  135. return [
  136. 'id' => $key,
  137. 'key' => $key,
  138. 'value' => urlencode($parameter['value']),
  139. 'description' => $parameter['description'],
  140. ];
  141. })->values()->toArray();
  142. return $base;
  143. }
  144. protected function getAuthHeader()
  145. {
  146. $auth = $this->auth;
  147. if (empty($auth) || ! is_string($auth['type'] ?? null)) {
  148. return null;
  149. }
  150. switch ($auth['type']) {
  151. case 'bearer':
  152. return 'Authorization';
  153. case 'apikey':
  154. $spec = $auth['apikey'];
  155. if (isset($spec['in']) && $spec['in'] !== 'header') {
  156. return null;
  157. }
  158. return $spec['key'];
  159. default:
  160. return null;
  161. }
  162. }
  163. protected function getBaseUrl($baseUrl)
  164. {
  165. if (Str::contains(app()->version(), 'Lumen')) { //Is Lumen
  166. $reflectionMethod = new ReflectionMethod(\Laravel\Lumen\Routing\UrlGenerator::class, 'getRootUrl');
  167. $reflectionMethod->setAccessible(true);
  168. $url = app('url');
  169. return $reflectionMethod->invokeArgs($url, ['', $baseUrl]);
  170. }
  171. return URL::formatRoot('', $baseUrl);
  172. }
  173. }