@@ -106,32 +106,40 @@ pub fn schema_for_empty_input() -> Arc<JsonObject> {
106106 EMPTY . clone ( )
107107}
108108
109- /// Generate a JSON schema for outputSchema (must have root type "object"; top-level "title" and "description" are removed)
110- pub fn schema_for_output < T : JsonSchema + std:: any:: Any > ( ) -> Result < Arc < JsonObject > , String > {
109+ /// Strip top-level `title` and `description` from a JSON schema for outputSchema.
110+ /// Unlike `validate_and_strip`, this performs no validation — output schemas are not
111+ /// restricted to `type: "object"` (per SEP-2106).
112+ fn strip_output ( raw : & Arc < JsonObject > ) -> Arc < JsonObject > {
113+ let mut object = raw. as_ref ( ) . clone ( ) ;
114+ object. remove ( "title" ) ;
115+ object. remove ( "description" ) ;
116+ Arc :: new ( object)
117+ }
118+
119+ /// Generate and strip a JSON schema for outputSchema (top-level "title" and
120+ /// "description" are removed; output schemas are not restricted to root type "object").
121+ pub fn schema_for_output < T : JsonSchema + std:: any:: Any > ( ) -> Arc < JsonObject > {
111122 thread_local ! {
112- static CACHE_FOR_OUTPUT : std:: sync:: RwLock <HashMap <TypeId , Result < Arc <JsonObject > , String >>> = Default :: default ( ) ;
123+ static CACHE_FOR_OUTPUT : std:: sync:: RwLock <HashMap <TypeId , Arc <JsonObject >>> = Default :: default ( ) ;
113124 } ;
114125
115126 CACHE_FOR_OUTPUT . with ( |cache| {
116- // Try to get from cache first
117- if let Some ( result) = cache
127+ if let Some ( schema) = cache
118128 . read ( )
119129 . expect ( "output schema cache lock poisoned" )
120130 . get ( & TypeId :: of :: < T > ( ) )
121131 {
122- return result . clone ( ) ;
132+ return schema . clone ( ) ;
123133 }
124134
125- // Generate, validate, and strip unnecessary top-level fields
126- let result = validate_and_strip ( & schema_for_type :: < T > ( ) , "outputSchema" ) ;
135+ let schema = strip_output ( & schema_for_type :: < T > ( ) ) ;
127136
128- // Cache the result (both success and error cases)
129137 cache
130138 . write ( )
131139 . expect ( "output schema cache lock poisoned" )
132- . insert ( TypeId :: of :: < T > ( ) , result . clone ( ) ) ;
140+ . insert ( TypeId :: of :: < T > ( ) , schema . clone ( ) ) ;
133141
134- result
142+ schema
135143 } )
136144}
137145
@@ -305,32 +313,52 @@ mod tests {
305313 assert ! ( Arc :: ptr_eq( & schema, & cloned) ) ;
306314 }
307315
316+ #[ test]
317+ fn test_schema_for_output_accepts_primitive ( ) {
318+ let schema = schema_for_output :: < i32 > ( ) ;
319+ assert_eq ! ( schema. get( "type" ) , Some ( & serde_json:: json!( "integer" ) ) ) ;
320+ }
321+
322+ #[ test]
323+ fn test_schema_for_output_accepts_object ( ) {
324+ let schema = schema_for_output :: < TestObject > ( ) ;
325+ assert_eq ! ( schema. get( "type" ) , Some ( & serde_json:: json!( "object" ) ) ) ;
326+ }
327+
328+ #[ test]
329+ fn test_schema_for_output_strips_top_level_title ( ) {
330+ let schema = schema_for_output :: < TestObject > ( ) ;
331+ assert ! ( !schema. contains_key( "title" ) ) ;
332+ }
333+
334+ #[ test]
335+ fn test_schema_for_output_strips_top_level_description ( ) {
336+ let schema = schema_for_output :: < TestObject > ( ) ;
337+ assert ! ( !schema. contains_key( "description" ) ) ;
338+ }
339+
308340 #[ rstest]
309- #[ case:: output( schema_for_output:: <i32 >) ]
310341 #[ case:: input( schema_for_input:: <i32 >) ]
311- fn test_schema_for_object_wrappers_reject_primitives (
342+ fn test_schema_for_input_rejects_primitives (
312343 #[ case] schema_fn : fn ( ) -> Result < Arc < JsonObject > , String > ,
313344 ) {
314345 let result = schema_fn ( ) ;
315346 assert ! ( result. is_err( ) ) ;
316347 }
317348
318349 #[ rstest]
319- #[ case:: output( schema_for_output:: <TestObject >) ]
320350 #[ case:: input( schema_for_input:: <TestObject >) ]
321- fn test_schema_for_object_wrappers_accept_objects (
351+ fn test_schema_for_input_accepts_objects (
322352 #[ case] schema_fn : fn ( ) -> Result < Arc < JsonObject > , String > ,
323353 ) {
324354 let result = schema_fn ( ) ;
325355 assert ! ( result. is_ok( ) ) ;
326356 }
327357
328358 #[ rstest]
329- #[ case:: output_title( schema_for_output:: <TestObject >, "title" ) ]
330- #[ case:: output_description( schema_for_output:: <TestObject >, "description" ) ]
331359 #[ case:: input_title( schema_for_input:: <TestObject >, "title" ) ]
332360 #[ case:: input_description( schema_for_input:: <TestObject >, "description" ) ]
333- fn test_schema_for_object_wrappers_strip_top_level_metadata (
361+ fn test_schema_for_input_strips_top_level_metadata (
334362 #[ case] schema_fn : fn ( ) -> Result < Arc < JsonObject > , String > ,
335363 #[ case] field : & str ,
336364 ) {
0 commit comments