Skip to content

Commit

Permalink
refactor: store the Pact source along with the loaded pact in the mai…
Browse files Browse the repository at this point in the history
…n server
  • Loading branch information
rholshausen committed May 24, 2023
1 parent 9b263e5 commit fa94af5
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 41 deletions.
23 changes: 17 additions & 6 deletions src/loading.rs
Expand Up @@ -71,16 +71,21 @@ impl From<anyhow::Error> for PactError {
}
}

fn walkdir(dir: &Path, ext: &str) -> Result<Vec<Result<Box<dyn Pact + Send + Sync + RefUnwindSafe>, PactError>>, PactError> {
fn walkdir(
dir: &Path,
ext: &str,
s: &PactSource
) -> Result<Vec<Result<(Box<dyn Pact + Send + Sync + RefUnwindSafe>, PactSource), PactError>>, PactError> {

Check warning on line 78 in src/loading.rs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, stable)

very complex type used. Consider factoring parts into `type` definitions

Check warning on line 78 in src/loading.rs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, beta)

very complex type used. Consider factoring parts into `type` definitions

Check warning on line 78 in src/loading.rs

View workflow job for this annotation

GitHub Actions / build (macos-latest, stable)

very complex type used. Consider factoring parts into `type` definitions

Check warning on line 78 in src/loading.rs

View workflow job for this annotation

GitHub Actions / build (macos-latest, beta)

very complex type used. Consider factoring parts into `type` definitions

Check warning on line 78 in src/loading.rs

View workflow job for this annotation

GitHub Actions / build (windows-latest, beta)

very complex type used. Consider factoring parts into `type` definitions

Check warning on line 78 in src/loading.rs

View workflow job for this annotation

GitHub Actions / build (windows-latest, stable)

very complex type used. Consider factoring parts into `type` definitions
let mut pacts = vec![];
debug!("Scanning {:?}", dir);
for entry in fs::read_dir(dir)? {
let path = entry?.path();
if path.is_dir() {
walkdir(&path, ext)?;
pacts.extend(walkdir(&path, ext, s)?);
} else if path.extension().is_some() && path.extension().unwrap_or_default() == ext {
debug!("Loading file '{:?}'", path);
pacts.push(read_pact(&path)
.map(|p| (p, s.clone()))
.map_err(|err| PactError::from(err).with_path(path.as_path())))
}
}
Expand Down Expand Up @@ -123,18 +128,22 @@ pub async fn load_pacts(
sources: Vec<PactSource>,
insecure_tls: bool,
ext: Option<&String>
) -> Vec<Result<Box<dyn Pact + Send + Sync + RefUnwindSafe>, PactError>> {
) -> Vec<Result<(Box<dyn Pact + Send + Sync + RefUnwindSafe>, PactSource), PactError>> {
futures::stream::iter(sources)
.then(| s| async move {
let values = match &s {
PactSource::File(file) => vec![
read_pact(Path::new(file)).map_err(PactError::from)
read_pact(Path::new(file))
.map(|p| (p, s.clone()))
.map_err(PactError::from)
],
PactSource::Dir(dir) => match walkdir(Path::new(dir), ext.unwrap_or(&"json".to_string())) {
PactSource::Dir(dir) => match walkdir(Path::new(dir), ext.unwrap_or(&"json".to_string()), &s) {
Ok(pacts) => pacts,
Err(err) => vec![Err(PactError::new(format!("Could not load pacts from directory '{}' - {}", dir, err)))]
},
PactSource::URL(url, auth) => vec![ pact_from_url(url, auth, insecure_tls).await ],
PactSource::URL(url, auth) => vec![
pact_from_url(url, auth, insecure_tls).await.map(|p| (p, s.clone()))
],
PactSource::Broker { url, auth, consumers, providers } => {
let client = HALClient::with_url(url, auth.clone());
match client.navigate("pb:latest-pact-versions", &hashmap!{}).await {
Expand All @@ -159,6 +168,7 @@ pub async fn load_pacts(
})
.filter(|result| filter_consumers(consumers, result))
.filter(|result| filter_providers(providers, result))
.map(|result| result.map(|p| (p, s.clone())))
.collect().await
},
Err(err) => vec![Err(PactError::new(err.to_string()))]
Expand All @@ -167,6 +177,7 @@ pub async fn load_pacts(
Err(err) => vec![Err(PactError::new(err.to_string()))]
}
}
PactSource::Unknown => vec![]
};
futures::stream::iter(values)
})
Expand Down
9 changes: 6 additions & 3 deletions src/main.rs
Expand Up @@ -145,7 +145,9 @@ pub enum PactSource {
consumers: Vec<Regex>,
/// Provider names to filter Pacts with
providers: Vec<Regex>
}
},
/// Source that is not known, only used for unit testing
Unknown
}

fn pact_source(matches: &ArgMatches) -> Vec<PactSource> {
Expand Down Expand Up @@ -221,10 +223,11 @@ async fn handle_command_args(args: Vec<String>) -> Result<(), ExitCode> {
let pacts = pacts.iter()
.map(|result| {
// Currently, as_v4_pact won't fail as it upgrades older formats to V4, so is safe to unwrap
result.as_ref().unwrap().as_v4_pact().unwrap()
let (p, s) = result.as_ref().unwrap();
(p.as_v4_pact().unwrap(), s.clone())
})
.collect::<Vec<_>>();
let interactions: usize = pacts.iter().map(|p| p.interactions.len()).sum();
let interactions: usize = pacts.iter().map(|(p, _)| p.interactions.len()).sum();
info!("Loaded {} pacts ({} total interactions)", pacts.len(), interactions);
let auto_cors = matches.get_flag("cors");
let referer = matches.get_flag("cors-referer");
Expand Down
66 changes: 34 additions & 32 deletions src/server.rs
Expand Up @@ -2,9 +2,9 @@ use std::pin::Pin;
use std::process::ExitCode;

use anyhow::anyhow;
use futures::executor::block_on;
use futures::future::Future;
use futures::stream::StreamExt;
use futures::executor::block_on;
use futures::task::{Context, Poll};
use http::{Error, StatusCode};
use hyper::{Body, Request as HyperRequest, Response as HyperResponse, Server};
Expand All @@ -20,11 +20,11 @@ use regex::Regex;
use tower_service::Service;
use tracing::{debug, error, info, warn};

use crate::pact_support;
use crate::{pact_support, PactSource};

#[derive(Clone)]
pub struct ServerHandler {
sources: Vec<V4Pact>,
sources: Vec<(V4Pact, PactSource)>,
auto_cors: bool,
cors_referer: bool,
provider_state: Option<Regex>,
Expand All @@ -34,7 +34,7 @@ pub struct ServerHandler {

impl ServerHandler {
pub fn new(
sources: Vec<V4Pact>,
sources: Vec<(V4Pact, PactSource)>,
auto_cors: bool,
cors_referer: bool,
provider_state: Option<Regex>,
Expand Down Expand Up @@ -136,7 +136,7 @@ async fn find_matching_request(
request: &HttpRequest,
auto_cors: bool,
cors_referer: bool,
sources: Vec<V4Pact>,
sources: Vec<(V4Pact, PactSource)>,
provider_state: Option<Regex>,
empty_provider_states: bool
) -> anyhow::Result<HttpResponse> {
Expand All @@ -147,7 +147,7 @@ async fn find_matching_request(

// Get a subset of all interactions across all pacts that match the method and path
let interactions = sources.iter()
.flat_map(|source| {
.flat_map(|(source, _)| {
source.filter_interactions(V4InteractionType::Synchronous_HTTP)
.iter()
.map(|i| (i.as_v4_http().unwrap(), source.clone()))
Expand Down Expand Up @@ -236,7 +236,7 @@ async fn handle_request(
request: HttpRequest,
auto_cors: bool,
cors_referrer: bool,
sources: Vec<V4Pact>,
sources: Vec<(V4Pact, PactSource)>,
provider_state: Option<Regex>,
empty_provider_states: bool
) -> HttpResponse {
Expand Down Expand Up @@ -273,6 +273,8 @@ mod test {
use pact_models::v4::interaction::V4Interaction;
use regex::Regex;

use crate::PactSource;

#[tokio::test]
async fn match_request_finds_the_most_appropriate_response() {
let interaction1 = SynchronousHttp::default();
Expand All @@ -284,7 +286,7 @@ mod test {

let request1 = HttpRequest::default();

expect!(super::find_matching_request(&request1, false, false, vec![pact], None, false).await)
expect!(super::find_matching_request(&request1, false, false, vec![(pact, PactSource::Unknown)], None, false).await)
.to(be_ok().value(interaction1.response));
}

Expand All @@ -300,7 +302,7 @@ mod test {

let request1 = HttpRequest { method: "POST".to_string(), .. HttpRequest::default() };

expect!(super::find_matching_request(&request1, false, false, vec![pact], None, false).await)
expect!(super::find_matching_request(&request1, false, false, vec![(pact, PactSource::Unknown)], None, false).await)
.to(be_err());
}

Expand All @@ -320,7 +322,7 @@ mod test {

let request1 = HttpRequest { path: "/two".to_string(), .. HttpRequest::default() };

expect!(super::find_matching_request(&request1, false, false, vec![pact], None, false).await)
expect!(super::find_matching_request(&request1, false, false, vec![(pact, PactSource::Unknown)], None, false).await)
.to(be_err());
}

Expand All @@ -339,7 +341,7 @@ mod test {
query: Some(hashmap!{ "A".to_string() => vec![ "C".to_string() ] }),
.. HttpRequest::default() };

expect!(super::find_matching_request(&request1, false, false, vec![pact], None, false).await)
expect!(super::find_matching_request(&request1, false, false, vec![(pact, PactSource::Unknown)], None, false).await)
.to(be_err());
}

Expand Down Expand Up @@ -378,10 +380,10 @@ mod test {
let request4 = HttpRequest { method: "PUT".to_string(), headers: Some(hashmap!{ "Content-Type".to_string() => vec!["application/json".to_string()] }),
.. HttpRequest::default() };

expect!(super::find_matching_request(&request1, false, false, vec![pact.clone()], None, false).await).to(be_ok());
expect!(super::find_matching_request(&request2, false, false, vec![pact.clone()], None, false).await).to(be_err());
expect!(super::find_matching_request(&request3, false, false, vec![pact.clone()], None, false).await).to(be_ok());
expect!(super::find_matching_request(&request4, false, false, vec![pact], None, false).await).to(be_ok());
expect!(super::find_matching_request(&request1, false, false, vec![(pact.clone(), PactSource::Unknown)], None, false).await).to(be_ok());
expect!(super::find_matching_request(&request2, false, false, vec![(pact.clone(), PactSource::Unknown)], None, false).await).to(be_err());
expect!(super::find_matching_request(&request3, false, false, vec![(pact.clone(), PactSource::Unknown)], None, false).await).to(be_ok());
expect!(super::find_matching_request(&request4, false, false, vec![(pact, PactSource::Unknown)], None, false).await).to(be_ok());
}

#[tokio::test]
Expand Down Expand Up @@ -411,7 +413,7 @@ mod test {
body: OptionalBody::Present("{\"a\": 1, \"b\": 4, \"c\": 6}".as_bytes().into(), None, None),
.. HttpRequest::default() };

expect!(super::find_matching_request(&request1, false, false, vec![pact1, pact2], None, false).await)
expect!(super::find_matching_request(&request1, false, false, vec![(pact1, PactSource::Unknown), (pact2, PactSource::Unknown)], None, false).await)
.to(be_ok().value(interaction2.response));
}

Expand All @@ -427,9 +429,9 @@ mod test {
method: "OPTIONS".to_string(),
.. HttpRequest::default() };

expect!(super::find_matching_request(&request1, true, false, vec![pact.clone()], None, false).await)
expect!(super::find_matching_request(&request1, true, false, vec![(pact.clone(), PactSource::Unknown)], None, false).await)
.to(be_ok());
expect!(super::find_matching_request(&request1, false, false, vec![pact], None, false).await)
expect!(super::find_matching_request(&request1, false, false, vec![(pact, PactSource::Unknown)], None, false).await)
.to(be_err());
}

Expand Down Expand Up @@ -469,7 +471,7 @@ mod test {
query: Some(hashmap!{ "page".to_string() => vec![ "3".to_string() ] }),
.. HttpRequest::default() };

expect!(super::find_matching_request(&request1, false, false, vec![pact], None, false).await)
expect!(super::find_matching_request(&request1, false, false, vec![(pact, PactSource::Unknown)], None, false).await)
.to(be_ok());
}

Expand Down Expand Up @@ -540,15 +542,15 @@ mod test {
] }),
.. HttpRequest::default() };

expect!(super::find_matching_request(&request1, false, false, vec![pact.clone()], None, false).await)
expect!(super::find_matching_request(&request1, false, false, vec![(pact.clone(), PactSource::Unknown)], None, false).await)
.to(be_err());
expect!(super::find_matching_request(&request2, false, false, vec![pact.clone()], None, false).await)
expect!(super::find_matching_request(&request2, false, false, vec![(pact.clone(), PactSource::Unknown)], None, false).await)
.to(be_ok());
expect!(super::find_matching_request(&request3, false, false, vec![pact.clone()], None, false).await)
expect!(super::find_matching_request(&request3, false, false, vec![(pact.clone(), PactSource::Unknown)], None, false).await)
.to(be_ok());
expect!(super::find_matching_request(&request4, false, false, vec![pact.clone()], None, false).await)
expect!(super::find_matching_request(&request4, false, false, vec![(pact.clone(), PactSource::Unknown)], None, false).await)
.to(be_ok());
expect!(super::find_matching_request(&request5, false, false, vec![pact.clone()], None, false).await)
expect!(super::find_matching_request(&request5, false, false, vec![(pact.clone(), PactSource::Unknown)], None, false).await)
.to(be_ok());
}

Expand Down Expand Up @@ -587,15 +589,15 @@ mod test {

let request = HttpRequest::default();

expect!(super::find_matching_request(&request, false, false, vec![pact.clone()],
expect!(super::find_matching_request(&request, false, false, vec![(pact.clone(), PactSource::Unknown)],
Some(Regex::new("state one").unwrap()), false).await).to(be_ok().value(response1.clone()));
expect!(super::find_matching_request(&request, false, false, vec![pact.clone()],
expect!(super::find_matching_request(&request, false, false, vec![(pact.clone(), PactSource::Unknown)],
Some(Regex::new("state two").unwrap()), false).await).to(be_ok().value(response2.clone()));
expect!(super::find_matching_request(&request, false, false, vec![pact.clone()],
expect!(super::find_matching_request(&request, false, false, vec![(pact.clone(), PactSource::Unknown)],
Some(Regex::new("state three").unwrap()), false).await).to(be_ok().value(response3.clone()));
expect!(super::find_matching_request(&request, false, false, vec![pact.clone()],
expect!(super::find_matching_request(&request, false, false, vec![(pact.clone(), PactSource::Unknown)],
Some(Regex::new("state four").unwrap()), false).await).to(be_err());
expect!(super::find_matching_request(&request, false, false, vec![pact.clone()],
expect!(super::find_matching_request(&request, false, false, vec![(pact.clone(), PactSource::Unknown)],
Some(Regex::new("state .*").unwrap()), false).await).to(be_ok().value(response1.clone()));
}

Expand Down Expand Up @@ -632,10 +634,10 @@ mod test {

let request = HttpRequest::default();

expect!(super::find_matching_request(&request, false, false, vec![pact1],
expect!(super::find_matching_request(&request, false, false, vec![(pact1, PactSource::Unknown)],
Some(Regex::new("any state").unwrap()), true).await).to(be_ok().value(response2.clone()));

expect!(super::find_matching_request(&request, false, false, vec![pact2],
expect!(super::find_matching_request(&request, false, false, vec![(pact2, PactSource::Unknown)],
Some(Regex::new("any state").unwrap()), true).await).to(be_ok().value(response3.clone()));
}

Expand All @@ -652,7 +654,7 @@ mod test {

let request = HttpRequest { headers: Some(hashmap!{ "TEST-X".to_string() => vec!["X, Y".to_string()] }), .. HttpRequest::default() };

let result = super::find_matching_request(&request, false, false, vec![pact], None, false).await;
let result = super::find_matching_request(&request, false, false, vec![(pact, PactSource::Unknown)], None, false).await;
expect!(result).to(be_ok().value(interaction.response));
}
}

0 comments on commit fa94af5

Please sign in to comment.