///|
/// One ordered result in a batch hostname lookup.
pub struct BatchItem {
index_ : Int
input_ : String
result_ : Result[Lookup, DomainError]
}
///|
pub fn BatchItem::index(self : BatchItem) -> Int {
self.index_
}
///|
pub fn BatchItem::input(self : BatchItem) -> String {
self.input_
}
///|
pub fn BatchItem::result(self : BatchItem) -> Result[Lookup, DomainError] {
self.result_
}
///|
/// Ordered results and summary counts for a recoverable batch lookup.
pub struct BatchReport {
items_ : ReadOnlyArray[BatchItem]
success_count_ : Int
error_count_ : Int
}
///|
pub fn BatchReport::items(self : BatchReport) -> ReadOnlyArray[BatchItem] {
self.items_
}
///|
pub fn BatchReport::length(self : BatchReport) -> Int {
self.items_.length()
}
///|
pub fn BatchReport::success_count(self : BatchReport) -> Int {
self.success_count_
}
///|
pub fn BatchReport::error_count(self : BatchReport) -> Int {
self.error_count_
}
///|
/// Look up hostnames in input order with browser-style defaults.
///
/// Invalid inputs are retained as row-level errors and do not stop the batch.
pub fn SuffixList::lookup_batch(
self : SuffixList,
domains : Array[String],
) -> BatchReport {
self.lookup_batch_with_options(domains, LookupOptions::browser_default())
}
///|
/// Look up hostnames in input order with explicit lookup options.
///
/// Invalid inputs are retained as row-level errors and do not stop the batch.
pub fn SuffixList::lookup_batch_with_options(
self : SuffixList,
domains : Array[String],
options : LookupOptions,
) -> BatchReport {
let items : Array[BatchItem] = []
let mut success_count = 0
let mut error_count = 0
for index, domain in domains {
let result = self.lookup_with_options(domain, options)
match result {
Ok(_) => success_count = success_count + 1
Err(_) => error_count = error_count + 1
}
items.push({ index_: index, input_: domain, result_: result, })
}
{
items_: ReadOnlyArray::from_array(items),
success_count_: success_count,
error_count_: error_count,
}
}
///|
fn rule_kind_name(kind : RuleKind) -> String {
match kind {
DefaultRule => "default"
ExactRule => "exact"
WildcardRule => "wildcard"
ExceptionRule => "exception"
}
}
///|
fn csv_field(text : String) -> String {
let output = StringBuilder()
output.write_char('"')
for character in text {
if character == '"' {
output.write_string("\"\"")
} else {
output.write_char(character)
}
}
output.write_char('"')
output.to_string()
}
///|
fn write_csv_row(output : StringBuilder, fields : Array[String]) -> Unit {
for index, field in fields {
if index > 0 {
output.write_char(',')
}
output.write_string(csv_field(field))
}
output.write_char('\n')
}
///|
/// Export the complete batch to deterministic CSV with RFC 4180-style field
/// quoting.
///
/// All fields are quoted, the input order is preserved, and output uses LF line
/// endings with a final newline.
pub fn BatchReport::to_csv(self : BatchReport) -> String {
let output = StringBuilder()
output.write_string(
"\"index\",\"input\",\"status\",\"normalized_domain\",\"public_suffix\",\"registrable_domain\",\"matched_rule\",\"rule_kind\",\"rule_section\",\"error\"\n",
)
for item in self.items_ {
let fields = match item.result_ {
Ok(result) => {
let registrable = match result.registrable_domain() {
Some(value) => value
None => ""
}
let section = match result.rule_section() {
Some(value) => section_name(value)
None => ""
}
[
item.index_.to_string(),
item.input_,
"ok",
result.normalized_domain(),
result.public_suffix(),
registrable,
result.matched_rule(),
rule_kind_name(result.rule_kind()),
section,
"",
]
}
Err(error) =>
[
item.index_.to_string(),
item.input_,
"error",
"",
"",
"",
"",
"",
"",
error.message(),
]
}
write_csv_row(output, fields)
}
output.to_string()
}