Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
target
dist
node_modules
playwright-report

# Environment files and secrets
.env
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,27 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Added - 2026-05-10
- Added `ras-version-core` `0.1.0` with the shared `VersionMigration<From, To>` trait for opt-in API compatibility migrations.
- `ras-jsonrpc-macro`: Added opt-in versioned JSON-RPC methods. Legacy wire methods can migrate legacy requests into canonical request types, call the canonical trait method, and migrate canonical responses back to legacy response types.
- `ras-rest-macro`: Added opt-in versioned REST endpoints. Legacy routes can migrate generated request-part structs into canonical request parts before invoking the canonical service method, then migrate response bodies back to legacy response types.
- `ras-jsonrpc-macro` and `ras-rest-macro`: Generated clients and OpenRPC/OpenAPI specs now include versioned compatibility methods/routes when configured.
- Added REST and JSON-RPC Playwright explorer coverage for versioned compatibility routes and wire methods.

### Fixed - 2026-05-10
- `ras-rest-macro`: Generated REST clients now serialize query parameters through reqwest's serde-backed query path, support repeated-key `Vec<T>` and `Option<Vec<T>>` query params, and honor serde-renamed enum values without requiring `Display`. Fixes #3.

### Changed - 2026-05-10
- `ras-jsonrpc-macro`: Generated service setup now matches REST's trait-backed model. Users implement the generated service trait and pass the implementation to `ServiceBuilder::new(service)`, with `.base_url(...)` for custom JSON-RPC route paths.
- Bumped `ras-jsonrpc-macro` from `0.1.2` to `0.2.0` because the generated JSON-RPC server setup changed from handler setters to a required service trait implementation.
- Bumped `ras-jsonrpc-core` from `0.1.1` to `0.1.2` for the additive `VersionMigration` re-export.
- Bumped `ras-rest-core` from `0.1.0` to `0.1.1` for the additive `VersionMigration` re-export.
- Bumped `ras-rest-macro` from `0.2.0` to `0.2.1` for additive versioned endpoint/client/spec generation.
- Bumped `ras-rest-macro` from `0.1.1` to `0.2.0` because generated client query params now use serde serialization instead of `Display`/`ToString`.

### Documentation - 2026-05-10
- Updated JSON-RPC, REST, identity, observability, example, and Playwright documentation for trait-backed service setup, current auth syntax, current crate names, and versioned API migration examples.

### Added - 2026-05-09
- Established repository versioning and changelog policy in `VERSIONING.md`.
- Added doc-comment support for generated API documentation:
Expand Down
14 changes: 10 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,21 @@ jsonrpc_service!({
// Implement the generated trait
struct TaskServiceImpl { /* ... */ }

#[async_trait::async_trait]
impl TaskServiceHandler for TaskServiceImpl {
async fn sign_in(&self, request: SignInRequest) -> JsonRpcResult<SignInResponse> {
impl TaskServiceTrait for TaskServiceImpl {
async fn sign_in(
&self,
request: SignInRequest,
) -> Result<SignInResponse, Box<dyn std::error::Error + Send + Sync>> {
// Your implementation
}
// ... other methods
}

// Use with the builder
let service = TaskService::builder()
let router = TaskServiceBuilder::new(TaskServiceImpl { /* ... */ })
.base_url("/rpc")
.auth_provider(JwtAuthProvider::new())
.build(Arc::new(TaskServiceImpl { /* ... */ }));
.build()?;
```

### Type-Safe REST APIs
Expand Down Expand Up @@ -278,10 +281,10 @@ use ras_observability_otel::standard_setup;
let otel = standard_setup("my-service")?;

// Use with service builders
let service = MyServiceBuilder::new(impl)
let service = MyServiceBuilder::new(MyServiceImpl::new())
.with_usage_tracker(otel.usage_tracker())
.with_method_duration_tracker(otel.duration_tracker())
.build();
.build()?;

// Metrics available at /metrics endpoint
```
Expand Down
10 changes: 10 additions & 0 deletions crates/core/ras-version-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "ras-version-core"
version = "0.1.0"
edition = "2024"
description = "Core traits for versioned API migrations in Rust Agent Stack"
license = "MIT OR Apache-2.0"
repository = "https://github.com/example/rust-agent-stack"
homepage = "https://github.com/example/rust-agent-stack"

[dependencies]
14 changes: 14 additions & 0 deletions crates/core/ras-version-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//! Core traits for versioned API migrations.

/// Converts one API version type into another.
///
/// Service macros use this trait for opt-in compatibility paths where a legacy
/// request is upgraded into the canonical request type, and the canonical
/// response is downgraded back into the legacy response type.
pub trait VersionMigration<From, To> {
/// Error returned when a version migration cannot be performed.
type Error: std::fmt::Display + Send + Sync + 'static;

/// Convert `value` from one API version type into another.
fn migrate(value: From) -> Result<To, Self::Error>;
}
30 changes: 28 additions & 2 deletions crates/identity/ras-identity-session/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,41 @@ Example JWT payload:

```rust
use ras_jsonrpc_macro::jsonrpc_service;
use ras_identity_session::JwtAuthProvider;

jsonrpc_service!({
service_name: MyService,
auth_provider: JwtAuthProvider,
methods: [
WITH_PERMISSIONS(["read"]) get_data(GetRequest) -> GetResponse,
WITH_PERMISSIONS(["write"]) update_data(UpdateRequest) -> UpdateResponse,
]
});

struct MyServiceImpl;

impl MyServiceTrait for MyServiceImpl {
async fn get_data(
&self,
user: &ras_jsonrpc_core::AuthenticatedUser,
request: GetRequest,
) -> Result<GetResponse, Box<dyn std::error::Error + Send + Sync>> {
// Load data for `user`.
}

async fn update_data(
&self,
user: &ras_jsonrpc_core::AuthenticatedUser,
request: UpdateRequest,
) -> Result<UpdateResponse, Box<dyn std::error::Error + Send + Sync>> {
// Update data for `user`.
}
}

let auth_provider = JwtAuthProvider::new(session_service.clone());
let router = MyServiceBuilder::new(MyServiceImpl)
.base_url("/rpc")
.auth_provider(auth_provider)
.build()?;
```

### With WebSocket Authentication
Expand Down Expand Up @@ -161,4 +187,4 @@ The session service integrates with bidirectional JSON-RPC WebSocket services, s
- **Secret**: JWT signing secret (required)
- **TTL**: Token time-to-live in seconds
- **Algorithm**: JWT signing algorithm (default: HS256)
- **Refresh**: Enable/disable refresh token support (experimental)
- **Refresh**: Enable/disable refresh token support (experimental)
11 changes: 8 additions & 3 deletions crates/observability/ras-observability-otel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,15 @@ let usage_tracker = {
}
};

// Add to your service
MyServiceBuilder::new()
// REST service builders take the trait implementation.
MyServiceBuilder::new(MyServiceImpl::new())
.with_usage_tracker(usage_tracker)
.build()

// JSON-RPC service builders also take the trait implementation.
MyRpcServiceBuilder::new(MyRpcServiceImpl::new())
.with_usage_tracker(rpc_usage_tracker)
.build()
```

## Metrics Exposed
Expand Down Expand Up @@ -80,4 +85,4 @@ See the `examples/` directory for:
cargo run --example simple_usage -p ras-observability-otel

# Then visit http://localhost:3000/metrics
```
```
5 changes: 3 additions & 2 deletions crates/rest/ras-rest-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ras-rest-core"
version = "0.1.0"
version = "0.1.1"
edition = "2024"
description = "Core types and traits for REST services in Rust Agent Stack"
license = "MIT OR Apache-2.0"
Expand All @@ -10,4 +10,5 @@ homepage = "https://github.com/example/rust-agent-stack"
[dependencies]
serde = { workspace = true }
thiserror = { workspace = true }
ras-auth-core = { path = "../../core/ras-auth-core" }
ras-auth-core = { path = "../../core/ras-auth-core" }
ras-version-core = { path = "../../core/ras-version-core" }
1 change: 1 addition & 0 deletions crates/rest/ras-rest-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use thiserror::Error;

// Re-export authentication types for convenience
pub use ras_auth_core::{AuthError, AuthProvider, AuthResult, AuthenticatedUser};
pub use ras_version_core::*;

/// Result type for REST handlers that allows explicit HTTP status codes.
pub type RestResult<T> = Result<RestResponse<T>, RestError>;
Expand Down
2 changes: 1 addition & 1 deletion crates/rest/ras-rest-macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ras-rest-macro"
version = "0.2.0"
version = "0.2.1"
edition = "2024"
description = "Procedural macro for type-safe REST APIs with auth integration and OpenAPI document generation"
license = "MIT OR Apache-2.0"
Expand Down
Loading
Loading