From 252580a3ca92a27365928dffb5aeff9353033a32 Mon Sep 17 00:00:00 2001 From: Tim Bruijnzeels Date: Thu, 24 Jan 2019 17:26:25 +0100 Subject: [PATCH] Moving all the http client stuff into crate::util::httpclient, and make it a bit smarter especially w.r.t. json handling. --- src/krillc/mod.rs | 222 ++++++++------------------------- src/krilld/endpoints.rs | 3 +- src/krilld/http/server.rs | 2 +- src/pubc/apiclient.rs | 112 ++--------------- src/pubc/cmsclient.rs | 76 +++--------- src/util/httpclient.rs | 245 +++++++++++++++++++++++++++++++++++++ src/util/mod.rs | 1 + tests/manage_publishers.rs | 4 +- 8 files changed, 329 insertions(+), 336 deletions(-) create mode 100644 src/util/httpclient.rs diff --git a/src/krillc/mod.rs b/src/krillc/mod.rs index 1102617a..7d5b24c0 100644 --- a/src/krillc/mod.rs +++ b/src/krillc/mod.rs @@ -2,10 +2,7 @@ pub mod data; pub mod options; use std::io; -use std::time::Duration; use bytes::Bytes; -use reqwest::{Client, Response, StatusCode}; -use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT}; use rpki::uri; use crate::api::data::Publisher; use crate::krillc::data::{ @@ -21,7 +18,7 @@ use crate::krillc::options::{ }; use crate::remote::rfc8183; use crate::util::file; -use serde::Serialize; +use crate::util::httpclient; /// Command line tool for Krill admin tasks pub struct KrillClient { @@ -60,7 +57,10 @@ impl KrillClient { } fn health(&self) -> Result { - self.get("api/v1/health")?; + httpclient::get_ok( + &self.resolve_uri("api/v1/health"), + Some(&self.token) + )?; Ok(ApiResponse::Health) } @@ -70,8 +70,10 @@ impl KrillClient { ) -> Result { match command { PublishersCommand::List => { - let res = self.get("api/v1/publishers")?; - let list: PublisherList = serde_json::from_str(&res)?; + let list: PublisherList = httpclient::get_json( + &self.resolve_uri("api/v1/publishers"), + Some(&self.token) + )?; Ok(ApiResponse::PublisherList(list)) }, PublishersCommand::Add(add) => { @@ -81,48 +83,41 @@ impl KrillClient { add.base_uri, None ); - match self.post_json("api/v1/publishers", pbl)? { - Some(body) => { - if body.is_empty() { - Ok(ApiResponse::Empty) - } else { - Ok(ApiResponse::GenericBody(body)) - } - }, - None => Ok(ApiResponse::Empty) - } + self.add_publisher(pbl) }, PublishersCommand::AddWithCms(add) => { let bytes = file::read(&add.xml)?; let pr = rfc8183::PublisherRequest::decode(bytes.as_ref())?; - let pbl = pr.into_publisher(add.token, add.base_uri); - match self.post_json("api/v1/publishers", pbl)? { - Some(body) => { - if body.is_empty() { - Ok(ApiResponse::Empty) - } else { - Ok(ApiResponse::GenericBody(body)) - } - }, - None => Ok(ApiResponse::Empty) - } + self.add_publisher(pbl) }, PublishersCommand::Remove(handle) => { let uri = format!("api/v1/publishers/{}", handle); - self.delete(&uri)?; + let uri = self.resolve_uri(&uri); + httpclient::delete(&uri, Some(&self.token))?; Ok(ApiResponse::Empty) }, PublishersCommand::Details(handle) => { let uri = format!("api/v1/publishers/{}", handle); - let res = self.get(uri.as_str())?; - let details: PublisherDetails = serde_json::from_str(&res)?; + let uri = self.resolve_uri(&uri); + + let details: PublisherDetails = httpclient::get_json( + &uri, + Some(&self.token) + )?; Ok(ApiResponse::PublisherDetails(details)) }, PublishersCommand::RepositoryResponseXml(handle, file_opt) => { let uri = format!("api/v1/publishers/{}/response.xml", handle); - let xml = self.get(uri.as_str())?; + let uri = self.resolve_uri(&uri); + + let xml = httpclient::get_text( + &uri, + "application/xml", + Some(&self.token) + )?; + match file_opt { Some(path) => { file::save(&Bytes::from(xml), &path)?; @@ -135,8 +130,13 @@ impl KrillClient { }, PublishersCommand::IdCert(handle, file) => { let uri = format!("api/v1/publishers/{}", handle); - let res = self.get(uri.as_str())?; - let details: PublisherDetails = serde_json::from_str(&res)?; + let uri = self.resolve_uri(&uri); + + let details: PublisherDetails = httpclient::get_json( + &uri, + Some(&self.token) + )?; + match details.identity_cert() { Some(cert) => { let bytes = cert.to_bytes(); @@ -145,138 +145,23 @@ impl KrillClient { }, None => Err(Error::NoIdCert) } - } } } - fn headers(&self) -> HeaderMap { - let mut headers = HeaderMap::new(); - headers.insert( - USER_AGENT, - HeaderValue::from_str("krillc").unwrap() - ); - headers.insert( - "Authorization", - HeaderValue::from_str(&format!("Bearer {}", &self.token)).unwrap() - ); - headers + fn add_publisher(&self, pbl: Publisher) -> Result { + httpclient::post_json( + &self.resolve_uri("api/v1/publishers"), + pbl, + Some(&self.token) + )?; + + Ok(ApiResponse::Empty) } - fn client(&self) -> Result { - Client::builder() - .gzip(true) - .timeout(Duration::from_secs(30)) - .build() - .map_err(|e| Error::RequestError(e)) + fn resolve_uri(&self, path: &str) -> String { + format!("{}{}", &self.server, path) } - - fn post_json( - &self, - rel: &str, - data: impl Serialize - ) -> Result, Error> { - let headers = self.headers(); - let body = serde_json::to_string(&data)?; - - let uri = format!("{}{}", &self.server.to_string(), rel); - let mut res = self.client()?.post(&uri) - .headers(headers) - .body(body) - .send()?; - - match res.status() { - StatusCode::OK => { - Ok(res.text().ok()) - }, - status => { - match res.text() { - Ok(body) => { - if body.is_empty() { - Err(Error::BadStatus(status)) - } else { - Err(Error::ErrorWithBody(body)) - } - }, - _ => Err(Error::BadStatus(status)) - } - } - } - } - - /// Sends a get request to the server, including the token for - /// authorization. - /// Note that the server uri ends with a '/', so leave out the '/' - /// from the start of the rel_path when calling this function. - fn get( - &self, - rel_path: &str - ) -> Result { - let mut res = self.get_generic(rel_path)?; - let txt = res.text()?; - Ok(txt) - } - - /// Sends a get request to the server, including the token for - /// authorization. - /// Note that the server uri ends with a '/', so leave out the '/' - /// from the start of the rel_path when calling this function. - #[allow(dead_code)] - fn get_binary( - &self, - rel_path: &str - ) -> Result { - let mut res = self.get_generic(rel_path)?; - let mut bytes: Vec = vec![]; - res.copy_to(&mut bytes)?; - Ok(Bytes::from(bytes)) - } - - fn get_generic( - &self, - rel_path: &str - ) -> Result { - let uri = format!("{}{}", &self.server.to_string(), rel_path); - let mut res = self.client()? - .get(&uri) - .headers(self.headers()) - .send()?; - - match res.status() { - StatusCode::OK => { - Ok(res) - }, - status => { - match res.text() { - Ok(body) => { - if body.is_empty() { - Err(Error::BadStatus(status)) - } else { - Err(Error::ErrorWithBody(body)) - } - }, - _ => Err(Error::BadStatus(status)) - } - } - } - } - - fn delete( - &self, - rel: &str - ) -> Result<(), Error> { - let uri = format!("{}{}", &self.server.to_string(), rel); - let res = self.client()? - .delete(&uri) - .headers(self.headers()) - .send()?; - - match res.status() { - StatusCode::OK => Ok(()), - status => Err(Error::BadStatus(status)) - } - } - } //------------ Error --------------------------------------------------------- @@ -289,14 +174,8 @@ pub enum Error { #[display(fmt="Server is not available.")] ServerDown, - #[display(fmt="Request Error: {}", _0)] - RequestError(reqwest::Error), - - #[display(fmt="Received bad status: {}", _0)] - BadStatus(StatusCode), - #[display(fmt="{}", _0)] - ErrorWithBody(String), + HttpClientError(httpclient::Error), #[display(fmt="Received invalid json response: {}", _0)] JsonError(serde_json::Error), @@ -311,13 +190,14 @@ pub enum Error { NoIdCert, #[display(fmt="Invalid RFC8183 XML")] - InvalidRfc8183 + InvalidRfc8183, + + #[display(fmt="Empty response received from server")] + EmptyResponse } -impl From for Error { - fn from(e: reqwest::Error) -> Self { - Error::RequestError(e) - } +impl From for Error { + fn from(e: httpclient::Error) -> Self { Error::HttpClientError(e) } } impl From for Error { diff --git a/src/krilld/endpoints.rs b/src/krilld/endpoints.rs index 29a8efe0..14b1ef3a 100644 --- a/src/krilld/endpoints.rs +++ b/src/krilld/endpoints.rs @@ -68,8 +68,7 @@ pub fn publishers(req: &HttpRequest) -> HttpResponse { Err(e) => server_error(Error::ServerError(e)), Ok(publishers) => { render_json( - responses::PublisherList::from(&publishers, - "/api/v1/publishers") + responses::PublisherList::from(&publishers, "/api/v1/publishers") ) } } diff --git a/src/krilld/http/server.rs b/src/krilld/http/server.rs index b274f8af..d2d0d55b 100644 --- a/src/krilld/http/server.rs +++ b/src/krilld/http/server.rs @@ -10,7 +10,7 @@ use actix_web::{pred, fs, server}; use actix_web::{App, FromRequest, HttpResponse}; use actix_web::dev::MessageBody; use actix_web::middleware; -use actix_web::http::{Method, StatusCode }; +use actix_web::http::{Method, StatusCode}; use bcder::decode; use futures::Future; use openssl::ssl::{SslMethod, SslAcceptor, SslAcceptorBuilder, SslFiletype}; diff --git a/src/pubc/apiclient.rs b/src/pubc/apiclient.rs index 511d9d5d..3e7fdd37 100644 --- a/src/pubc/apiclient.rs +++ b/src/pubc/apiclient.rs @@ -1,12 +1,9 @@ //! Publication Client that uses the JSON/Rest API use std::path::PathBuf; use clap::{App, Arg, SubCommand}; -use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT, CONTENT_TYPE}; -use reqwest::{Client, Response, StatusCode}; use rpki::uri; -use serde::Serialize; -use std::time::Duration; -use api::responses; +use crate::api::responses; +use crate::util::httpclient; //------------ PubClientOptions ---------------------------------------------- @@ -125,15 +122,12 @@ pub fn execute(options: PubClientOptions) -> Result { &options.handle ); - match get_text(&uri, &options.token) { - Err(e) => Err(e), - Ok(None) => Err(Error::NoResponse), - Ok(Some(text)) => { - let list: responses::ListReply = - serde_json::from_str(&text)?; - - Ok(ApiResponse::List(list)) - } + match httpclient::get_json::( + &uri, + Some(&options.token) + ) { + Err(e) => Err(Error::HttpClientError(e)), + Ok(list) => Ok(ApiResponse::List(list)) } }, Command::Sync(_dir) => { @@ -143,77 +137,6 @@ pub fn execute(options: PubClientOptions) -> Result { } -fn client() -> Result { - Client::builder() - .gzip(true) - .timeout(Duration::from_secs(30)) - .build() - .map_err(|e| Error::RequestError(e)) -} - - -fn headers(token: &str) -> HeaderMap { - let mut headers = HeaderMap::new(); - headers.insert( - USER_AGENT, - HeaderValue::from_str("krill-pubc").unwrap() - ); - headers.insert( - CONTENT_TYPE, - HeaderValue::from_str("application/json").unwrap() - ); - headers.insert( - "Authorization", - HeaderValue::from_str(&format!("Bearer {}", token)).unwrap() - ); - headers -} - -fn process_response(mut res: Response) -> Result, Error> { - match res.status() { - StatusCode::OK => { - Ok(res.text().ok()) - }, - status => { - match res.text() { - Ok(body) => { - if body.is_empty() { - Err(Error::BadStatus(status)) - } else { - Err(Error::ErrorWithBody(body)) - } - }, - _ => Err(Error::BadStatus(status)) - } - } - } -} - -fn get_text( - uri: &str, - token: &str -) -> Result, Error> { - let headers = headers(token); - let res = client()?.get(uri).headers(headers).send()?; - process_response(res) -} - -#[allow(dead_code)] -fn post_json( - uri: &str, - data: impl Serialize, - token: &str -) -> Result, Error> { - let headers = headers(token); - let body = serde_json::to_string(&data)?; - let client = client()?; - - let res = client.post(uri).headers(headers).body(body).send()?; - process_response(res) -} - - - //------------ Command ------------------------------------------------------- #[derive(Clone, Debug, Eq, PartialEq)] @@ -252,21 +175,14 @@ pub enum Error { #[display(fmt = "Specify an action: list, or sync --dir ")] NoCommand, - #[display(fmt="Request Error: {}", _0)] - RequestError(reqwest::Error), - - #[display(fmt="Received bad status: {}", _0)] - BadStatus(StatusCode), - - #[display(fmt="{}", _0)] - ErrorWithBody(String), - #[display(fmt = "Expected a response body, but got nothing.")] NoResponse, + #[display(fmt="{}", _0)] + HttpClientError(httpclient::Error), + #[display(fmt="Received invalid json response: {}", _0)] JsonError(serde_json::Error), - } impl From for Error { @@ -276,9 +192,3 @@ impl From for Error { impl From for Error { fn from(e: serde_json::Error) -> Self { Error::JsonError(e) } } - -impl From for Error { - fn from(e: reqwest::Error) -> Self { - Error::RequestError(e) - } -} diff --git a/src/pubc/cmsclient.rs b/src/pubc/cmsclient.rs index 6308344d..75ff1442 100644 --- a/src/pubc/cmsclient.rs +++ b/src/pubc/cmsclient.rs @@ -1,14 +1,10 @@ use std::io; -use std::io::Read; use std::path::PathBuf; use std::sync::Arc; -use std::time::Duration; use bcder::{Captured, Mode}; use bcder::decode; use bcder::encode::Values; use clap::{App, Arg, SubCommand}; -use reqwest::{Client, Response, StatusCode}; -use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT, CONTENT_TYPE}; use rpki::x509::ValidationError; use rpki::crypto::{PublicKeyFormat, Signer}; use toml; @@ -20,6 +16,7 @@ use crate::remote::rfc8181; use crate::remote::sigmsg::SignedMessage; use crate::storage::caching_ks::CachingDiskKeyStore; use crate::storage::keystore::{self, Info, Key, KeyStore}; +use crate::util::httpclient; use crate::util::softsigner::{self, OpenSslSigner}; use crate::util::file::{self, CurrentFile, RecursorError}; @@ -277,51 +274,15 @@ impl PubClient { req: Captured ) -> Result { let parent = self.get_my_parent()?; + let post_bytes = req.into_bytes(); - let mut headers = HeaderMap::new(); - headers.insert( - USER_AGENT, - HeaderValue::from_str("krill-pubc-cms").unwrap() - ); - headers.insert( - CONTENT_TYPE, - HeaderValue::from_str("application/rpki-publication").unwrap() - ); + let res_bytes = httpclient::post_binary( + &parent.service_uri().to_string(), + &post_bytes, + "application/rpki-publication" + )?; - let client = Client::builder() - .gzip(true) - // 5 minute timeout is enough to sync the entire RIPE NCC repo. - .timeout(Duration::from_secs(300)) - .build()?; - - let mut res = client.post(&parent.service_uri().to_string()) - .headers(headers) - .body(req.to_vec()) - .send()?; - - match res.status() { - StatusCode::OK => { - self.parse_res(res) - }, - _ => { - println!("{}", res.text() - .unwrap_or("unspecified error".to_string())); - Err(Error::PubServerHttpError(res.status())) - } - } - } - - fn parse_res( - &mut self, - mut res: Response - ) -> Result { - let parent = self.get_my_parent()?; - - let mut bytes: Vec = vec![]; - res.read_to_end(&mut bytes).unwrap(); - let bytes = bytes::Bytes::from(bytes); - - let signed_msg = SignedMessage::decode(bytes, true)?; + let signed_msg = SignedMessage::decode(res_bytes, true)?; signed_msg.validate(parent.id_cert())?; rfc8181::Message::from_signed_message(&signed_msg).map_err(|e| { Error::MessageError(e) @@ -526,12 +487,6 @@ pub enum Error { #[display(fmt="{}", _0)] KeyStoreError(keystore::Error), - #[display(fmt="Received bad HTTP status code: {}", _0)] - PubServerHttpError(StatusCode), - - #[display(fmt="Request Error: {}", _0)] - RequestError(reqwest::Error), - #[display(fmt="{}", _0)] ValidationError(ValidationError), @@ -551,7 +506,10 @@ pub enum Error { RecursorError(RecursorError), #[display(fmt="{}", _0)] - BuilderError(builder::Error) + BuilderError(builder::Error), + + #[display(fmt="{}", _0)] + HttpClientError(httpclient::Error), } impl From for Error { @@ -566,12 +524,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: reqwest::Error) -> Self { - Error::RequestError(e) - } -} - impl From for Error { fn from(e: ValidationError) -> Self { Error::ValidationError(e) @@ -602,5 +554,9 @@ impl From> for Error { } } +impl From for Error { + fn from(e: httpclient::Error) -> Self { Error::HttpClientError(e) } +} + // Tested in integration tests in tests folder \ No newline at end of file diff --git a/src/util/httpclient.rs b/src/util/httpclient.rs new file mode 100644 index 00000000..c235302f --- /dev/null +++ b/src/util/httpclient.rs @@ -0,0 +1,245 @@ +//! Some helper functions for HTTP calls +use std::io::Read; +use std::time::Duration; +use bytes::Bytes; +use reqwest::{Client, Response, StatusCode}; +use reqwest::header::{ + HeaderMap, + HeaderValue, + InvalidHeaderValue, + USER_AGENT, + CONTENT_TYPE}; +use serde::Serialize; +use serde::de::DeserializeOwned; + +const JSON_CONTENT: &'static str = "application/json"; + + +/// Performs a GET request that expects a json response that can be +/// deserialized into the an owned value of the expected type. Returns an error +/// if nothing is returned. +pub fn get_json( + uri: &str, + token: Option<&str> +) -> Result { + let headers = headers(Some(JSON_CONTENT), token)?; + let res = client()?.get(uri).headers(headers).send()?; + process_json_response(res) +} + +/// Performs a get request and expects a response that can be turned +/// into a string (in particular, not a binary response). +pub fn get_text( + uri: &str, + content_type: &str, + token: Option<&str> +) -> Result { + let headers = headers(Some(content_type), token)?; + let res = client()?.get(uri).headers(headers).send()?; + match opt_text_response(res)? { + Some(res) => Ok(res), + None => Err(Error::EmptyResponse) + } +} + +/// Checks that there is a 200 OK response at the given URI. Discards the +/// response body. +pub fn get_ok(uri: &str, token: Option<&str>) -> Result<(), Error> { + let headers = headers(None, token)?; + let res = client()?.get(uri).headers(headers).send()?; + opt_text_response(res)?; // Will return nice errors with possible body. + Ok(()) +} + + +/// Performs a POST of data that can be serialized into json, and expects +/// a 200 OK response, without a body. +pub fn post_json( + uri: &str, + data: impl Serialize, + token: Option<&str> +) -> Result<(), Error> { + let headers = headers(Some(JSON_CONTENT), token)?; + let body = serde_json::to_string(&data)?; + let res = client()?.post(uri).headers(headers).body(body).send()?; + if let Some(res) = opt_text_response(res)? { + Err(Error::UnexpectedResponse(res)) + } else { + Ok(()) + } +} + + +/// Performs a POST of data that can be serialized into json, and expects +/// a json response that can be deserialized into the an owned value of the +/// expected type. +pub fn post_json_with_response( + uri: &str, + data: impl Serialize, + token: Option<&str> +) -> Result { + let headers = headers(Some(JSON_CONTENT), token)?; + let body = serde_json::to_string(&data)?; + let res = client()?.post(uri).headers(headers).body(body).send()?; + process_json_response(res) +} + + +/// Posts binary data, and expects a binary response. +/// +/// Note: Bytes may be empty if the post was successful, but the response was +/// empty. +pub fn post_binary( + uri: &str, + data: &Bytes, + content_type: &str +) -> Result { + let headers = headers(Some(content_type), None)?; + let body = data.to_vec(); + + let mut res = client()?.post(uri).headers(headers).body(body).send()?; + + match res.status() { + StatusCode::OK => { + let mut bytes: Vec = vec![]; + res.read_to_end(&mut bytes).unwrap(); + let bytes = bytes::Bytes::from(bytes); + Ok(bytes) + }, + status => { + match res.text() { + Ok(body) => { + if body.is_empty() { + Err(Error::BadStatus(status)) + } else { + Err(Error::ErrorWithBody(status, body)) + } + }, + _ => Err(Error::BadStatus(status)) + } + } + } +} + +/// Sends a delete request to the specified url. +pub fn delete( + uri: &str, + token: Option<&str> +) -> Result<(), Error> { + let headers = headers(None, token)?; + client()?.delete(uri).headers(headers).send()?; + Ok(()) +} + + +fn client() -> Result { + Client::builder() + .gzip(true) + .timeout(Duration::from_secs(30)) + .build() + .map_err(|e| Error::RequestError(e)) +} + +fn headers( + content_type: Option<&str>, + token: Option<&str> +) -> Result { + let mut headers = HeaderMap::new(); + headers.insert( + USER_AGENT, + HeaderValue::from_str("krill")? + ); + if let Some(content_type) = content_type { + headers.insert( + CONTENT_TYPE, + HeaderValue::from_str(content_type)? + ); + } + if let Some(token) = token { + headers.insert( + "Authorization", + HeaderValue::from_str(&format!("Bearer {}", token))? + ); + } + Ok(headers) +} + +fn process_json_response( + res: Response +) -> Result { + match opt_text_response(res) { + Err(e) => Err(e), + Ok(None) => Err(Error::EmptyResponse), + Ok(Some(s)) => { + let res: T = serde_json::from_str(&s)?; + Ok(res) + } + } +} + +fn opt_text_response(mut res: Response) -> Result, Error> { + match res.status() { + StatusCode::OK => { + match res.text().ok() { + None => Ok(None), + Some(s) => { + if s.is_empty() { + Ok(None) + } else { + Ok(Some(s)) + } + } + } + }, + status => { + match res.text() { + Ok(body) => { + if body.is_empty() { + Err(Error::BadStatus(status)) + } else { + Err(Error::ErrorWithBody(status, body)) + } + }, + _ => Err(Error::BadStatus(status)) + } + } + } +} + +//------------ Error --------------------------------------------------------- + +#[derive(Debug, Display)] +pub enum Error { + #[display(fmt="Request Error: {}", _0)] + RequestError(reqwest::Error), + + #[display(fmt="Received bad status: {}", _0)] + BadStatus(StatusCode), + + #[display(fmt="Status: {}, Error: {}", _0, _1)] + ErrorWithBody(StatusCode, String), + + #[display(fmt="{}", _0)] + JsonError(serde_json::Error), + + #[display(fmt="{}", _0)] + InvalidHeader(InvalidHeaderValue), + + #[display(fmt="Empty response received from server")] + EmptyResponse, + + #[display(fmt="Unexpected response: {}", _0)] + UnexpectedResponse(String) +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { Error::RequestError(e) } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { Error::JsonError(e) } +} + +impl From for Error { + fn from(v: InvalidHeaderValue) -> Self { Error::InvalidHeader(v) } +} diff --git a/src/util/mod.rs b/src/util/mod.rs index dd4eabe4..caad724d 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -4,6 +4,7 @@ use rpki::crypto::DigestAlgorithm; pub mod ext_serde; pub mod file; +pub mod httpclient; pub mod softsigner; pub mod test; pub mod xml; diff --git a/tests/manage_publishers.rs b/tests/manage_publishers.rs index 344bd000..9567a99b 100644 --- a/tests/manage_publishers.rs +++ b/tests/manage_publishers.rs @@ -25,6 +25,7 @@ use krill::krillc::options::{ }; use krill::pubc::cmsclient::PubClient; use krill::util::test; +use krill::util::httpclient; use krill::remote::rfc8183::RepositoryResponse; use reqwest::StatusCode; @@ -179,7 +180,8 @@ fn manage_publishers() { let res = KrillClient::process(krillc_opts); match res { - Err(krillc::Error::BadStatus(code)) => { + Err(krillc::Error::HttpClientError( + httpclient::Error::BadStatus(code))) => { assert_eq!(code, StatusCode::NOT_FOUND); }, _ => assert!(false) // should have failed!