Skip to main content

nautilus_core/python/
parsing.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.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    prelude::*,
20    types::{PyDict, PyList},
21};
22
23use super::{to_pykey_err, to_pyvalue_err};
24
25/// Helper function to get a required string value from a Python dictionary.
26///
27/// # Returns
28///
29/// Returns the extracted string value or a `PyErr` if the key is missing or extraction fails.
30///
31/// # Errors
32///
33/// Returns `PyErr` if the key is missing or value extraction fails.
34pub fn get_required_string(dict: &Bound<'_, PyDict>, key: &str) -> PyResult<String> {
35    dict.get_item(key)?
36        .ok_or_else(|| to_pykey_err(format!("Missing required key: {key}")))?
37        .extract()
38}
39
40/// Helper function to get a required value from a Python dictionary and extract it.
41///
42/// # Returns
43///
44/// Returns the extracted value or a `PyErr` if the key is missing or extraction fails.
45///
46/// # Errors
47///
48/// Returns `PyErr` if the key is missing or value extraction fails.
49pub fn get_required<T>(dict: &Bound<'_, PyDict>, key: &str) -> PyResult<T>
50where
51    T: for<'a, 'py> FromPyObject<'a, 'py>,
52    for<'a, 'py> PyErr: From<<T as FromPyObject<'a, 'py>>::Error>,
53{
54    dict.get_item(key)?
55        .ok_or_else(|| to_pykey_err(format!("Missing required key: {key}")))?
56        .extract()
57        .map_err(PyErr::from)
58}
59
60/// Helper function to get an optional value from a Python dictionary.
61///
62/// # Returns
63///
64/// Returns Some(value) if the key exists and extraction succeeds, None if the key is missing
65/// or if the value is Python None, or a `PyErr` if extraction fails.
66///
67/// # Errors
68///
69/// Returns `PyErr` if value extraction fails (but not if the key is missing or value is None).
70pub fn get_optional<T>(dict: &Bound<'_, PyDict>, key: &str) -> PyResult<Option<T>>
71where
72    T: for<'a, 'py> FromPyObject<'a, 'py>,
73    for<'a, 'py> PyErr: From<<T as FromPyObject<'a, 'py>>::Error>,
74{
75    match dict.get_item(key)? {
76        Some(value) => {
77            if value.is_none() {
78                Ok(None)
79            } else {
80                value.extract().map(Some).map_err(PyErr::from)
81            }
82        }
83        None => Ok(None),
84    }
85}
86
87/// Helper function to get a required value, parse it with a closure, and handle parse errors.
88///
89/// # Returns
90///
91/// Returns the parsed value or a `PyErr` if the key is missing, extraction fails, or parsing fails.
92///
93/// # Errors
94///
95/// Returns `PyErr` if the key is missing, value extraction fails, or parsing fails.
96pub fn get_required_parsed<T, F>(dict: &Bound<'_, PyDict>, key: &str, parser: F) -> PyResult<T>
97where
98    F: FnOnce(String) -> Result<T, String>,
99{
100    let value_str = get_required_string(dict, key)?;
101    parser(value_str).map_err(|e| to_pyvalue_err(format!("Failed to parse '{key}': {e}")))
102}
103
104/// Helper function to get an optional value, parse it with a closure, and handle parse errors.
105///
106/// # Returns
107///
108/// Returns `Some(parsed_value)` if the key exists and parsing succeeds, None if the key is missing
109/// or if the value is Python None, or a `PyErr` if extraction or parsing fails.
110///
111/// # Errors
112///
113/// Returns `PyErr` if value extraction or parsing fails (but not if the key is missing or value is None).
114pub fn get_optional_parsed<T, F>(
115    dict: &Bound<'_, PyDict>,
116    key: &str,
117    parser: F,
118) -> PyResult<Option<T>>
119where
120    F: FnOnce(String) -> Result<T, String>,
121{
122    match dict.get_item(key)? {
123        Some(value) => {
124            if value.is_none() {
125                Ok(None)
126            } else {
127                let value_str: String = value.extract()?;
128                parser(value_str)
129                    .map(Some)
130                    .map_err(|e| to_pyvalue_err(format!("Failed to parse '{key}': {e}")))
131            }
132        }
133        None => Ok(None),
134    }
135}
136
137/// Helper function to get a required `PyList` from a Python dictionary.
138///
139/// # Returns
140///
141/// Returns the extracted `PyList` or a `PyErr` if the key is missing or extraction fails.
142///
143/// # Errors
144///
145/// Returns `PyErr` if the key is missing or value extraction fails.
146pub fn get_required_list<'py>(
147    dict: &Bound<'py, PyDict>,
148    key: &str,
149) -> PyResult<Bound<'py, PyList>> {
150    dict.get_item(key)?
151        .ok_or_else(|| to_pykey_err(format!("Missing required key: {key}")))?
152        .downcast_into()
153        .map_err(Into::into)
154}