Parse and validate AT Protocol Lexicons with DTO generation for Laravel
at dev 2.7 kB view raw
1<?php 2 3namespace SocialDept\AtpSchema\Parser; 4 5use SocialDept\AtpSchema\Contracts\LexiconParser; 6use SocialDept\AtpSchema\Data\LexiconDocument; 7use SocialDept\AtpSchema\Exceptions\SchemaParseException; 8 9class DefaultLexiconParser implements LexiconParser 10{ 11 /** 12 * Parse raw Lexicon JSON into structured objects. 13 */ 14 public function parse(string $json): LexiconDocument 15 { 16 $data = json_decode($json, true); 17 18 if (json_last_error() !== JSON_ERROR_NONE) { 19 throw SchemaParseException::invalidJson('unknown', json_last_error_msg()); 20 } 21 22 if (! is_array($data)) { 23 throw SchemaParseException::malformed('unknown', 'Schema must be a JSON object'); 24 } 25 26 return $this->parseArray($data); 27 } 28 29 /** 30 * Parse Lexicon from array data. 31 */ 32 public function parseArray(array $data): LexiconDocument 33 { 34 return LexiconDocument::fromArray($data); 35 } 36 37 /** 38 * Validate Lexicon schema structure. 39 */ 40 public function validate(array $data): bool 41 { 42 try { 43 // Required fields 44 if (! isset($data['lexicon'])) { 45 return false; 46 } 47 48 if (! isset($data['id'])) { 49 return false; 50 } 51 52 if (! isset($data['defs'])) { 53 return false; 54 } 55 56 // Validate lexicon version 57 $lexicon = (int) $data['lexicon']; 58 if ($lexicon !== 1) { 59 return false; 60 } 61 62 // Validate NSID format 63 Nsid::parse($data['id']); 64 65 // Validate defs is an object/array 66 if (! is_array($data['defs'])) { 67 return false; 68 } 69 70 return true; 71 } catch (\Exception) { 72 return false; 73 } 74 } 75 76 /** 77 * Resolve $ref references to other schemas. 78 */ 79 public function resolveReference(string $ref, LexiconDocument $context): mixed 80 { 81 // Local reference (starting with #) 82 if (str_starts_with($ref, '#')) { 83 $defName = substr($ref, 1); 84 85 return $context->getDefinition($defName); 86 } 87 88 // External reference with fragment (e.g., com.atproto.label.defs#selfLabels) 89 if (str_contains($ref, '#')) { 90 [$nsid, $defName] = explode('#', $ref, 2); 91 92 // Return the ref as-is - external refs need schema loading which should be handled by caller 93 return [ 94 'type' => 'ref', 95 'ref' => $ref, 96 ]; 97 } 98 99 // Full NSID reference - return as ref definition 100 return [ 101 'type' => 'ref', 102 'ref' => $ref, 103 ]; 104 } 105}