@smbcloud / cli / commits / 18b47ce

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 {