Skip to content

Commit

Permalink
now return exit codes if errors found
Browse files Browse the repository at this point in the history
  • Loading branch information
anewton1998 committed Jan 3, 2025
1 parent f98de26 commit f6d1737
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 22 deletions.
18 changes: 12 additions & 6 deletions icann-rdap-cli/src/bin/rdap-test/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::process::{ExitCode, Termination};

use icann_rdap_cli::rt::exec::TestError;
use icann_rdap_cli::rt::exec::TestExecutionError;
use icann_rdap_client::iana_request::IanaResponseError;
use icann_rdap_client::RdapClientError;
use thiserror::Error;
Expand All @@ -9,18 +9,22 @@ use thiserror::Error;
pub enum RdapTestError {
#[error("No errors encountered")]
Success,
#[error("Tests completed with execution errors.")]
TestsCompletedExecutionErrors,
#[error("Tests completed, warning checks found.")]
TestsCompletedWarningsFound,
#[error("Tests completed, error checks found.")]
TestsCompletedErrorsFound,
#[error(transparent)]
RdapClient(#[from] RdapClientError),
#[error(transparent)]
TestError(#[from] TestError),
TestExecutionError(#[from] TestExecutionError),
#[error(transparent)]
Termimad(#[from] termimad::Error),
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error("Unknown output type")]
UnknownOutputType,
#[error("RDAP response failed checks.")]
ErrorOnChecks,
#[error(transparent)]
Json(#[from] serde_json::Error),
#[error(transparent)]
Expand All @@ -40,13 +44,16 @@ impl Termination for RdapTestError {
let exit_code: u8 = match self {
// Success
RdapTestError::Success => 0,
RdapTestError::TestsCompletedExecutionErrors => 1,
RdapTestError::TestsCompletedWarningsFound => 2,
RdapTestError::TestsCompletedErrorsFound => 3,

// Internal Errors
RdapTestError::Termimad(_) => 10,

// I/O Errors
RdapTestError::IoError(_) => 40,
RdapTestError::TestError(_) => 40,
RdapTestError::TestExecutionError(_) => 40,

// RDAP Errors
RdapTestError::Json(_) => 100,
Expand All @@ -58,7 +65,6 @@ impl Termination for RdapTestError {

// User Errors
RdapTestError::UnknownOutputType => 200,
RdapTestError::ErrorOnChecks => 201,

// RDAP Client Errrors
RdapTestError::RdapClient(e) => match e {
Expand Down
67 changes: 67 additions & 0 deletions icann-rdap-cli/src/bin/rdap-test/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ use icann_rdap_cli::dirs::fcbs::FileCacheBootstrapStore;
use icann_rdap_cli::rt::exec::execute_tests;
use icann_rdap_cli::rt::exec::ExtensionGroup;
use icann_rdap_cli::rt::exec::TestOptions;
use icann_rdap_cli::rt::results::RunOutcome;
use icann_rdap_cli::rt::results::TestResults;
use icann_rdap_client::client::ClientConfig;
use icann_rdap_client::md::MdOptions;
use icann_rdap_client::QueryType;
use icann_rdap_common::check::traverse_checks;
use icann_rdap_common::check::CheckClass;
use termimad::crossterm::style::Color::*;
use termimad::Alignment;
Expand Down Expand Up @@ -412,9 +415,73 @@ pub async fn wrapped_main() -> Result<(), RdapTestError> {
}
}

// if some tests could not execute
//
let execution_errors = test_results
.test_runs
.iter()
.filter(|r| !matches!(r.outcome, RunOutcome::Tested | RunOutcome::Skipped))
.count();
if execution_errors != 0 {
return Err(RdapTestError::TestsCompletedExecutionErrors);
}

// if tests had check errors
//
// get the error classes but only if they were specified.
let error_classes = check_classes
.iter()
.filter(|c| {
matches!(
c,
CheckClass::StdError | CheckClass::Cidr0Error | CheckClass::IcannError
)
})
.copied()
.collect::<Vec<CheckClass>>();
// return proper exit code if errors found
if are_there_checks(error_classes, &test_results) {
return Err(RdapTestError::TestsCompletedErrorsFound);
}

// if tests had check warnings
//
// get the warning classes but only if they were specified.
let warning_classes = check_classes
.iter()
.filter(|c| matches!(c, CheckClass::StdWarning))
.copied()
.collect::<Vec<CheckClass>>();
// return proper exit code if errors found
if are_there_checks(warning_classes, &test_results) {
return Err(RdapTestError::TestsCompletedWarningsFound);
}

Ok(())
}

fn are_there_checks(classes: Vec<CheckClass>, test_results: &TestResults) -> bool {
// see if there are any checks in the test runs
let run_count = test_results
.test_runs
.iter()
.filter(|r| {
if let Some(checks) = &r.checks {
traverse_checks(checks, &classes, None, &mut |_, _| {})
} else {
false
}
})
.count();
// see if there are any classes in the service checks
let service_count = test_results
.service_checks
.iter()
.filter(|c| classes.contains(&c.check_class))
.count();
run_count + service_count != 0
}

#[cfg(test)]
mod tests {
use crate::Cli;
Expand Down
34 changes: 18 additions & 16 deletions icann-rdap-cli/src/rt/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub enum ExtensionGroup {
}

#[derive(Debug, Error)]
pub enum TestError {
pub enum TestExecutionError {
#[error(transparent)]
RdapClient(#[from] RdapClientError),
#[error(transparent)]
Expand Down Expand Up @@ -73,7 +73,7 @@ pub async fn execute_tests<'a, BS: BootstrapStore>(
value: &QueryType,
options: &TestOptions,
client_config: &ClientConfig,
) -> Result<TestResults, TestError> {
) -> Result<TestResults, TestExecutionError> {
let bs_client = create_client(client_config)?;

// normalize extensions
Expand All @@ -87,7 +87,7 @@ pub async fn execute_tests<'a, BS: BootstrapStore>(

// get the query url
let mut query_url = match value {
QueryType::Help => return Err(TestError::UnsupportedQueryType),
QueryType::Help => return Err(TestExecutionError::UnsupportedQueryType),
QueryType::Url(url) => url.to_owned(),
_ => {
let base_url = qtype_to_bootstrap_url(&bs_client, bs, value, |reg| {
Expand All @@ -103,7 +103,7 @@ pub async fn execute_tests<'a, BS: BootstrapStore>(
let response_data = rdap_url_request(&query_url, &client).await?;
query_url = get_related_links(&response_data.rdap)
.first()
.ok_or(TestError::NoReferralToChase)?
.ok_or(TestExecutionError::NoReferralToChase)?
.to_string();
debug!("Chasing referral {query_url}");
}
Expand All @@ -116,7 +116,9 @@ pub async fn execute_tests<'a, BS: BootstrapStore>(
80
}
});
let host = parsed_url.host_str().ok_or(TestError::NoHostToResolve)?;
let host = parsed_url
.host_str()
.ok_or(TestExecutionError::NoHostToResolve)?;

info!("Testing {query_url}");
let dns_data = get_dns_records(host).await?;
Expand Down Expand Up @@ -172,7 +174,7 @@ pub async fn execute_tests<'a, BS: BootstrapStore>(
Ok(test_results)
}

async fn get_dns_records(host: &str) -> Result<DnsData, TestError> {
async fn get_dns_records(host: &str) -> Result<DnsData, TestExecutionError> {
let conn = UdpClientConnection::new("8.8.8.8:53".parse()?)
.unwrap()
.new_stream(None);
Expand All @@ -194,10 +196,10 @@ async fn get_dns_records(host: &str) -> Result<DnsData, TestError> {
RecordType::CNAME => {
let cname = answer
.data()
.ok_or(TestError::NoRdata)?
.ok_or(TestExecutionError::NoRdata)?
.clone()
.into_cname()
.map_err(|_e| TestError::BadRdata)?
.map_err(|_e| TestExecutionError::BadRdata)?
.0
.to_string();
debug!("Found cname {cname}");
Expand All @@ -206,10 +208,10 @@ async fn get_dns_records(host: &str) -> Result<DnsData, TestError> {
RecordType::A => {
let addr = answer
.data()
.ok_or(TestError::NoRdata)?
.ok_or(TestExecutionError::NoRdata)?
.clone()
.into_a()
.map_err(|_e| TestError::BadRdata)?
.map_err(|_e| TestExecutionError::BadRdata)?
.0;
debug!("Found IPv4 {addr}");
dns_data.v4_addrs.push(addr);
Expand All @@ -235,10 +237,10 @@ async fn get_dns_records(host: &str) -> Result<DnsData, TestError> {
RecordType::CNAME => {
let cname = answer
.data()
.ok_or(TestError::NoRdata)?
.ok_or(TestExecutionError::NoRdata)?
.clone()
.into_cname()
.map_err(|_e| TestError::BadRdata)?
.map_err(|_e| TestExecutionError::BadRdata)?
.0
.to_string();
debug!("Found cname {cname}");
Expand All @@ -247,10 +249,10 @@ async fn get_dns_records(host: &str) -> Result<DnsData, TestError> {
RecordType::AAAA => {
let addr = answer
.data()
.ok_or(TestError::NoRdata)?
.ok_or(TestExecutionError::NoRdata)?
.clone()
.into_aaaa()
.map_err(|_e| TestError::BadRdata)?
.map_err(|_e| TestExecutionError::BadRdata)?
.0;
debug!("Found IPv6 {addr}");
dns_data.v6_addrs.push(addr);
Expand All @@ -264,14 +266,14 @@ async fn get_dns_records(host: &str) -> Result<DnsData, TestError> {
Ok(dns_data)
}

fn normalize_extension_ids(options: &TestOptions) -> Result<Vec<String>, TestError> {
fn normalize_extension_ids(options: &TestOptions) -> Result<Vec<String>, TestExecutionError> {
let mut retval = options.expect_extensions.clone();

// check for unregistered extensions
if !options.allow_unregistered_extensions {
for ext in &retval {
if ExtensionId::from_str(ext).is_err() {
return Err(TestError::UnregisteredExtension);
return Err(TestExecutionError::UnregisteredExtension);
}
}
}
Expand Down

0 comments on commit f6d1737

Please sign in to comment.