taskchampion-rb 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/TaskchampionRbErrors.md +54 -0
- data/ext/taskchampion/src/error.rs +15 -30
- data/ext/taskchampion/src/replica.rs +1 -1
- data/ext/taskchampion/src/task.rs +12 -12
- data/ext/taskchampion/src/thread_check.rs +2 -2
- data/ext/taskchampion/src/util.rs +0 -23
- data/lib/taskchampion/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6d963e64502ad438957e3345671387a3915de84947a63bbcced83270f5c50848
|
|
4
|
+
data.tar.gz: 19309ecfe6ccf1a5a077a513a7b6ff769109ba7949c0282617b8b74e3b54f116
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '052509a0688871de92bc3d4219d7c257b712b7f4b7f5693f4e051d2ff38f891d5b6095d66aaf0cec8d06466ec272ec1fe5bd7e166dd790fb2a0c649592a29d4b'
|
|
7
|
+
data.tar.gz: f21cca16bd5da5921474361eb81d18937a1e328313a4c3dac2ea521ccc4d7b93cf356816d11e67abfa7292bbcfb76e40548766b94718030e42a315a80c3e5531
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Taskchampion Ruby Errors
|
|
2
|
+
|
|
3
|
+
## Hierarchy
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
StandardError
|
|
7
|
+
└── Taskchampion::Error
|
|
8
|
+
├── Taskchampion::ThreadError
|
|
9
|
+
├── Taskchampion::StorageError
|
|
10
|
+
├── Taskchampion::ValidationError
|
|
11
|
+
├── Taskchampion::ConfigError
|
|
12
|
+
└── Taskchampion::SyncError
|
|
13
|
+
└── Taskchampion::OutOfSyncError
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Error Classes
|
|
17
|
+
|
|
18
|
+
- **`Taskchampion::Error`** — Base class for all Taskchampion errors.
|
|
19
|
+
- **`Taskchampion::ThreadError`** — Raised when an object (Replica, Task, etc.) is accessed from a thread other than the one that created it.
|
|
20
|
+
- **`Taskchampion::StorageError`** — Raised for database and storage failures (`Error::Database`), and for wrapped IO/SQLite/cloud infrastructure errors (`Error::Other`).
|
|
21
|
+
- **`Taskchampion::ValidationError`** — Raised for incorrect API usage (`Error::Usage`): bad tag names, empty descriptions, invalid UUIDs, wrong argument types, invalid status symbols.
|
|
22
|
+
- **`Taskchampion::ConfigError`** — Defined for compatibility; not raised by the current TaskChampion error variants. Missing sync keyword arguments raise Ruby `ArgumentError` instead.
|
|
23
|
+
- **`Taskchampion::SyncError`** — Raised for server communication errors (`Error::Server`).
|
|
24
|
+
- **`Taskchampion::OutOfSyncError`** — Raised when the local replica is irrecoverably out of sync with the server (`Error::OutOfSync`). Subclass of `SyncError`. Signals that re-syncing from scratch is required, not just a retry.
|
|
25
|
+
|
|
26
|
+
## Mapping from TaskChampion Rust errors
|
|
27
|
+
|
|
28
|
+
| Rust variant | Ruby class |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `Error::Database(msg)` | `StorageError` |
|
|
31
|
+
| `Error::Server(msg)` | `SyncError` |
|
|
32
|
+
| `Error::OutOfSync` | `OutOfSyncError` |
|
|
33
|
+
| `Error::Usage(msg)` | `ValidationError` |
|
|
34
|
+
| `Error::Other(_)` / unknown | `StorageError` |
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
Rescue any error via the base class or individually:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
begin
|
|
42
|
+
replica.sync(config)
|
|
43
|
+
rescue Taskchampion::OutOfSyncError => e
|
|
44
|
+
# local replica is irrecoverably diverged — must re-sync from scratch
|
|
45
|
+
rescue Taskchampion::SyncError => e
|
|
46
|
+
# transient server/network error — may retry
|
|
47
|
+
rescue Taskchampion::StorageError => e
|
|
48
|
+
# database or IO failure
|
|
49
|
+
rescue Taskchampion::ValidationError => e
|
|
50
|
+
# bad input
|
|
51
|
+
rescue Taskchampion::Error => e
|
|
52
|
+
# catch-all for any Taskchampion error
|
|
53
|
+
end
|
|
54
|
+
```
|
|
@@ -6,7 +6,8 @@ pub fn init_errors(module: &RModule) -> Result<(), Error> {
|
|
|
6
6
|
module.define_error("StorageError", error_class)?;
|
|
7
7
|
module.define_error("ValidationError", error_class)?;
|
|
8
8
|
module.define_error("ConfigError", error_class)?;
|
|
9
|
-
module.define_error("SyncError", error_class)?;
|
|
9
|
+
let sync_error_class = module.define_error("SyncError", error_class)?;
|
|
10
|
+
module.define_error("OutOfSyncError", sync_error_class)?;
|
|
10
11
|
Ok(())
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -34,45 +35,29 @@ pub fn validation_error() -> magnus::ExceptionClass {
|
|
|
34
35
|
.expect("ValidationError class not initialized")
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
pub fn
|
|
38
|
+
pub fn sync_error() -> magnus::ExceptionClass {
|
|
38
39
|
let ruby = magnus::Ruby::get().expect("Ruby not available");
|
|
39
40
|
let module = ruby.class_object().const_get::<_, RModule>("Taskchampion")
|
|
40
41
|
.expect("Taskchampion module not found");
|
|
41
|
-
module.const_get::<_, magnus::ExceptionClass>("
|
|
42
|
-
.expect("
|
|
42
|
+
module.const_get::<_, magnus::ExceptionClass>("SyncError")
|
|
43
|
+
.expect("SyncError class not initialized")
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
pub fn
|
|
46
|
+
pub fn out_of_sync_error() -> magnus::ExceptionClass {
|
|
46
47
|
let ruby = magnus::Ruby::get().expect("Ruby not available");
|
|
47
48
|
let module = ruby.class_object().const_get::<_, RModule>("Taskchampion")
|
|
48
49
|
.expect("Taskchampion module not found");
|
|
49
|
-
module.const_get::<_, magnus::ExceptionClass>("
|
|
50
|
-
.expect("
|
|
50
|
+
module.const_get::<_, magnus::ExceptionClass>("OutOfSyncError")
|
|
51
|
+
.expect("OutOfSyncError class not initialized")
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
// Enhanced error mapping function with context-aware error types
|
|
54
54
|
pub fn map_taskchampion_error(error: taskchampion::Error) -> Error {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
Error::new(
|
|
61
|
-
|
|
62
|
-
error_msg.contains("network") || error_msg.contains("remote") {
|
|
63
|
-
Error::new(sync_error(), format!("Synchronization error: {}", error_msg))
|
|
64
|
-
} else if error_msg.contains("config") || error_msg.contains("invalid config") {
|
|
65
|
-
Error::new(config_error(), format!("Configuration error: {}", error_msg))
|
|
66
|
-
} else if error_msg.contains("invalid") || error_msg.contains("parse") ||
|
|
67
|
-
error_msg.contains("format") || error_msg.contains("validation") {
|
|
68
|
-
Error::new(validation_error(), format!("Validation error: {}", error_msg))
|
|
69
|
-
} else {
|
|
70
|
-
// Generic TaskChampion error for unknown types
|
|
71
|
-
let ruby = magnus::Ruby::get().expect("Ruby not available");
|
|
72
|
-
let module = ruby.class_object().const_get::<_, RModule>("Taskchampion")
|
|
73
|
-
.expect("Taskchampion module not found");
|
|
74
|
-
let error_class = module.const_get::<_, magnus::ExceptionClass>("Error")
|
|
75
|
-
.expect("Error class not initialized");
|
|
76
|
-
Error::new(error_class, format!("TaskChampion error: {}", error_msg))
|
|
55
|
+
match error {
|
|
56
|
+
taskchampion::Error::Database(msg) => Error::new(storage_error(), msg),
|
|
57
|
+
taskchampion::Error::Server(msg) => Error::new(sync_error(), msg),
|
|
58
|
+
taskchampion::Error::OutOfSync => Error::new(out_of_sync_error(),
|
|
59
|
+
"Local replica is out of sync with the server"),
|
|
60
|
+
taskchampion::Error::Usage(msg) => Error::new(validation_error(), msg),
|
|
61
|
+
_ => Error::new(storage_error(), error.to_string()),
|
|
77
62
|
}
|
|
78
63
|
}
|
|
@@ -58,7 +58,7 @@ impl Replica {
|
|
|
58
58
|
let tc_task = tc_replica.create_task(tc_uuid, &mut tc_ops).map_err(into_error)?;
|
|
59
59
|
|
|
60
60
|
// Add the resulting operations to the provided Operations object
|
|
61
|
-
operations.extend_from_tc(tc_ops)
|
|
61
|
+
operations.extend_from_tc(tc_ops)?;
|
|
62
62
|
|
|
63
63
|
// Convert to Ruby Task object
|
|
64
64
|
let task = Task::from_tc_task(tc_task);
|
|
@@ -137,24 +137,22 @@ impl Task {
|
|
|
137
137
|
|
|
138
138
|
fn get_uda(&self, namespace: String, key: String) -> Result<Value, Error> {
|
|
139
139
|
let task = self.0.get()?;
|
|
140
|
-
|
|
140
|
+
let combined_key = if namespace.is_empty() { key } else { format!("{}.{}", namespace, key) };
|
|
141
|
+
match task.get_user_defined_attribute(&combined_key) {
|
|
141
142
|
Some(value) => Ok(value.into_value()),
|
|
142
|
-
None => Ok(().into_value()),
|
|
143
|
+
None => Ok(().into_value()),
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
fn udas(&self) -> Result<RArray, Error> {
|
|
147
148
|
let task = self.0.get()?;
|
|
148
|
-
let udas: Vec<(
|
|
149
|
-
.map(|(
|
|
149
|
+
let udas: Vec<(String, String)> = task.get_user_defined_attributes()
|
|
150
|
+
.map(|(key, value)| (key.to_string(), value.to_string()))
|
|
150
151
|
.collect();
|
|
151
152
|
|
|
152
|
-
vec_to_ruby(udas, |(
|
|
153
|
+
vec_to_ruby(udas, |(key, value)| {
|
|
153
154
|
let array = RArray::new();
|
|
154
|
-
|
|
155
|
-
key_array.push(key_tuple.0)?;
|
|
156
|
-
key_array.push(key_tuple.1)?;
|
|
157
|
-
array.push(key_array)?;
|
|
155
|
+
array.push(key)?;
|
|
158
156
|
array.push(value)?;
|
|
159
157
|
Ok(array.into_value())
|
|
160
158
|
})
|
|
@@ -360,7 +358,7 @@ impl Task {
|
|
|
360
358
|
Some(timestamp_str) => {
|
|
361
359
|
// Parse the string as Unix timestamp (seconds since epoch)
|
|
362
360
|
if let Ok(timestamp_secs) = timestamp_str.parse::<i64>() {
|
|
363
|
-
use chrono::
|
|
361
|
+
use chrono::DateTime;
|
|
364
362
|
if let Some(dt) = DateTime::from_timestamp(timestamp_secs, 0) {
|
|
365
363
|
return datetime_to_ruby(dt);
|
|
366
364
|
}
|
|
@@ -387,8 +385,9 @@ impl Task {
|
|
|
387
385
|
}
|
|
388
386
|
|
|
389
387
|
let mut task = self.0.get_mut()?;
|
|
388
|
+
let combined_key = format!("{}.{}", namespace, key);
|
|
390
389
|
operations.with_inner_mut(|ops| {
|
|
391
|
-
task.
|
|
390
|
+
task.set_user_defined_attribute(&combined_key, &value, ops)
|
|
392
391
|
})?;
|
|
393
392
|
Ok(())
|
|
394
393
|
}
|
|
@@ -408,8 +407,9 @@ impl Task {
|
|
|
408
407
|
}
|
|
409
408
|
|
|
410
409
|
let mut task = self.0.get_mut()?;
|
|
410
|
+
let combined_key = format!("{}.{}", namespace, key);
|
|
411
411
|
operations.with_inner_mut(|ops| {
|
|
412
|
-
task.
|
|
412
|
+
task.remove_user_defined_attribute(&combined_key, ops)
|
|
413
413
|
})?;
|
|
414
414
|
Ok(())
|
|
415
415
|
}
|
|
@@ -32,12 +32,12 @@ impl<T> ThreadBound<T> {
|
|
|
32
32
|
Ok(())
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
pub fn get(&self) -> Result<std::cell::Ref<T>, Error> {
|
|
35
|
+
pub fn get(&self) -> Result<std::cell::Ref<'_, T>, Error> {
|
|
36
36
|
self.check_thread()?;
|
|
37
37
|
Ok(self.inner.borrow())
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
pub fn get_mut(&self) -> Result<std::cell::RefMut<T>, Error> {
|
|
40
|
+
pub fn get_mut(&self) -> Result<std::cell::RefMut<'_, T>, Error> {
|
|
41
41
|
self.check_thread()?;
|
|
42
42
|
Ok(self.inner.borrow_mut())
|
|
43
43
|
}
|
|
@@ -31,8 +31,6 @@ pub fn datetime_to_ruby(dt: DateTime<Utc>) -> Result<Value, Error> {
|
|
|
31
31
|
|
|
32
32
|
/// Convert Ruby DateTime/Time/String to Rust DateTime<Utc> with enhanced validation
|
|
33
33
|
pub fn ruby_to_datetime(value: Value) -> Result<DateTime<Utc>, Error> {
|
|
34
|
-
let ruby = magnus::Ruby::get().map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
|
|
35
|
-
|
|
36
34
|
// If it's a string, parse it
|
|
37
35
|
if let Ok(s) = RString::try_convert(value) {
|
|
38
36
|
let s = unsafe { s.as_str()? };
|
|
@@ -99,15 +97,6 @@ where
|
|
|
99
97
|
}
|
|
100
98
|
}
|
|
101
99
|
|
|
102
|
-
/// Convert HashMap to Ruby Hash
|
|
103
|
-
pub fn hashmap_to_ruby(map: HashMap<String, String>) -> Result<RHash, Error> {
|
|
104
|
-
let hash = RHash::new();
|
|
105
|
-
for (k, v) in map {
|
|
106
|
-
hash.aset(k, v)?;
|
|
107
|
-
}
|
|
108
|
-
Ok(hash)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
100
|
/// Convert Ruby Hash to HashMap
|
|
112
101
|
pub fn ruby_to_hashmap(hash: RHash) -> Result<HashMap<String, String>, Error> {
|
|
113
102
|
let mut map = HashMap::new();
|
|
@@ -130,15 +119,3 @@ where
|
|
|
130
119
|
Ok(array)
|
|
131
120
|
}
|
|
132
121
|
|
|
133
|
-
/// Convert Ruby Array to Vec
|
|
134
|
-
pub fn ruby_to_vec<T, F>(array: RArray, converter: F) -> Result<Vec<T>, Error>
|
|
135
|
-
where
|
|
136
|
-
F: Fn(Value) -> Result<T, Error>,
|
|
137
|
-
{
|
|
138
|
-
let mut vec = Vec::with_capacity(array.len());
|
|
139
|
-
for i in 0..array.len() {
|
|
140
|
-
let value: Value = array.entry(i as isize)?;
|
|
141
|
-
vec.push(converter(value)?);
|
|
142
|
-
}
|
|
143
|
-
Ok(vec)
|
|
144
|
-
}
|
data/lib/taskchampion/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: taskchampion-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tim Case
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-05-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|
|
@@ -41,6 +41,7 @@ files:
|
|
|
41
41
|
- Cargo.toml
|
|
42
42
|
- README.md
|
|
43
43
|
- Rakefile
|
|
44
|
+
- TaskchampionRbErrors.md
|
|
44
45
|
- docs/ANNOTATION_IMPLEMENTATION.md
|
|
45
46
|
- docs/API_REFERENCE.md
|
|
46
47
|
- docs/THREAD_SAFETY.md
|