Skip to content

Commit f36abaf

Browse files
committed
fix(bindings): replace Result<_, String> with typed ShoppingListError
UniFFI 0.28 rejects plain String error types during binding generation, causing release workflow to fail with "unknown throw type: Some(String)" on both Android (Kotlin) and iOS (Swift) jobs. Introduce a ShoppingListError enum (Parse/Serialize variants) derived with thiserror::Error and uniffi::Error, and update the three exported shopping list functions to return it.
1 parent 1e85c03 commit f36abaf

4 files changed

Lines changed: 35 additions & 11 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindings/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ readme = "README.md"
1212
[dependencies]
1313
anyhow = "1.0"
1414
cooklang = { path = "..", default-features = false, features = ["aisle", "shopping_list"] }
15+
thiserror = "2"
1516
uniffi = "0.28.1"
1617

1718
[lib]

bindings/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ fn parse_fraction(s: &str) -> Option<f64> {
598598
#[uniffi::export]
599599
pub fn parse_shopping_list(
600600
input: String,
601-
) -> Result<shopping_list::ShoppingList, String> {
601+
) -> Result<shopping_list::ShoppingList, shopping_list::ShoppingListError> {
602602
shopping_list::parse_shopping_list_impl(&input)
603603
}
604604

@@ -612,7 +612,7 @@ pub fn parse_shopping_list(
612612
#[uniffi::export]
613613
pub fn write_shopping_list(
614614
list: &shopping_list::ShoppingList,
615-
) -> Result<String, String> {
615+
) -> Result<String, shopping_list::ShoppingListError> {
616616
shopping_list::write_shopping_list_impl(list)
617617
}
618618

@@ -664,7 +664,7 @@ pub fn shopping_checked_set(
664664
#[uniffi::export]
665665
pub fn write_shopping_check_entry(
666666
entry: &shopping_list::CheckEntry,
667-
) -> Result<String, String> {
667+
) -> Result<String, shopping_list::ShoppingListError> {
668668
shopping_list::write_check_entry_impl(entry)
669669
}
670670

bindings/src/shopping_list.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ use cooklang::shopping_list::{
55
};
66
use std::collections::HashSet;
77

8+
/// Errors returned by shopping list binding functions.
9+
///
10+
/// UniFFI does not support returning plain `String` as an error across the FFI
11+
/// boundary, so we wrap string messages in a typed error enum.
12+
#[derive(Debug, thiserror::Error, uniffi::Error)]
13+
pub enum ShoppingListError {
14+
#[error("failed to parse shopping list: {message}")]
15+
Parse { message: String },
16+
#[error("failed to serialize shopping list: {message}")]
17+
Serialize { message: String },
18+
}
19+
820
// ---------------------------------------------------------------------------
921
// UniFFI wrapper types
1022
// ---------------------------------------------------------------------------
@@ -126,18 +138,24 @@ impl From<&CheckEntry> for OriginalCheckEntry {
126138
// ---------------------------------------------------------------------------
127139

128140
/// Parse a `.shopping-list` file into a ShoppingList.
129-
pub fn parse_shopping_list_impl(input: &str) -> Result<ShoppingList, String> {
141+
pub fn parse_shopping_list_impl(input: &str) -> Result<ShoppingList, ShoppingListError> {
130142
sl::parse(input)
131143
.map(|list| ShoppingList::from(&list))
132-
.map_err(|e| e.to_string())
144+
.map_err(|e| ShoppingListError::Parse {
145+
message: e.to_string(),
146+
})
133147
}
134148

135149
/// Serialize a ShoppingList back to the `.shopping-list` format.
136-
pub fn write_shopping_list_impl(list: &ShoppingList) -> Result<String, String> {
150+
pub fn write_shopping_list_impl(list: &ShoppingList) -> Result<String, ShoppingListError> {
137151
let original = OriginalShoppingList::from(list);
138152
let mut buf = Vec::new();
139-
sl::write(&original, &mut buf).map_err(|e| e.to_string())?;
140-
String::from_utf8(buf).map_err(|e| e.to_string())
153+
sl::write(&original, &mut buf).map_err(|e| ShoppingListError::Serialize {
154+
message: e.to_string(),
155+
})?;
156+
String::from_utf8(buf).map_err(|e| ShoppingListError::Serialize {
157+
message: e.to_string(),
158+
})
141159
}
142160

143161
/// Parse a `.shopping-checked` log file.
@@ -156,11 +174,15 @@ pub fn checked_set_impl(entries: &[CheckEntry]) -> HashSet<String> {
156174
}
157175

158176
/// Serialize a single check entry to string.
159-
pub fn write_check_entry_impl(entry: &CheckEntry) -> Result<String, String> {
177+
pub fn write_check_entry_impl(entry: &CheckEntry) -> Result<String, ShoppingListError> {
160178
let original = OriginalCheckEntry::from(entry);
161179
let mut buf = Vec::new();
162-
sl::write_check_entry(&original, &mut buf).map_err(|e| e.to_string())?;
163-
String::from_utf8(buf).map_err(|e| e.to_string())
180+
sl::write_check_entry(&original, &mut buf).map_err(|e| ShoppingListError::Serialize {
181+
message: e.to_string(),
182+
})?;
183+
String::from_utf8(buf).map_err(|e| ShoppingListError::Serialize {
184+
message: e.to_string(),
185+
})
164186
}
165187

166188
/// Compact a checked log against the ingredient names currently in the

0 commit comments

Comments
 (0)