Something went wrong. Try again.
Parse and validate AT Protocol Lexicons with DTO generation for Laravel
Something went wrong. Try again.
5.2 kB · 186 lines
PHP
at dev
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187<?php
namespace SocialDept\AtpSchema\Parser;
use SocialDept\AtpSchema\Data\LexiconDocument;use SocialDept\AtpSchema\Data\TypeDefinition;use SocialDept\AtpSchema\Exceptions\TypeResolutionException;
class TypeParser{ /** * Primitive type parser. */ protected PrimitiveParser $primitiveParser;
/** * Complex type parser. */ protected ComplexTypeParser $complexParser;
/** * Schema loader for resolving external references. */ protected ?SchemaLoader $schemaLoader;
/** * Cache of resolved types to prevent infinite loops. * * @var array<string, TypeDefinition> */ protected array $resolvedTypes = [];
/** * Current resolution chain to detect circular references. * * @var array<string> */ protected array $resolutionChain = [];
/** * Create a new TypeParser. */ public function __construct( ?PrimitiveParser $primitiveParser = null, ?ComplexTypeParser $complexParser = null, ?SchemaLoader $schemaLoader = null ) { $this->primitiveParser = $primitiveParser ?? new PrimitiveParser(); $this->complexParser = $complexParser ?? new ComplexTypeParser($this->primitiveParser); $this->schemaLoader = $schemaLoader; }
/** * Parse a type definition from array data. * * @throws TypeResolutionException */ public function parse(array $data, ?LexiconDocument $context = null): TypeDefinition { $type = $data['type'] ?? null;
if ($type === null) { throw TypeResolutionException::unknownType('(missing type field)'); }
// Handle primitive types if ($this->primitiveParser->isPrimitive($type)) { return $this->primitiveParser->parse($data); }
// Handle complex types if ($this->complexParser->isComplex($type)) { return $this->complexParser->parse($data); }
throw TypeResolutionException::unknownType($type); }
/** * Resolve a reference to its actual type definition. * * @throws TypeResolutionException */ public function resolveReference(string $ref, LexiconDocument $context): TypeDefinition { // Check if already resolved $cacheKey = $context->getNsid().':'.$ref;
if (isset($this->resolvedTypes[$cacheKey])) { return $this->resolvedTypes[$cacheKey]; }
// Check for circular reference if (in_array($cacheKey, $this->resolutionChain)) { throw TypeResolutionException::circularReference($ref, $this->resolutionChain); }
$this->resolutionChain[] = $cacheKey;
try { $type = $this->resolveReferenceInternal($ref, $context); $this->resolvedTypes[$cacheKey] = $type;
return $type; } finally { array_pop($this->resolutionChain); } }
/** * Internal reference resolution logic. * * @throws TypeResolutionException */ protected function resolveReferenceInternal(string $ref, LexiconDocument $context): TypeDefinition { // Local reference (#defName) if (str_starts_with($ref, '#')) { $defName = substr($ref, 1);
if (! $context->hasDefinition($defName)) { throw TypeResolutionException::unresolvableReference($ref, $context->getNsid()); }
$defData = $context->getDefinition($defName);
return $this->parse($defData, $context); }
// External reference (nsid#defName or just nsid for #main) if ($this->schemaLoader === null) { throw new \RuntimeException('Cannot resolve external reference without SchemaLoader'); }
[$nsid, $defName] = $this->parseExternalReference($ref);
// Load external schema $externalSchema = $this->schemaLoader->load($nsid); $externalDoc = LexiconDocument::fromArray($externalSchema);
// Get the definition if (! $externalDoc->hasDefinition($defName)) { throw TypeResolutionException::unresolvableReference($ref, $context->getNsid()); }
$defData = $externalDoc->getDefinition($defName);
return $this->parse($defData, $externalDoc); }
/** * Parse an external reference into NSID and definition name. * * @return array{0: string, 1: string} */ protected function parseExternalReference(string $ref): array { if (str_contains($ref, '#')) { [$nsid, $defName] = explode('#', $ref, 2);
return [$nsid, $defName]; }
// If no # is present, default to 'main' return [$ref, 'main']; }
/** * Clear the resolution cache. */ public function clearCache(): void { $this->resolvedTypes = []; $this->resolutionChain = []; }
/** * Get the resolved types cache. * * @return array<string, TypeDefinition> */ public function getResolvedTypes(): array { return $this->resolvedTypes; }}