1414use Symfony \AI \Platform \Contract \JsonSchema \Attribute \With ;
1515use Symfony \AI \Platform \Exception \InvalidArgumentException ;
1616use Symfony \Component \TypeInfo \Type ;
17+ use Symfony \Component \TypeInfo \Type \BackedEnumType ;
1718use Symfony \Component \TypeInfo \Type \BuiltinType ;
1819use Symfony \Component \TypeInfo \Type \CollectionType ;
20+ use Symfony \Component \TypeInfo \Type \NullableType ;
1921use Symfony \Component \TypeInfo \Type \ObjectType ;
2022use Symfony \Component \TypeInfo \TypeIdentifier ;
2123use Symfony \Component \TypeInfo \TypeResolver \TypeResolver ;
5153 * }
5254 *
5355 * @author Christopher Hertel <mail@christopher-hertel.de>
56+ * @author Oskar Stark <oskarstark@googlemail.com>
5457 */
5558final readonly class Factory
5659{
@@ -135,6 +138,19 @@ private function convertTypes(array $elements): ?array
135138 */
136139 private function getTypeSchema (Type $ type ): array
137140 {
141+ // Handle BackedEnumType directly
142+ if ($ type instanceof BackedEnumType) {
143+ return $ this ->buildEnumSchema ($ type ->getClassName ());
144+ }
145+
146+ // Handle NullableType that wraps a BackedEnumType
147+ if ($ type instanceof NullableType) {
148+ $ wrappedType = $ type ->getWrappedType ();
149+ if ($ wrappedType instanceof BackedEnumType) {
150+ return $ this ->buildEnumSchema ($ wrappedType ->getClassName ());
151+ }
152+ }
153+
138154 switch (true ) {
139155 case $ type ->isIdentifiedBy (TypeIdentifier::INT ):
140156 return ['type ' => 'integer ' ];
@@ -168,11 +184,14 @@ private function getTypeSchema(Type $type): array
168184 throw new InvalidArgumentException ('Cannot build schema from plain object type. ' );
169185 }
170186 \assert ($ type instanceof ObjectType);
171- if (\in_array ($ type ->getClassName (), ['DateTime ' , 'DateTimeImmutable ' , 'DateTimeInterface ' ], true )) {
187+
188+ $ className = $ type ->getClassName ();
189+
190+ if (\in_array ($ className , ['DateTime ' , 'DateTimeImmutable ' , 'DateTimeInterface ' ], true )) {
172191 return ['type ' => 'string ' , 'format ' => 'date-time ' ];
173192 } else {
174193 // Recursively build the schema for an object type
175- return $ this ->buildProperties ($ type -> getClassName () ) ?? ['type ' => 'object ' ];
194+ return $ this ->buildProperties ($ className ) ?? ['type ' => 'object ' ];
176195 }
177196
178197 // no break
@@ -182,4 +201,36 @@ private function getTypeSchema(Type $type): array
182201 return ['type ' => 'string ' ];
183202 }
184203 }
204+
205+ /**
206+ * @return array<string, mixed>
207+ */
208+ private function buildEnumSchema (string $ enumClassName ): array
209+ {
210+ $ reflection = new \ReflectionEnum ($ enumClassName );
211+
212+ if (!$ reflection ->isBacked ()) {
213+ throw new InvalidArgumentException (\sprintf ('Enum "%s" is not backed. ' , $ enumClassName ));
214+ }
215+
216+ $ cases = $ reflection ->getCases ();
217+ $ values = [];
218+ $ backingType = $ reflection ->getBackingType ();
219+
220+ foreach ($ cases as $ case ) {
221+ $ values [] = $ case ->getBackingValue ();
222+ }
223+
224+ if (null === $ backingType ) {
225+ throw new InvalidArgumentException (\sprintf ('Backed enum "%s" has no backing type. ' , $ enumClassName ));
226+ }
227+
228+ $ typeName = $ backingType ->getName ();
229+ $ jsonType = 'string ' === $ typeName ? 'string ' : ('int ' === $ typeName ? 'integer ' : 'string ' );
230+
231+ return [
232+ 'type ' => $ jsonType ,
233+ 'enum ' => $ values ,
234+ ];
235+ }
185236}
0 commit comments