Parse and validate AT Protocol Lexicons with DTO generation for Laravel
at dev 4.8 kB view raw
1<?php 2 3namespace SocialDept\AtpSchema\Data; 4 5use SocialDept\AtpSchema\Exceptions\SchemaValidationException; 6use SocialDept\AtpSchema\Parser\Nsid; 7 8class LexiconDocument 9{ 10 /** 11 * Lexicon version. 12 */ 13 public readonly int $lexicon; 14 15 /** 16 * NSID identifier. 17 */ 18 public readonly Nsid $id; 19 20 /** 21 * Schema description. 22 */ 23 public readonly ?string $description; 24 25 /** 26 * Schema definitions. 27 * 28 * @var array<string, array> 29 */ 30 public readonly array $defs; 31 32 /** 33 * Raw schema data. 34 */ 35 public readonly array $raw; 36 37 /** 38 * Source of the schema (file path, URL, etc). 39 */ 40 public readonly ?string $source; 41 42 /** 43 * Create a new LexiconDocument. 44 * 45 * @param array<string, array> $defs 46 */ 47 public function __construct( 48 int $lexicon, 49 Nsid $id, 50 array $defs, 51 ?string $description = null, 52 ?string $source = null, 53 ?array $raw = null 54 ) { 55 $this->lexicon = $lexicon; 56 $this->id = $id; 57 $this->defs = $defs; 58 $this->description = $description; 59 $this->source = $source; 60 $this->raw = $raw ?? []; 61 } 62 63 /** 64 * Create from array data. 65 */ 66 public static function fromArray(array $data, ?string $source = null): self 67 { 68 if (! isset($data['lexicon'])) { 69 throw SchemaValidationException::missingField('unknown', 'lexicon'); 70 } 71 72 if (! isset($data['id'])) { 73 throw SchemaValidationException::missingField('unknown', 'id'); 74 } 75 76 if (! isset($data['defs'])) { 77 throw SchemaValidationException::missingField($data['id'], 'defs'); 78 } 79 80 $lexicon = (int) $data['lexicon']; 81 if ($lexicon !== 1) { 82 throw SchemaValidationException::invalidVersion($data['id'], $lexicon); 83 } 84 85 return new self( 86 lexicon: $lexicon, 87 id: Nsid::parse($data['id']), 88 defs: $data['defs'], 89 description: $data['description'] ?? null, 90 source: $source, 91 raw: $data 92 ); 93 } 94 95 /** 96 * Create from JSON string. 97 */ 98 public static function fromJson(string $json, ?string $source = null): self 99 { 100 $data = json_decode($json, true); 101 102 if (json_last_error() !== JSON_ERROR_NONE) { 103 throw new \InvalidArgumentException('Invalid JSON: '.json_last_error_msg()); 104 } 105 106 if (! is_array($data)) { 107 throw new \InvalidArgumentException('JSON must decode to an array'); 108 } 109 110 return self::fromArray($data, $source); 111 } 112 113 /** 114 * Get a definition by name. 115 */ 116 public function getDefinition(string $name): ?array 117 { 118 return $this->defs[$name] ?? null; 119 } 120 121 /** 122 * Check if definition exists. 123 */ 124 public function hasDefinition(string $name): bool 125 { 126 return isset($this->defs[$name]); 127 } 128 129 /** 130 * Get the main definition. 131 */ 132 public function getMainDefinition(): ?array 133 { 134 return $this->getDefinition('main'); 135 } 136 137 /** 138 * Get all definition names. 139 * 140 * @return array<string> 141 */ 142 public function getDefinitionNames(): array 143 { 144 return array_keys($this->defs); 145 } 146 147 /** 148 * Get NSID as string. 149 */ 150 public function getNsid(): string 151 { 152 return $this->id->toString(); 153 } 154 155 /** 156 * Get lexicon version. 157 */ 158 public function getVersion(): int 159 { 160 return $this->lexicon; 161 } 162 163 /** 164 * Convert to array. 165 */ 166 public function toArray(): array 167 { 168 return [ 169 'lexicon' => $this->lexicon, 170 'id' => $this->id->toString(), 171 'description' => $this->description, 172 'defs' => $this->defs, 173 ]; 174 } 175 176 /** 177 * Check if this is a record schema. 178 */ 179 public function isRecord(): bool 180 { 181 $main = $this->getMainDefinition(); 182 183 return $main !== null && ($main['type'] ?? null) === 'record'; 184 } 185 186 /** 187 * Check if this is a query schema. 188 */ 189 public function isQuery(): bool 190 { 191 $main = $this->getMainDefinition(); 192 193 return $main !== null && ($main['type'] ?? null) === 'query'; 194 } 195 196 /** 197 * Check if this is a procedure schema. 198 */ 199 public function isProcedure(): bool 200 { 201 $main = $this->getMainDefinition(); 202 203 return $main !== null && ($main['type'] ?? null) === 'procedure'; 204 } 205 206 /** 207 * Check if this is a subscription schema. 208 */ 209 public function isSubscription(): bool 210 { 211 $main = $this->getMainDefinition(); 212 213 return $main !== null && ($main['type'] ?? null) === 'subscription'; 214 } 215}