nautilus_core/python/parsing.rs
1// -------------------------------------------------------------------------------------------------
2// Copyright (C) 2015-2025 Posei Systems Pty Ltd. All rights reserved.
3// https://poseitrader.io
4//
5// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6// You may not use this file except in compliance with the License.
7// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! JSON / string parsing helpers for Python inputs.
17
18use pyo3::{
19 exceptions::PyKeyError,
20 prelude::*,
21 types::{PyDict, PyList},
22};
23
24/// Helper function to get a required string value from a Python dictionary.
25///
26/// # Returns
27///
28/// Returns the extracted string value or a `PyErr` if the key is missing or extraction fails.
29///
30/// # Errors
31///
32/// Returns `PyErr` if the key is missing or value extraction fails.
33pub fn get_required_string(dict: &Bound<'_, PyDict>, key: &str) -> PyResult<String> {
34 dict.get_item(key)?
35 .ok_or_else(|| PyKeyError::new_err(format!("Missing required key: {key}")))?
36 .extract()
37}
38
39/// Helper function to get a required value from a Python dictionary and extract it.
40///
41/// # Returns
42///
43/// Returns the extracted value or a `PyErr` if the key is missing or extraction fails.
44///
45/// # Errors
46///
47/// Returns `PyErr` if the key is missing or value extraction fails.
48pub fn get_required<T>(dict: &Bound<'_, PyDict>, key: &str) -> PyResult<T>
49where
50 T: for<'py> FromPyObject<'py>,
51{
52 dict.get_item(key)?
53 .ok_or_else(|| PyKeyError::new_err(format!("Missing required key: {key}")))?
54 .extract()
55}
56
57/// Helper function to get an optional value from a Python dictionary.
58///
59/// # Returns
60///
61/// Returns Some(value) if the key exists and extraction succeeds, None if the key is missing
62/// or if the value is Python None, or a `PyErr` if extraction fails.
63///
64/// # Errors
65///
66/// Returns `PyErr` if value extraction fails (but not if the key is missing or value is None).
67pub fn get_optional<T>(dict: &Bound<'_, PyDict>, key: &str) -> PyResult<Option<T>>
68where
69 T: for<'py> FromPyObject<'py>,
70{
71 match dict.get_item(key)? {
72 Some(value) => {
73 if value.is_none() {
74 Ok(None)
75 } else {
76 Ok(Some(value.extract()?))
77 }
78 }
79 None => Ok(None),
80 }
81}
82
83/// Helper function to get a required value, parse it with a closure, and handle parse errors.
84///
85/// # Returns
86///
87/// Returns the parsed value or a `PyErr` if the key is missing, extraction fails, or parsing fails.
88///
89/// # Errors
90///
91/// Returns `PyErr` if the key is missing, value extraction fails, or parsing fails.
92pub fn get_required_parsed<T, F>(dict: &Bound<'_, PyDict>, key: &str, parser: F) -> PyResult<T>
93where
94 F: FnOnce(String) -> Result<T, String>,
95{
96 let value_str = get_required_string(dict, key)?;
97 parser(value_str).map_err(|e| PyKeyError::new_err(format!("Failed to parse {key}: {e}")))
98}
99
100/// Helper function to get an optional value, parse it with a closure, and handle parse errors.
101///
102/// # Returns
103///
104/// Returns `Some(parsed_value)` if the key exists and parsing succeeds, None if the key is missing
105/// or if the value is Python None, or a `PyErr` if extraction or parsing fails.
106///
107/// # Errors
108///
109/// Returns `PyErr` if value extraction or parsing fails (but not if the key is missing or value is None).
110pub fn get_optional_parsed<T, F>(
111 dict: &Bound<'_, PyDict>,
112 key: &str,
113 parser: F,
114) -> PyResult<Option<T>>
115where
116 F: FnOnce(String) -> Result<T, String>,
117{
118 match dict.get_item(key)? {
119 Some(value) => {
120 if value.is_none() {
121 Ok(None)
122 } else {
123 let value_str: String = value.extract()?;
124 parser(value_str)
125 .map(Some)
126 .map_err(|e| PyKeyError::new_err(format!("Failed to parse {key}: {e}")))
127 }
128 }
129 None => Ok(None),
130 }
131}
132
133/// Helper function to get a required `PyList` from a Python dictionary.
134///
135/// # Returns
136///
137/// Returns the extracted `PyList` or a `PyErr` if the key is missing or extraction fails.
138///
139/// # Errors
140///
141/// Returns `PyErr` if the key is missing or value extraction fails.
142pub fn get_required_list<'py>(
143 dict: &Bound<'py, PyDict>,
144 key: &str,
145) -> PyResult<Bound<'py, PyList>> {
146 dict.get_item(key)?
147 .ok_or_else(|| PyKeyError::new_err(format!("Missing required key: {key}")))?
148 .extract()
149}