pact_verifier_cli 1.3.1

Standalone pact verifier for provider pact verification
Documentation
use std::fs;
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;

#[cfg(feature = "junit")] use junit_report::{ReportBuilder, TestCaseBuilder, TestSuiteBuilder};
#[cfg(feature = "junit")] use strip_ansi_escapes;
use serde_json::Value;
use tracing::debug;
use xrust::{Error, ErrorKind, Item, Node, SequenceTrait};
use xrust::parser::ParseError;
use xrust::parser::xml::parse;
use xrust::transform::context::StaticContextBuilder;
use xrust::trees::smite::RNode;
use xrust::xslt::from_document;

#[cfg(feature = "junit")] use pact_verifier::{interaction_mismatch_output, MismatchResult};
use pact_verifier::verification_result::VerificationExecutionResult;

mod xml;

const XSLT: &str = include_str!("verification-report.xsl");

fn node_from_str(s: &str) -> Result<RNode, Error> {
  parse(RNode::new_document(), s, Some(|_: &_| Err(ParseError::MissingNameSpace)))
}

fn apply_xslt(xml_str: &str, xslt: Option<&String>) -> anyhow::Result<String> {
  let doc = parse(RNode::new_document(), xml_str, Some(|_: &_| Err(ParseError::MissingNameSpace)))?;

  let mut s = String::new();
  let xslt_doc = if let Some(xslt) = xslt {
    let mut f = File::open(xslt)?;
    f.read_to_string(&mut s)?;
    s.as_str()
  } else {
    XSLT
  };
  let xslt_doc = parse(RNode::new_document(), xslt_doc, Some(|_: &_| Err(ParseError::MissingNameSpace)))?;

  let mut static_context = StaticContextBuilder::new()
    .message(|_| Ok(()))
    .fetcher(|_| Err(Error::new(ErrorKind::NotImplemented, "not implemented")))
    .parser(|_| Err::<RNode, Error>(Error::new(ErrorKind::NotImplemented, "not implemented")))
    .build();

  let mut ctxt = from_document(xslt_doc, None, node_from_str, |_| Ok(String::new()))?;
  ctxt.context(vec![Item::Node(doc)], 0);
  ctxt.result_document(RNode::new_document());
  let seq = ctxt.evaluate(&mut static_context)?;

  Ok(seq.to_xml())
}

pub(crate) fn write_json_report(result: &VerificationExecutionResult, file_name: &str) -> anyhow::Result<()> {
  debug!("Writing JSON result of the verification to '{file_name}'");
  let mut f = File::create(file_name)?;
  let json: Value = result.into();
  f.write_all(json.to_string().as_bytes())?;
  Ok(())
}

#[cfg(feature = "junit")]
pub(crate) fn write_junit_report(result: &VerificationExecutionResult, file_name: &str, provider: &String) -> anyhow::Result<()> {
  debug!("Writing JUnit result of the verification to '{file_name}'");
  let mut f = File::create(file_name)?;

  let mut test_suite = TestSuiteBuilder::new(provider);
  let stripped_system_out = strip_ansi_escapes::strip_str(result.output.join("\n"));
  test_suite.set_system_out(&stripped_system_out);
  for interaction_result in &result.interaction_results {
    let duration = time::Duration::try_from(interaction_result.duration).unwrap_or_default();
    let test_case = match &interaction_result.result {
      Ok(_) => TestCaseBuilder::success(interaction_result.description.as_str(), duration),
      Err(result) => {
        if interaction_result.pending {
          TestCaseBuilder::skipped(interaction_result.description.as_str())
        } else {
          match result {
            MismatchResult::Mismatches { mismatches, expected, actual, .. } => {
              let mut output_buffer = vec![];
              interaction_mismatch_output(&mut output_buffer, false, 1, &interaction_result.description,
                &mismatches, expected.as_ref(), actual.as_ref());
              let mut builder = TestCaseBuilder::failure(
                interaction_result.description.as_str(),
                duration,
                "",
                "Verification for interaction failed"
              );
              let stripped_output = strip_ansi_escapes::strip_str(output_buffer.join("\n"));
              builder.set_system_out(&stripped_output);
              builder
            },
            MismatchResult::Error(error, _) => TestCaseBuilder::error(
              interaction_result.description.as_str(),
              duration,
              "",
              error.as_str()
            )
          }
        }
      }
    };
    test_suite.add_testcase(test_case.build());
  }

  let mut report_builder = ReportBuilder::new();
  report_builder.add_testsuite(test_suite.build());
  let report = report_builder.build();
  report.write_xml(&mut f)?;
  Ok(())
}

pub(crate) fn write_html_report(
  result: &VerificationExecutionResult,
  file_name: &str,
  provider: &str,
  xslt: Option<&String>
) -> anyhow::Result<()> {
  let path = PathBuf::from(file_name);
  let parent = if let Some(parent) = path.parent() {
    parent
  } else {
    return Err(anyhow::anyhow!("No parent directory found for {}", file_name));
  };
  fs::create_dir_all(parent)?;

  let filename = if let Some(filename) = path.file_name() {
    filename
  } else {
    return Err(anyhow::anyhow!("Failed to get file name of '{}'", path.display()));
  };

  let xml_path = if let Some(_extension) = path.extension() {
    path.with_extension("xml")
  } else {
    parent.join(filename).with_extension("xml")
  };

  let xml_str = xml::to_xml_string(result, provider)?;

  debug!("Writing XML report of the verification to '{}'", xml_path.display());
  File::create(&xml_path)?.write_all(xml_str.as_bytes())?;

  debug!("Writing HTML report of the verification to '{file_name}'");
  let html = apply_xslt(&xml_str, xslt)?;
  File::create(file_name)?.write_all(html.as_bytes())?;

  Ok(())
}