Parse and validate AT Protocol Lexicons with DTO generation for Laravel
1<?php
2
3namespace SocialDept\AtpSchema\Parser;
4
5use SocialDept\AtpSchema\Data\TypeDefinition;
6use SocialDept\AtpSchema\Data\Types\ArrayType;
7use SocialDept\AtpSchema\Data\Types\BlobType;
8use SocialDept\AtpSchema\Data\Types\ObjectType;
9use SocialDept\AtpSchema\Data\Types\RefType;
10use SocialDept\AtpSchema\Data\Types\UnionType;
11use SocialDept\AtpSchema\Exceptions\TypeResolutionException;
12
13class ComplexTypeParser
14{
15 /**
16 * Primitive parser for nested types.
17 */
18 protected PrimitiveParser $primitiveParser;
19
20 /**
21 * Type parser for resolving references.
22 */
23 protected ?object $typeParser = null;
24
25 /**
26 * Create a new ComplexTypeParser.
27 */
28 public function __construct(?PrimitiveParser $primitiveParser = null)
29 {
30 $this->primitiveParser = $primitiveParser ?? new PrimitiveParser();
31 }
32
33 /**
34 * Set the type parser for reference resolution.
35 */
36 public function setTypeParser(object $typeParser): void
37 {
38 $this->typeParser = $typeParser;
39 }
40
41 /**
42 * Parse a complex type definition from array data.
43 *
44 * @throws TypeResolutionException
45 */
46 public function parse(array $data): TypeDefinition
47 {
48 $type = $data['type'] ?? null;
49
50 if ($type === null) {
51 throw TypeResolutionException::unknownType('(missing type field)');
52 }
53
54 return match ($type) {
55 'object' => $this->parseObject($data),
56 'array' => $this->parseArray($data),
57 'union' => UnionType::fromArray($data),
58 'ref' => RefType::fromArray($data),
59 'blob' => BlobType::fromArray($data),
60 default => throw TypeResolutionException::unknownType($type),
61 };
62 }
63
64 /**
65 * Parse an object type with nested properties.
66 */
67 protected function parseObject(array $data): ObjectType
68 {
69 $object = ObjectType::fromArray($data);
70
71 // Parse properties if present
72 if (isset($data['properties']) && is_array($data['properties'])) {
73 $properties = [];
74
75 foreach ($data['properties'] as $key => $propertyData) {
76 $properties[$key] = $this->parseNestedType($propertyData);
77 }
78
79 $object = $object->withProperties($properties);
80 }
81
82 return $object;
83 }
84
85 /**
86 * Parse an array type with nested items.
87 */
88 protected function parseArray(array $data): ArrayType
89 {
90 $array = ArrayType::fromArray($data);
91
92 // Parse items if present
93 if (isset($data['items']) && is_array($data['items'])) {
94 $items = $this->parseNestedType($data['items']);
95 $array = $array->withItems($items);
96 }
97
98 return $array;
99 }
100
101 /**
102 * Parse a nested type definition (can be primitive or complex).
103 */
104 protected function parseNestedType(array $data): TypeDefinition
105 {
106 $type = $data['type'] ?? null;
107
108 if ($type === null) {
109 throw TypeResolutionException::unknownType('(missing type field)');
110 }
111
112 // Try primitive types first
113 if ($this->primitiveParser->isPrimitive($type)) {
114 return $this->primitiveParser->parse($data);
115 }
116
117 // Try complex types
118 return $this->parse($data);
119 }
120
121 /**
122 * Check if a type is a complex type.
123 */
124 public function isComplex(string $type): bool
125 {
126 return in_array($type, [
127 'object',
128 'array',
129 'union',
130 'ref',
131 'blob',
132 ]);
133 }
134
135 /**
136 * Get all supported complex types.
137 *
138 * @return array<string>
139 */
140 public function getSupportedTypes(): array
141 {
142 return [
143 'object',
144 'array',
145 'union',
146 'ref',
147 'blob',
148 ];
149 }
150}