Repository: knuckleswtf/scribe (php)

Issue:
enum support as parameters
- [x] Version info - I'm using `scribe 4.16.0` with `laravel 10`
- [x] I've read the [documentation](https://scribe.knuckles.wtf/laravel) and I can't find details on how to achieve this.
- [x] I've searched the word `enum` in the documentation, but only found one occurence which was irrelevant.

The question is rather related to the generated `openapi.yaml` file.   
As shown in the [documentation](https://swagger.io/docs/specification/adding-examples/) page, openapi specification supports `enum`s as parameters. 
```
parameters:
  - in: query
    name: status
    schema:
      type: string
      enum: [approved, pending, closed, new]
      example: approved 
```   
This helps the documentation to show dropdown menus for parameters having several predefined values, as in the following image:   

![image](https://user-images.githubusercontent.com/61732943/221942940-97916f4d-ca3e-4a9d-8f31-dba72cda97b6.png)
  
**Question**
So, do we have a way to annotate a parameter in the comment blocks so that scribe generates the `openapi.yaml` file with `enum`  value list? I guess I can override this via `nn.yaml` or `config/scribe.php` files, but it would be nice to do this via comment blocks.  
Thanks!


Pull request description:
Support limitng parameter values (enums)
Adds support for limiting parameter values. Works with either a PHP 8.1 enum or an array of values.

```php
#[BodyParam("status", enum: ['approved', 'pending', 'closed', 'new'])]
#[BodyParam("status", enum: ArticleStatus::class)]
```

Also supported in docblocks:

```
@bodyParam status string Enum: approved, pending, closed, new
@bodyParam status string Enum: approved, pending, closed, new. Example: new
```

This will:
1. ensure the generated example is from one of these.
2. add a line in the docs that says "Must be one of...", and add these values
3. set the `enum` key in the OpenAPI spec


It does not currently restrict the values that can be entered in Try It Out, as there may still be some value in seeing how invalid values are responded to.

This may be a breaking change, as it replaces the earlier manual description setting (automatically generated for validation rules) with a structured field. 

In the internal Scribe API, this adds a new `enumValues` field to the parameter types. This is an array of all valid values.

Closes #626 



Patch:
diff --git a/camel/Extraction/Parameter.php b/camel/Extraction/Parameter.php
index d1538953..beb03787 100644
--- a/camel/Extraction/Parameter.php
+++ b/camel/Extraction/Parameter.php
@@ -12,6 +12,7 @@ class Parameter extends BaseDTO
     public bool $required = false;
     public mixed $example = null;
     public string $type = 'string';
+    public array $enumValues = [];
 
     public function __construct(array $parameters = [])
     {
diff --git a/camel/Extraction/ResponseField.php b/camel/Extraction/ResponseField.php
index 85d82e64..ffc92baf 100644
--- a/camel/Extraction/ResponseField.php
+++ b/camel/Extraction/ResponseField.php
@@ -7,6 +7,9 @@ use Knuckles\Camel\BaseDTO;
 
 class ResponseField extends BaseDTO
 {
+    // TODO make this extend Parameter, so we can have strong types and a unified API
+    //   but first we need to normalize incoming data
+
     /** @var string */
     public $name;
 
@@ -15,4 +18,6 @@ class ResponseField extends BaseDTO
 
     /** @var string */
     public $type;
+
+    public array $enumValues = [];
 }
diff --git a/resources/views/components/field-details.blade.php b/resources/views/components/field-details.blade.php
index 57200c1c..222a094a 100644
--- a/resources/views/components/field-details.blade.php
+++ b/resources/views/components/field-details.blade.php
@@ -69,3 +69,7 @@
     }
 @endphp
 {!! Parsedown::instance()->text(trim($description)) !!}
+@if(!empty($enumValues))
+Must be one of:
+<ul style="list-style-type: square;">{!! implode(" ", array_map(fn($val) => "<li><code>$val</code></li>", $enumValues)) !!}</ul>
+@endif
diff --git a/resources/views/components/nested-fields.blade.php b/resources/views/components/nested-fields.blade.php
index 1deb7c0b..e4137835 100644
--- a/resources/views/components/nested-fields.blade.php
+++ b/resources/views/components/nested-fields.blade.php
@@ -25,6 +25,7 @@
                           'required' => $subfield['required'] ?? false,
                           'description' => $subfield['description'] ?? '',
                           'example' => $subfield['example'] ?? '',
+                          'enumValues' => $subfield['enumValues'] ?? null,
                           'endpointId' => $endpointId,
                           'hasChildren' => false,
                           'component' => 'body',
@@ -66,6 +67,7 @@
                           'required' => $subfield['required'] ?? false,
                           'description' => $subfield['description'] ?? '',
                           'example' => $subfield['example'] ?? '',
+                          'enumValues' => $field['enumValues'] ?? null,
                           'endpointId' => $endpointId,
                           'hasChildren' => false,
                           'component' => 'body',
@@ -86,6 +88,7 @@
               'required' => $field['required'] ?? false,
               'description' => $field['description'] ?? '',
               'example' => $field['example'] ?? '',
+              'enumValues' => $field['enumValues'] ?? null,
               'endpointId' => $endpointId,
               'hasChildren' => false,
               'component' => 'body',
diff --git a/resources/views/themes/default/endpoint.blade.php b/resources/views/themes/default/endpoint.blade.php
index 94e77598..8da30799 100644
--- a/resources/views/themes/default/endpoint.blade.php
+++ b/resources/views/themes/default/endpoint.blade.php
@@ -132,6 +132,7 @@
                   'required' => $parameter->required,
                   'description' => $parameter->description,
                   'example' => $parameter->example ?? '',
+                  'enumValues' => $parameter->enumValues,
                   'endpointId' => $endpoint->endpointId(),
                   'component' => 'url',
                   'isInput' => true,
@@ -156,6 +157,7 @@
                   'required' => $parameter->required,
                   'description' => $parameter->description,
                   'example' => $parameter->example ?? '',
+                  'enumValues' => $parameter->enumValues,
                   'endpointId' => $endpoint->endpointId(),
                   'component' => 'query',
                   'isInput' => true,
diff --git a/resources/views/themes/elements/components/field-details.blade.php b/resources/views/themes/elements/components/field-details.blade.php
index ea41dedc..b2682fc0 100644
--- a/resources/views/themes/elements/components/field-details.blade.php
+++ b/resources/views/themes/elements/components/field-details.blade.php
@@ -36,6 +36,10 @@
             {!! Parsedown::instance()->text($description) !!}
         </div>
         @endif
+        @if(!empty($enumValues))
+            Must be one of:
+            <ul style="list-style-position: inside; list-style-type: square;">{!! implode(" ", array_map(fn($val) => "<li><code>$val</code></li>", $enumValues)) !!}</ul>
+        @endif
         @if($isArrayBody)
             <div class="sl-flex sl-items-baseline sl-text-base">
                 <div class="sl-font-mono sl-font-semibold sl-mr-2">array of:</div>
diff --git a/resources/views/themes/elements/components/nested-fields.blade.php b/resources/views/themes/elements/components/nested-fields.blade.php
index ec7583e7..1e2ab3de 100644
--- a/resources/views/themes/elements/components/nested-fields.blade.php
+++ b/resources/views/themes/elements/components/nested-fields.blade.php
@@ -15,6 +15,7 @@
           'required' => $field['required'] ?? false,
           'description' => $field['description'] ?? '',
           'example' => $field['example'] ?? '',
+          'enumValues' => $field['enumValues'] ?? null,
           'endpointId' => $endpointId,
           'hasChildren' => !empty($field['__fields']),
           'component' => 'body',
diff --git a/resources/views/themes/elements/endpoint.blade.php b/resources/views/themes/elements/endpoint.blade.php
index 61690c28..470c1617 100644
--- a/resources/views/themes/elements/endpoint.blade.php
+++ b/resources/views/themes/elements/endpoint.blade.php
@@ -81,6 +81,7 @@
                                       'required' => $parameter->required,
                                       'description' => $parameter->description,
                                       'example' => $parameter->example ?? '',
+                                      'enumValues' => $parameter->enumValues,
                                       'endpointId' => $endpoint->endpointId(),
                                       'component' => 'url',
                                       'isInput' => true,
@@ -104,6 +105,7 @@
                                           'required' => $parameter->required,
                                           'description' => $parameter->description,
                                           'example' => $parameter->example ?? '',
+                                          'enumValues' => $parameter->enumValues,
                                           'endpointId' => $endpoint->endpointId(),
                                           'component' => 'query',
                                           'isInput' => true,
diff --git a/src/Attributes/GenericParam.php b/src/Attributes/GenericParam.php
index df977173..2e34de38 100644
--- a/src/Attributes/GenericParam.php
+++ b/src/Attributes/GenericParam.php
@@ -13,6 +13,7 @@ class GenericParam
         public ?string $description = '',
         public ?bool $required = true,
         public mixed $example = null, /* Pass 'No-example' to omit the example */
+        public mixed $enum = null, // Can pass a list of values, or a native PHP enum
     ) {
     }
 
@@ -24,6 +25,29 @@ class GenericParam
             "type" => $this->type,
             "required" => $this->required,
             "example" => $this->example,
+            "enumValues" => $this->getEnumValues(),
         ];
     }
+
+    protected function getEnumValues()
+    {
+        if (!$this->enum) {
+            return null;
+        }
+
+        if (is_array($this->enum)) {
+            return $this->enum;
+        }
+
+        if (function_exists('enum_exists') && enum_exists($this->enum)) {
+            return array_map(
+                fn ($case) => $case->value,
+                $this->enum::cases()
+            );
+        }
+
+        throw new \InvalidArgumentException(
+            'The enum property of a parameter must be either a PHP enum or an array of values'
+        );
+    }
 }
diff --git a/src/Attributes/ResponseField.php b/src/Attributes/ResponseField.php
index 1ebe576b..a11c790e 100644
--- a/src/Attributes/ResponseField.php
+++ b/src/Attributes/ResponseField.php
@@ -5,21 +5,17 @@ namespace Knuckles\Scribe\Attributes;
 use Attribute;
 
 #[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
-class ResponseField
+class ResponseField extends GenericParam
 {
+    // Don't default to string; type inference is currently handled by the normalizer
+    // TODO change this in the future
     public function __construct(
         public string $name,
         public ?string $type = null,
         public ?string $description = '',
+        public ?bool $required = true,
+        public mixed $example = null, /* Pass 'No-example' to omit the example */
+        public mixed $enum = null, // Can pass a list of values, or a native PHP enum
     ) {
     }
-
-    public function toArray()
-    {
-        return [
-            "name" => $this->name,
-            "description" => $this->description,
-            "type" => $this->type,
-        ];
-    }
 }
diff --git a/src/Extracting/ParamHelpers.php b/src/Extracting/ParamHelpers.php
index 8afd230b..a8125bab 100644
--- a/src/Extracting/ParamHelpers.php
+++ b/src/Extracting/ParamHelpers.php
@@ -4,6 +4,7 @@ namespace Knuckles\Scribe\Extracting;
 
 use Faker\Factory;
 use Illuminate\Http\UploadedFile;
+use Illuminate\Support\Arr;
 use Illuminate\Support\Str;
 
 trait ParamHelpers
@@ -46,6 +47,10 @@ trait ParamHelpers
 
     protected function generateDummyValue(string $type, array $hints = [])
     {
+        if(!empty($hints['enumValues'])) {
+            return Arr::random($hints['enumValues']);
+        }
+
         $fakeFactory = $this->getDummyValueGenerator($type, $hints);
 
         return $fakeFactory();
@@ -232,6 +237,8 @@ trait ParamHelpers
     protected function parseExampleFromParamDescription(string $description, string $type): array
     {
         $example = null;
+        $enumValues = [];
+
         if (preg_match('/(.*)\bExample:\s*([\s\S]+)\s*/s', $description, $content)) {
             $description = trim($content[1]);
 
@@ -239,6 +246,15 @@ trait ParamHelpers
             $example = $this->castToType($content[2], $type);
         }
 
-        return [$description, $example];
+        if (preg_match('/(.*)\bEnum:\s*([\s\S]+)\s*/s', $description, $content)) {
+            $description = trim($content[1]);
+
+            $enumValues = array_map(
+                fn ($value) => $this->castToType(trim($value), $type),
+                explode(',', rtrim(trim($content[2]), '.'))
+            );
+        }
+
+        return [$description, $example, $enumValues];
     }
 }
diff --git a/src/Extracting/ParsesValidationRules.php b/src/Extracting/ParsesValidationRules.php
index c39dce40..2e1a47a8 100644
--- a/src/Extracting/ParsesValidationRules.php
+++ b/src/Extracting/ParsesValidationRules.php
@@ -203,7 +203,7 @@ trait ParsesValidationRules
             if (enum_exists($type) && method_exists($type, 'tryFrom')) {
                 $cases = array_map(fn ($case) => $case->value, $type::cases());
                 $parameterData['type'] = gettype($cases[0]);
-                $parameterData['description'] .= ' Must be one of ' . w::getListOfValuesAsFriendlyHtmlString($cases) . ' ';
+                $parameterData['enumValues'] = $cases;
                 $parameterData['setter'] = fn () => Arr::random($cases);
             }
 
@@ -464,8 +464,7 @@ trait ParsesValidationRules
                  * Other rules.
                  */
                 case 'in':
-                    // Not using the rule description here because it only says "The attribute is invalid"
-                    $parameterData['description'] .= ' Must be one of ' . w::getListOfValuesAsFriendlyHtmlString($arguments) . ' ';
+                    $parameterData['enumValues'] = $arguments;
                     $parameterData['setter'] = function () use ($arguments) {
                         return Arr::random($arguments);
                     };
diff --git a/src/Extracting/Strategies/BodyParameters/GetFromBodyParamTag.php b/src/Extracting/Strategies/BodyParameters/GetFromBodyParamTag.php
index 33992997..1d52e579 100644
--- a/src/Extracting/Strategies/BodyParameters/GetFromBodyParamTag.php
+++ b/src/Extracting/Strategies/BodyParameters/GetFromBodyParamTag.php
@@ -33,8 +33,9 @@ class GetFromBodyParamTag extends GetFieldsFromTagStrategy
         }
 
         $type = static::normalizeTypeName($type);
-        [$description, $example] = $this->getDescriptionAndExample($description, $type, $tagContent, $name);
+        [$description, $example, $enumValues] =
+            $this->getDescriptionAndExample($description, $type, $tagContent, $name);
 
-        return compact('name', 'type', 'description', 'required', 'example');
+        return compact('name', 'type', 'description', 'required', 'example', 'enumValues');
     }
 }
diff --git a/src/Extracting/Strategies/GetFieldsFromTagStrategy.php b/src/Extracting/Strategies/GetFieldsFromTagStrategy.php
index 60b4c0f8..1fdf59f3 100644
--- a/src/Extracting/Strategies/GetFieldsFromTagStrategy.php
+++ b/src/Extracting/Strategies/GetFieldsFromTagStrategy.php
@@ -33,17 +33,21 @@ abstract class GetFieldsFromTagStrategy extends TagStrategyWithFormRequestFallba
 
     abstract protected function parseTag(string $tagContent): array;
 
-    protected function getDescriptionAndExample(string $description, string $type, string $tagContent, string $fieldName): array
+    protected function getDescriptionAndExample(
+        string $description, string $type, string $tagContent, string $fieldName
+    ): array
     {
-        [$description, $example] = $this->parseExampleFromParamDescription($description, $type);
-        $example = $this->setExampleIfNeeded($example, $type, $tagContent, $fieldName);
-        return [$description, $example];
+        [$description, $example, $enumValues] = $this->parseExampleFromParamDescription($description, $type);
+        $example = $this->setExampleIfNeeded($example, $type, $tagContent, $fieldName, $enumValues);
+        return [$description, $example, $enumValues];
     }
 
-    protected function setExampleIfNeeded(mixed $currentExample, string $type, string $tagContent, string $fieldName): mixed
+    protected function setExampleIfNeeded(
+        mixed $currentExample, string $type, string $tagContent, string $fieldName, ?array $enumValues = []
+    ): mixed
     {
         return (is_null($currentExample) && !$this->shouldExcludeExample($tagContent))
-            ? $this->generateDummyValue($type, hints: ['name' => $fieldName])
+            ? $this->generateDummyValue($type, hints: ['name' => $fieldName, 'enumValues' => $enumValues])
             : $currentExample;
     }
 }
diff --git a/src/Extracting/Strategies/QueryParameters/GetFromQueryParamTag.php b/src/Extracting/Strategies/QueryParameters/GetFromQueryParamTag.php
index 3f1cbe83..de84944c 100644
--- a/src/Extracting/Strategies/QueryParameters/GetFromQueryParamTag.php
+++ b/src/Extracting/Strategies/QueryParameters/GetFromQueryParamTag.php
@@ -63,8 +63,9 @@ class GetFromQueryParamTag extends GetFieldsFromTagStrategy
 
         }
 
-        [$description, $example] = $this->getDescriptionAndExample($description, $type, $tagContent, $name);
+        [$description, $example, $enumValues] =
+            $this->getDescriptionAndExample($description, $type, $tagContent, $name);
 
-        return compact('name', 'description', 'required', 'example', 'type');
+        return compact('name', 'description', 'required', 'example', 'type', 'enumValues');
     }
 }
diff --git a/src/Extracting/Strategies/UrlParameters/GetFromUrlParamTag.php b/src/Extracting/Strategies/UrlParameters/GetFromUrlParamTag.php
index b6194971..48cb5714 100644
--- a/src/Extracting/Strategies/UrlParameters/GetFromUrlParamTag.php
+++ b/src/Extracting/Strategies/UrlParameters/GetFromUrlParamTag.php
@@ -46,8 +46,9 @@ class GetFromUrlParamTag extends GetFieldsFromTagStrategy
                 : static::normalizeTypeName($type);
         }
 
-        [$description, $example] = $this->getDescriptionAndExample($description, $type, $tagContent, $name);
+        [$description, $example, $enumValues] =
+            $this->getDescriptionAndExample($description, $type, $tagContent, $name);
 
-        return compact('name', 'description', 'required', 'example', 'type');
+        return compact('name', 'description', 'required', 'example', 'type', 'enumValues');
     }
 }
diff --git a/src/Writing/OpenAPISpecWriter.php b/src/Writing/OpenAPISpecWriter.php
index 4c9a6cd0..0857bfe6 100644
--- a/src/Writing/OpenAPISpecWriter.php
+++ b/src/Writing/OpenAPISpecWriter.php
@@ -177,7 +177,7 @@ class OpenAPISpecWriter
 
         if (count($endpoint->headers)) {
             foreach ($endpoint->headers as $name => $value) {
-                if (in_array($name, ['Content-Type', 'content-type', 'Accept', 'accept']))
+                if (in_array(strtolower($name), ['content-type', 'accept', 'authorization']))
                     // These headers are not allowed in the spec.
                     // https://swagger.io/docs/specification/describing-parameters/#header-parameters
                     continue;
@@ -510,11 +510,16 @@ class OpenAPISpecWriter
                 })->all()),
             ];
         } else {
-            return [
+            $schema = [
                 'type' => static::normalizeTypeName($field->type),
                 'description' => $field->description ?: '',
                 'example' => $field->example,
             ];
+            if (!empty($field->enumValues)) {
+                $schema['enum'] = $field->enumValues;
+            }
+
+            return $schema;
         }
     }
 
@@ -523,30 +528,30 @@ class OpenAPISpecWriter
         if ($endpoint->metadata->title) return preg_replace('/[^\w+]/', '', Str::camel($endpoint->metadata->title));
 
         $parts = preg_split('/[^\w+]/', $endpoint->uri, -1, PREG_SPLIT_NO_EMPTY);
-        return Str::lower($endpoint->httpMethods[0]) . join('', array_map(fn ($part) => ucfirst($part), $parts));
+        return Str::lower($endpoint->httpMethods[0]) . join('', array_map(fn($part) => ucfirst($part), $parts));
     }
 
     /**
      * Given an array, return an object if the array is empty. To be used with fields that are
      * required by OpenAPI spec to be objects, since empty arrays get serialised as [].
      */
-    protected function objectIfEmpty(array $field): array | \stdClass
+    protected function objectIfEmpty(array $field): array|\stdClass
     {
         return count($field) > 0 ? $field : new \stdClass();
     }
 
     /**
-     * Given a value, generate the schema for it. The schema consists of: {type:, example:, properties: (if value is an object)},
-     * and possibly a description for each property.
-     * The $endpoint and $path are used for looking up response field descriptions.
+     * Given a value, generate the schema for it. The schema consists of: {type:, example:, properties: (if value is an
+     * object)}, and possibly a description for each property. The $endpoint and $path are used for looking up response
+     * field descriptions.
      */
     public function generateSchemaForValue(mixed $value, OutputEndpointData $endpoint, string $path): array
     {
         if ($value instanceof \stdClass) {
-            $value = (array) $value;
+            $value = (array)$value;
             $properties = [];
             // Recurse into the object
-            foreach($value as $subField => $subValue){
+            foreach ($value as $subField => $subValue) {
                 $subFieldPath = sprintf('%s.%s', $path, $subField);
                 $properties[$subField] = $this->generateSchemaForValue($subValue, $endpoint, $subFieldPath);
             }


Test patch:
diff --git a/src/Extracting/Strategies/GetParamsFromAttributeStrategy.php b/src/Extracting/Strategies/GetParamsFromAttributeStrategy.php
index b0bd36ce..42ff07cc 100644
--- a/src/Extracting/Strategies/GetParamsFromAttributeStrategy.php
+++ b/src/Extracting/Strategies/GetParamsFromAttributeStrategy.php
@@ -30,7 +30,10 @@ class GetParamsFromAttributeStrategy extends PhpAttributeStrategy
     {
         $data['type'] = static::normalizeTypeName($data['type']);
         if (is_null($data['example'])) {
-            $data['example'] = $this->generateDummyValue($data['type'], ['name' => $data['name']]);
+            $data['example'] = $this->generateDummyValue($data['type'], [
+                'name' => $data['name'],
+                'enumValues' => $data['enumValues'],
+            ]);
         } else if ($data['example'] == 'No-example' || $data['example'] == 'No-example.') {
             $data['example'] = null;
         }
diff --git a/tests/GenerateDocumentation/OutputTest.php b/tests/GenerateDocumentation/OutputTest.php
index 234defe1..28e41a34 100644
--- a/tests/GenerateDocumentation/OutputTest.php
+++ b/tests/GenerateDocumentation/OutputTest.php
@@ -417,6 +417,7 @@ class OutputTest extends BaseLaravelTest
             'required' => true,
             'example' => 6,
             'type' => 'integer',
+            'enumValues' => [],
             'custom' => [],
         ];
         $group['endpoints'][0]['urlParameters']['a_param'] = $extraParam;
diff --git a/tests/Strategies/BodyParameters/GetFromBodyParamAttributeTest.php b/tests/Strategies/BodyParameters/GetFromBodyParamAttributeTest.php
index 6669063f..93c90be0 100644
--- a/tests/Strategies/BodyParameters/GetFromBodyParamAttributeTest.php
+++ b/tests/Strategies/BodyParameters/GetFromBodyParamAttributeTest.php
@@ -89,6 +89,12 @@ class GetFromBodyParamAttributeTest extends TestCase
                 'description' => '',
                 'required' => true,
             ],
+            'state' => [
+                'type' => 'string',
+                'description' => '',
+                'required' => true,
+                'enumValues' => ["active", "pending"]
+            ],
             'users' => [
                 'type' => 'object[]',
                 'description' => 'Users\' details',
@@ -216,6 +222,7 @@ class BodyParamAttributeTestController
     #[BodyParam("book.author_id", type: "integer")]
     #[BodyParam("book.pages_count", type: "integer")]
     #[BodyParam("ids", "integer[]")]
+    #[BodyParam("state", enum: ["active", "pending"])]
     #[BodyParam("users", "object[]", "Users' details", required: false)]
     #[BodyParam("users[].first_name", "string", "The first name of the user.", example: "John", required: false)]
     #[BodyParam("users[].last_name", "string", "The last name of the user.", example: "Doe", required: false)]
diff --git a/tests/Strategies/GetFromInlineValidatorTest.php b/tests/Strategies/GetFromInlineValidatorTest.php
index 8d4d9d93..4235b598 100644
--- a/tests/Strategies/GetFromInlineValidatorTest.php
+++ b/tests/Strategies/GetFromInlineValidatorTest.php
@@ -26,7 +26,7 @@ class GetFromInlineValidatorTest extends BaseLaravelTest
         'room_id' => [
             'type' => 'string',
             'required' => false,
-            'description' => 'The id of the room. Must be one of <code>3</code>, <code>5</code>, or <code>6</code>.',
+            'description' => 'The id of the room.',
         ],
         'forever' => [
             'type' => 'boolean',
@@ -193,12 +193,12 @@ class GetFromInlineValidatorTest extends BaseLaravelTest
         $expected = [
             'enum_class' => [
                 'type' => 'string',
-                'description' => 'Must be one of <code>red</code>, <code>green</code>, or <code>blue</code>.',
+                'description' => '',
                 'required' => true,
             ],
             'enum_string' => [
                 'type' => 'string',
-                'description' => 'Must be one of <code>1</code>, <code>2</code>, or <code>3</code>.',
+                'description' => '',
                 'required' => true,
             ],
             'enum_inexistent' => [
diff --git a/tests/Unit/ValidationRuleParsingTest.php b/tests/Unit/ValidationRuleParsingTest.php
index 2380162f..10ba7e4b 100644
--- a/tests/Unit/ValidationRuleParsingTest.php
+++ b/tests/Unit/ValidationRuleParsingTest.php
@@ -68,8 +68,8 @@ class ValidationRuleParsingTest extends BaseLaravelTest
             'in_param' => ['numeric', Rule::in([3,5,6])]
         ]);
         $this->assertEquals(
-            'Must be one of <code>3</code>, <code>5</code>, or <code>6</code>.',
-            $results['in_param']['description']
+            [3, 5, 6],
+            $results['in_param']['enumValues']
         );
     }
 
@@ -228,8 +228,9 @@ class ValidationRuleParsingTest extends BaseLaravelTest
             ['in_param' => 'in:3,5,6'],
             ['in_param' => ['description' => $description]],
             [
-                'description' => "$description. Must be one of <code>3</code>, <code>5</code>, or <code>6</code>.",
+                'description' => $description.".",
                 'type' => 'string',
+                'enumValues' => [3,5,6]
             ],
         ];
         yield 'not_in' => [
@@ -533,8 +534,8 @@ class ValidationRuleParsingTest extends BaseLaravelTest
         ]);
         $this->assertEquals('string', $results['enum']['type']);
         $this->assertEquals(
-            'Must be one of <code>red</code>, <code>green</code>, or <code>blue</code>.',
-            $results['enum']['description']
+            ['red', 'green', 'blue'],
+            $results['enum']['enumValues']
         );
         $this->assertTrue(in_array(
             $results['enum']['example'],
@@ -547,8 +548,8 @@ class ValidationRuleParsingTest extends BaseLaravelTest
         ]);
         $this->assertEquals('integer', $results['enum']['type']);
         $this->assertEquals(
-            'Must be one of <code>1</code>, <code>2</code>, or <code>3</code>.',
-            $results['enum']['description']
+            [1, 2, 3],
+            $results['enum']['enumValues']
         );
         $this->assertTrue(in_array(
             $results['enum']['example'],
@@ -562,7 +563,7 @@ class ValidationRuleParsingTest extends BaseLaravelTest
         ]);
         $this->assertEquals('string', $results['enum']['type']);
         $this->assertEquals(
-            'A description. Must be one of <code>red</code>, <code>green</code>, or <code>blue</code>.',
+            'A description.',
             $results['enum']['description']
         );
         $this->assertTrue(in_array(