You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Refactor Rust SDK errors to use structs with a kind() method (#1400)
* Refactor Rust SDK errors to use structs with a `kind()` method
The conventional `#[non_exhaustive] enum Error { ... }` pattern appears
safe but creates problems as a library evolves. This PR changes all
error types to the struct-with-`kind()` pattern, which also aligns with
the Azure SDK for Rust error design.
Why not a flat error enum:
- `#[non_exhaustive]` on the enum prevents exhaustive matching, but
individual variants are still fixed. Adding a field to any variant —
even just to improve an error message with a line number or file path
— is a breaking change.
- Adding context data is harder than it looks. With a flat enum, new
fields touch every affected variant and all match arms across the
codebase. With a struct, new fields are added in one place and callers
who don't use them are unaffected.
- A single enum conflates all failure modes, making it impossible to
document or guarantee which variants a given function can actually
return. Callers must handle unrelated variants they will never see, or
accept a wildcard arm that silently swallows future additions.
The struct + kind pattern:
| Concern | Flat enum | Struct + `kind()` |
|---|---|---|
| Categorization | Match directly on variant | Call `.kind()` → `&*Kind` |
| Adding context | Breaking: add fields to variant | Non-breaking: add fields to struct |
| `non_exhaustive` | On enum; variants are fixed | Not needed on struct with only private fields |
| Simple display | Must match all variants | `format!("{err}")` — no match needed |
Callers who only want to display or propagate an error with `?` do not
need to call `.kind()` at all. Only callers who need to inspect the
failure category call `.kind()`, and they get a stable, scoped `*Kind`
enum to match against.
* Remove unnecessary error structs
Sticking with `*Kind` as a convention for error enums.
* Add backtrace support to Error struct
Enhanced the Error struct to include an optional backtrace, which is captured only when `RUST_BACKTRACE` is set. This change helps in debugging by providing context on error occurrences without inflating the Error size unnecessarily.
* Resolve PR feedback
Copy file name to clipboardExpand all lines: .github/copilot-instructions.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -31,6 +31,7 @@
31
31
- Java single test: `cd java && mvn test -Dtest=CopilotClientTest` | single method: `mvn test -Dtest=ToolsTest#testToolInvocation`
32
32
- Java format check only: `mvn spotless:check` | Build without tests: `mvn clean package -DskipTests`
33
33
-**Java testing note:** Always use `mvn verify` without `-q` and without piping through `grep`. Never add `InternalsVisibleTo` equivalent — tests must only access public APIs.
34
+
- Use configured LSPs for supported operations like finding references instead of pattern matching, renaming symbols, etc.
Copy file name to clipboardExpand all lines: .github/skills/rust-coding-skill/SKILL.md
+15-16Lines changed: 15 additions & 16 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,11 +13,10 @@ Opinionated Rust rules for the Copilot Rust SDK (`rust/`). Priority order:
13
13
14
14
## Error handling
15
15
16
-
The SDK's public error type is `crate::Error` (`rust/src/error.rs`). Add new
17
-
variants there rather than introducing parallel error enums per module — every
18
-
public failure mode is part of the API contract and should be expressible in one
19
-
type. Internal modules can use `thiserror` enums when a richer local taxonomy
20
-
helps; convert at the boundary.
16
+
The SDK's public error type is `crate::Error` (`rust/src/errors.rs`). Add new
17
+
variants to `crate::ErrorKind` rather than introducing parallel error enums
18
+
per module — every public failure mode is part of the API contract and should
19
+
be expressible in one type.
21
20
22
21
`anyhow` is reserved for binaries and example code. Library code never returns
23
22
`anyhow::Result` — callers can't pattern-match on `anyhow::Error`, so it would
@@ -42,7 +41,7 @@ it on shutdown. Fire-and-forget spawns silently swallow panics and outlive the
42
41
session; don't.
43
42
44
43
Blocking calls (filesystem, subprocess wait) belong in
45
-
`tokio::task::spawn_blocking`, *not* on the async runtime. The blocking pool is
44
+
`tokio::task::spawn_blocking`, _not_ on the async runtime. The blocking pool is
46
45
bounded, so for genuinely long-lived workers (think: file watchers that run for
47
46
the lifetime of a session) prefer `std::thread::spawn` with a channel back into
48
47
async land.
@@ -81,12 +80,12 @@ Trivial field re-shaping is best inlined. Closures should stay short (under ~10
81
80
82
81
**Channels, not callback closures, for event flow.** Closures fight `Send + Sync + 'static` and don't compose with `select!`. Channel choice by semantics:
83
82
84
-
| Use case | Primitive |
85
-
|---|---|
86
-
| One producer → one consumer with backpressure |`tokio::sync::mpsc` (cap 1) or `tokio::sync::oneshot` for single value |
87
-
| Many producers → one consumer |`tokio::sync::mpsc`|
88
-
| One producer → many consumers, every event delivered (pub/sub) |`tokio::sync::broadcast`|
89
-
| One producer → many consumers, only the latest value matters |`tokio::sync::watch`|
| One producer → one consumer with backpressure |`tokio::sync::mpsc` (cap 1) or `tokio::sync::oneshot` for single value |
86
+
| Many producers → one consumer |`tokio::sync::mpsc`|
87
+
| One producer → many consumers, every event delivered (pub/sub) |`tokio::sync::broadcast`|
88
+
| One producer → many consumers, only the latest value matters |`tokio::sync::watch`|
90
89
91
90
For the **public** API, prefer returning `impl Stream<Item = Event>` (wrap a `broadcast::Receiver` in `tokio_stream::wrappers::BroadcastStream`). `Stream` composes with `select!`, `take`, `map`, `filter`, `timeout`. See `EventSubscription` and `LifecycleSubscription`.
92
91
@@ -115,7 +114,7 @@ JSON: `#[serde(rename_all = "camelCase")]` at the type level, per-field `#[serde
115
114
Banned via `clippy.toml`. Use manual spans with `error_span!`:
116
115
117
116
-**Almost always use `error_span!`**, not `info_span!`. Span level controls
118
-
the *minimum* filter at which the span appears. An `info_span` disappears when
117
+
the _minimum_ filter at which the span appears. An `info_span` disappears when
119
118
the filter is `warn` or `error` — taking all child events with it, even
120
119
errors. `error_span!` ensures the span is always present.
121
120
-**Spawned tasks lose parent context.** Attach a span with `.instrument()` or
@@ -239,9 +238,9 @@ Match those exact commands locally before pushing.
239
238
240
239
JSON-RPC and session-event types are generated from the Copilot CLI schema:
0 commit comments