@@ -8,6 +8,41 @@ use super::{
88 RequestOptionalParam ,
99} ;
1010
11+ /// Float deserializers that also accept the number map serde_json's
12+ /// `arbitrary_precision` feature produces for buffered (untagged/flattened) values.
13+ /// Like JSON itself, they reject NaN and infinities.
14+ pub ( crate ) mod json_float {
15+ use serde:: { Deserialize , Deserializer , de:: Error } ;
16+ use serde_json:: Number ;
17+
18+ fn to_f64 < E : Error > ( number : Number ) -> Result < f64 , E > {
19+ // `None` only for a value beyond `f64`, which serde_json also rejects
20+ // without the feature.
21+ number
22+ . as_f64 ( )
23+ . ok_or_else ( || E :: custom ( format_args ! ( "number out of range: {number}" ) ) )
24+ }
25+
26+ pub ( crate ) fn f64 < ' de , D : Deserializer < ' de > > ( deserializer : D ) -> Result < f64 , D :: Error > {
27+ to_f64 ( Number :: deserialize ( deserializer) ?)
28+ }
29+
30+ pub ( crate ) fn option_f64 < ' de , D : Deserializer < ' de > > (
31+ deserializer : D ,
32+ ) -> Result < Option < f64 > , D :: Error > {
33+ Option :: < Number > :: deserialize ( deserializer) ?
34+ . map ( to_f64)
35+ . transpose ( )
36+ }
37+
38+ pub ( crate ) fn option_f32 < ' de , D : Deserializer < ' de > > (
39+ deserializer : D ,
40+ ) -> Result < Option < f32 > , D :: Error > {
41+ // serde_json also reads an `f32` field as `f64` and narrows it with `as`.
42+ Ok ( option_f64 ( deserializer) ?. map ( |value| value as f32 ) )
43+ }
44+ }
45+
1146/// Wire-side view of `params`: the `_meta` map plus the remaining fields.
1247///
1348/// All metadata types are transparent wrappers over [`JsonObject`], so the
@@ -848,4 +883,165 @@ mod test {
848883 let output = serde_json:: to_value ( & req) . unwrap ( ) ;
849884 assert_eq ! ( input, output) ;
850885 }
886+
887+ /// The float fields read through [`super::json_float`].
888+ mod json_float {
889+ use std:: fmt:: Debug ;
890+
891+ use rstest:: rstest;
892+ use serde:: { Deserialize , Serialize , de:: DeserializeOwned } ;
893+ use serde_json:: { Value , json} ;
894+
895+ use crate :: model:: {
896+ Annotations , CreateMessageRequestParams , ModelPreferences , NumberSchema ,
897+ ProgressNotificationParam ,
898+ } ;
899+
900+ /// Untagged, so serde buffers the input before `T` reads it, as it
901+ /// does for every JSON-RPC message.
902+ #[ derive( Deserialize ) ]
903+ #[ serde( untagged) ]
904+ enum Buffered < T > {
905+ Inner ( T ) ,
906+ }
907+
908+ /// Decodes `text` directly and through serde's buffer, checks that both
909+ /// agree, and that the value survives a round trip.
910+ fn decode < T > ( text : & str ) -> T
911+ where
912+ T : DeserializeOwned + Serialize + PartialEq + Debug ,
913+ {
914+ let direct: T = serde_json:: from_str ( text) . unwrap ( ) ;
915+ let Buffered :: Inner ( buffered) = serde_json:: from_str ( text) . unwrap ( ) ;
916+ assert_eq ! ( direct, buffered, "{text}" ) ;
917+ let encoded = serde_json:: to_string ( & direct) . unwrap ( ) ;
918+ assert_eq ! (
919+ serde_json:: from_str:: <T >( & encoded) . unwrap( ) ,
920+ direct,
921+ "{encoded}"
922+ ) ;
923+ direct
924+ }
925+
926+ fn rejects < T : DeserializeOwned > ( text : & str ) {
927+ assert ! ( serde_json:: from_str:: <T >( text) . is_err( ) , "{text}" ) ;
928+ assert ! ( serde_json:: from_str:: <Buffered <T >>( text) . is_err( ) , "{text}" ) ;
929+ }
930+
931+ #[ rstest]
932+ #[ case:: decimal( "0.6" , 0.6 ) ]
933+ #[ case:: trailing_zero( "0.60" , 0.6 ) ]
934+ #[ case:: exponent( "6e-1" , 0.6 ) ]
935+ #[ case:: integer( "1" , 1.0 ) ]
936+ #[ case:: integral_decimal( "1.0" , 1.0 ) ]
937+ #[ case:: zero( "0" , 0.0 ) ]
938+ fn fields_read_every_spelling ( #[ case] n : & str , #[ case] expected : f64 ) {
939+ // An `f32` field holds the `f64` narrowed, as serde_json reads it.
940+ let narrowed = Some ( expected as f32 ) ;
941+
942+ let annotations: Annotations = decode ( & format ! ( r#"{{"priority":{n}}}"# ) ) ;
943+ assert_eq ! ( annotations. priority, narrowed) ;
944+
945+ let progress: ProgressNotificationParam = decode ( & format ! (
946+ r#"{{"progressToken":1,"progress":{n},"total":{n}}}"#
947+ ) ) ;
948+ assert_eq ! ( progress. progress, expected) ;
949+ assert_eq ! ( progress. total, Some ( expected) ) ;
950+
951+ let params: CreateMessageRequestParams = decode ( & format ! (
952+ r#"{{"messages":[],"maxTokens":1,"temperature":{n}}}"#
953+ ) ) ;
954+ assert_eq ! ( params. temperature, narrowed) ;
955+
956+ let preferences: ModelPreferences = decode ( & format ! (
957+ r#"{{"costPriority":{n},"speedPriority":{n},"intelligencePriority":{n}}}"#
958+ ) ) ;
959+ assert_eq ! ( preferences. cost_priority, narrowed) ;
960+ assert_eq ! ( preferences. speed_priority, narrowed) ;
961+ assert_eq ! ( preferences. intelligence_priority, narrowed) ;
962+
963+ let schema: NumberSchema = decode ( & format ! (
964+ r#"{{"type":"number","minimum":{n},"maximum":{n},"default":{n}}}"#
965+ ) ) ;
966+ assert_eq ! ( schema. minimum, Some ( expected) ) ;
967+ assert_eq ! ( schema. maximum, Some ( expected) ) ;
968+ assert_eq ! ( schema. default , Some ( expected) ) ;
969+ }
970+
971+ #[ test]
972+ fn decimal_narrows_to_the_nearest_f32 ( ) {
973+ let annotations: Annotations = decode ( r#"{"priority":0.6}"# ) ;
974+ assert_eq ! ( annotations. priority, Some ( 0.6_f32 ) ) ;
975+ }
976+
977+ #[ rstest]
978+ #[ case:: missing( None ) ]
979+ #[ case:: null( Some ( Value :: Null ) ) ]
980+ fn optional_fields_read_absent_as_none ( #[ case] value : Option < Value > ) {
981+ // `base` with each of `keys` set to `value`, or left out.
982+ let with = |mut base : Value , keys : & [ & str ] | {
983+ if let Some ( value) = & value {
984+ for key in keys {
985+ base[ * key] = value. clone ( ) ;
986+ }
987+ }
988+ base. to_string ( )
989+ } ;
990+
991+ let annotations: Annotations = decode ( & with ( json ! ( { } ) , & [ "priority" ] ) ) ;
992+ assert_eq ! ( annotations, Annotations :: default ( ) ) ;
993+
994+ let progress: ProgressNotificationParam = decode ( & with (
995+ json ! ( { "progressToken" : 1 , "progress" : 0 } ) ,
996+ & [ "total" ] ,
997+ ) ) ;
998+ assert_eq ! ( progress. total, None ) ;
999+
1000+ let params: CreateMessageRequestParams = decode ( & with (
1001+ json ! ( { "messages" : [ ] , "maxTokens" : 1 } ) ,
1002+ & [ "temperature" ] ,
1003+ ) ) ;
1004+ assert_eq ! ( params. temperature, None ) ;
1005+
1006+ let preferences: ModelPreferences = decode ( & with (
1007+ json ! ( { } ) ,
1008+ & [ "costPriority" , "speedPriority" , "intelligencePriority" ] ,
1009+ ) ) ;
1010+ assert_eq ! ( preferences, ModelPreferences :: new( ) ) ;
1011+
1012+ let schema: NumberSchema = decode ( & with (
1013+ json ! ( { "type" : "number" } ) ,
1014+ & [ "minimum" , "maximum" , "default" ] ,
1015+ ) ) ;
1016+ assert_eq ! ( schema, NumberSchema :: new( ) ) ;
1017+ }
1018+
1019+ #[ test]
1020+ fn progress_is_required ( ) {
1021+ rejects :: < ProgressNotificationParam > ( r#"{"progressToken":1}"# ) ;
1022+ rejects :: < ProgressNotificationParam > ( r#"{"progressToken":1,"progress":null}"# ) ;
1023+ }
1024+
1025+ #[ rstest]
1026+ #[ case:: string( r#""high""# ) ]
1027+ #[ case:: out_of_range( "1e400" ) ]
1028+ fn fields_reject_anything_but_a_finite_number ( #[ case] n : & str ) {
1029+ rejects :: < Annotations > ( & format ! ( r#"{{"priority":{n}}}"# ) ) ;
1030+ rejects :: < ProgressNotificationParam > ( & format ! (
1031+ r#"{{"progressToken":1,"progress":{n}}}"#
1032+ ) ) ;
1033+ rejects :: < ProgressNotificationParam > ( & format ! (
1034+ r#"{{"progressToken":1,"progress":0,"total":{n}}}"#
1035+ ) ) ;
1036+ rejects :: < CreateMessageRequestParams > ( & format ! (
1037+ r#"{{"messages":[],"maxTokens":1,"temperature":{n}}}"#
1038+ ) ) ;
1039+ for key in [ "costPriority" , "speedPriority" , "intelligencePriority" ] {
1040+ rejects :: < ModelPreferences > ( & format ! ( r#"{{"{key}":{n}}}"# ) ) ;
1041+ }
1042+ for key in [ "minimum" , "maximum" , "default" ] {
1043+ rejects :: < NumberSchema > ( & format ! ( r#"{{"type":"number","{key}":{n}}}"# ) ) ;
1044+ }
1045+ }
1046+ }
8511047}
0 commit comments