Parse and validate AT Protocol Lexicons with DTO generation for Laravel
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}