Parse and validate AT Protocol Lexicons with DTO generation for Laravel
1<?php
2
3namespace SocialDept\AtpSchema\Support;
4
5use InvalidArgumentException;
6use SocialDept\AtpSchema\Contracts\DiscriminatedUnion;
7use SocialDept\AtpSchema\Data\Data;
8
9/**
10 * Helper for resolving discriminated unions based on $type field.
11 *
12 * This class uses the DiscriminatedUnion interface to build type maps
13 * and resolve union data to the correct variant class.
14 */
15class UnionHelper
16{
17 /**
18 * Resolve a closed union to the correct variant class.
19 *
20 * @param array $data The union data containing a $type field
21 * @param array<class-string<Data>> $variants Array of possible variant class names
22 * @return Data The resolved variant instance
23 *
24 * @throws InvalidArgumentException If $type is missing or unknown
25 */
26 public static function resolveClosedUnion(array $data, array $variants): Data
27 {
28 // Validate $type field exists
29 if (! isset($data['$type'])) {
30 throw new InvalidArgumentException(
31 'Closed union data must contain a $type field for discrimination'
32 );
33 }
34
35 $type = $data['$type'];
36
37 // Build type map using DiscriminatedUnion interface
38 $typeMap = static::buildTypeMap($variants);
39
40 // Check if type is known
41 if (! isset($typeMap[$type])) {
42 $knownTypes = implode(', ', array_keys($typeMap));
43
44 throw new InvalidArgumentException(
45 "Unknown union type '{$type}'. Expected one of: {$knownTypes}"
46 );
47 }
48
49 // Resolve to correct variant class
50 $class = $typeMap[$type];
51
52 return $class::fromArray($data);
53 }
54
55 /**
56 * Validate an open union has $type field.
57 *
58 * Open unions pass data through as-is but must have $type for future discrimination.
59 *
60 * @param array $data The union data
61 * @return array The validated union data
62 *
63 * @throws InvalidArgumentException If $type is missing
64 */
65 public static function validateOpenUnion(array $data): array
66 {
67 if (! isset($data['$type'])) {
68 throw new InvalidArgumentException(
69 'Open union data must contain a $type field for future discrimination'
70 );
71 }
72
73 return $data;
74 }
75
76 /**
77 * Build a type map from variant classes using DiscriminatedUnion interface.
78 *
79 * @param array<class-string<Data>> $variants Array of variant class names
80 * @return array<string, class-string<Data>> Map of discriminator => class name
81 */
82 protected static function buildTypeMap(array $variants): array
83 {
84 $typeMap = [];
85
86 foreach ($variants as $class) {
87 // Ensure class implements DiscriminatedUnion
88 if (! is_subclass_of($class, DiscriminatedUnion::class)) {
89 throw new InvalidArgumentException(
90 "Variant class {$class} must implement DiscriminatedUnion interface"
91 );
92 }
93
94 // Get discriminator from the class
95 $discriminator = $class::getDiscriminator();
96 $typeMap[$discriminator] = $class;
97 }
98
99 return $typeMap;
100 }
101}