mod conformance;
mod http;
mod modules;
mod paging;
mod peer;
mod registration;
mod resync;
pub use conformance::{Check, Conformance, Outcome, Report};
pub use http::{OcpiRequest, Transport, check_outgoing};
pub use modules::{
CdrsClient, ChargingProfilesClient, CommandsClient, HubClientInfoClient, LocationsReceiver,
LocationsSender, ModuleClient, PaymentsClient, SessionsReceiver, SessionsSender, TariffsReceiver,
TariffsSender, TokensReceiver, TokensSender, correlated_ids,
};
pub use paging::{DEFAULT_MAX_PAGES, PageStream};
pub use peer::{Peer, PeerBuilder};
pub use registration::{Discovered, PeerState, Registration, Selected};
pub use resync::{Resync, ResyncPlan};
use std::time::Duration;
use crate::types::UrlPolicy;
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct ClientConfig {
pub url_policy: UrlPolicy,
pub timeout: Duration,
pub retry: RetryPolicy,
pub validate_outgoing: bool,
}
impl Default for ClientConfig {
fn default() -> Self {
Self {
url_policy: UrlPolicy::default(),
timeout: Duration::from_secs(30),
retry: RetryPolicy::default(),
validate_outgoing: true,
}
}
}
impl ClientConfig {
#[must_use]
pub fn for_testing() -> Self {
Self { url_policy: UrlPolicy::permissive(), ..Self::default() }
}
#[must_use]
pub fn with_url_policy(mut self, policy: UrlPolicy) -> Self {
self.url_policy = policy;
self
}
#[must_use]
pub const fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
#[must_use]
pub const fn without_outgoing_validation(mut self) -> Self {
self.validate_outgoing = false;
self
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct RetryPolicy {
pub max_attempts: u32,
pub initial_delay: Duration,
pub max_delay: Duration,
}
impl Default for RetryPolicy {
fn default() -> Self {
Self {
max_attempts: 3,
initial_delay: Duration::from_millis(250),
max_delay: Duration::from_secs(10),
}
}
}
impl RetryPolicy {
#[must_use]
pub const fn none() -> Self {
Self { max_attempts: 1, initial_delay: Duration::from_millis(0), max_delay: Duration::from_millis(0) }
}
}
#[derive(Clone, Debug)]
pub struct OcpiClient {
transport: Transport,
}
impl OcpiClient {
pub fn new() -> Result<Self, reqwest::Error> {
Self::with_config(ClientConfig::default())
}
pub fn with_config(config: ClientConfig) -> Result<Self, reqwest::Error> {
let http =
reqwest::Client::builder().user_agent(concat!("ocpi-kit/", env!("CARGO_PKG_VERSION"))).build()?;
Ok(Self { transport: Transport::new(http, config) })
}
#[must_use]
pub fn from_http(http: reqwest::Client, config: ClientConfig) -> Self {
Self { transport: Transport::new(http, config) }
}
#[must_use]
pub const fn transport(&self) -> &Transport {
&self.transport
}
#[must_use]
pub const fn config(&self) -> &ClientConfig {
self.transport.config()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_default_configuration_is_the_careful_one() {
let config = ClientConfig::default();
assert!(config.validate_outgoing, "a non-conformant object should not reach a partner");
assert!(
config.url_policy.check(&crate::types::Url::new("http://e.com/a").unwrap()).is_err(),
"plain HTTP is refused by default"
);
assert_eq!(config.retry.max_attempts, 3);
}
#[test]
fn the_testing_configuration_allows_localhost() {
let config = ClientConfig::for_testing();
assert!(
config.url_policy.check(&crate::types::Url::new("http://127.0.0.1:8080/ocpi").unwrap()).is_ok()
);
}
#[test]
fn retrying_can_be_switched_off() {
assert_eq!(RetryPolicy::none().max_attempts, 1);
}
}