OutputEndpointData.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. <?php
  2. namespace Knuckles\Camel\Output;
  3. use Illuminate\Http\UploadedFile;
  4. use Illuminate\Routing\Route;
  5. use Illuminate\Support\Str;
  6. use Knuckles\Camel\BaseDTO;
  7. use Knuckles\Camel\Extraction\Metadata;
  8. use Knuckles\Camel\Extraction\ResponseCollection;
  9. use Knuckles\Camel\Extraction\ResponseField;
  10. use Knuckles\Scribe\Extracting\Extractor;
  11. use Knuckles\Scribe\Tools\Utils as u;
  12. use Knuckles\Scribe\Tools\WritingUtils;
  13. /**
  14. * Endpoint DTO, optimized for generating HTML output.
  15. * Unneeded properties removed, extra properties and helper methods added.
  16. */
  17. class OutputEndpointData extends BaseDTO
  18. {
  19. /**
  20. * @var array<string>
  21. */
  22. public array $httpMethods;
  23. public string $uri;
  24. public Metadata $metadata;
  25. /**
  26. * @var array<string,string>
  27. */
  28. public array $headers = [];
  29. /**
  30. * @var array<string,\Knuckles\Camel\Output\Parameter>
  31. */
  32. public array $urlParameters = [];
  33. /**
  34. * @var array<string,mixed>
  35. */
  36. public array $cleanUrlParameters = [];
  37. /**
  38. * @var array<string,\Knuckles\Camel\Output\Parameter>
  39. */
  40. public array $queryParameters = [];
  41. /**
  42. * @var array<string,mixed>
  43. */
  44. public array $cleanQueryParameters = [];
  45. /**
  46. * @var array<string, \Knuckles\Camel\Output\Parameter>
  47. */
  48. public array $bodyParameters = [];
  49. /**
  50. * @var array<string,mixed>
  51. */
  52. public array $cleanBodyParameters = [];
  53. /**
  54. * @var array<string,\Illuminate\Http\UploadedFile>
  55. */
  56. public array $fileParameters = [];
  57. public ResponseCollection $responses;
  58. /**
  59. * @var array<string,\Knuckles\Camel\Extraction\ResponseField>
  60. */
  61. public array $responseFields = [];
  62. /**
  63. * The same as bodyParameters, but organized in a hierarchy.
  64. * So, top-level items first, with a __fields property containing their children, and so on.
  65. * Useful so we can easily render and nest on the frontend.
  66. * @var array<string, array>
  67. */
  68. public array $nestedBodyParameters = [];
  69. /**
  70. * @var array<string, array>
  71. */
  72. public array $nestedResponseFields = [];
  73. public ?string $boundUri;
  74. public function __construct(array $parameters = [])
  75. {
  76. // spatie/dto currently doesn't auto-cast nested DTOs like that
  77. $parameters['responses'] = new ResponseCollection($parameters['responses'] ?? []);
  78. $parameters['bodyParameters'] = array_map(fn($param) => new Parameter($param), $parameters['bodyParameters'] ?? []);
  79. $parameters['queryParameters'] = array_map(fn($param) => new Parameter($param), $parameters['queryParameters'] ?? []);
  80. $parameters['urlParameters'] = array_map(fn($param) => new Parameter($param), $parameters['urlParameters'] ?? []);
  81. $parameters['responseFields'] = array_map(fn($param) => new ResponseField($param), $parameters['responseFields'] ?? []);
  82. parent::__construct($parameters);
  83. $this->cleanBodyParameters = Extractor::cleanParams($this->bodyParameters);
  84. $this->cleanQueryParameters = Extractor::cleanParams($this->queryParameters);
  85. $this->cleanUrlParameters = Extractor::cleanParams($this->urlParameters);
  86. $this->nestedBodyParameters = Extractor::nestArrayAndObjectFields($this->bodyParameters, $this->cleanBodyParameters);
  87. $this->nestedResponseFields = Extractor::nestArrayAndObjectFields($this->responseFields);
  88. $this->boundUri = u::getUrlWithBoundParameters($this->uri, $this->cleanUrlParameters);
  89. [$files, $regularParameters] = static::splitIntoFileAndRegularParameters($this->cleanBodyParameters);
  90. if (count($files)) {
  91. $this->headers['Content-Type'] = 'multipart/form-data';
  92. }
  93. $this->fileParameters = $files;
  94. $this->cleanBodyParameters = $regularParameters;
  95. }
  96. /**
  97. * @param Route $route
  98. *
  99. * @return array<string>
  100. */
  101. public static function getMethods(Route $route): array
  102. {
  103. $methods = $route->methods();
  104. // Laravel adds an automatic "HEAD" endpoint for each GET request, so we'll strip that out,
  105. // but not if there's only one method (means it was intentional)
  106. if (count($methods) === 1) {
  107. return $methods;
  108. }
  109. return array_diff($methods, ['HEAD']);
  110. }
  111. public static function fromExtractedEndpointArray(array $endpoint): OutputEndpointData
  112. {
  113. return new self($endpoint);
  114. }
  115. public function endpointId(): string
  116. {
  117. return $this->httpMethods[0] . str_replace(['/', '?', '{', '}', ':', '\\', '+', '|', '.'], '-', $this->uri);
  118. }
  119. public function name(): string
  120. {
  121. return $this->metadata->title ?: ($this->httpMethods[0] . " " . $this->uri);
  122. }
  123. public function fullSlug(): string
  124. {
  125. $groupSlug = Str::slug($this->metadata->groupName);
  126. $endpointId = $this->endpointId();
  127. return "$groupSlug-$endpointId";
  128. }
  129. public function hasResponses(): bool
  130. {
  131. return count($this->responses) > 0;
  132. }
  133. public function hasFiles(): bool
  134. {
  135. return count($this->fileParameters) > 0;
  136. }
  137. public function isArrayBody(): bool
  138. {
  139. return count($this->nestedBodyParameters) === 1
  140. && array_keys($this->nestedBodyParameters)[0] === "[]";
  141. }
  142. public function isGet(): bool
  143. {
  144. return in_array('GET', $this->httpMethods);
  145. }
  146. public function isAuthed(): bool
  147. {
  148. return $this->metadata->authenticated;
  149. }
  150. public function hasJsonBody(): bool
  151. {
  152. if ($this->hasFiles() || empty($this->nestedBodyParameters))
  153. return false;
  154. $contentType = data_get($this->headers, "Content-Type", data_get($this->headers, "content-type", ""));
  155. return str_contains($contentType, "json");
  156. }
  157. public function getSampleBody()
  158. {
  159. return WritingUtils::getSampleBody($this->nestedBodyParameters);
  160. }
  161. public function hasHeadersOrQueryOrBodyParams(): bool
  162. {
  163. return !empty($this->headers)
  164. || !empty($this->cleanQueryParameters)
  165. || !empty($this->cleanBodyParameters);
  166. }
  167. public static function splitIntoFileAndRegularParameters(array $parameters): array
  168. {
  169. $files = [];
  170. $regularParameters = [];
  171. foreach ($parameters as $name => $example) {
  172. if ($example instanceof UploadedFile) {
  173. $files[$name] = $example;
  174. } else if (is_array($example) && !empty($example)) {
  175. [$subFiles, $subRegulars] = static::splitIntoFileAndRegularParameters($example);
  176. foreach ($subFiles as $subName => $subExample) {
  177. $files[$name][$subName] = $subExample;
  178. }
  179. foreach ($subRegulars as $subName => $subExample) {
  180. $regularParameters[$name][$subName] = $subExample;
  181. }
  182. } else {
  183. $regularParameters[$name] = $example;
  184. }
  185. }
  186. return [$files, $regularParameters];
  187. }
  188. }