11mod context;
22
33use :: cel:: objects:: { Key , TryIntoValue } ;
4+ use :: cel:: types:: map:: MapValue ;
45use :: cel:: { Context as CelContext , ExecutionError , Program , Value } ;
56use log:: debug;
67use pyo3:: exceptions:: { PyRuntimeError , PyTypeError , PyValueError } ;
78use pyo3:: prelude:: * ;
89use pyo3:: BoundObject ;
10+ use pyo3:: IntoPyObject ;
911use std:: panic:: { self , AssertUnwindSafe } ;
1012
1113use chrono:: { DateTime , Duration as ChronoDuration , Offset , TimeZone } ;
12- use pyo3:: types:: { PyBool , PyBytes , PyDict , PyList , PyTuple } ;
14+ use pyo3:: types:: { PyBool , PyBytes , PyDict , PyList , PyMapping , PyTuple } ;
1315
1416use std:: collections:: HashMap ;
1517use std:: error:: Error ;
@@ -66,6 +68,17 @@ impl<'py> IntoPyObject<'py> for RustyCelType {
6668
6769 python_dict. into_any ( )
6870 }
71+ RustyCelType ( Value :: DynamicMap ( map) ) => {
72+ let python_dict = PyDict :: new ( py) ;
73+
74+ for ( key, value) in map. iter ( ) {
75+ let key_obj = PyMappingValue :: key_to_python ( py, & key) ;
76+ let value_obj = RustyCelType ( value) . into_pyobject ( py) ?;
77+ python_dict. set_item ( key_obj. bind ( py) , & value_obj) ?;
78+ }
79+
80+ python_dict. into_any ( )
81+ }
6982
7083 // Turn everything else into a String:
7184 nonprimitive => format ! ( "{nonprimitive:?}" ) . into_pyobject ( py) ?. into_any ( ) ,
@@ -77,6 +90,115 @@ impl<'py> IntoPyObject<'py> for RustyCelType {
7790#[ derive( Debug ) ]
7891struct RustyPyType < ' a > ( & ' a Bound < ' a , PyAny > ) ;
7992
93+ #[ derive( Clone ) ]
94+ struct PyMappingValue {
95+ mapping : Py < PyAny > ,
96+ }
97+
98+ impl PyMappingValue {
99+ fn new ( mapping : Py < PyAny > ) -> Self {
100+ Self { mapping }
101+ }
102+
103+ fn key_to_python ( py : Python < ' _ > , key : & Key ) -> Py < PyAny > {
104+ match key {
105+ Key :: Int ( value) => value. into_pyobject ( py) . unwrap ( ) . unbind ( ) . into ( ) ,
106+ Key :: Uint ( value) => value. into_pyobject ( py) . unwrap ( ) . unbind ( ) . into ( ) ,
107+ Key :: Bool ( value) => value. into_pyobject ( py) . unwrap ( ) . unbind ( ) . into ( ) ,
108+ Key :: String ( value) => value. as_str ( ) . into_pyobject ( py) . unwrap ( ) . unbind ( ) . into ( ) ,
109+ }
110+ }
111+
112+ fn py_to_key ( obj : & Bound < ' _ , PyAny > ) -> Option < Key > {
113+ if obj. is_none ( ) {
114+ return None ;
115+ }
116+
117+ if let Ok ( value) = obj. extract :: < i64 > ( ) {
118+ Some ( Key :: Int ( value) )
119+ } else if let Ok ( value) = obj. extract :: < u64 > ( ) {
120+ Some ( Key :: Uint ( value) )
121+ } else if let Ok ( value) = obj. extract :: < bool > ( ) {
122+ Some ( Key :: Bool ( value) )
123+ } else if let Ok ( value) = obj. extract :: < String > ( ) {
124+ Some ( Key :: String ( value. into ( ) ) )
125+ } else {
126+ None
127+ }
128+ }
129+ }
130+
131+ impl fmt:: Debug for PyMappingValue {
132+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
133+ f. debug_struct ( "PyMappingValue" ) . finish ( )
134+ }
135+ }
136+
137+ impl MapValue for PyMappingValue {
138+ fn get ( & self , key : & Key ) -> Option < Value > {
139+ Python :: with_gil ( |py| {
140+ let bound = self . mapping . bind ( py) ;
141+ let mapping = bound. downcast :: < PyMapping > ( ) . ok ( ) ?;
142+ let py_key = Self :: key_to_python ( py, key) ;
143+ let value = mapping. get_item ( py_key) . ok ( ) ?;
144+ RustyPyType ( & value) . try_into_value ( ) . ok ( )
145+ } )
146+ }
147+
148+ fn contains_key ( & self , key : & Key ) -> bool {
149+ Python :: with_gil ( |py| {
150+ let bound = self . mapping . bind ( py) ;
151+ let mapping = match bound. downcast :: < PyMapping > ( ) {
152+ Ok ( mapping) => mapping,
153+ Err ( _) => return false ,
154+ } ;
155+ let py_key = Self :: key_to_python ( py, key) ;
156+ mapping. contains ( py_key) . unwrap_or ( false )
157+ } )
158+ }
159+
160+ fn len ( & self ) -> usize {
161+ Python :: with_gil ( |py| {
162+ let bound = self . mapping . bind ( py) ;
163+ bound
164+ . downcast :: < PyMapping > ( )
165+ . ok ( )
166+ . and_then ( |mapping| mapping. len ( ) . ok ( ) )
167+ . unwrap_or ( 0 )
168+ } )
169+ }
170+
171+ fn iter ( & self ) -> Box < dyn Iterator < Item = ( Key , Value ) > + ' _ > {
172+ let items = Python :: with_gil ( |py| {
173+ let bound = self . mapping . bind ( py) ;
174+ let mapping = match bound. downcast :: < PyMapping > ( ) {
175+ Ok ( mapping) => mapping,
176+ Err ( _) => return Vec :: new ( ) ,
177+ } ;
178+ let list = match mapping. items ( ) {
179+ Ok ( items) => items,
180+ Err ( _) => return Vec :: new ( ) ,
181+ } ;
182+
183+ list. iter ( )
184+ . filter_map ( |item| {
185+ let tuple = item. downcast :: < PyTuple > ( ) . ok ( ) ?;
186+ if tuple. len ( ) != 2 {
187+ return None ;
188+ }
189+ let key_obj = tuple. get_item ( 0 ) . ok ( ) ?;
190+ let value_obj = tuple. get_item ( 1 ) . ok ( ) ?;
191+ let key = Self :: py_to_key ( & key_obj) ?;
192+ let value = RustyPyType ( & value_obj) . try_into_value ( ) . ok ( ) ?;
193+ Some ( ( key, value) )
194+ } )
195+ . collect :: < Vec < _ > > ( )
196+ } ) ;
197+
198+ Box :: new ( items. into_iter ( ) )
199+ }
200+ }
201+
80202#[ derive( Debug , PartialEq , Clone ) ]
81203pub enum CelError {
82204 ConversionError ( String ) ,
@@ -212,35 +334,45 @@ impl TryIntoValue for RustyPyType<'_> {
212334 . map ( |item| RustyPyType ( & item) . try_into_value ( ) )
213335 . collect :: < Result < Vec < Value > , Self :: Error > > ( ) ;
214336 list. map ( |v| Value :: List ( Arc :: new ( v) ) )
215- } else if let Ok ( value) = pyobject. downcast :: < PyDict > ( ) {
216- let mut map: HashMap < Key , Value > = HashMap :: new ( ) ;
217- for ( key, value) in value. into_iter ( ) {
218- let key = if key. is_none ( ) {
219- return Err ( CelError :: ConversionError (
220- "None cannot be used as a key in dictionaries" . to_string ( ) ,
221- ) ) ;
222- } else if let Ok ( k) = key. extract :: < i64 > ( ) {
223- Key :: Int ( k)
224- } else if let Ok ( k) = key. extract :: < u64 > ( ) {
225- Key :: Uint ( k)
226- } else if let Ok ( k) = key. extract :: < bool > ( ) {
227- Key :: Bool ( k)
228- } else if let Ok ( k) = key. extract :: < String > ( ) {
229- Key :: String ( k. into ( ) )
230- } else {
231- return Err ( CelError :: ConversionError (
232- "Failed to convert PyDict key to Key" . to_string ( ) ,
233- ) ) ;
234- } ;
235- if let Ok ( dict_value) = RustyPyType ( & value) . try_into_value ( ) {
236- map. insert ( key, dict_value) ;
237- } else {
238- return Err ( CelError :: ConversionError (
239- "Failed to convert PyDict value to Value" . to_string ( ) ,
240- ) ) ;
337+ } else if let Ok ( dict) = pyobject. downcast :: < PyDict > ( ) {
338+ if pyobject. is_exact_instance_of :: < PyDict > ( ) {
339+ let mut map: HashMap < Key , Value > = HashMap :: new ( ) ;
340+ for ( key, value) in dict. iter ( ) {
341+ let key = if key. is_none ( ) {
342+ return Err ( CelError :: ConversionError (
343+ "None cannot be used as a key in dictionaries" . to_string ( ) ,
344+ ) ) ;
345+ } else if let Ok ( k) = key. extract :: < i64 > ( ) {
346+ Key :: Int ( k)
347+ } else if let Ok ( k) = key. extract :: < u64 > ( ) {
348+ Key :: Uint ( k)
349+ } else if let Ok ( k) = key. extract :: < bool > ( ) {
350+ Key :: Bool ( k)
351+ } else if let Ok ( k) = key. extract :: < String > ( ) {
352+ Key :: String ( k. into ( ) )
353+ } else {
354+ return Err ( CelError :: ConversionError (
355+ "Failed to convert PyDict key to Key" . to_string ( ) ,
356+ ) ) ;
357+ } ;
358+ if let Ok ( dict_value) = RustyPyType ( & value) . try_into_value ( ) {
359+ map. insert ( key, dict_value) ;
360+ } else {
361+ return Err ( CelError :: ConversionError (
362+ "Failed to convert PyDict value to Value" . to_string ( ) ,
363+ ) ) ;
364+ }
241365 }
366+ Ok ( Value :: Map ( map. into ( ) ) )
367+ } else {
368+ Ok ( Value :: DynamicMap ( Arc :: new ( PyMappingValue :: new (
369+ dict. clone ( ) . into_any ( ) . unbind ( ) ,
370+ ) ) ) )
242371 }
243- Ok ( Value :: Map ( map. into ( ) ) )
372+ } else if let Ok ( mapping) = pyobject. downcast :: < PyMapping > ( ) {
373+ Ok ( Value :: DynamicMap ( Arc :: new ( PyMappingValue :: new (
374+ mapping. clone ( ) . into_any ( ) . unbind ( ) ,
375+ ) ) ) )
244376 } else if let Ok ( value) = pyobject. extract :: < Vec < u8 > > ( ) {
245377 Ok ( Value :: Bytes ( value. into ( ) ) )
246378 } else {
0 commit comments