Refactor Python FFI error handling for clarity and propagation
- Use combinators to propagate errors in all #[pyfunction] wrappers - Remove unnecessary `.into()` in serialize - Suppress clippy::useless_conversion for macro-generated code - Simplify Apple callback state check with let-else pattern
keypair34 committed
Apr 12, 2026 at 08:13 UTC
18b47cebb4a06b2dc959f806c0bad1151d30c790
3 files changed
+61
-57
crates/cli/src/deploy/known_hosts.rs
-1
@@ -29,7 +29,6 @@
29
/// ```sh
30
/// ssh-keyscan -t ed25519 api.smbcloud.xyz api-1.smbcloud.xyz 2>/dev/null
31
/// ```
32
-
32
/// Pinned ed25519 host key for `api.smbcloud.xyz` (NodeJs / Static tier).
33
pub const API_SMBCLOUD_XYZ: &str =
34
"api.smbcloud.xyz ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINGy4LHGyOilS7SXo770V3tQnXDRVQr7X7JsPLCfy4XB";
crates/smbcloud-auth-sdk-py/src/lib.rs
+54
-49
@@ -1,4 +1,9 @@
1
#![allow(unexpected_cfgs, unsafe_op_in_unsafe_fn)]
2
+// The #[pyfunction] macro generates FFI wrapper code that calls From<PyErr>::from(err)
3
+// on the error path of every wrapped function. Since PyErr: From<PyErr> is the identity
4
+// impl, clippy flags it as useless_conversion. We cannot change macro-generated code,
5
+// so suppress the lint at crate level.
6
+#![allow(clippy::useless_conversion)]
7
8
use pyo3::{
9
create_exception,
@@ -67,7 +72,7 @@ fn serialize<'py, T>(py: Python<'py>, value: &T) -> PyResult<PyObject>
72
where
73
T: Serialize,
74
{
70
- Ok(pythonize(py, value)?.unbind().into())
75
+ Ok(pythonize(py, value)?.unbind())
76
}
77
78
fn login_payload(status: AccountStatus) -> LoginPayload {
@@ -120,18 +125,18 @@ fn signup_with_client(
125
email: &str,
126
password: &str,
127
) -> PyResult<PyObject> {
123
- let env = parse_env(env)?;
124
- let client = client_credentials(app_id, app_secret);
125
- let result = runtime()
126
- .block_on(rust_signup_with_client(
127
- env,
128
- client,
129
- email.to_string(),
130
- password.to_string(),
131
- ))
132
- .map_err(native_error)?;
133
-
134
- serialize(py, &result)
128
+ parse_env(env).and_then(|env| {
129
+ let client = client_credentials(app_id, app_secret);
130
+ runtime()
131
+ .block_on(rust_signup_with_client(
132
+ env,
133
+ client,
134
+ email.to_string(),
135
+ password.to_string(),
136
+ ))
137
+ .map_err(native_error)
138
+ .and_then(|result| serialize(py, &result))
139
+ })
140
}
141
142
#[pyfunction]
@@ -143,19 +148,19 @@ fn login_with_client(
148
email: &str,
149
password: &str,
150
) -> PyResult<PyObject> {
146
- let env = parse_env(env)?;
147
- let client = client_credentials(app_id, app_secret);
148
- let result = runtime()
149
- .block_on(rust_login_with_client(
150
- env,
151
- client,
152
- email.to_string(),
153
- password.to_string(),
154
- ))
155
- .map(login_payload)
156
- .map_err(native_error)?;
157
-
158
- serialize(py, &result)
151
+ parse_env(env).and_then(|env| {
152
+ let client = client_credentials(app_id, app_secret);
153
+ runtime()
154
+ .block_on(rust_login_with_client(
155
+ env,
156
+ client,
157
+ email.to_string(),
158
+ password.to_string(),
159
+ ))
160
+ .map(login_payload)
161
+ .map_err(native_error)
162
+ .and_then(|result| serialize(py, &result))
163
+ })
164
}
165
166
#[pyfunction]
@@ -165,16 +170,16 @@ fn logout_with_client(
170
app_secret: &str,
171
access_token: &str,
172
) -> PyResult<()> {
168
- let env = parse_env(env)?;
169
- let client = client_credentials(app_id, app_secret);
170
-
171
- runtime()
172
- .block_on(rust_logout_with_client(
173
- env,
174
- client,
175
- access_token.to_string(),
176
- ))
177
- .map_err(native_error)
173
+ parse_env(env).and_then(|env| {
174
+ let client = client_credentials(app_id, app_secret);
175
+ runtime()
176
+ .block_on(rust_logout_with_client(
177
+ env,
178
+ client,
179
+ access_token.to_string(),
180
+ ))
181
+ .map_err(native_error)
182
+ })
183
}
184
185
#[pyfunction]
@@ -185,13 +190,13 @@ fn me_with_client(
190
app_secret: &str,
191
access_token: &str,
192
) -> PyResult<PyObject> {
188
- let env = parse_env(env)?;
189
- let client = client_credentials(app_id, app_secret);
190
- let result = runtime()
191
- .block_on(rust_me_with_client(env, client, access_token))
192
- .map_err(native_error)?;
193
-
194
- serialize(py, &result)
193
+ parse_env(env).and_then(|env| {
194
+ let client = client_credentials(app_id, app_secret);
195
+ runtime()
196
+ .block_on(rust_me_with_client(env, client, access_token))
197
+ .map_err(native_error)
198
+ .and_then(|result| serialize(py, &result))
199
+ })
200
}
201
202
#[pyfunction]
@@ -201,12 +206,12 @@ fn remove_with_client(
206
app_secret: &str,
207
access_token: &str,
208
) -> PyResult<()> {
204
- let env = parse_env(env)?;
205
- let client = client_credentials(app_id, app_secret);
206
-
207
- runtime()
208
- .block_on(rust_remove_with_client(env, client, access_token))
209
- .map_err(native_error)
209
+ parse_env(env).and_then(|env| {
210
+ let client = client_credentials(app_id, app_secret);
211
+ runtime()
212
+ .block_on(rust_remove_with_client(env, client, access_token))
213
+ .map_err(native_error)
214
+ })
215
}
216
217
#[pymodule(name = "_native")]
crates/smbcloud-auth-sdk/src/apple.rs
+7
-7
@@ -87,13 +87,13 @@ pub fn parse_callback_url(
87
});
88
}
89
90
- if let Some(expected_state) = expected_state {
91
- if state.as_deref() != Some(expected_state) {
92
- return Err(ErrorResponse::Error {
93
- error_code: ErrorCode::InvalidParams,
94
- message: "Apple callback state mismatch.".to_string(),
95
- });
96
- }
90
+ if let Some(expected_state) = expected_state
91
+ && state.as_deref() != Some(expected_state)
92
+ {
93
+ return Err(ErrorResponse::Error {
94
+ error_code: ErrorCode::InvalidParams,
95
+ message: "Apple callback state mismatch.".to_string(),
96
+ });
97
}
98
99
Ok(AppleAuthSession {