Skip to content

Commit 8935e42

Browse files
chore(internal): codegen related update
1 parent c1b5c85 commit 8935e42

File tree

4 files changed

+294
-0
lines changed

4 files changed

+294
-0
lines changed

src/Core/Attributes/Api.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Stagehand\Core\Attributes;
6+
7+
use Stagehand\Core\Conversion\Contracts\Converter;
8+
use Stagehand\Core\Conversion\Contracts\ConverterSource;
9+
use Stagehand\Core\Conversion\EnumOf;
10+
use Stagehand\Core\Conversion\ListOf;
11+
use Stagehand\Core\Conversion\MapOf;
12+
13+
/**
14+
* @internal
15+
*/
16+
#[\Attribute(\Attribute::TARGET_PROPERTY)]
17+
final class Api
18+
{
19+
/** @var class-string<ConverterSource>|Converter|string|null */
20+
public readonly Converter|string|null $type;
21+
22+
/** @var array<string,Converter> */
23+
private static array $enumConverters = [];
24+
25+
/**
26+
* @param class-string<ConverterSource>|Converter|string|null $type
27+
* @param class-string<\BackedEnum>|Converter|null $enum
28+
* @param class-string<ConverterSource>|Converter|null $union
29+
* @param class-string<ConverterSource>|Converter|string|null $list
30+
* @param class-string<ConverterSource>|Converter|string|null $map
31+
*/
32+
public function __construct(
33+
public readonly ?string $apiName = null,
34+
Converter|string|null $type = null,
35+
Converter|string|null $enum = null,
36+
Converter|string|null $union = null,
37+
Converter|string|null $list = null,
38+
Converter|string|null $map = null,
39+
public readonly bool $nullable = false,
40+
public readonly bool $optional = false,
41+
) {
42+
$type ??= $union;
43+
if (null !== $list) {
44+
$type ??= new ListOf($list);
45+
}
46+
if (null !== $map) {
47+
$type ??= new MapOf($map);
48+
}
49+
if (null !== $enum) {
50+
$type ??= $enum instanceof Converter ? $enum : $this->getEnumConverter($enum);
51+
}
52+
53+
$this->type = $type;
54+
}
55+
56+
/** @property class-string<\BackedEnum> $enum */
57+
private function getEnumConverter(string $enum): Converter
58+
{
59+
if (!isset(self::$enumConverters[$enum])) {
60+
$converter = new EnumOf(array_column($enum::cases(), 'value')); // @phpstan-ignore-line
61+
self::$enumConverters[$enum] = $converter;
62+
}
63+
64+
return self::$enumConverters[$enum];
65+
}
66+
}

src/Core/Concerns/SdkResponse.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Stagehand\Core\Concerns;
4+
5+
use Psr\Http\Message\ResponseInterface;
6+
use Stagehand\Core\Util;
7+
8+
/**
9+
* @internal
10+
* SdkResponse must only be used in conjunction with classes that use the SdkModel trait
11+
*/
12+
trait SdkResponse
13+
{
14+
private ?ResponseInterface $_rawResponse;
15+
16+
public static function fromResponse(ResponseInterface $response): static
17+
{
18+
$instance = new static;
19+
$instance->_rawResponse = $response;
20+
$instance->__unserialize(Util::decodeContent($response)); // @phpstan-ignore-line
21+
22+
return $instance;
23+
}
24+
25+
public function getRawResponse(): ?ResponseInterface
26+
{
27+
return $this->_rawResponse;
28+
}
29+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Stagehand\Core\Conversion\Contracts;
6+
7+
use Psr\Http\Message\ResponseInterface;
8+
9+
/**
10+
* @internal
11+
*/
12+
interface ResponseConverter
13+
{
14+
/**
15+
* @internal
16+
*/
17+
public static function fromResponse(ResponseInterface $response): static;
18+
}

tests/Core/TestModel.php

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
<?php
2+
3+
namespace Tests\Core;
4+
5+
use PHPUnit\Framework\Attributes\CoversNothing;
6+
use PHPUnit\Framework\Attributes\Test;
7+
use PHPUnit\Framework\TestCase;
8+
use Stagehand\Core\Attributes\Optional;
9+
use Stagehand\Core\Attributes\Required;
10+
use Stagehand\Core\Concerns\SdkModel;
11+
use Stagehand\Core\Contracts\BaseModel;
12+
13+
class TestModel implements BaseModel
14+
{
15+
/** @use SdkModel<array<string, mixed>> */
16+
use SdkModel;
17+
18+
#[Required]
19+
public string $name;
20+
21+
#[Required('age_years')]
22+
public int $ageYears;
23+
24+
/** @var list<string>|null */
25+
#[Optional]
26+
public ?array $friends;
27+
28+
#[Required]
29+
public ?string $owner;
30+
31+
/**
32+
* @param list<string>|null $friends
33+
*/
34+
public function __construct(
35+
string $name,
36+
int $ageYears,
37+
?string $owner,
38+
?array $friends = null,
39+
) {
40+
$this->initialize();
41+
42+
$this->name = $name;
43+
$this->ageYears = $ageYears;
44+
$this->owner = $owner;
45+
46+
null != $friends && $this->friends = $friends;
47+
}
48+
}
49+
50+
/**
51+
* @internal
52+
*
53+
* @coversNothing
54+
*/
55+
#[CoversNothing]
56+
class TestModelTest extends TestCase
57+
{
58+
#[Test]
59+
public function testBasicGetAndSet(): void
60+
{
61+
$model = new TestModel(
62+
name: 'Bob',
63+
ageYears: 12,
64+
owner: null,
65+
);
66+
$this->assertEquals(12, $model->ageYears);
67+
68+
++$model->ageYears;
69+
$this->assertEquals(13, $model->ageYears);
70+
}
71+
72+
#[Test]
73+
public function testNullAccess(): void
74+
{
75+
$model = new TestModel(
76+
name: 'Bob',
77+
ageYears: 12,
78+
owner: null,
79+
);
80+
$this->assertNull($model->owner);
81+
$this->assertNull($model->friends);
82+
}
83+
84+
#[Test]
85+
public function testArrayGetAndSet(): void
86+
{
87+
$model = new TestModel(
88+
name: 'Bob',
89+
ageYears: 12,
90+
owner: null,
91+
);
92+
$model->friends ??= [];
93+
$this->assertEquals([], $model->friends);
94+
$model->friends[] = 'Alice';
95+
$this->assertEquals(['Alice'], $model->friends);
96+
}
97+
98+
#[Test]
99+
public function testDiscernsBetweenNullAndUnset(): void
100+
{
101+
$modelUnsetFriends = new TestModel(
102+
name: 'Bob',
103+
ageYears: 12,
104+
owner: null,
105+
);
106+
$modelNullFriends = new TestModel(
107+
name: 'bob',
108+
ageYears: 12,
109+
owner: null,
110+
);
111+
$modelNullFriends->friends = null;
112+
113+
$this->assertEquals(12, $modelUnsetFriends->ageYears);
114+
$this->assertEquals(12, $modelNullFriends->ageYears);
115+
116+
$this->assertTrue($modelUnsetFriends->offsetExists('ageYears'));
117+
$this->assertTrue($modelNullFriends->offsetExists('ageYears'));
118+
119+
$this->assertNull($modelUnsetFriends->friends);
120+
$this->assertNull($modelNullFriends->friends);
121+
122+
$this->assertFalse($modelUnsetFriends->offsetExists('friends'));
123+
$this->assertTrue($modelNullFriends->offsetExists('friends'));
124+
}
125+
126+
#[Test]
127+
public function testIssetOnOmittedProperties(): void
128+
{
129+
$model = new TestModel(
130+
name: 'Bob',
131+
ageYears: 12,
132+
owner: null,
133+
);
134+
$this->assertFalse(isset($model->owner));
135+
$this->assertFalse(isset($model->friends));
136+
}
137+
138+
#[Test]
139+
public function testSerializeBasicModel(): void
140+
{
141+
$model = new TestModel(
142+
name: 'Bob',
143+
ageYears: 12,
144+
owner: 'Eve',
145+
friends: ['Alice', 'Charlie'],
146+
);
147+
$this->assertEquals(
148+
'{"name":"Bob","age_years":12,"friends":["Alice","Charlie"],"owner":"Eve"}',
149+
json_encode($model)
150+
);
151+
}
152+
153+
#[Test]
154+
public function testSerializeModelWithOmittedProperties(): void
155+
{
156+
$model = new TestModel(
157+
name: 'Bob',
158+
ageYears: 12,
159+
owner: null,
160+
);
161+
$this->assertEquals(
162+
'{"name":"Bob","age_years":12,"owner":null}',
163+
json_encode($model)
164+
);
165+
}
166+
167+
#[Test]
168+
public function testSerializeModelWithExplicitNull(): void
169+
{
170+
$model = new TestModel(
171+
name: 'Bob',
172+
ageYears: 12,
173+
owner: null,
174+
);
175+
$model->friends = null;
176+
$this->assertEquals(
177+
'{"name":"Bob","age_years":12,"friends":null,"owner":null}',
178+
json_encode($model)
179+
);
180+
}
181+
}

0 commit comments

Comments
 (0)