@@ -184,6 +184,12 @@ private function applyNullability(array $schema, bool $isNullable): array
184184 $ currentType = $ schema ['type ' ];
185185 $ schema ['type ' ] = \is_array ($ currentType ) ? array_merge ($ currentType , ['null ' ]) : [$ currentType , 'null ' ];
186186
187+ if (isset ($ schema ['enum ' ])) {
188+ $ schema ['enum ' ][] = null ;
189+
190+ return $ schema ;
191+ }
192+
187193 return $ schema ;
188194 }
189195
@@ -199,18 +205,23 @@ private function getJsonSchemaFromType(Type $type, ?bool $readableLink = null):
199205 {
200206 $ isNullable = $ type ->isNullable ();
201207
202- while ($ type instanceof WrappingTypeInterface) {
203- $ type = $ type ->getWrappedType ();
204- }
205-
206208 if ($ type instanceof UnionType) {
207209 $ subTypes = array_filter ($ type ->getTypes (), fn ($ t ) => !($ t instanceof BuiltinType && $ t ->isIdentifiedBy (TypeIdentifier::NULL )));
210+
211+ foreach ($ subTypes as $ t ) {
212+ $ s = $ this ->getJsonSchemaFromType ($ t , $ readableLink );
213+ // We can not find what type this is, let it be computed at runtime by the SchemaFactory
214+ if (($ s ['type ' ] ?? null ) === Schema::UNKNOWN_TYPE ) {
215+ return $ s ;
216+ }
217+ }
218+
208219 $ schemas = array_map (fn ($ t ) => $ this ->getJsonSchemaFromType ($ t , $ readableLink ), $ subTypes );
209220
210221 if (0 === \count ($ schemas )) {
211222 $ schema = [];
212223 } elseif (1 === \count ($ schemas )) {
213- $ schema = $ schemas[ 0 ] ;
224+ $ schema = current ( $ schemas) ;
214225 } else {
215226 $ schema = ['anyOf ' => $ schemas ];
216227 }
@@ -235,20 +246,20 @@ private function getJsonSchemaFromType(Type $type, ?bool $readableLink = null):
235246 }
236247
237248 if ($ type instanceof CollectionType) {
238- $ keyType = $ type ->getCollectionKeyType ();
239249 $ valueType = $ type ->getCollectionValueType ();
240- $ schema = [];
250+ $ valueSchema = $ this ->getJsonSchemaFromType ($ valueType , $ readableLink );
251+ $ keyType = $ type ->getCollectionKeyType ();
241252
242253 // Associative array (string keys)
243- if ($ keyType ->isSatisfiedBy (fn (Type $ t ) => $ t instanceof BuiltinType && $ t ->isIdentifiedBy (TypeIdentifier::STRING ))) {
254+ if ($ keyType ->isSatisfiedBy (fn (Type $ t ) => $ t instanceof BuiltinType && $ t ->isIdentifiedBy (TypeIdentifier::INT ))) {
244255 $ schema = [
245- 'type ' => 'object ' ,
246- 'additionalProperties ' => $ this -> getJsonSchemaFromType ( $ valueType , $ readableLink ) ,
256+ 'type ' => 'array ' ,
257+ 'items ' => $ valueSchema ,
247258 ];
248259 } else { // List (int keys)
249260 $ schema = [
250- 'type ' => 'array ' ,
251- 'items ' => $ this -> getJsonSchemaFromType ( $ valueType , $ readableLink ) ,
261+ 'type ' => 'object ' ,
262+ 'additionalProperties ' => $ valueSchema ,
252263 ];
253264 }
254265
@@ -262,10 +273,6 @@ private function getJsonSchemaFromType(Type $type, ?bool $readableLink = null):
262273 }
263274
264275 if ($ type instanceof BuiltinType) {
265- if ($ type ->isIdentifiedBy (TypeIdentifier::NULL )) {
266- return ['type ' => 'null ' ];
267- }
268-
269276 $ schema = match ($ type ->getTypeIdentifier ()) {
270277 TypeIdentifier::INT => ['type ' => 'integer ' ],
271278 TypeIdentifier::FLOAT => ['type ' => 'number ' ],
@@ -284,7 +291,7 @@ private function getJsonSchemaFromType(Type $type, ?bool $readableLink = null):
284291 return $ this ->applyNullability ($ schema , $ isNullable );
285292 }
286293
287- return $ this -> applyNullability ( ['type ' => Schema::UNKNOWN_TYPE ], $ isNullable ) ;
294+ return ['type ' => Schema::UNKNOWN_TYPE ];
288295 }
289296
290297 /**
@@ -316,34 +323,27 @@ private function getClassSchemaDefinition(?string $className, ?bool $readableLin
316323 return ['type ' => 'string ' , 'format ' => 'binary ' ];
317324 }
318325
319- if (is_a ($ className , \BackedEnum::class, true )) {
326+ $ isResourceClass = $ this ->isResourceClass ($ className );
327+ if (!$ isResourceClass && is_a ($ className , \BackedEnum::class, true )) {
320328 $ enumCases = array_map (static fn (\BackedEnum $ enum ): string |int => $ enum ->value , $ className ::cases ());
321329 $ type = \is_string ($ enumCases [0 ] ?? '' ) ? 'string ' : 'integer ' ;
322330
323331 return ['type ' => $ type , 'enum ' => $ enumCases ];
324332 }
325333
326- $ isResource = $ this ->isResourceClass ($ className );
327-
328334 // If it's a resource and links are not readable, represent as IRI string.
329- if ($ isResource && true !== $ readableLink ) {
335+ if ($ isResourceClass && true !== $ readableLink ) {
330336 return [
331337 'type ' => 'string ' ,
332338 'format ' => 'iri-reference ' ,
333339 'example ' => 'https://example.com/ ' , // Add a generic example
334340 ];
335341 }
336342
337- // If it's a known resource represent it as UNKNOWN_TYPE this gets resolved at runtime by the SchemaFactory
338- if ($ isResource ) {
339- return ['type ' => Schema::UNKNOWN_TYPE ];
340- }
341-
342- // For non-resource objects that aren't handled specifically, default to object.
343- return ['type ' => 'object ' ];
343+ return ['type ' => Schema::UNKNOWN_TYPE ];
344344 }
345345
346- private function getLegacyTypeSchema (ApiProperty $ propertyMetadata , array $ propertySchema , string $ resourceClass , string $ property , bool $ link ): array
346+ private function getLegacyTypeSchema (ApiProperty $ propertyMetadata , array $ propertySchema , string $ resourceClass , string $ property , ? bool $ link ): array
347347 {
348348 $ types = $ propertyMetadata ->getBuiltinTypes () ?? [];
349349
0 commit comments