Parse and validate AT Protocol Lexicons with DTO generation for Laravel
at main 10 kB view raw
1<?php 2 3namespace SocialDept\AtpSchema; 4 5use Illuminate\Support\ServiceProvider; 6 7class SchemaServiceProvider extends ServiceProvider 8{ 9 /** 10 * Register any package services. 11 */ 12 public function register(): void 13 { 14 $this->mergeConfigFrom(__DIR__.'/../config/schema.php', 'schema'); 15 16 // Register SchemaLoader 17 $this->app->singleton(Parser\SchemaLoader::class, function ($app) { 18 return new Parser\SchemaLoader( 19 sources: config('schema.sources', []), 20 useCache: config('schema.cache.enabled', true), 21 cacheTtl: config('schema.cache.schema_ttl', 3600), 22 cachePrefix: config('schema.cache.prefix', 'schema'), 23 dnsResolutionEnabled: config('schema.dns_resolution.enabled', true), 24 httpTimeout: config('schema.http.timeout', 10) 25 ); 26 }); 27 28 // Register LexiconValidator 29 $this->app->singleton(Validation\LexiconValidator::class, function ($app) { 30 return new Validation\LexiconValidator( 31 schemaLoader: $app->make(Parser\SchemaLoader::class) 32 ); 33 }); 34 35 // Register NamingConverter 36 $this->app->singleton(Generator\NamingConverter::class, function ($app) { 37 return new Generator\NamingConverter( 38 baseNamespace: config('schema.lexicons.base_namespace', 'App\\Lexicons') 39 ); 40 }); 41 42 // Register NamespaceResolver 43 $this->app->singleton(Generator\NamespaceResolver::class, function ($app) { 44 return new Generator\NamespaceResolver( 45 baseNamespace: config('schema.lexicons.base_namespace', 'App\\Lexicons') 46 ); 47 }); 48 49 // Register UnionResolver 50 $this->app->singleton(Services\UnionResolver::class); 51 52 // Register ExtensionManager 53 $this->app->singleton(Support\ExtensionManager::class); 54 55 // Register DefaultLexiconParser 56 $this->app->singleton(Parser\DefaultLexiconParser::class); 57 58 // Register InMemoryLexiconRegistry 59 $this->app->singleton(Parser\InMemoryLexiconRegistry::class); 60 61 // Register DnsLexiconResolver 62 $this->app->singleton(Parser\DnsLexiconResolver::class, function ($app) { 63 return new Parser\DnsLexiconResolver( 64 enabled: config('schema.dns_resolution.enabled', true), 65 httpTimeout: config('schema.http.timeout', 10), 66 parser: $app->make(Parser\DefaultLexiconParser::class) 67 ); 68 }); 69 70 // Register DefaultBlobHandler 71 $this->app->singleton(Support\DefaultBlobHandler::class, function ($app) { 72 return new Support\DefaultBlobHandler( 73 disk: config('schema.blobs.disk'), 74 path: config('schema.blobs.path', 'blobs') 75 ); 76 }); 77 78 // Bind BlobHandler contract to DefaultBlobHandler 79 $this->app->bind(Contracts\BlobHandler::class, Support\DefaultBlobHandler::class); 80 81 // Bind LexiconParser contract to DefaultLexiconParser 82 $this->app->bind(Contracts\LexiconParser::class, Parser\DefaultLexiconParser::class); 83 84 // Bind LexiconRegistry contract to InMemoryLexiconRegistry 85 $this->app->bind(Contracts\LexiconRegistry::class, Parser\InMemoryLexiconRegistry::class); 86 87 // Bind LexiconResolver contract to DnsLexiconResolver 88 $this->app->bind(Contracts\LexiconResolver::class, Parser\DnsLexiconResolver::class); 89 90 // Bind SchemaRepository contract to SchemaLoader 91 $this->app->bind(Contracts\SchemaRepository::class, Parser\SchemaLoader::class); 92 93 // Register DTOGenerator 94 $this->app->singleton(Generator\DTOGenerator::class, function ($app) { 95 return new Generator\DTOGenerator( 96 schemaLoader: $app->make(Parser\SchemaLoader::class), 97 baseNamespace: config('schema.lexicons.base_namespace', 'App\\Lexicons'), 98 outputDirectory: config('schema.lexicons.output_path', app_path('Lexicons')), 99 typeParser: null, 100 namespaceResolver: $app->make(Generator\NamespaceResolver::class) 101 ); 102 }); 103 104 // Register SchemaManager 105 $this->app->singleton('schema', function ($app) { 106 return new SchemaManager( 107 loader: $app->make(Parser\SchemaLoader::class), 108 validator: $app->make(Validation\LexiconValidator::class), 109 generator: $app->make(Generator\DTOGenerator::class) 110 ); 111 }); 112 } 113 114 /** 115 * Bootstrap the application services. 116 */ 117 public function boot(): void 118 { 119 $this->bootValidationRules(); 120 121 if ($this->app->runningInConsole()) { 122 $this->bootForConsole(); 123 } 124 } 125 126 /** 127 * Register custom validation rules. 128 */ 129 protected function bootValidationRules(): void 130 { 131 $validator = $this->app->make('validator'); 132 133 // Register AT Protocol validation rules 134 $validator->extend('nsid', function ($attribute, $value) { 135 $rule = new Validation\Rules\Nsid(); 136 $failed = false; 137 $rule->validate($attribute, $value, function () use (&$failed) { 138 $failed = true; 139 }); 140 141 return ! $failed; 142 }, 'The :attribute is not a valid NSID.'); 143 144 $validator->extend('did', function ($attribute, $value) { 145 $rule = new Validation\Rules\Did(); 146 $failed = false; 147 $rule->validate($attribute, $value, function () use (&$failed) { 148 $failed = true; 149 }); 150 151 return ! $failed; 152 }, 'The :attribute is not a valid DID.'); 153 154 $validator->extend('handle', function ($attribute, $value) { 155 $rule = new Validation\Rules\Handle(); 156 $failed = false; 157 $rule->validate($attribute, $value, function () use (&$failed) { 158 $failed = true; 159 }); 160 161 return ! $failed; 162 }, 'The :attribute is not a valid handle.'); 163 164 $validator->extend('at_uri', function ($attribute, $value) { 165 $rule = new Validation\Rules\AtUri(); 166 $failed = false; 167 $rule->validate($attribute, $value, function () use (&$failed) { 168 $failed = true; 169 }); 170 171 return ! $failed; 172 }, 'The :attribute is not a valid AT URI.'); 173 174 $validator->extend('at_datetime', function ($attribute, $value) { 175 $rule = new Validation\Rules\AtDatetime(); 176 $failed = false; 177 $rule->validate($attribute, $value, function () use (&$failed) { 178 $failed = true; 179 }); 180 181 return ! $failed; 182 }, 'The :attribute is not a valid AT Protocol datetime.'); 183 184 $validator->extend('cid', function ($attribute, $value) { 185 $rule = new Validation\Rules\Cid(); 186 $failed = false; 187 $rule->validate($attribute, $value, function () use (&$failed) { 188 $failed = true; 189 }); 190 191 return ! $failed; 192 }, 'The :attribute is not a valid CID.'); 193 194 $validator->extend('max_graphemes', function ($attribute, $value, $parameters) { 195 if (empty($parameters)) { 196 return false; 197 } 198 $rule = new Validation\Rules\MaxGraphemes((int) $parameters[0]); 199 $failed = false; 200 $rule->validate($attribute, $value, function () use (&$failed) { 201 $failed = true; 202 }); 203 204 return ! $failed; 205 }, 'The :attribute may not be greater than :max_graphemes graphemes.'); 206 207 $validator->extend('min_graphemes', function ($attribute, $value, $parameters) { 208 if (empty($parameters)) { 209 return false; 210 } 211 $rule = new Validation\Rules\MinGraphemes((int) $parameters[0]); 212 $failed = false; 213 $rule->validate($attribute, $value, function () use (&$failed) { 214 $failed = true; 215 }); 216 217 return ! $failed; 218 }, 'The :attribute must be at least :min_graphemes graphemes.'); 219 220 $validator->extend('language', function ($attribute, $value) { 221 $rule = new Validation\Rules\Language(); 222 $failed = false; 223 $rule->validate($attribute, $value, function () use (&$failed) { 224 $failed = true; 225 }); 226 227 return ! $failed; 228 }, 'The :attribute is not a valid BCP 47 language code.'); 229 230 // Register replacements for parameterized rules 231 $validator->replacer('max_graphemes', function ($message, $attribute, $rule, $parameters) { 232 return str_replace(':max_graphemes', $parameters[0], $message); 233 }); 234 235 $validator->replacer('min_graphemes', function ($message, $attribute, $rule, $parameters) { 236 return str_replace(':min_graphemes', $parameters[0], $message); 237 }); 238 } 239 240 /** 241 * Get the services provided by the provider. 242 * 243 * @return array<string> 244 */ 245 public function provides(): array 246 { 247 return ['schema']; 248 } 249 250 /** 251 * Console-specific booting. 252 */ 253 protected function bootForConsole(): void 254 { 255 // Publish config 256 $this->publishes([ 257 __DIR__.'/../config/schema.php' => config_path('schema.php'), 258 ], 'schema-config'); 259 260 // Publish stubs (will be created in later commits) 261 $this->publishes([ 262 __DIR__.'/../stubs' => base_path('stubs/schema'), 263 ], 'schema-stubs'); 264 265 // Publish lexicon JSON files 266 $this->publishes([ 267 __DIR__.'/../resources/lexicons' => resource_path('lexicons'), 268 ], 'atp-lexicons'); 269 270 // Register commands 271 $this->commands([ 272 Console\GenerateCommand::class, 273 Console\ValidateCommand::class, 274 Console\ListCommand::class, 275 Console\ClearCacheCommand::class, 276 ]); 277 } 278}