Parse and validate AT Protocol Lexicons with DTO generation for Laravel
1<?php
2
3namespace SocialDept\AtpSchema\Data\Types;
4
5use SocialDept\AtpSchema\Data\TypeDefinition;
6use SocialDept\AtpSchema\Exceptions\RecordValidationException;
7
8class UnionType extends TypeDefinition
9{
10 /**
11 * Possible types (refs).
12 *
13 * @var array<string>
14 */
15 public readonly array $refs;
16
17 /**
18 * Whether this is a closed union (only listed refs allowed).
19 */
20 public readonly bool $closed;
21
22 /**
23 * Create a new UnionType.
24 *
25 * @param array<string> $refs
26 */
27 public function __construct(
28 array $refs = [],
29 bool $closed = false,
30 ?string $description = null
31 ) {
32 parent::__construct('union', $description);
33
34 $this->refs = $refs;
35 $this->closed = $closed;
36 }
37
38 /**
39 * Create from array data.
40 */
41 public static function fromArray(array $data): self
42 {
43 return new self(
44 refs: $data['refs'] ?? [],
45 closed: $data['closed'] ?? false,
46 description: $data['description'] ?? null
47 );
48 }
49
50 /**
51 * Convert to array.
52 */
53 public function toArray(): array
54 {
55 $array = ['type' => $this->type];
56
57 if ($this->description !== null) {
58 $array['description'] = $this->description;
59 }
60
61 if (! empty($this->refs)) {
62 $array['refs'] = $this->refs;
63 }
64
65 if ($this->closed) {
66 $array['closed'] = $this->closed;
67 }
68
69 return $array;
70 }
71
72 /**
73 * Validate a value against this type definition.
74 */
75 public function validate(mixed $value, string $path = ''): void
76 {
77 if (! is_array($value)) {
78 throw RecordValidationException::invalidType($path, 'union (object with $type)', gettype($value));
79 }
80
81 // Union types must have a $type discriminator
82 if (! isset($value['$type'])) {
83 throw RecordValidationException::invalidValue($path, 'must contain $type property');
84 }
85
86 $typeRef = $value['$type'];
87
88 if (! is_string($typeRef)) {
89 throw RecordValidationException::invalidValue($path, '$type must be a string');
90 }
91
92 // If closed, validate the type is in refs
93 if ($this->closed && ! in_array($typeRef, $this->refs, true)) {
94 $allowed = implode(', ', $this->refs);
95
96 throw RecordValidationException::invalidValue($path, "type must be one of: {$allowed}");
97 }
98
99 // Note: Actual validation of the referenced type would happen
100 // in a higher-level validator that has access to the schema repository
101 }
102}