pact_mock_server_cli 2.0.0

Standalone pact mock server for consumer pact tests
Documentation
use std::sync::Mutex;
use anyhow::anyhow;

use clap::ArgMatches;
use http::StatusCode;
use pact_models::json_utils::json_to_string;
use serde_json::Value;
use tracing::error;

use pact_mock_server::{
  mock_server::MockServer,
  server_manager::ServerManager
};

use crate::handle_error;

pub async fn verify_mock_server(host: &str, port: u16, matches: &ArgMatches, usage: &str) -> Result<(), i32> {
  let mock_server_id = matches.get_one::<String>("mock-server-id");
  let mock_server_port = matches.get_one::<u16>("mock-server-port");
  let (id, id_type) = match (mock_server_id, mock_server_port) {
    (Some(id), _) => (id.clone(), "id"),
    (_, Some(port)) => (port.to_string(), "port"),
    _ => crate::display_error("Either an ID or port must be provided".to_string(), usage, 40)
  };

  let client = reqwest::Client::new();
  let url = format!("http://{}:{}/mockserver/{}/verify", host, port, id);
  let resp = client.post(&url)
    .send().await;
  match resp {
    Ok(result) => {
      let status = result.status();
      if !status.is_success() {
        match status {
          StatusCode::NOT_FOUND => {
            println!("No mock server found with {} '{}', use the 'list' command to get a list of available mock servers.", id_type, id);
            Err(3)
          },
          StatusCode::UNPROCESSABLE_ENTITY => {
            match result.text().await {
              Ok(body) => {
                match serde_json::from_str::<Value>(body.as_str()) {
                  Ok(json) => {
                    let mock_server = json.get("mockServer")
                      .ok_or_else(|| handle_error("Invalid JSON received from master server - no mockServer attribute"))?;
                    let id = mock_server.get("id")
                      .ok_or_else(|| handle_error("Invalid JSON received from master server - mockServer has no id attribute"))?
                      .as_str().ok_or_else(|| handle_error("Invalid JSON received from master server - mockServer id attribute is not a string"))?;
                    let port = mock_server.get("port")
                      .ok_or_else(|| handle_error("Invalid JSON received from master server - mockServer has no port attribute"))?
                      .as_u64().ok_or_else(|| handle_error("Invalid JSON received from master server - mockServer port attribute is not a number"))?;
                    display_verification_errors(id, port, &json);
                    Err(2)
                  },
                  Err(err) => {
                    error!("Failed to parse JSON: {}\n{}", err, body);
                    crate::display_error(format!("Failed to parse JSON: {}\n{}", err, body), usage, 40);
                  }
                }
              },
              Err(err) => {
                error!("Failed to parse JSON: {}", err);
                crate::display_error(format!("Failed to parse JSON: {}", err), usage, 40);
              }
            }
          },
          _ => crate::display_error(format!("Unexpected response from master mock server '{}': {}", url, result.status()), usage, 40)
        }
      } else {
        println!("Mock server with {} '{}' verified ok", id, id_type);
        Ok(())
      }
    },
    Err(err) => {
      crate::display_error(format!("Failed to connect to the master mock server '{}': {}", url, err), usage, 40);
    }
  }
}

fn validate_port(port: u16, server_manager: &Mutex<ServerManager>) -> anyhow::Result<MockServer> {
  server_manager.lock().unwrap()
    .find_mock_server_by_port(port, &|_, _, ms| ms.left().cloned())
    .flatten()
    .ok_or_else(|| anyhow!("No mock server running with port '{}'", port))
}

fn validate_uuid(id: &str, server_manager: &Mutex<ServerManager>) -> anyhow::Result<MockServer> {
  server_manager.lock().unwrap()
    .find_mock_server_by_id(id, &|_, ms| ms.left().cloned())
    .flatten()
    .ok_or_else(|| anyhow!("No mock server running with id '{}'", id))
}

pub fn validate_id(id: &str, server_manager: &Mutex<ServerManager>) -> anyhow::Result<MockServer> {
    if id.chars().all(|ch| ch.is_digit(10)) {
        validate_port(id.parse::<u16>().unwrap(), server_manager)
    } else {
        validate_uuid(id, server_manager)
    }
}

fn display_verification_errors(id: &str, port: u64, json: &serde_json::Value) {
  let mismatches = json.get("mismatches").unwrap().as_array().unwrap();
  println!("Mock server {}/{} failed verification with {} errors\n", id, port, mismatches.len());

  for (i, mismatch) in mismatches.iter().enumerate() {
    match json_to_string(mismatch.get("type").unwrap()).as_str() {
      "missing-request" => {
        let request = mismatch.get("request").unwrap();
        println!("{} - Expected request was not received - {}", i, request)
      },
      "request-not-found" => {
        let request = mismatch.get("request").unwrap();
        println!("{} - Received a request that was not expected - {}", i, request)
      },
      "request-mismatch" => {
        let path = mismatch.get("path").unwrap().to_string();
        let method = mismatch.get("method").unwrap().to_string();
        println!("{} - Received a request that did not match with expected - {} {}", i, method, path);
        let request_mismatches = mismatch.get("mismatches").unwrap().as_array().unwrap();
        for request_mismatch in request_mismatches {
          println!("        {}", request_mismatch.get("mismatch").unwrap().to_string())
        }
      },
      _ => println!("{} - Unknown failure - {}", i, mismatch),
    }
  }
}