Przeglądaj źródła

Implement sorting of endpoints

shalvah 4 lat temu
rodzic
commit
683e9694fa
2 zmienionych plików z 68 dodań i 23 usunięć
  1. 35 10
      camel/Camel.php
  2. 33 13
      src/Commands/GenerateDocumentation.php

+ 35 - 10
camel/Camel.php

@@ -103,36 +103,61 @@ class Camel
         return $userDefinedEndpoints;
     }
 
-    public static function doesGroupContainEndpoint(array $group, $endpoint): bool
+    public static function doesGroupContainEndpoint(array $group, OutputEndpointData $endpoint): bool
     {
         return boolval(Arr::first($group['endpoints'], function ($e) use ($endpoint) {
             return $e->endpointId() === $endpoint->endpointId();
         }));
     }
 
+    public static function getEndpointIndexInGroup(array $groups, OutputEndpointData $endpoint): ?int
+    {
+        foreach ($groups as $group) {
+            foreach ($group['endpoints'] as $index => $endpointInGroup) {
+                if ($endpointInGroup->endpointId() === $endpoint->endpointId()) {
+                    return $index;
+                }
+            }
+        }
+
+        return null;
+    }
+
     /**
      * @param array[] $endpoints
+     * @param array $endpointGroupIndexes Mapping of endpoint IDs to their index within their group
      *
      * @return array[]
      */
-    public static function groupEndpoints(array $endpoints): array
+    public static function groupEndpoints(array $endpoints, array $endpointGroupIndexes): array
     {
         $groupedEndpoints = collect($endpoints)
             ->groupBy('metadata.groupName')
             ->sortKeys(SORT_NATURAL);
 
-        return $groupedEndpoints->map(fn(Collection $group) => [
-            'name' => $group[0]->metadata->groupName,
-            'description' => Arr::first($group, function (ExtractedEndpointData $endpointData) {
-                    return $endpointData->metadata->groupDescription !== '';
-                })->metadata->groupDescription ?? '',
-            'endpoints' => $group->map(fn(ExtractedEndpointData $endpointData) => $endpointData->forSerialisation()->toArray())->all(),
-        ])->values()->all();
+        return $groupedEndpoints->map(function (Collection $endpointsInGroup) use ($endpointGroupIndexes) {
+            $sortedEndpoints = $endpointsInGroup;
+            if (!empty($endpointGroupIndexes)) {
+                $sortedEndpoints = $endpointsInGroup->sortBy(
+                    fn(ExtractedEndpointData $e) => $endpointGroupIndexes[$e->endpointId()] ?? INF,
+                );
+            }
+
+            return [
+                'name' => Arr::first($endpointsInGroup, function (ExtractedEndpointData $endpointData) {
+                        return !empty($endpointData->metadata->groupName);
+                    })->metadata->groupName ?? '',
+                'description' => Arr::first($endpointsInGroup, function (ExtractedEndpointData $endpointData) {
+                        return !empty($endpointData->metadata->groupDescription);
+                    })->metadata->groupDescription ?? '',
+                'endpoints' => $sortedEndpoints->map(fn(ExtractedEndpointData $endpointData) => $endpointData->forSerialisation()->toArray())->values()->all(),
+            ];
+        })->values()->all();
     }
 
     public static function prepareGroupedEndpointsForOutput(array $groupedEndpoints): array
     {
-        $groups =  array_map(function (array $group) {
+        $groups = array_map(function (array $group) {
             return [
                 'name' => $group['name'],
                 'description' => $group['description'],

+ 33 - 13
src/Commands/GenerateDocumentation.php

@@ -7,7 +7,7 @@ use Illuminate\Support\Arr;
 use Illuminate\Support\Facades\URL;
 use Illuminate\Support\Str;
 use Knuckles\Camel\Extraction\ExtractedEndpointData;
-use Knuckles\Camel\Output\OutputEndpointData as OutputEndpointData;
+use Knuckles\Camel\Output\OutputEndpointData;
 use Knuckles\Camel\Camel;
 use Knuckles\Scribe\Extracting\Extractor;
 use Knuckles\Scribe\Extracting\ApiDetails;
@@ -43,6 +43,7 @@ class GenerateDocumentation extends Command
 
     private bool $forcing;
     private bool $encounteredErrors = false;
+    private array $endpointGroupIndexes = [];
 
     public function handle(RouteMatcherInterface $routeMatcher): void
     {
@@ -76,13 +77,16 @@ class GenerateDocumentation extends Command
      * @param MatchedRoute[] $matches
      * @param array $cachedEndpoints
      * @param array $latestEndpointsData
+     * @param array[] $groups
      *
      * @return array
+     * @throws \Exception
      */
-    private function extractEndpointsInfoFromLaravelApp(array $matches, array $cachedEndpoints = [], array $latestEndpointsData = []): array
+    private function extractEndpointsInfoFromLaravelApp(array $matches, array $cachedEndpoints, array $latestEndpointsData, array $groups): array
     {
         $generator = new Extractor($this->docConfig);
-        $parsedRoutes = [];
+        $parsedEndpoints = [];
+
         foreach ($matches as $routeItem) {
             $route = $routeItem->getRoute();
 
@@ -106,8 +110,13 @@ class GenerateDocumentation extends Command
                 c::info('Processing route: ' . c::getRouteRepresentation($route));
                 $currentEndpointData = $generator->processRoute($route, $routeItem->getRules());
                 // If latest data is different from cached data, merge latest into current
-                $currentEndpointData = $this->mergeAnyEndpointDataUpdates($currentEndpointData, $cachedEndpoints, $latestEndpointsData);
-                $parsedRoutes[] = $currentEndpointData;
+                [$currentEndpointData, $index] = $this->mergeAnyEndpointDataUpdates($currentEndpointData, $cachedEndpoints, $latestEndpointsData, $groups);
+
+                // We need to preserve order of endpoints, in case user did custom sorting
+                $parsedEndpoints[] = $currentEndpointData;
+                if ($index !== null) {
+                    $this->endpointGroupIndexes[$currentEndpointData->endpointId()] = $index;
+                }
                 c::success('Processed route: ' . c::getRouteRepresentation($route));
             } catch (\Exception $exception) {
                 $this->encounteredErrors = true;
@@ -116,24 +125,32 @@ class GenerateDocumentation extends Command
             }
         }
 
-        return $parsedRoutes;
+        return $parsedEndpoints;
     }
 
-    private function mergeAnyEndpointDataUpdates(ExtractedEndpointData $endpointData, array $cachedEndpoints, array $latestEndpointsData): ExtractedEndpointData
+    /**
+     * @param ExtractedEndpointData $endpointData
+     * @param array[] $cachedEndpoints
+     * @param array[] $latestEndpointsData
+     * @param array[] $groups
+     *
+     * @return array{ExtractedEndpointData, int}
+     */
+    private function mergeAnyEndpointDataUpdates(ExtractedEndpointData $endpointData, array $cachedEndpoints, array $latestEndpointsData, array $groups): array
     {
         // First, find the corresponding endpoint in cached and latest
         $thisEndpointCached = Arr::first($cachedEndpoints, function (array $endpoint) use ($endpointData) {
             return $endpoint['uri'] === $endpointData->uri && $endpoint['httpMethods'] === $endpointData->httpMethods;
         });
         if (!$thisEndpointCached) {
-            return $endpointData;
+            return [$endpointData, null];
         }
 
         $thisEndpointLatest = Arr::first($latestEndpointsData, function (array $endpoint) use ($endpointData) {
             return $endpoint['uri'] === $endpointData->uri && $endpoint['httpMethods'] == $endpointData->httpMethods;
         });
         if (!$thisEndpointLatest) {
-            return $endpointData;
+            return [$endpointData, null];
         }
 
         // Then compare cached and latest to see what sections changed.
@@ -155,12 +172,13 @@ class GenerateDocumentation extends Command
         }
 
         // Finally, merge any changed sections.
+        $thisEndpointLatest = OutputEndpointData::create($thisEndpointLatest);
         foreach ($changed as $property) {
-            $thisEndpointLatest = OutputEndpointData::create($thisEndpointLatest);
             $endpointData->$property = $thisEndpointLatest->$property;
         }
+        $index = Camel::getEndpointIndexInGroup($groups, $thisEndpointLatest);
 
-        return $endpointData;
+        return [$endpointData, $index];
     }
 
     private function isValidRoute(array $routeControllerAndMethod = null): bool
@@ -292,15 +310,17 @@ class GenerateDocumentation extends Command
     {
         $latestEndpointsData = [];
         $cachedEndpoints = [];
+        $groups = [];
 
         if ($preserveUserChanges && is_dir(static::$camelDir) && is_dir(static::$cacheDir)) {
             $latestEndpointsData = Camel::loadEndpointsToFlatPrimitivesArray(static::$camelDir);
             $cachedEndpoints = Camel::loadEndpointsToFlatPrimitivesArray(static::$cacheDir, true);
+            $groups = Camel::loadEndpointsIntoGroups(static::$camelDir);
         }
 
         $routes = $routeMatcher->getRoutes($this->docConfig->get('routes'), $this->docConfig->get('router'));
-        $endpoints = $this->extractEndpointsInfoFromLaravelApp($routes, $cachedEndpoints, $latestEndpointsData);
-        $groupedEndpoints = Camel::groupEndpoints($endpoints);
+        $endpoints = $this->extractEndpointsInfoFromLaravelApp($routes, $cachedEndpoints, $latestEndpointsData, $groups);
+        $groupedEndpoints = Camel::groupEndpoints($endpoints, $this->endpointGroupIndexes);
         $this->writeEndpointsToDisk($groupedEndpoints);
         $this->writeExampleCustomEndpoint();
         $groupedEndpoints = Camel::prepareGroupedEndpointsForOutput($groupedEndpoints);