Skip to content

Refactor project structure, separate concerns, and enhance usage documentation #57

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 3, 2025
Merged
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ hyper = { version = "1", features = ["full"] }
hyper-rustls = { version = "0.27", default-features = false, features = ["webpki-roots", "ring", "http1"] }
hyper-util = { version = "0.1", features = ["client", "client-legacy", "tokio", "http2"] }
opentelemetry = { version = "0.28", optional = true }
quick-error = "2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
slog-scope = "4"
smart-default = "0.7"
thiserror = "2"
tokio = { version = "1", features = ["full"] }
ureq = { version = "3", features = ["json"] }

[dev-dependencies]
tokio-test = "0.4" # Explicitly add if missing
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,69 @@ Simply include the rs-consul in your Cargo dependencies.
[dependencies]
rs-consul = "0.9.0"
```
## Usage
Check [/examples](/examples) for more detailed usage
### Initialize the client
#### Environment Configuration (Recommended)
The client can be configured automatically using environment variables:
```rust
use rs_consul::{types::*, Config, Consul};

let consul_config = Config::from_env();
let consul = Consul::new(consul_config);
```
#### Manual Configuration
Alternatively, you can configure the client manually:
```rust
let consul_config = Config {
address: "http://localhost:8500".to_string(),
token: None, // No token required in development mode
..Default::default() // Uses default values for other settings
};

let consul = Consul::new(consul_config);
```
### Register a Service
```rust
let node_id = "root-node"; //node name
let service_name = "new-service-1"; //service name

let payload = RegisterEntityPayload {
ID: None,
Node: node_id.to_string(),
Address: "127.0.0.1".to_string(), //server address
Datacenter: None,
TaggedAddresses: Default::default(),
NodeMeta: Default::default(),
Service: Some(RegisterEntityService {
ID: None,
Service: service_name.to_string(),
Tags: vec![],
TaggedAddresses: Default::default(),
Meta: Default::default(),
Port: Some(42424),
Namespace: None,
}),
Check: None,
SkipNodeUpdate: None,
};

consul.register_entity(&payload).await.unwrap();
```
### Deregister a service
```rust
let node_id = "root-node";
let service_name = "new-service-1";

let payload = DeregisterEntityPayload {
Node: Some(node_id.to_string()),
Datacenter: None,
CheckID: None,
ServiceID: Some(service_name.to_string()),
Namespace: None,
};
consul.deregister_entity(&payload).await.unwrap();
```

## Development

Expand Down
23 changes: 23 additions & 0 deletions examples/deregister_service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use rs_consul::{types::*, Config, Consul};

#[tokio::main] // Enables async main
async fn main() {
let consul_config = Config {
address: "http://localhost:8500".to_string(),
token: None, // Token is None in developpement mode
..Default::default()
};
let consul = Consul::new(consul_config);

let node_id = "root-node";
let service_name = "new-service-1";

let payload = DeregisterEntityPayload {
Node: Some(node_id.to_string()),
Datacenter: None,
CheckID: None,
ServiceID: Some(service_name.to_string()),
Namespace: None,
};
consul.deregister_entity(&payload).await.unwrap();
}
34 changes: 34 additions & 0 deletions examples/register_service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use rs_consul::{types::*, Config, Consul};

#[tokio::main] // Enables async main
async fn main() {
let consul_config = Config {
address: "http://localhost:8500".to_string(),
token: None, // Token is None in developpement mode
..Default::default()
};
let consul = Consul::new(consul_config);

let node_id = "root-node";
let service_name = "new-service-1";
let payload = RegisterEntityPayload {
ID: None,
Node: node_id.to_string(),
Address: "127.0.0.1".to_string(),
Datacenter: None,
TaggedAddresses: Default::default(),
NodeMeta: Default::default(),
Service: Some(RegisterEntityService {
ID: None,
Service: service_name.to_string(),
Tags: vec![],
TaggedAddresses: Default::default(),
Meta: Default::default(),
Port: Some(42424),
Namespace: None,
}),
Check: None,
SkipNodeUpdate: None,
};
consul.register_entity(&payload).await.unwrap();
}
71 changes: 71 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use thiserror::Error;

pub(crate) type Result<T> = std::result::Result<T, ConsulError>;

/// The error type returned from all calls into this crate.
#[derive(Debug, Error)]
pub enum ConsulError {
/// The request was invalid and could not be serialized to valid json.
#[error("Invalid request: {0}")]
InvalidRequest(#[source] serde_json::Error),

/// The request was invalid and could not be converted into a proper http request.
#[error("Request error: {0}")]
RequestError(#[source] http::Error),

/// The consul server response could not be converted into a proper http response.
#[error("Response error: {0}")]
ResponseError(#[source] hyper_util::client::legacy::Error),

/// The consul server response was invalid.
#[error("Invalid response: {0}")]
InvalidResponse(#[source] hyper::Error),

/// The consul server response could not be deserialized from json.
#[error("Response deserialization failed: {0}")]
ResponseDeserializationFailed(#[source] serde_json::Error),

/// The consul server response could not be deserialized from bytes.
#[error("Response string deserialization failed: {0}")]
ResponseStringDeserializationFailed(#[source] std::str::Utf8Error),

/// The consul server response was something other than 200.
#[error("Unexpected response code: {0}, body: {1:?}")]
UnexpectedResponseCode(hyper::http::StatusCode, Option<String>),

/// The consul server refused a lock acquisition.
#[error("Lock acquisition failure: {0}")]
LockAcquisitionFailure(u64),

/// Consul returned invalid UTF8.
#[error("Invalid UTF8: {0}")]
InvalidUtf8(#[from] std::str::Utf8Error),

/// Consul returned invalid base64.
#[error("Invalid base64: {0}")]
InvalidBase64(#[from] base64::DecodeError),

/// IO error from sync api.
#[error("Sync IO error: {0}")]
SyncIoError(#[from] std::io::Error),

/// Response parse error from sync api.
#[error("Sync invalid response error: {0}")]
SyncInvalidResponseError(#[from] std::str::ParseBoolError),

/// Unexpected response code from sync api.
#[error("Sync unexpected response code: {0}, body: {1}")]
SyncUnexpectedResponseCode(u16, String),

/// Consul request exceeded specified timeout.
#[error("Consul request exceeded timeout of {0:?}")]
TimeoutExceeded(std::time::Duration),

/// Unable to resolve the service's instances in Consul.
#[error("Unable to resolve service '{0}' to a concrete list of addresses and ports for its instances via consul.")]
ServiceInstanceResolutionFailed(String),

/// An error from ureq occurred.
#[error("UReq error: {0}")]
UReqError(#[from] ureq::Error),
}
Loading