knuckleswtf / scribe

Generate API documentation for humans from your Laravel codebase.✍
https://scribe.knuckles.wtf/laravel/
MIT License
1.72k stars 307 forks source link

Undefined index $required #102

Closed knvpk closed 4 years ago

knvpk commented 4 years ago

What happened?

  1. I set my configuration to... openapi.enabled = true openapi.overrides = []
  2. Then I ran php artisan scribe:generate ...
  3. But I saw... an error as below stack trace instead of... successfull openapi.yaml generation message

Screenshots and stack traces: 🔊 info Writing source Markdown files to: resources/docs 🔊 info Wrote source Markdown files to: resources/docs 🔊 info Generating API HTML code 👍 success Wrote HTML documentation to: public/docs 🔊 info Generating Postman collection 👍 success Wrote Postman collection to: public/docs/collection.json 🔊 info Generating OpenAPI specification

ErrorException : Undefined index: required

at /var/www/api.vpay/vendor/knuckleswtf/scribe/src/Writing/OpenAPISpecWriter.php:187 183| $hasRequiredParameter = false; 184| $hasFileParameter = false; 185| 186| foreach ($endpoint['bodyParameters'] as $name => $details) {

187| if ($details['required']) { 188| $hasRequiredParameter = true; 189| // Don't declare this earlier. 190| // Can't have an empty required array. Must have something there. 191| $schema['required'][] = $name;

Exception trace:

1 Illuminate\Foundation\Bootstrap\HandleExceptions::handleError("Undefined index: required", "/var/www/api.vpay/vendor/knuckleswtf/scribe/src/Writing/OpenAPISpecWriter.php") /var/www/api.vpay/vendor/knuckleswtf/scribe/src/Writing/OpenAPISpecWriter.php:187

2 Knuckles\Scribe\Writing\OpenAPISpecWriter::generateEndpointRequestBodySpec() /var/www/api.vpay/vendor/knuckleswtf/scribe/src/Writing/OpenAPISpecWriter.php:76

Please use the argument -v to see more details.

My environment:

My Scribe config (minus the comments):

 [
     "type" => "static",
     "static" => [
       "output_path" => "public/docs",
     ],
     "laravel" => [
       "add_routes" => true,
       "docs_url" => "/docs",
       "middleware" => [],
     ],
     "auth" => [
       "enabled" => false,
       "in" => "bearer",
       "name" => "token",
       "use_value" => null,
       "placeholder" => "{YOUR_AUTH_KEY}",
       "extra_info" => "You can retrieve your token by visiting your dashboard and clicking <b>Generate API token</b>.",
     ],
     "intro_text" => """
       Welcome to our VPAY API documentation!\n
       \n
       <aside>As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile), and you can switch the programming language of the examples with the tabs in the top right (or from the nav menu at the top left on mobile).</aside>
       """,
     "example_languages" => [
       "bash",
       "javascript",
     ],
     "base_url" => "https://uat.api.tejarisolutions.com/",
     "title" => "VPAY",
     "description" => "",
     "postman" => [
       "enabled" => true,
       "overrides" => [],
     ],
     "openapi" => [
       "enabled" => true,
       "overrides" => [],
     ],
     "default_group" => "Endpoints",
     "logo" => false,
     "router" => "laravel",
     "routes" => [
       [
         "match" => [
           "domains" => [
             "*",
           ],
           "prefixes" => [
             "*",
           ],
           "versions" => [
             "v1",
           ],
         ],
         "include" => [],
         "exclude" => [],
         "apply" => [
           "headers" => [
             "Content-Type" => "application/json",
             "Accept" => "application/json",
           ],
           "response_calls" => [
             "methods" => [],
             "config" => [
               "app.env" => "documentation",
             ],
             "cookies" => [],
             "queryParams" => [],
             "bodyParams" => [],
             "fileParams" => [],
           ],
         ],
       ],
     ],
     "fractal" => [
       "serializer" => null,
     ],
     "faker_seed" => null,
     "strategies" => [
       "metadata" => [
         "Knuckles\Scribe\Extracting\Strategies\Metadata\GetFromDocBlocks",
       ],
       "urlParameters" => [
         "Knuckles\Scribe\Extracting\Strategies\UrlParameters\GetFromUrlParamTag",
       ],
       "queryParameters" => [
         "Knuckles\Scribe\Extracting\Strategies\QueryParameters\GetFromQueryParamTag",
       ],
       "headers" => [
         "Knuckles\Scribe\Extracting\Strategies\Headers\GetFromRouteRules",
         "Knuckles\Scribe\Extracting\Strategies\Headers\GetFromHeaderTag",
       ],
       "bodyParameters" => [
         "Knuckles\Scribe\Extracting\Strategies\BodyParameters\GetFromFormRequest",
         "Knuckles\Scribe\Extracting\Strategies\BodyParameters\GetFromBodyParamTag",
         "App\Framework\ScribeDocumentation",
       ],
       "responses" => [
         "Knuckles\Scribe\Extracting\Strategies\Responses\UseTransformerTags",
         "Knuckles\Scribe\Extracting\Strategies\Responses\UseResponseTag",
         "Knuckles\Scribe\Extracting\Strategies\Responses\UseResponseFileTag",
         "Knuckles\Scribe\Extracting\Strategies\Responses\UseApiResourceTags",
         "Knuckles\Scribe\Extracting\Strategies\Responses\ResponseCalls",
       ],
       "responseFields" => [
         "Knuckles\Scribe\Extracting\Strategies\ResponseFields\GetFromResponseFieldTag",
       ],
     ],
     "routeMatcher" => "Knuckles\Scribe\Matching\RouteMatcher",
     "continue_without_database_transactions" => [],
   ]
shalvah commented 4 years ago

Are you using any custom strategies?

On Tue, Sep 22, 2020, 15:52 Pavan kumar notifications@github.com wrote:

What happened?

  1. I set my configuration to... openapi.enabled = true openapi.overrides = []
  2. Then I ran php artisan scribe:generate ...
  3. But I saw... an error as below stack trace instead of... successfull openapi.yaml generation message

Screenshots and stack traces: 🔊 info Writing source Markdown files to: resources/docs 🔊 info Wrote source Markdown files to: resources/docs 🔊 info Generating API HTML code 👍 success Wrote HTML documentation to: public/docs 🔊 info Generating Postman collection 👍 success Wrote Postman collection to: public/docs/collection.json 🔊 info Generating OpenAPI specification

ErrorException : Undefined index: required

at /var/www/api.vpay/vendor/knuckleswtf/scribe/src/Writing/OpenAPISpecWriter.php:187 183| $hasRequiredParameter = false; 184| $hasFileParameter = false; 185| 186| foreach ($endpoint['bodyParameters'] as $name => $details) {

187| if ($details['required']) { 188| $hasRequiredParameter = true; 189| // Don't declare this earlier. 190| // Can't have an empty required array. Must have something there. 191| $schema['required'][] = $name;

Exception trace:

1 Illuminate\Foundation\Bootstrap\HandleExceptions::handleError("Undefined index: required", "/var/www/api.vpay/vendor/knuckleswtf/scribe/src/Writing/OpenAPISpecWriter.php")

/var/www/api.vpay/vendor/knuckleswtf/scribe/src/Writing/OpenAPISpecWriter.php:187

2 Knuckles\Scribe\Writing\OpenAPISpecWriter::generateEndpointRequestBodySpec()

/var/www/api.vpay/vendor/knuckleswtf/scribe/src/Writing/OpenAPISpecWriter.php:76

Please use the argument -v to see more details.

My environment:

  • PHP version (from php -v): 7.2.33
  • Framework (Laravel/Lumen): Laravel 5.8
  • Laravel/Lumen version (from composer show laravel/framework or composer show lumen/framework): 5.8.38
  • Scribe version (from composer show knuckleswtf/scribe): 1.8

My Scribe config (minus the comments):

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/knuckleswtf/scribe/issues/102, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADNSD4PDV3H7I5VHOVQ2SDTSHC22PANCNFSM4RV4BQSQ .

knvpk commented 4 years ago

Yes I am using the custom strategry, we keep our validation rules (indirectly body params) in static function of thier respective models and in my custom strategy i parsing them as below

        $parameters = collect($tags)
            ->filter(function ($tag) {
                return $tag instanceof Tag && $tag->getName() === 'rules';
            })
            ->mapWithKeys(function (Tag $tag) {
                // Format:
                // @bodyParam <name> <type> <"required" (optional)> <description>
                // Examples:
                // @bodyParam text string required The text.
                // @bodyParam user_id integer The ID of the user.
                $result = str_replace('@', '::', $tag->getContent())();
                $data = [];
                foreach ($result as $k=>$r) {
                $str = $k . ' ' . str_replace('|', ' ', $r);
                $tagContent = $str;
                preg_match('/(.+?)\s+(.+?)\s+(required\s+)?([\s\S]*)/', $tagContent, $content);
                $content = preg_replace('/\s?No-example.?/', '', $content);
                if (empty($content)) {
                    // this means only name and type were supplied
                    [$name, $type] = preg_split('/\s+/', $tagContent);
                    $required = false;
                    $description = '';
                } else {
                    [$_, $name, $type, $required, $description] = $content;
                    $description = trim($description);
                    if ($description == 'required' && empty(trim($required))) {
                        $required = $description;
                        $description = '';
                    }
                    $required = trim($required) == 'required' ? true : false;
                }

                $type = $this->normalizeParameterType($type);
                [$description, $example] = $this->parseExampleFromParamDescription($description, $type);
                $value = is_null($example) && ! $this->shouldExcludeExample($tagContent)
                    ? $this->generateDummyValue($type)
                    : $example;
                $data = array_merge($data,[
                    $name => compact('type', 'description',  'value')
                ]);
            }
                return $data;
            })->toArray();
        return $parameters;
knvpk commented 4 years ago

I solved by checking the code in GetFromFormRequest class, infact using that class method as below

        $methodDocBlock = RouteDocBlocker::getDocBlocksFromRoute($route)['method'];

        $tags = $methodDocBlock->getTags();

        $rulesTag = collect($tags)
            ->filter(function ($tag) {
                return $tag instanceof Tag && $tag->getName() === 'rules';
            })->first();

        $rules = [];
        if (!is_null($rulesTag)) {
            $rules = str_replace('@', '::', $rulesTag->getContent())();
        }

        $rulesMetaTag = collect($tags)
            ->filter(function ($tag) {
                return $tag instanceof Tag && $tag->getName() === 'rulesMeta';
            })->first();

        $customParametersData = [];
        if (!is_null($rulesMetaTag)) {
            $customParametersData = str_replace('@', '::', $rulesMetaTag->getContent())();
        }

        return app(GetFromFormRequest::class)->getBodyParametersFromValidationRules($rules, $customParametersData);

Doing like this also able to generate the open api spec successfully

knvpk commented 4 years ago

@shalvah , am i doing correct?

shalvah commented 4 years ago

Well, I don't know if you need to copy the code from that other strategy, but it looks like the problem here is your custom strategy isn't returning the required key as part of the parameter data. Adding that should fix that, I think.