#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GleamFailureLayer {
DependencyLayer,
NotDependencyLayer,
}
const REGISTRY_FAILURE_TITLES: &[&str] = &[
"Failed to download package",
"Failure opening tar archive",
"Hex API failure",
"HTTP error",
];
const RESOLUTION_FAILURE_TITLE: &str = "Dependency resolution failed";
const RESOLUTION_TRANSPORT_MARKERS: &[&str] = &["error sending request", "repo.hex.pm"];
const FILE_IO_FAILURE_TITLE: &str = "File IO failure";
const FILE_IO_PACKAGE_MARKERS: &[&str] = &[
"failed to unpack",
"/build/packages/",
r"\build\packages\",
"/hex/hexpm/packages/",
r"\hex\hexpm\packages\",
];
#[must_use]
pub fn classify_gleam_failure(combined: &str) -> GleamFailureLayer {
for line in combined.lines() {
let Some(title) = line.trim_start().strip_prefix("error: ") else {
continue;
};
let title = title.trim();
if REGISTRY_FAILURE_TITLES.contains(&title) {
return GleamFailureLayer::DependencyLayer;
}
if title == RESOLUTION_FAILURE_TITLE
&& RESOLUTION_TRANSPORT_MARKERS
.iter()
.any(|marker| combined.contains(marker))
{
return GleamFailureLayer::DependencyLayer;
}
if title == FILE_IO_FAILURE_TITLE
&& FILE_IO_PACKAGE_MARKERS
.iter()
.any(|marker| combined.contains(marker))
{
return GleamFailureLayer::DependencyLayer;
}
}
GleamFailureLayer::NotDependencyLayer
}
#[cfg(test)]
mod tests {
use super::{GleamFailureLayer, classify_gleam_failure};
const HEX_OUTAGE: &str = " Resolving versions\nerror: HTTP error\n\nA HTTP request failed.\n\
The error from the HTTP client was:\n\n error sending request for url \
(https://hex.pm/api/packages/gleam_stdlib/releases/1.0.3)\n";
#[test]
fn recognised_dependency_layer_diagnostics_are_classified_from_realistic_output() {
let cases = [
(
"P1 failed package download",
" Resolving versions\nerror: Failed to download package\n\nThe package \
gleam_stdlib could not be downloaded from Hex.\n",
GleamFailureLayer::DependencyLayer,
),
(
"P2 tar archive open failure",
" Downloading packages\nerror: Failure opening tar archive\n\nThe archive for \
gleam_json could not be opened.\n",
GleamFailureLayer::DependencyLayer,
),
(
"P3 Hex API failure",
" Resolving versions\nerror: Hex API failure\n\nThe Hex API returned an \
error while fetching package metadata.\n",
GleamFailureLayer::DependencyLayer,
),
(
"P4 HTTP error",
" Resolving versions\nerror: HTTP error\n\nA HTTP request failed while \
downloading gleam_stdlib.\n",
GleamFailureLayer::DependencyLayer,
),
(
"P5 file IO failure while unpacking the issue package",
" Downloading packages\nerror: File IO failure\n\nAn error occurred while \
writing a downloaded package:\n\n failed to unpack gleam@uri.erl\n",
GleamFailureLayer::DependencyLayer,
),
(
"P6 file IO failure in project-local package directories",
" Compiling packages\nerror: File IO failure\n\nCould not write package \
contents to /tmp/sample/build/packages/gleam_stdlib/src or \
C:\\work\\sample\\build\\packages\\gleam_stdlib\\src.\n",
GleamFailureLayer::DependencyLayer,
),
(
"P7 file IO failure in global Hex package caches",
" Downloading packages\nerror: File IO failure\n\nCould not update the Hex \
cache at /home/author/.cache/gleam/hex/hexpm/packages/gleam_json or \
C:\\Users\\author\\AppData\\Local\\gleam\\hex\\hexpm\\packages\\gleam_json.\n",
GleamFailureLayer::DependencyLayer,
),
(
"P8 project source file IO failure is not infrastructure",
" Compiling app\nerror: File IO failure\n\nCould not read project source \
file /home/author/app/src/main.gleam: permission denied.\n",
GleamFailureLayer::NotDependencyLayer,
),
];
for (name, fixture, expected) in cases {
assert_eq!(classify_gleam_failure(fixture), expected, "{name}");
}
}
#[test]
fn the_hex_outage_that_opened_this_task_is_a_dependency_layer_failure() {
assert_eq!(
classify_gleam_failure(HEX_OUTAGE),
GleamFailureLayer::DependencyLayer
);
}
#[test]
fn a_tar_archive_failure_is_a_dependency_layer_failure() {
assert_eq!(
classify_gleam_failure("error: Failure opening tar archive\n"),
GleamFailureLayer::DependencyLayer
);
}
#[test]
fn a_type_error_is_not_a_dependency_layer_failure() {
let output =
"error: Type mismatch\n\nExpected type:\n\n Int\n\nFound type:\n\n String\n";
assert_eq!(
classify_gleam_failure(output),
GleamFailureLayer::NotDependencyLayer
);
}
#[test]
fn an_unsatisfiable_requirement_is_not_excused_as_infrastructure() {
let output = "error: Dependency resolution failed\n\nAn error occurred while choosing \
the version of gleam_stdlib: no compatible version exists\n";
assert_eq!(
classify_gleam_failure(output),
GleamFailureLayer::NotDependencyLayer
);
}
#[test]
fn a_resolution_failure_that_names_a_transport_fault_is_infrastructure() {
let output = "error: Dependency resolution failed\n\nAn error occurred while choosing \
the version of gleam_json: error sending request for url \
(https://repo.hex.pm/packages/gleam_json)\n";
assert_eq!(
classify_gleam_failure(output),
GleamFailureLayer::DependencyLayer
);
}
#[test]
fn a_source_snippet_quoting_a_registry_title_does_not_decide_the_classification() {
let output = "error: Syntax error\n\n 3 │ let message = \"HTTP error\"\n │\
\n ╵ I was expecting a value here\n";
assert_eq!(
classify_gleam_failure(output),
GleamFailureLayer::NotDependencyLayer,
"a registry title inside a QUOTED SOURCE LINE must not classify the failure"
);
}
#[test]
fn download_progress_alone_is_not_a_dependency_layer_failure() {
let output = " Downloading packages\n Downloaded 2 packages in 0.11s\n\
error: Type mismatch\n";
assert_eq!(
classify_gleam_failure(output),
GleamFailureLayer::NotDependencyLayer
);
}
#[test]
fn empty_output_is_not_a_dependency_layer_failure() {
assert_eq!(
classify_gleam_failure(""),
GleamFailureLayer::NotDependencyLayer
);
}
}