|
1 | | -# servicestack-rust |
2 | | -ServiceStack Client Rust Library |
| 1 | +# ServiceStack Rust Client Library |
| 2 | + |
| 3 | +A Rust client library for making typed API requests to [ServiceStack](https://servicestack.net/) services. |
| 4 | + |
| 5 | +## Features |
| 6 | + |
| 7 | +- 🚀 **Type-safe API requests** - Use Rust DTOs for compile-time safety |
| 8 | +- 🔄 **Full HTTP method support** - GET, POST, PUT, DELETE, PATCH |
| 9 | +- 🔐 **Bearer token authentication** - Built-in support for authentication |
| 10 | +- ⚡ **Async/await** - Built on tokio and reqwest for async operations |
| 11 | +- 🎯 **ServiceStack conventions** - Follows ServiceStack's REST API patterns |
| 12 | +- 🛠️ **Customizable** - Flexible configuration options |
| 13 | + |
| 14 | +## Installation |
| 15 | + |
| 16 | +Add this to your `Cargo.toml`: |
| 17 | + |
| 18 | +```toml |
| 19 | +[dependencies] |
| 20 | +servicestack = "0.1" |
| 21 | +serde = { version = "1.0", features = ["derive"] } |
| 22 | +tokio = { version = "1", features = ["full"] } |
| 23 | +``` |
| 24 | + |
| 25 | +## Quick Start |
| 26 | + |
| 27 | +```rust |
| 28 | +use serde::{Deserialize, Serialize}; |
| 29 | +use servicestack::{JsonServiceClient, ServiceStackRequest, ServiceStackResponse}; |
| 30 | + |
| 31 | +// Define your request DTO |
| 32 | +#[derive(Serialize)] |
| 33 | +struct Hello { |
| 34 | + name: String, |
| 35 | +} |
| 36 | + |
| 37 | +// Implement ServiceStackRequest trait |
| 38 | +impl ServiceStackRequest for Hello { |
| 39 | + type Response = HelloResponse; |
| 40 | + |
| 41 | + fn path(&self) -> String { |
| 42 | + "/hello".to_string() |
| 43 | + } |
| 44 | +} |
| 45 | + |
| 46 | +// Define your response DTO |
| 47 | +#[derive(Deserialize, Debug)] |
| 48 | +struct HelloResponse { |
| 49 | + result: String, |
| 50 | +} |
| 51 | + |
| 52 | +impl ServiceStackResponse for HelloResponse {} |
| 53 | + |
| 54 | +#[tokio::main] |
| 55 | +async fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 56 | + // Create a client |
| 57 | + let client = JsonServiceClient::new("https://test.servicestack.net"); |
| 58 | + |
| 59 | + // Make a request |
| 60 | + let request = Hello { name: "World".to_string() }; |
| 61 | + let response: HelloResponse = client.post(request).await?; |
| 62 | + |
| 63 | + println!("Response: {:?}", response); |
| 64 | + Ok(()) |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +## Usage |
| 69 | + |
| 70 | +### Creating a Client |
| 71 | + |
| 72 | +```rust |
| 73 | +use servicestack::JsonServiceClient; |
| 74 | + |
| 75 | +// Basic client |
| 76 | +let client = JsonServiceClient::new("https://api.example.com"); |
| 77 | + |
| 78 | +// With custom reqwest client |
| 79 | +let http_client = reqwest::Client::builder() |
| 80 | + .timeout(std::time::Duration::from_secs(60)) |
| 81 | + .build()?; |
| 82 | +let client = JsonServiceClient::with_client("https://api.example.com", http_client); |
| 83 | +``` |
| 84 | + |
| 85 | +### Making Requests |
| 86 | + |
| 87 | +#### POST Request |
| 88 | + |
| 89 | +```rust |
| 90 | +let request = Hello { name: "Rust".to_string() }; |
| 91 | +let response: HelloResponse = client.post(request).await?; |
| 92 | +``` |
| 93 | + |
| 94 | +#### GET Request |
| 95 | + |
| 96 | +```rust |
| 97 | +let request = GetUser { id: 123 }; |
| 98 | +let response: UserResponse = client.get(request).await?; |
| 99 | +``` |
| 100 | + |
| 101 | +#### PUT Request |
| 102 | + |
| 103 | +```rust |
| 104 | +let request = UpdateUser { id: 123, name: "John".to_string() }; |
| 105 | +let response: UpdateResponse = client.put(request).await?; |
| 106 | +``` |
| 107 | + |
| 108 | +#### DELETE Request |
| 109 | + |
| 110 | +```rust |
| 111 | +let request = DeleteUser { id: 123 }; |
| 112 | +let response: DeleteResponse = client.delete(request).await?; |
| 113 | +``` |
| 114 | + |
| 115 | +#### Using the request method specified in the DTO |
| 116 | + |
| 117 | +```rust |
| 118 | +// The DTO can specify its own HTTP method |
| 119 | +impl ServiceStackRequest for MyRequest { |
| 120 | + type Response = MyResponse; |
| 121 | + |
| 122 | + fn path(&self) -> String { |
| 123 | + "/my-endpoint".to_string() |
| 124 | + } |
| 125 | + |
| 126 | + fn method(&self) -> HttpMethod { |
| 127 | + HttpMethod::Put // Custom method |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | +// Use the send method to use the DTO's specified method |
| 132 | +let response: MyResponse = client.send(request).await?; |
| 133 | +``` |
| 134 | + |
| 135 | +### Authentication |
| 136 | + |
| 137 | +```rust |
| 138 | +// Set bearer token |
| 139 | +let mut client = JsonServiceClient::new("https://api.example.com"); |
| 140 | +client.set_bearer_token("your-token-here"); |
| 141 | + |
| 142 | +// Make authenticated requests |
| 143 | +let response = client.post(request).await?; |
| 144 | + |
| 145 | +// Clear token |
| 146 | +client.clear_bearer_token(); |
| 147 | +``` |
| 148 | + |
| 149 | +### Error Handling |
| 150 | + |
| 151 | +```rust |
| 152 | +use servicestack::ServiceStackError; |
| 153 | + |
| 154 | +match client.post(request).await { |
| 155 | + Ok(response) => println!("Success: {:?}", response), |
| 156 | + Err(ServiceStackError::ApiError { status, message }) => { |
| 157 | + println!("API error {}: {}", status, message); |
| 158 | + } |
| 159 | + Err(ServiceStackError::RequestError(e)) => { |
| 160 | + println!("Request error: {}", e); |
| 161 | + } |
| 162 | + Err(e) => println!("Other error: {}", e), |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +### Custom HTTP Methods |
| 167 | + |
| 168 | +You can specify custom HTTP methods by implementing the `method()` function in your request DTO: |
| 169 | + |
| 170 | +```rust |
| 171 | +use servicestack::HttpMethod; |
| 172 | + |
| 173 | +impl ServiceStackRequest for CustomRequest { |
| 174 | + type Response = CustomResponse; |
| 175 | + |
| 176 | + fn path(&self) -> String { |
| 177 | + "/custom".to_string() |
| 178 | + } |
| 179 | + |
| 180 | + fn method(&self) -> HttpMethod { |
| 181 | + HttpMethod::Patch |
| 182 | + } |
| 183 | +} |
| 184 | +``` |
| 185 | + |
| 186 | +### Raw Requests |
| 187 | + |
| 188 | +For more control, you can use the raw request method: |
| 189 | + |
| 190 | +```rust |
| 191 | +let response: MyResponse = client |
| 192 | + .request("POST", "/custom-endpoint", Some(&request_body)) |
| 193 | + .await?; |
| 194 | +``` |
| 195 | + |
| 196 | +## Examples |
| 197 | + |
| 198 | +See the [examples](examples/) directory for more usage examples: |
| 199 | + |
| 200 | +- [basic_usage.rs](examples/basic_usage.rs) - Simple request/response example |
| 201 | +- [authenticated_request.rs](examples/authenticated_request.rs) - Using bearer token authentication |
| 202 | +- [custom_method.rs](examples/custom_method.rs) - Custom HTTP methods |
| 203 | + |
| 204 | +Run examples with: |
| 205 | + |
| 206 | +```bash |
| 207 | +cargo run --example basic_usage |
| 208 | +``` |
| 209 | + |
| 210 | +## Testing |
| 211 | + |
| 212 | +Run the test suite: |
| 213 | + |
| 214 | +```bash |
| 215 | +cargo test |
| 216 | +``` |
| 217 | + |
| 218 | +Run with output: |
| 219 | + |
| 220 | +```bash |
| 221 | +cargo test -- --nocapture |
| 222 | +``` |
| 223 | + |
| 224 | +## API Documentation |
| 225 | + |
| 226 | +Generate and view the documentation: |
| 227 | + |
| 228 | +```bash |
| 229 | +cargo doc --open |
| 230 | +``` |
| 231 | + |
| 232 | +## ServiceStack DTOs |
| 233 | + |
| 234 | +This library works seamlessly with ServiceStack's Add ServiceStack Reference feature. You can generate Rust DTOs from your ServiceStack services and use them directly with this client. |
| 235 | + |
| 236 | +Learn more about ServiceStack's typed client patterns at [docs.servicestack.net](https://docs.servicestack.net/). |
| 237 | + |
| 238 | +## License |
| 239 | + |
| 240 | +This project is licensed under the MIT License. |
| 241 | + |
| 242 | +## Contributing |
| 243 | + |
| 244 | +Contributions are welcome! Please feel free to submit a Pull Request. |
0 commit comments