Parse and validate AT Protocol Lexicons with DTO generation for Laravel
at dev 3.8 kB view raw
1<?php 2 3namespace SocialDept\AtpSchema\Validation; 4 5use JsonSerializable; 6 7class ValidationError implements JsonSerializable 8{ 9 /** 10 * Tracks which optional values were explicitly set. 11 */ 12 protected bool $hasExpectedValue = false; 13 14 protected bool $hasActualValue = false; 15 16 /** 17 * Create a new ValidationError. 18 */ 19 public function __construct( 20 public readonly string $field, 21 public readonly string $message, 22 public readonly ?string $rule = null, 23 mixed $expected = null, 24 mixed $actual = null, 25 public readonly array $context = [] 26 ) { 27 $this->hasExpectedValue = func_num_args() >= 4; 28 $this->hasActualValue = func_num_args() >= 5; 29 30 // Use object properties for values that can be null 31 $this->expected = $expected; 32 $this->actual = $actual; 33 } 34 35 public readonly mixed $expected; 36 37 public readonly mixed $actual; 38 39 /** 40 * Create from field and message. 41 */ 42 public static function make(string $field, string $message): self 43 { 44 return new self($field, $message); 45 } 46 47 /** 48 * Create with rule context. 49 */ 50 public static function withRule(string $field, string $message, string $rule): self 51 { 52 return new self($field, $message, $rule); 53 } 54 55 /** 56 * Create with full context. 57 */ 58 public static function withContext( 59 string $field, 60 string $message, 61 string $rule, 62 mixed $expected = null, 63 mixed $actual = null, 64 array $context = [] 65 ): self { 66 return new self($field, $message, $rule, $expected, $actual, $context); 67 } 68 69 /** 70 * Get the field path. 71 */ 72 public function getField(): string 73 { 74 return $this->field; 75 } 76 77 /** 78 * Get the error message. 79 */ 80 public function getMessage(): string 81 { 82 return $this->message; 83 } 84 85 /** 86 * Get the validation rule that failed. 87 */ 88 public function getRule(): ?string 89 { 90 return $this->rule; 91 } 92 93 /** 94 * Get the expected value. 95 */ 96 public function getExpected(): mixed 97 { 98 return $this->expected; 99 } 100 101 /** 102 * Get the actual value. 103 */ 104 public function getActual(): mixed 105 { 106 return $this->actual; 107 } 108 109 /** 110 * Get additional context. 111 */ 112 public function getContext(): array 113 { 114 return $this->context; 115 } 116 117 /** 118 * Check if error has rule information. 119 */ 120 public function hasRule(): bool 121 { 122 return $this->rule !== null; 123 } 124 125 /** 126 * Check if error has expected value. 127 */ 128 public function hasExpected(): bool 129 { 130 return $this->hasExpectedValue; 131 } 132 133 /** 134 * Check if error has actual value. 135 */ 136 public function hasActual(): bool 137 { 138 return $this->hasActualValue; 139 } 140 141 /** 142 * Convert to array. 143 * 144 * @return array<string, mixed> 145 */ 146 public function toArray(): array 147 { 148 $data = [ 149 'field' => $this->field, 150 'message' => $this->message, 151 ]; 152 153 if ($this->rule !== null) { 154 $data['rule'] = $this->rule; 155 } 156 157 if ($this->hasExpectedValue) { 158 $data['expected'] = $this->expected; 159 } 160 161 if ($this->hasActualValue) { 162 $data['actual'] = $this->actual; 163 } 164 165 if (! empty($this->context)) { 166 $data['context'] = $this->context; 167 } 168 169 return $data; 170 } 171 172 /** 173 * Convert to JSON. 174 */ 175 public function jsonSerialize(): array 176 { 177 return $this->toArray(); 178 } 179 180 /** 181 * Convert to string. 182 */ 183 public function __toString(): string 184 { 185 return "{$this->field}: {$this->message}"; 186 } 187}