Updated directory_client reqwest to 0.10 (#226)

* Updated directory_client reqwest to 0.10

* Using consistent syntax
This commit is contained in:
Jędrzej Stuczyński
2020-05-12 12:52:31 +01:00
committed by GitHub
parent f47b7cfb13
commit ff2e24afbc
25 changed files with 759 additions and 480 deletions
@@ -8,8 +8,9 @@ edition = "2018"
[dependencies]
log = "0.4"
futures = "0.3"
pretty_env_logger = "0.3"
reqwest = "0.9.22"
reqwest = { version = "0.10", features = ["json"] }
serde = { version = "1.0.104", features = ["derive"] }
## internal
@@ -17,3 +18,4 @@ topology = {path = "../../topology"}
[dev-dependencies]
mockito = "0.23.0"
tokio = { version = "0.2", features = ["macros"] }
+103 -41
View File
@@ -12,24 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::requests::health_check_get::{HealthCheckRequester, Request as HealthCheckRequest};
use crate::requests::metrics_mixes_get::{MetricsMixRequester, Request as MetricsMixRequest};
use crate::requests::metrics_mixes_post::{MetricsMixPoster, Request as MetricsMixPost};
use crate::requests::presence_coconodes_post::{
PresenceCocoNodesPoster, Request as PresenceCocoNodesPost,
};
use crate::requests::presence_gateways_post::{
PresenceGatewayPoster, Request as PresenceGatewayPost,
};
use crate::requests::presence_mixnodes_post::{
PresenceMixNodesPoster, Request as PresenceMixNodesPost,
};
use crate::requests::presence_providers_post::{
PresenceMixProviderPoster, Request as PresenceProvidersPost,
};
use crate::requests::presence_topology_get::{
PresenceTopologyGetRequester, Request as PresenceTopologyRequest,
use crate::requests::health_check_get::Request as HealthCheckRequest;
use crate::requests::metrics_mixes_get::Request as MetricsMixRequest;
use crate::requests::metrics_mixes_post::Request as MetricsMixPost;
use crate::requests::presence_coconodes_post::Request as PresenceCocoNodesPost;
use crate::requests::presence_gateways_post::Request as PresenceGatewayPost;
use crate::requests::presence_mixnodes_post::Request as PresenceMixNodesPost;
use crate::requests::presence_providers_post::Request as PresenceProvidersPost;
use crate::requests::presence_topology_get::Request as PresenceTopologyRequest;
use metrics::{MixMetric, PersistedMixMetric};
use presence::{
coconodes::CocoPresence, gateways::GatewayPresence, mixnodes::MixNodePresence,
providers::MixProviderPresence, Topology,
};
use requests::{health_check_get::HealthCheckResponse, DirectoryGetRequest, DirectoryPostRequest};
pub mod metrics;
pub mod presence;
@@ -50,35 +47,100 @@ pub trait DirectoryClient {
}
pub struct Client {
pub health_check: HealthCheckRequest,
pub metrics_mixes: MetricsMixRequest,
pub metrics_post: MetricsMixPost,
pub presence_coconodes_post: PresenceCocoNodesPost,
pub presence_gateway_post: PresenceGatewayPost,
pub presence_mix_nodes_post: PresenceMixNodesPost,
pub presence_providers_post: PresenceProvidersPost,
pub presence_topology: PresenceTopologyRequest,
base_url: String,
reqwest_client: reqwest::Client,
}
impl DirectoryClient for Client {
fn new(config: Config) -> Client {
let health_check = HealthCheckRequest::new(config.base_url.clone());
let metrics_mixes = MetricsMixRequest::new(config.base_url.clone());
let metrics_post = MetricsMixPost::new(config.base_url.clone());
let presence_topology = PresenceTopologyRequest::new(config.base_url.clone());
let presence_coconodes_post = PresenceCocoNodesPost::new(config.base_url.clone());
let presence_gateway_post = PresenceGatewayPost::new(config.base_url.clone());
let presence_mix_nodes_post = PresenceMixNodesPost::new(config.base_url.clone());
let presence_providers_post = PresenceProvidersPost::new(config.base_url);
let reqwest_client = reqwest::Client::new();
Client {
health_check,
metrics_mixes,
metrics_post,
presence_coconodes_post,
presence_gateway_post,
presence_mix_nodes_post,
presence_providers_post,
presence_topology,
base_url: config.base_url,
reqwest_client,
}
}
}
impl Client {
async fn post<R: DirectoryPostRequest>(
&self,
request: R,
) -> reqwest::Result<reqwest::Response> {
self.reqwest_client
.post(&request.url())
.json(request.json_payload())
.send()
.await
}
async fn get<R: DirectoryGetRequest>(&self, request: R) -> reqwest::Result<R::JSONResponse> {
self.reqwest_client
.get(&request.url())
.send()
.await?
.json()
.await
}
pub async fn get_healthcheck(&self) -> reqwest::Result<HealthCheckResponse> {
let req = HealthCheckRequest::new(&self.base_url);
self.get(req).await
}
pub async fn post_mix_metrics(&self, metrics: MixMetric) -> reqwest::Result<reqwest::Response> {
let req = MetricsMixPost::new(&self.base_url, metrics);
self.post(req).await
}
pub async fn get_mix_metrics(&self) -> reqwest::Result<Vec<PersistedMixMetric>> {
let req = MetricsMixRequest::new(&self.base_url);
self.get(req).await
}
pub async fn post_coconode_presence(
&self,
presence: CocoPresence,
) -> reqwest::Result<reqwest::Response> {
let req = PresenceCocoNodesPost::new(&self.base_url, presence);
self.post(req).await
}
pub async fn post_gateway_presence(
&self,
presence: GatewayPresence,
) -> reqwest::Result<reqwest::Response> {
let req = PresenceGatewayPost::new(&self.base_url, presence);
self.post(req).await
}
pub async fn post_mixnode_presence(
&self,
presence: MixNodePresence,
) -> reqwest::Result<reqwest::Response> {
let req = PresenceMixNodesPost::new(&self.base_url, presence);
self.post(req).await
}
// this should be soft-deprecated as the whole concept of provider will
// be removed in the next topology rework
pub async fn post_provider_presence(
&self,
presence: MixProviderPresence,
) -> reqwest::Result<reqwest::Response> {
let req = PresenceProvidersPost::new(&self.base_url, presence);
self.post(req).await
}
pub async fn get_topology(&self) -> reqwest::Result<Topology> {
let req = PresenceTopologyRequest::new(&self.base_url);
self.get(req).await
}
}
#[cfg(test)]
pub(crate) fn client_test_fixture(base_url: &str) -> Client {
Client {
base_url: base_url.to_string(),
reqwest_client: reqwest::Client::new(),
}
}
@@ -12,122 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::requests::presence_topology_get::PresenceTopologyGetRequester;
use crate::{Client, Config, DirectoryClient};
use log::*;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use topology::{coco, gateway, mix, provider, NymTopology};
pub mod coconodes;
pub mod gateways;
pub mod mixnodes;
pub mod providers;
pub mod topology;
// Topology shows us the current state of the overall Nym network
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Topology {
pub coco_nodes: Vec<coconodes::CocoPresence>,
pub mix_nodes: Vec<mixnodes::MixNodePresence>,
pub mix_provider_nodes: Vec<providers::MixProviderPresence>,
pub gateway_nodes: Vec<gateways::GatewayPresence>,
}
impl NymTopology for Topology {
// TODO: this will need some changes to not imply having to make an HTTP request in constructor
fn new(directory_server: String) -> Self {
debug!("Using directory server: {:?}", directory_server);
let directory_config = Config {
base_url: directory_server,
};
let directory = Client::new(directory_config);
directory
.presence_topology
.get()
.expect("Failed to retrieve network topology.")
}
fn new_from_nodes(
mix_nodes: Vec<mix::Node>,
mix_provider_nodes: Vec<provider::Node>,
coco_nodes: Vec<coco::Node>,
gateway_nodes: Vec<gateway::Node>,
) -> Self {
Topology {
coco_nodes: coco_nodes.into_iter().map(|node| node.into()).collect(),
mix_nodes: mix_nodes.into_iter().map(|node| node.into()).collect(),
mix_provider_nodes: mix_provider_nodes
.into_iter()
.map(|node| node.into())
.collect(),
gateway_nodes: gateway_nodes.into_iter().map(|node| node.into()).collect(),
}
}
fn mix_nodes(&self) -> Vec<mix::Node> {
self.mix_nodes
.iter()
.filter_map(|x| x.clone().try_into().ok())
.collect()
}
fn providers(&self) -> Vec<provider::Node> {
self.mix_provider_nodes
.iter()
.map(|x| x.clone().into())
.collect()
}
fn gateways(&self) -> Vec<gateway::Node> {
self.gateway_nodes
.iter()
.map(|x| x.clone().into())
.collect()
}
fn coco_nodes(&self) -> Vec<topology::coco::Node> {
self.coco_nodes.iter().map(|x| x.clone().into()).collect()
}
}
#[cfg(test)]
mod converting_mixnode_presence_into_topology_mixnode {
use super::*;
#[test]
fn it_returns_error_on_unresolvable_hostname() {
let unresolvable_hostname = "foomp.foomp.foomp:1234";
let mix_presence = mixnodes::MixNodePresence {
location: "".to_string(),
host: unresolvable_hostname.to_string(),
pub_key: "".to_string(),
layer: 0,
last_seen: 0,
version: "".to_string(),
};
let _result: Result<mix::Node, std::io::Error> = mix_presence.try_into();
// assert!(result.is_err()) // This fails only for me. Why?
// ¯\_(ツ)_/¯ - works on my machine (and travis)
}
#[test]
fn it_returns_resolved_ip_on_resolvable_hostname() {
let resolvable_hostname = "nymtech.net:1234";
let mix_presence = mixnodes::MixNodePresence {
location: "".to_string(),
host: resolvable_hostname.to_string(),
pub_key: "".to_string(),
layer: 0,
last_seen: 0,
version: "".to_string(),
};
let result: Result<topology::mix::Node, std::io::Error> = mix_presence.try_into();
assert!(result.is_ok())
}
}
pub use self::topology::Topology;
@@ -0,0 +1,133 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{coconodes, gateways, mixnodes, providers};
use crate::{Client, Config, DirectoryClient};
use futures::future::BoxFuture;
use log::*;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use topology::{coco, gateway, mix, provider, NymTopology};
// Topology shows us the current state of the overall Nym network
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Topology {
pub coco_nodes: Vec<coconodes::CocoPresence>,
pub mix_nodes: Vec<mixnodes::MixNodePresence>,
pub mix_provider_nodes: Vec<providers::MixProviderPresence>,
pub gateway_nodes: Vec<gateways::GatewayPresence>,
}
impl NymTopology for Topology {
// TODO: this will need some changes to not imply having to make an HTTP request in constructor
// TODO2: and also be reworked/removed with the topology re-work
fn new<'a>(directory_server: String) -> BoxFuture<'a, Self> {
debug!("Using directory server: {:?}", directory_server);
let directory_config = Config {
base_url: directory_server,
};
let directory = Client::new(directory_config);
Box::pin({
async move {
directory
.get_topology()
.await
.expect("Failed to retrieve network topology.")
}
})
}
fn new_from_nodes(
mix_nodes: Vec<mix::Node>,
mix_provider_nodes: Vec<provider::Node>,
coco_nodes: Vec<coco::Node>,
gateway_nodes: Vec<gateway::Node>,
) -> Self {
Topology {
coco_nodes: coco_nodes.into_iter().map(|node| node.into()).collect(),
mix_nodes: mix_nodes.into_iter().map(|node| node.into()).collect(),
mix_provider_nodes: mix_provider_nodes
.into_iter()
.map(|node| node.into())
.collect(),
gateway_nodes: gateway_nodes.into_iter().map(|node| node.into()).collect(),
}
}
fn mix_nodes(&self) -> Vec<mix::Node> {
self.mix_nodes
.iter()
.filter_map(|x| x.clone().try_into().ok())
.collect()
}
fn providers(&self) -> Vec<provider::Node> {
self.mix_provider_nodes
.iter()
.map(|x| x.clone().into())
.collect()
}
fn gateways(&self) -> Vec<gateway::Node> {
self.gateway_nodes
.iter()
.map(|x| x.clone().into())
.collect()
}
fn coco_nodes(&self) -> Vec<topology::coco::Node> {
self.coco_nodes.iter().map(|x| x.clone().into()).collect()
}
}
#[cfg(test)]
mod converting_mixnode_presence_into_topology_mixnode {
use super::*;
#[test]
fn it_returns_error_on_unresolvable_hostname() {
let unresolvable_hostname = "foomp.foomp.foomp:1234";
let mix_presence = mixnodes::MixNodePresence {
location: "".to_string(),
host: unresolvable_hostname.to_string(),
pub_key: "".to_string(),
layer: 0,
last_seen: 0,
version: "".to_string(),
};
let result: Result<mix::Node, std::io::Error> = mix_presence.try_into();
assert!(result.is_err()) // This fails only for me. Why?
// ¯\_(ツ)_/¯ - works on my machine (and travis)
// Is it still broken?
}
#[test]
fn it_returns_resolved_ip_on_resolvable_hostname() {
let resolvable_hostname = "nymtech.net:1234";
let mix_presence = mixnodes::MixNodePresence {
location: "".to_string(),
host: resolvable_hostname.to_string(),
pub_key: "".to_string(),
layer: 0,
last_seen: 0,
version: "".to_string(),
};
let result: Result<topology::mix::Node, std::io::Error> = mix_presence.try_into();
assert!(result.is_ok())
}
}
@@ -12,49 +12,53 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use reqwest::Response;
use super::{DirectoryGetRequest, DirectoryRequest};
use serde::{Deserialize, Serialize};
const PATH: &str = "/api/healthcheck";
#[derive(Deserialize, Serialize)]
pub struct HealthCheckResponse {
pub ok: bool,
}
pub struct Request {
base_url: String,
path: String,
}
pub trait HealthCheckRequester {
fn new(base_url: String) -> Self;
fn get(&self) -> Result<Response, reqwest::Error>;
impl DirectoryRequest for Request {
fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl HealthCheckRequester for Request {
fn new(base_url: String) -> Self {
Request {
base_url,
path: "/api/healthcheck".to_string(),
}
}
impl DirectoryGetRequest for Request {
type JSONResponse = HealthCheckResponse;
fn get(&self) -> Result<Response, reqwest::Error> {
let url = format!("{}{}", self.base_url, self.path);
reqwest::get(&url)
fn new(base_url: &str) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
}
}
}
#[cfg(test)]
mod healthcheck_requests {
use super::*;
#[cfg(test)]
use crate::client_test_fixture;
use mockito::mock;
#[cfg(test)]
mod on_a_400_status {
use super::*;
#[test]
#[should_panic]
fn it_returns_an_error() {
#[tokio::test]
async fn it_returns_an_error() {
let _m = mock("GET", "/api/healthcheck").with_status(400).create();
let req = Request::new(mockito::server_url());
assert!(req.get().is_err());
let client = client_test_fixture(&mockito::server_url());
let res = client.get_healthcheck().await;
assert!(res.is_err());
_m.assert();
}
}
@@ -63,11 +67,18 @@ mod healthcheck_requests {
mod on_a_200 {
use super::*;
#[test]
fn it_returns_a_response_with_200_status() {
let _m = mock("GET", "/api/healthcheck").with_status(200).create();
let req = Request::new(mockito::server_url());
assert!(req.get().is_ok());
#[tokio::test]
async fn it_returns_a_response_with_200_status() {
let json = r#"{
"ok": true
}"#;
let _m = mock("GET", "/api/healthcheck")
.with_status(200)
.with_body(json)
.create();
let client = client_test_fixture(&mockito::server_url());
let res = client.get_healthcheck().await;
assert!(res.unwrap().ok);
_m.assert();
}
}
@@ -12,50 +12,49 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{DirectoryGetRequest, DirectoryRequest};
use crate::metrics::PersistedMixMetric;
const PATH: &str = "/api/metrics/mixes";
pub struct Request {
base_url: String,
path: String,
}
pub trait MetricsMixRequester {
fn new(base_url: String) -> Self;
fn get(&self) -> Result<Vec<PersistedMixMetric>, reqwest::Error>;
impl DirectoryRequest for Request {
fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl MetricsMixRequester for Request {
fn new(base_url: String) -> Self {
Request {
base_url,
path: "/api/metrics/mixes".to_string(),
}
}
impl DirectoryGetRequest for Request {
type JSONResponse = Vec<PersistedMixMetric>;
fn get(&self) -> Result<Vec<PersistedMixMetric>, reqwest::Error> {
let url = format!("{}{}", self.base_url, self.path);
let mix_metric_vec = reqwest::get(&url)?.json()?;
Ok(mix_metric_vec)
fn new(base_url: &str) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
}
}
}
#[cfg(test)]
mod metrics_get_request {
use super::*;
#[cfg(test)]
use crate::client_test_fixture;
use mockito::mock;
#[cfg(test)]
mod on_a_400_status {
use super::*;
#[test]
#[should_panic]
fn it_returns_an_error() {
let _m = mock("GET", "/api/metrics/mixes").with_status(400).create();
let req = Request::new(mockito::server_url());
req.get().unwrap();
#[tokio::test]
async fn it_returns_an_error() {
let _m = mock("GET", PATH).with_status(400).create();
let client = client_test_fixture(&mockito::server_url());
let result = client.get_mix_metrics().await;
assert!(result.is_err());
_m.assert();
}
}
@@ -63,20 +62,15 @@ mod metrics_get_request {
#[cfg(test)]
mod on_a_200 {
use super::*;
#[test]
fn it_returns_a_response_with_200_status_and_a_correct_topology() {
#[tokio::test]
async fn it_returns_a_response_with_200_status_and_a_correct_metrics() {
let json = fixtures::mix_metrics_response_json();
let _m = mock("GET", "/api/metrics/mixes")
.with_status(200)
.with_body(json)
.create();
let req = Request::new(mockito::server_url());
let result = req.get();
assert_eq!(true, result.is_ok());
assert_eq!(
1576061080635800000,
result.unwrap().first().unwrap().timestamp
);
let _m = mock("GET", PATH).with_status(200).with_body(json).create();
let client = client_test_fixture(&mockito::server_url());
let result = client.get_mix_metrics().await;
let unwrapped = result.unwrap();
assert_eq!(10, unwrapped.first().unwrap().received);
_m.assert();
}
}
@@ -84,7 +78,7 @@ mod metrics_get_request {
#[cfg(test)]
mod fixtures {
#[cfg(test)]
pub fn mix_metrics_response_json() -> String {
pub fn mix_metrics_response_json() -> &'static str {
r#"[
{
"pubKey": "OwOqwWjh_IlnaWS2PxO6odnhNahOYpRCkju50beQCTA=",
@@ -247,7 +241,6 @@ mod metrics_get_request {
"timestamp": 1576061080501732900
}
]"#
.to_string()
}
}
}
@@ -12,52 +12,53 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{DirectoryPostRequest, DirectoryRequest};
use crate::metrics::MixMetric;
use reqwest::Response;
const PATH: &str = "/api/metrics/mixes";
pub struct Request {
base_url: String,
path: String,
payload: MixMetric,
}
pub trait MetricsMixPoster {
fn new(base_url: String) -> Self;
fn post(&self, metric: &MixMetric) -> Result<Response, reqwest::Error>;
impl DirectoryRequest for Request {
fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl MetricsMixPoster for Request {
fn new(base_url: String) -> Self {
Request {
base_url,
path: "/api/metrics/mixes".to_string(),
}
impl DirectoryPostRequest for Request {
type Payload = MixMetric;
fn json_payload(&self) -> &MixMetric {
&self.payload
}
fn post(&self, metric: &MixMetric) -> Result<Response, reqwest::Error> {
let url = format!("{}{}", self.base_url, self.path);
let client = reqwest::Client::new();
let mix_metric_vec = client.post(&url).json(&metric).send()?;
Ok(mix_metric_vec)
fn new(base_url: &str, payload: Self::Payload) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
payload,
}
}
}
#[cfg(test)]
mod metrics_get_request {
mod metrics_post_request {
use super::*;
#[cfg(test)]
use crate::client_test_fixture;
use mockito::mock;
#[cfg(test)]
mod on_a_400_status {
use super::*;
#[test]
fn it_returns_an_error() {
let _m = mock("POST", "/api/metrics/mixes").with_status(400).create();
let req = Request::new(mockito::server_url());
let metric = fixtures::new_metric();
let result = req.post(&metric);
#[tokio::test]
async fn it_returns_an_error() {
let _m = mock("POST", PATH).with_status(400).create();
let client = client_test_fixture(&mockito::server_url());
let result = client.post_mix_metrics(fixtures::new_metric()).await;
assert_eq!(400, result.unwrap().status());
_m.assert();
}
@@ -66,17 +67,16 @@ mod metrics_get_request {
#[cfg(test)]
mod on_a_200 {
use super::*;
#[test]
fn it_returns_a_response_with_200() {
#[tokio::test]
async fn it_returns_a_response_with_200() {
let json = fixtures::mix_metrics_response_json();
let _m = mock("POST", "/api/metrics/mixes")
.with_status(201)
.with_body(json)
.create();
let req = Request::new(mockito::server_url());
let metric = fixtures::new_metric();
let result = req.post(&metric);
assert_eq!(true, result.is_ok());
let client = client_test_fixture(&mockito::server_url());
let result = client.post_mix_metrics(fixtures::new_metric()).await;
assert!(result.is_ok());
_m.assert();
}
}
@@ -20,3 +20,26 @@ pub mod presence_gateways_post;
pub mod presence_mixnodes_post;
pub mod presence_providers_post;
pub mod presence_topology_get;
use serde::{de::DeserializeOwned, Serialize};
pub(crate) trait DirectoryRequest {
fn url(&self) -> String;
}
pub(crate) trait DirectoryGetRequest: DirectoryRequest {
// perhaps the name of this is not the best because it's technically not a JSON,
// but something that can be deserialised from JSON.
// I'm open to all suggestions on how to rename it
type JSONResponse: DeserializeOwned;
fn new(base_url: &str) -> Self;
}
pub(crate) trait DirectoryPostRequest: DirectoryRequest {
// Similarly this, it's something that can be serialized into a JSON
type Payload: Serialize + ?Sized;
fn new(base_url: &str, payload: Self::Payload) -> Self;
fn json_payload(&self) -> &Self::Payload;
}
@@ -12,54 +12,54 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{DirectoryPostRequest, DirectoryRequest};
use crate::presence::coconodes::CocoPresence;
use reqwest::Response;
const PATH: &str = "/api/presence/coconodes";
pub struct Request {
base_url: String,
path: String,
payload: CocoPresence,
}
pub trait PresenceCocoNodesPoster {
fn new(base_url: String) -> Self;
fn post(&self, presence: &CocoPresence) -> Result<Response, reqwest::Error>;
impl DirectoryRequest for Request {
fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl PresenceCocoNodesPoster for Request {
fn new(base_url: String) -> Self {
Request {
base_url,
path: "/api/presence/coconodes".to_string(),
}
impl DirectoryPostRequest for Request {
type Payload = CocoPresence;
fn json_payload(&self) -> &CocoPresence {
&self.payload
}
fn post(&self, presence: &CocoPresence) -> Result<Response, reqwest::Error> {
let url = format!("{}{}", self.base_url, self.path);
let client = reqwest::Client::new();
let p = client.post(&url).json(&presence).send()?;
Ok(p)
fn new(base_url: &str, payload: Self::Payload) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
payload,
}
}
}
#[cfg(test)]
mod metrics_get_request {
mod presence_coconodes_post_request {
use super::*;
#[cfg(test)]
use crate::client_test_fixture;
use mockito::mock;
#[cfg(test)]
mod on_a_400_status {
use super::*;
#[test]
fn it_returns_an_error() {
let _m = mock("POST", "/api/presence/coconodes")
.with_status(400)
.create();
let req = Request::new(mockito::server_url());
#[tokio::test]
async fn it_returns_an_error() {
let _m = mock("POST", PATH).with_status(400).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = req.post(&presence);
let result = client.post_coconode_presence(presence).await;
assert_eq!(400, result.unwrap().status());
_m.assert();
}
@@ -68,19 +68,16 @@ mod metrics_get_request {
#[cfg(test)]
mod on_a_200 {
use super::*;
#[test]
fn it_returns_a_response_with_201() {
#[tokio::test]
async fn it_returns_a_response_with_201() {
let json = r#"{
"ok": true
}"#;
let _m = mock("POST", "/api/presence/coconodes")
.with_status(201)
.with_body(json)
.create();
let req = Request::new(mockito::server_url());
let _m = mock("POST", PATH).with_status(201).with_body(json).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = req.post(&presence);
assert_eq!(true, result.is_ok());
let result = client.post_coconode_presence(presence).await;
assert!(result.is_ok());
_m.assert();
}
}
@@ -12,54 +12,54 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{DirectoryPostRequest, DirectoryRequest};
use crate::presence::gateways::GatewayPresence;
use reqwest::Response;
const PATH: &str = "/api/presence/gateways";
pub struct Request {
base_url: String,
path: String,
payload: GatewayPresence,
}
pub trait PresenceGatewayPoster {
fn new(base_url: String) -> Self;
fn post(&self, presence: &GatewayPresence) -> Result<Response, reqwest::Error>;
impl DirectoryRequest for Request {
fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl PresenceGatewayPoster for Request {
fn new(base_url: String) -> Self {
Request {
base_url,
path: "/api/presence/gateways".to_string(),
}
impl DirectoryPostRequest for Request {
type Payload = GatewayPresence;
fn json_payload(&self) -> &GatewayPresence {
&self.payload
}
fn post(&self, presence: &GatewayPresence) -> Result<Response, reqwest::Error> {
let url = format!("{}{}", self.base_url, self.path);
let client = reqwest::Client::new();
let p = client.post(&url).json(&presence).send()?;
Ok(p)
fn new(base_url: &str, payload: Self::Payload) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
payload,
}
}
}
#[cfg(test)]
mod metrics_get_request {
mod presence_gateways_post_request {
use super::*;
#[cfg(test)]
use crate::client_test_fixture;
use mockito::mock;
#[cfg(test)]
mod on_a_400_status {
use super::*;
#[test]
fn it_returns_an_error() {
let _m = mock("POST", "/api/presence/gateways")
.with_status(400)
.create();
let req = Request::new(mockito::server_url());
#[tokio::test]
async fn it_returns_an_error() {
let _m = mock("POST", PATH).with_status(400).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = req.post(&presence);
let result = client.post_gateway_presence(presence).await;
assert_eq!(400, result.unwrap().status());
_m.assert();
}
@@ -68,19 +68,16 @@ mod metrics_get_request {
#[cfg(test)]
mod on_a_200 {
use super::*;
#[test]
fn it_returns_a_response_with_201() {
#[tokio::test]
async fn it_returns_a_response_with_201() {
let json = r#"{
"ok": true
}"#;
let _m = mock("POST", "/api/presence/gateways")
.with_status(201)
.with_body(json)
.create();
let req = Request::new(mockito::server_url());
let _m = mock("POST", PATH).with_status(201).with_body(json).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = req.post(&presence);
assert_eq!(true, result.is_ok());
let result = client.post_gateway_presence(presence).await;
assert!(result.is_ok());
_m.assert();
}
}
@@ -12,54 +12,54 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{DirectoryPostRequest, DirectoryRequest};
use crate::presence::mixnodes::MixNodePresence;
use reqwest::Response;
const PATH: &str = "/api/presence/mixnodes";
pub struct Request {
base_url: String,
path: String,
payload: MixNodePresence,
}
pub trait PresenceMixNodesPoster {
fn new(base_url: String) -> Self;
fn post(&self, presence: &MixNodePresence) -> Result<Response, reqwest::Error>;
impl DirectoryRequest for Request {
fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl PresenceMixNodesPoster for Request {
fn new(base_url: String) -> Self {
Request {
base_url,
path: "/api/presence/mixnodes".to_string(),
}
impl DirectoryPostRequest for Request {
type Payload = MixNodePresence;
fn json_payload(&self) -> &MixNodePresence {
&self.payload
}
fn post(&self, presence: &MixNodePresence) -> Result<Response, reqwest::Error> {
let url = format!("{}{}", self.base_url, self.path);
let client = reqwest::Client::new();
let p = client.post(&url).json(&presence).send()?;
Ok(p)
fn new(base_url: &str, payload: Self::Payload) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
payload,
}
}
}
#[cfg(test)]
mod metrics_get_request {
mod presence_mixnodes_post_request {
use super::*;
#[cfg(test)]
use crate::client_test_fixture;
use mockito::mock;
#[cfg(test)]
mod on_a_400_status {
use super::*;
#[test]
fn it_returns_an_error() {
let _m = mock("POST", "/api/presence/mixnodes")
.with_status(400)
.create();
let req = Request::new(mockito::server_url());
#[tokio::test]
async fn it_returns_an_error() {
let _m = mock("POST", PATH).with_status(400).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = req.post(&presence);
let result = client.post_mixnode_presence(presence).await;
assert_eq!(400, result.unwrap().status());
_m.assert();
}
@@ -68,19 +68,16 @@ mod metrics_get_request {
#[cfg(test)]
mod on_a_200 {
use super::*;
#[test]
fn it_returns_a_response_with_201() {
#[tokio::test]
async fn it_returns_a_response_with_201() {
let json = r#"{
"ok": true
}"#;
let _m = mock("POST", "/api/presence/mixnodes")
.with_status(201)
.with_body(json)
.create();
let req = Request::new(mockito::server_url());
let _m = mock("POST", PATH).with_status(201).with_body(json).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = req.post(&presence);
assert_eq!(true, result.is_ok());
let result = client.post_mixnode_presence(presence).await;
assert!(result.is_ok());
_m.assert();
}
}
@@ -12,54 +12,54 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{DirectoryPostRequest, DirectoryRequest};
use crate::presence::providers::MixProviderPresence;
use reqwest::Response;
const PATH: &str = "/api/presence/mixproviders";
pub struct Request {
base_url: String,
path: String,
payload: MixProviderPresence,
}
pub trait PresenceMixProviderPoster {
fn new(base_url: String) -> Self;
fn post(&self, presence: &MixProviderPresence) -> Result<Response, reqwest::Error>;
impl DirectoryRequest for Request {
fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl PresenceMixProviderPoster for Request {
fn new(base_url: String) -> Self {
Request {
base_url,
path: "/api/presence/mixproviders".to_string(),
}
impl DirectoryPostRequest for Request {
type Payload = MixProviderPresence;
fn json_payload(&self) -> &MixProviderPresence {
&self.payload
}
fn post(&self, presence: &MixProviderPresence) -> Result<Response, reqwest::Error> {
let url = format!("{}{}", self.base_url, self.path);
let client = reqwest::Client::new();
let p = client.post(&url).json(&presence).send()?;
Ok(p)
fn new(base_url: &str, payload: Self::Payload) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
payload,
}
}
}
#[cfg(test)]
mod metrics_get_request {
mod presence_providers_post_request {
use super::*;
#[cfg(test)]
use crate::client_test_fixture;
use mockito::mock;
#[cfg(test)]
mod on_a_400_status {
use super::*;
#[test]
fn it_returns_an_error() {
let _m = mock("POST", "/api/presence/mixproviders")
.with_status(400)
.create();
let req = Request::new(mockito::server_url());
#[tokio::test]
async fn it_returns_an_error() {
let _m = mock("POST", PATH).with_status(400).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = req.post(&presence);
let result = client.post_provider_presence(presence).await;
assert_eq!(400, result.unwrap().status());
_m.assert();
}
@@ -68,19 +68,16 @@ mod metrics_get_request {
#[cfg(test)]
mod on_a_200 {
use super::*;
#[test]
fn it_returns_a_response_with_201() {
#[tokio::test]
async fn it_returns_a_response_with_201() {
let json = r#"{
"ok": true
}"#;
let _m = mock("POST", "/api/presence/mixproviders")
.with_status(201)
.with_body(json)
.create();
let req = Request::new(mockito::server_url());
let _m = mock("POST", PATH).with_status(201).with_body(json).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = req.post(&presence);
assert_eq!(true, result.is_ok());
let result = client.post_provider_presence(presence).await;
assert!(result.is_ok());
_m.assert();
}
}
@@ -12,73 +12,71 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{DirectoryGetRequest, DirectoryRequest};
use crate::presence::Topology;
const PATH: &str = "/api/presence/topology";
pub struct Request {
base_url: String,
path: String,
}
pub trait PresenceTopologyGetRequester {
fn new(base_url: String) -> Self;
fn get(&self) -> Result<Topology, reqwest::Error>;
impl DirectoryRequest for Request {
fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl PresenceTopologyGetRequester for Request {
fn new(base_url: String) -> Self {
Request {
base_url,
path: "/api/presence/topology".to_string(),
}
}
impl DirectoryGetRequest for Request {
type JSONResponse = Topology;
fn get(&self) -> Result<Topology, reqwest::Error> {
let url = format!("{}{}", self.base_url, self.path);
let topology: Topology = reqwest::get(&url)?.json()?;
Ok(topology)
fn new(base_url: &str) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
}
}
}
#[cfg(test)]
mod topology_requests {
use super::*;
#[cfg(test)]
use crate::client_test_fixture;
use mockito::mock;
#[cfg(test)]
mod on_a_400_status {
use super::*;
#[test]
#[should_panic]
fn it_panics() {
let _m = mock("GET", "/api/presence/topology")
#[tokio::test]
async fn it_returns_an_error() {
let _m = mock("GET", PATH)
.with_status(400)
.with_body("bad body")
.create();
let req = Request::new(mockito::server_url());
req.get().unwrap();
let client = client_test_fixture(&mockito::server_url());
let result = client.get_topology().await;
assert!(result.is_err());
_m.assert();
}
}
#[cfg(test)]
mod on_a_200 {
use super::*;
#[test]
fn it_returns_a_response_with_200_status_and_a_correct_topology() {
#[tokio::test]
async fn it_returns_a_response_with_200_status_and_a_correct_topology() {
let json = fixtures::topology_response_json();
let _m = mock("GET", "/api/presence/topology")
.with_status(200)
.with_body(json)
.create();
let req = Request::new(mockito::server_url());
let result = req.get();
assert!(result.is_ok());
let _m = mock("GET", PATH).with_status(200).with_body(json).create();
let client = client_test_fixture(&mockito::server_url());
let result = client.get_topology().await;
assert_eq!(
1575915097085539300,
1_575_915_097_085_539_300,
result.unwrap().coco_nodes.first().unwrap().last_seen
);
_m.assert();
}
}
#[cfg(test)]
pub mod fixtures {
#[cfg(test)]
+1
View File
@@ -9,6 +9,7 @@ edition = "2018"
[dependencies]
bs58 = "0.3.0"
itertools = "0.8.2"
futures = "0.3"
log = "0.4"
pretty_env_logger = "0.3"
rand = "0.7.2"
+5 -1
View File
@@ -13,6 +13,7 @@
// limitations under the License.
use crate::filter::VersionFilterable;
use futures::future::BoxFuture;
use itertools::Itertools;
use nymsphinx::Node as SphinxNode;
use rand::seq::IteratorRandom;
@@ -28,7 +29,10 @@ pub mod provider;
// TODO: Figure out why 'Clone' was required to have 'TopologyAccessor<T>' working
// even though it only contains an Arc
pub trait NymTopology: Sized + std::fmt::Debug + Send + Sync + Clone {
fn new(directory_server: String) -> Self;
// this is just a temporary work-around to make current code work without having to
// do major topology rewrites now.
// This will be removed once topology is re-worked
fn new<'a>(directory_server: String) -> BoxFuture<'a, Self>;
fn new_from_nodes(
mix_nodes: Vec<mix::Node>,
mix_provider_nodes: Vec<provider::Node>,