Skip to content

Commit 57f51f0

Browse files
authored
feat: add AsyncVarlinkService for async introspection support (#139)
Add introspection support (GetInfo, GetInterfaceDescription) for async varlink services, addressing issue #138. Changes: - Add AsyncInterface trait extending AsyncConnectionHandler with get_name() and get_description() methods - Add AsyncVarlinkService struct that wraps async handlers and provides org.varlink.service introspection methods - Add push_request() method to sansio::Server for request delegation - Update code generator to implement AsyncInterface for async handlers - Update async_ping example to demonstrate introspection usage - Update documentation to show AsyncVarlinkService as the recommended approach for async servers Closes #138 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents 21e63d6 + 13b13c0 commit 57f51f0

10 files changed

Lines changed: 505 additions & 61 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.

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,12 @@ varlink-rust-generator --async src/org.example.ping.varlink > org_example_ping_a
175175

176176
#### Server Implementation
177177

178+
Use `AsyncVarlinkService` to create an async server with full introspection support (`org.varlink.service.GetInfo` and `GetInterfaceDescription`):
179+
178180
```rust
179181
use async_trait::async_trait;
180182
use std::sync::Arc;
181-
use varlink::{listen_async, ListenAsyncConfig};
183+
use varlink::{listen_async, AsyncVarlinkService, ListenAsyncConfig};
182184

183185
// Include generated code
184186
mod org_example_ping;
@@ -204,8 +206,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
204206
let service = Arc::new(PingService);
205207
let handler = Arc::new(org_example_ping::new(service));
206208

209+
// Create service with introspection support
210+
let varlink_service = Arc::new(AsyncVarlinkService::new(
211+
"org.example", // vendor
212+
"My Ping Service", // product
213+
"1.0", // version
214+
"https://example.org", // url
215+
vec![handler], // list of interface handlers
216+
));
217+
207218
listen_async(
208-
handler,
219+
varlink_service,
209220
"tcp:127.0.0.1:12345",
210221
&ListenAsyncConfig::default(),
211222
)

examples/async_ping/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ tokio = []
1515
anyhow = { workspace = true }
1616
async-trait = { workspace = true }
1717
varlink = { workspace = true, features = ["tokio"] }
18+
varlink_stdinterfaces = { workspace = true, features = ["tokio"] }
1819
serde = { workspace = true }
1920
serde_derive = { workspace = true }
2021
serde_json = { workspace = true }

examples/async_ping/README.md

Lines changed: 67 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,69 @@
11
# Async Varlink Example with Tokio
22

3-
This example demonstrates how to use the varlink sans-io state machines with Tokio's async I/O.
3+
This example demonstrates how to use the varlink async API with Tokio, including full introspection support.
44

55
## Overview
66

77
The sans-io architecture in varlink separates protocol logic from I/O operations, making it easy to integrate with any async runtime. This example shows:
88

9-
- **Async Server**: Uses `varlink::sansio::Server` with `tokio::net::TcpListener`
10-
- **Async Client**: Uses `varlink::sansio::Client` with `tokio::net::TcpStream`
9+
- **Async Server with Introspection**: Uses `AsyncVarlinkService` to provide `org.varlink.service.GetInfo` and `GetInterfaceDescription`
10+
- **Async Client**: Uses the generated `VarlinkClient` with `AsyncConnection`
1111
- **Concurrent Connections**: Server handles multiple clients concurrently using `tokio::spawn`
12-
- **Poll-based API**: Demonstrates the complete event loop pattern
12+
- **Service Discovery**: Demonstrates how clients can discover available interfaces
1313

1414
## Key Features
1515

16-
### Sans-IO State Machines
16+
### AsyncVarlinkService with Introspection
1717

18-
The example uses the pure protocol state machines that operate independently of I/O:
18+
The example uses `AsyncVarlinkService` to wrap interface handlers and provide standard introspection:
1919

2020
```rust
21-
// Server side
22-
let mut server = Server::new();
21+
use varlink::{listen_async, AsyncVarlinkService, ListenAsyncConfig};
22+
23+
// Create the interface handler
24+
let ping_service = Arc::new(PingService);
25+
let ping_handler = Arc::new(org_example_ping::new(ping_service));
26+
27+
// Wrap with AsyncVarlinkService for introspection support
28+
let service = Arc::new(AsyncVarlinkService::new(
29+
"org.example",
30+
"Async Ping Example",
31+
"1.0",
32+
"https://github.com/varlink/rust",
33+
vec![ping_handler],
34+
));
35+
36+
// Start listening
37+
listen_async(service, "tcp:127.0.0.1:9999", &ListenAsyncConfig::default()).await?;
38+
```
2339

24-
// Feed data from the network
25-
server.handle_input(&buf[..n])?;
40+
### Async Client
2641

27-
// Process protocol events
28-
while let Some(event) = server.poll_event() {
29-
// Handle requests and send replies
30-
}
42+
```rust
43+
// Connect to the service
44+
let connection = varlink::AsyncConnection::with_address("tcp:127.0.0.1:9999").await?;
45+
let client = org_example_ping::VarlinkClient::new(connection);
3146

32-
// Get data to send over the network
33-
while let Some(transmit) = server.poll_transmit() {
34-
stream.write_all(&transmit.payload).await?;
35-
}
47+
// Make a request
48+
let reply = client.ping("Hello!".to_string()).call().await?;
49+
println!("Response: {}", reply.pong);
3650
```
3751

38-
### Async Client
52+
### Service Discovery via Introspection
3953

4054
```rust
41-
// Client side
42-
let mut client = Client::new();
43-
44-
// Send a request
45-
client.send_request(method, request)?;
55+
use varlink_stdinterfaces::org_varlink_service_async::VarlinkClientInterface;
4656

47-
// Transmit outgoing data
48-
while let Some(transmit) = client.poll_transmit() {
49-
stream.write_all(&transmit.payload).await?;
50-
}
57+
let connection = varlink::AsyncConnection::with_address("tcp:127.0.0.1:9999").await?;
58+
let client = varlink_stdinterfaces::org_varlink_service_async::VarlinkClient::new(connection);
5159

52-
// Receive response
53-
let n = stream.read(&mut buf).await?;
54-
client.handle_input(&buf[..n])?;
60+
// Discover service info
61+
let info = client.get_info().call().await?;
62+
println!("Interfaces: {:?}", info.interfaces);
5563

56-
// Process events
57-
while let Some(event) = client.poll_event() {
58-
// Handle replies
59-
}
64+
// Get interface description
65+
let desc = client.get_interface_description("org.example.ping".to_string()).call().await?;
66+
println!("IDL: {}", desc.description);
6067
```
6168

6269
## Running the Example
@@ -72,25 +79,40 @@ cargo test --package async_ping
7279
## Expected Output
7380

7481
```
75-
Server: Listening on 127.0.0.1:9999
82+
Server: Listening on 127.0.0.1:9999 (with introspection)
83+
84+
=== Running Introspection Example ===
85+
86+
Client: Connecting to 127.0.0.1:9999 for introspection
87+
Client: Calling org.varlink.service.GetInfo...
88+
Vendor: org.example
89+
Product: Async Ping Example
90+
Version: 1.0
91+
URL: https://github.com/varlink/rust
92+
Interfaces: ["org.varlink.service", "org.example.ping"]
93+
94+
Client: Calling org.varlink.service.GetInterfaceDescription for 'org.example.ping'...
95+
Description:
96+
# Example async service
97+
interface org.example.ping
98+
99+
# Returns the same string
100+
method Ping(ping: string) -> (pong: string)
101+
102+
error PingError(parameter: int)
76103
77104
=== Running Client Example ===
78105
79106
Client: Connecting to 127.0.0.1:9999
80-
Server: New client connected
81107
Client: Sending Ping request: 'Hello, Async Varlink!'
82-
Server: Received request: "org.example.ping.Ping"
83108
Server: Ping request with: 'Hello, Async Varlink!'
84-
Client: Received reply for method: org.example.ping.Ping
85109
Client: Pong response: 'Hello, Async Varlink!'
86110
87111
=== Running Second Client Example ===
88112
89113
Client: Connecting to 127.0.0.1:9999
90114
Client: Sending Ping request: 'Testing sans-io with Tokio'
91-
Server: Received request: "org.example.ping.Ping"
92115
Server: Ping request with: 'Testing sans-io with Tokio'
93-
Client: Received reply for method: org.example.ping.Ping
94116
Client: Pong response: 'Testing sans-io with Tokio'
95117
96118
=== Example Complete ===
@@ -101,10 +123,10 @@ Client: Pong response: 'Testing sans-io with Tokio'
101123
The example is structured as follows:
102124

103125
1. **Protocol Definition**: `org.example.ping.varlink` defines the Ping method
104-
2. **Generated Code**: `build.rs` generates Rust bindings using `varlink_generator`
105-
3. **Async Server**: `handle_client()` processes connections using the sans-io Server
106-
4. **Async Client**: `run_client()` makes requests using the sans-io Client
107-
5. **Main Function**: Coordinates server startup and client requests
126+
2. **Generated Code**: `build.rs` generates async Rust bindings using `varlink_generator`
127+
3. **Async Server**: `AsyncVarlinkService` wraps interface handlers and provides introspection
128+
4. **Async Client**: `VarlinkClient` makes requests using `AsyncConnection`
129+
5. **Main Function**: Demonstrates introspection and regular method calls
108130

109131
## Benefits of Sans-IO with Async
110132

examples/async_ping/src/main.rs

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
//! Async Varlink Client Example using Tokio
22
//!
3-
//! This example demonstrates how to use the generated async client and server API.
3+
//! This example demonstrates how to use the generated async client and server API,
4+
//! including introspection support via AsyncVarlinkService.
45
56
use anyhow::Result;
67
use async_trait::async_trait;
78
use std::sync::Arc;
8-
use varlink::{listen_async, ListenAsyncConfig};
9+
use varlink::{listen_async, AsyncVarlinkService, ListenAsyncConfig};
10+
use varlink_stdinterfaces::org_varlink_service_async::VarlinkClientInterface as ServiceClientInterface;
911

1012
// Include the generated code
1113
mod org_example_ping;
@@ -26,10 +28,23 @@ impl VarlinkInterface for PingService {
2628
}
2729
}
2830

29-
async fn run_server(addr: &str) -> Result<()> {
30-
println!("Server: Listening on {}", addr);
31+
/// Run a server with AsyncVarlinkService for introspection support
32+
async fn run_server_with_introspection(addr: &str) -> Result<()> {
33+
println!("Server: Listening on {} (with introspection)", addr);
34+
35+
// Create the ping service handler
3136
let ping_service = Arc::new(PingService);
32-
let service = Arc::new(org_example_ping::new(ping_service));
37+
let ping_handler = Arc::new(org_example_ping::new(ping_service));
38+
39+
// Wrap with AsyncVarlinkService for introspection support
40+
let service = Arc::new(AsyncVarlinkService::new(
41+
"org.example",
42+
"Async Ping Example",
43+
"1.0",
44+
"https://github.com/varlink/rust",
45+
vec![ping_handler],
46+
));
47+
3348
listen_async(
3449
service,
3550
format!("tcp:{}", addr),
@@ -43,7 +58,6 @@ async fn run_server(addr: &str) -> Result<()> {
4358
async fn run_client(addr: &str, ping_message: &str) -> Result<()> {
4459
println!("Client: Connecting to {}", addr);
4560

46-
// NEW API: Use the generated VarlinkClient with AsyncConnection
4761
let connection = varlink::AsyncConnection::with_address(format!("tcp:{}", addr))
4862
.await
4963
.map_err(|e| anyhow::anyhow!("Connection error: {:?}", e))?;
@@ -52,7 +66,6 @@ async fn run_client(addr: &str, ping_message: &str) -> Result<()> {
5266

5367
println!("Client: Sending Ping request: '{}'", ping_message);
5468

55-
// NEW API: call().await instead of call_async().await
5669
let reply = client
5770
.ping(ping_message.to_string())
5871
.call()
@@ -64,21 +77,69 @@ async fn run_client(addr: &str, ping_message: &str) -> Result<()> {
6477
Ok(())
6578
}
6679

80+
/// Demonstrate introspection by calling GetInfo and GetInterfaceDescription
81+
async fn run_introspection_client(addr: &str) -> Result<()> {
82+
println!("Client: Connecting to {} for introspection", addr);
83+
84+
let connection = varlink::AsyncConnection::with_address(format!("tcp:{}", addr))
85+
.await
86+
.map_err(|e| anyhow::anyhow!("Connection error: {:?}", e))?;
87+
88+
let client = varlink_stdinterfaces::org_varlink_service_async::VarlinkClient::new(connection);
89+
90+
// Call GetInfo to discover service metadata
91+
println!("Client: Calling org.varlink.service.GetInfo...");
92+
let info = client
93+
.get_info()
94+
.call()
95+
.await
96+
.map_err(|e| anyhow::anyhow!("GetInfo error: {:?}", e))?;
97+
98+
println!(" Vendor: {}", info.vendor);
99+
println!(" Product: {}", info.product);
100+
println!(" Version: {}", info.version);
101+
println!(" URL: {}", info.url);
102+
println!(" Interfaces: {:?}", info.interfaces);
103+
104+
// Call GetInterfaceDescription for org.example.ping
105+
println!(
106+
"\nClient: Calling org.varlink.service.GetInterfaceDescription for 'org.example.ping'..."
107+
);
108+
let connection = varlink::AsyncConnection::with_address(format!("tcp:{}", addr))
109+
.await
110+
.map_err(|e| anyhow::anyhow!("Connection error: {:?}", e))?;
111+
let client = varlink_stdinterfaces::org_varlink_service_async::VarlinkClient::new(connection);
112+
113+
let desc = client
114+
.get_interface_description("org.example.ping".to_string())
115+
.call()
116+
.await
117+
.map_err(|e| anyhow::anyhow!("GetInterfaceDescription error: {:?}", e))?;
118+
119+
println!(" Description:\n{}", desc.description);
120+
121+
Ok(())
122+
}
123+
67124
#[tokio::main]
68125
async fn main() -> Result<()> {
69126
let addr = "127.0.0.1:9999";
70127

71-
// Spawn the server in the background
128+
// Spawn the server with introspection support
72129
let server_handle = tokio::spawn(async move {
73-
if let Err(e) = run_server(addr).await {
130+
if let Err(e) = run_server_with_introspection(addr).await {
74131
eprintln!("Server error: {}", e);
75132
}
76133
});
77134

78135
// Give the server time to start
79136
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
80137

81-
// Run the client
138+
// Demonstrate introspection
139+
println!("\n=== Running Introspection Example ===\n");
140+
run_introspection_client(addr).await?;
141+
142+
// Run the regular client
82143
println!("\n=== Running Client Example ===\n");
83144
run_client(addr, "Hello, Async Varlink!").await?;
84145

@@ -111,10 +172,20 @@ mod tests {
111172
let stop = Arc::new(AtomicBool::new(false));
112173
let stop_clone = Arc::clone(&stop);
113174

114-
// Spawn server with graceful shutdown support
175+
// Spawn server with graceful shutdown support and introspection
115176
let server_handle = tokio::spawn(async move {
116177
let ping_service = Arc::new(PingService);
117-
let service = Arc::new(org_example_ping::new(ping_service));
178+
let ping_handler = Arc::new(org_example_ping::new(ping_service));
179+
180+
// Use AsyncVarlinkService for introspection support
181+
let service = Arc::new(AsyncVarlinkService::new(
182+
"org.example",
183+
"Async Ping Test",
184+
"1.0",
185+
"https://github.com/varlink/rust",
186+
vec![ping_handler],
187+
));
188+
118189
let config = ListenAsyncConfig {
119190
idle_timeout: Duration::ZERO,
120191
stop_listening: Some(stop_clone),
@@ -127,7 +198,15 @@ mod tests {
127198
// Give server time to start
128199
tokio::time::sleep(Duration::from_millis(100)).await;
129200

130-
// Test client
201+
// Test introspection
202+
let introspection_result = run_introspection_client(addr).await;
203+
assert!(
204+
introspection_result.is_ok(),
205+
"Introspection failed: {:?}",
206+
introspection_result.err()
207+
);
208+
209+
// Test ping client
131210
let result = run_client(addr, "test").await;
132211
assert!(result.is_ok());
133212

examples/async_ping/src/org_example_ping.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,11 @@ impl varlink::AsyncConnectionHandler for VarlinkInterfaceHandler {
268268
Ok(None)
269269
}
270270
}
271+
impl varlink::AsyncInterface for VarlinkInterfaceHandler {
272+
fn get_name(&self) -> &'static str {
273+
"org.example.ping"
274+
}
275+
fn get_description(&self) -> &'static str {
276+
"# Example async service\ninterface org.example.ping\n\n# Returns the same string\nmethod Ping(ping: string) -> (pong: string)\n\nerror PingError(parameter: int)\n"
277+
}
278+
}

0 commit comments

Comments
 (0)