Repository: dedoc/scramble (php)

Issue:
Timestamp columns are incorrectly recognized as objects, instead of a string with the date-time format
This only seems to occur if returning a Resource object from a route, whereas returning a model directly correctly infers the type.

Pull request description:
Fixed `Carbon` attributes in resources being not documented as `string<date-time>` in Open API spec
fixes #239

Patch:
diff --git a/src/Support/TypeToSchemaExtensions/JsonResourceTypeToSchema.php b/src/Support/TypeToSchemaExtensions/JsonResourceTypeToSchema.php
index c83f407..2abdd52 100644
--- a/src/Support/TypeToSchemaExtensions/JsonResourceTypeToSchema.php
+++ b/src/Support/TypeToSchemaExtensions/JsonResourceTypeToSchema.php
@@ -2,6 +2,7 @@
 
 namespace Dedoc\Scramble\Support\TypeToSchemaExtensions;
 
+use Carbon\Carbon;
 use Dedoc\Scramble\Extensions\TypeToSchemaExtension;
 use Dedoc\Scramble\Support\Generator\Reference;
 use Dedoc\Scramble\Support\Generator\Response;
@@ -14,6 +15,7 @@ use Dedoc\Scramble\Support\Type\ArrayType;
 use Dedoc\Scramble\Support\Type\Generic;
 use Dedoc\Scramble\Support\Type\Literal\LiteralBooleanType;
 use Dedoc\Scramble\Support\Type\ObjectType;
+use Dedoc\Scramble\Support\Type\StringType;
 use Dedoc\Scramble\Support\Type\Type;
 use Dedoc\Scramble\Support\Type\TypeWalker;
 use Dedoc\Scramble\Support\Type\Union;
@@ -70,6 +72,19 @@ class JsonResourceTypeToSchema extends TypeToSchemaExtension
                     return [$item];
                 }
 
+                if (
+                    $item->value instanceof Union
+                    && (new TypeWalker)->first($item->value, fn (Type $t) => $t->isInstanceOf(Carbon::class))
+                ) {
+                    (new TypeWalker)->replace($item->value, function (Type $t) {
+                        return $t->isInstanceOf(Carbon::class)
+                            ? tap(new StringType, fn ($t) => $t->setAttribute('format', 'date-time'))
+                            : null;
+                    });
+
+                    return [$item];
+                }
+
                 if ($item->value->isInstanceOf(JsonResource::class)) {
                     $resource = $this->getResourceType($item->value);
 


Test patch:
diff --git a/tests/TypeToSchemaTransformerTest.php b/tests/TypeToSchemaTransformerTest.php
index c14d118..fcca9db 100644
--- a/tests/TypeToSchemaTransformerTest.php
+++ b/tests/TypeToSchemaTransformerTest.php
@@ -15,6 +15,7 @@ use Dedoc\Scramble\Support\Type\Union;
 use Dedoc\Scramble\Support\TypeToSchemaExtensions\AnonymousResourceCollectionTypeToSchema;
 use Dedoc\Scramble\Support\TypeToSchemaExtensions\EnumToSchema;
 use Dedoc\Scramble\Support\TypeToSchemaExtensions\JsonResourceTypeToSchema;
+use Dedoc\Scramble\Tests\Files\SamplePostModel;
 use Illuminate\Http\Resources\Json\JsonResource;
 
 use function Spatie\Snapshots\assertMatchesSnapshot;
@@ -132,6 +133,21 @@ it('gets nullable type reference', function () {
     ]);
 });
 
+it('infers date column directly referenced in json as date-time', function () {
+    $transformer = new TypeTransformer($infer = app(Infer::class), $components = new Components, [JsonResourceTypeToSchema::class]);
+
+    $type = new ObjectType(InferTypesTest_JsonResourceWithCarbonAttribute::class);
+
+    expect($transformer->transform($type)->toArray())->toBe([
+        '$ref' => '#/components/schemas/InferTypesTest_JsonResourceWithCarbonAttribute',
+    ]);
+
+    expect($components->getSchema(InferTypesTest_JsonResourceWithCarbonAttribute::class)->toArray()['properties']['created_at'])->toBe([
+        'type' => ['string', 'null'],
+        'format' => 'date-time',
+    ]);
+});
+
 class ComplexTypeHandlersTest_SampleType extends JsonResource
 {
     public function toArray($request)
@@ -220,6 +236,21 @@ class ComplexTypeHandlersWithWhenCounted_SampleType extends JsonResource
     }
 }
 
+/**
+ * @property SamplePostModel $resource
+ */
+class InferTypesTest_JsonResourceWithCarbonAttribute extends JsonResource
+{
+    public function toArray($request)
+    {
+        return [
+            'id' => $this->id,
+            'created_at' => $this->created_at,
+            'updated_at' => $this->updated_at,
+        ];
+    }
+}
+
 enum StatusTwo: string
 {
     case DRAFT = 'draft';