///|
pub struct HarLimits {
max_input_bytes_value : Int
max_pages_value : Int
max_entries_value : Int
max_headers_per_message_value : Int
max_cookies_per_message_value : Int
max_query_parameters_value : Int
} derive(Eq, Debug)
///|
pub fn HarLimits::standard() -> HarLimits {
{
max_input_bytes_value: 64 * 1024 * 1024,
max_pages_value: 10000,
max_entries_value: 100000,
max_headers_per_message_value: 10000,
max_cookies_per_message_value: 10000,
max_query_parameters_value: 10000,
}
}
///|
pub fn HarLimits::new(
max_input_bytes : Int,
max_pages : Int,
max_entries : Int,
max_headers_per_message : Int,
max_cookies_per_message : Int,
max_query_parameters : Int,
) -> Result[HarLimits, HarError] {
for
item in [
("max_input_bytes", max_input_bytes),
("max_pages", max_pages),
("max_entries", max_entries),
("max_headers_per_message", max_headers_per_message),
("max_cookies_per_message", max_cookies_per_message),
("max_query_parameters", max_query_parameters),
] {
let (name, value) = item
if value < 0 {
return Err(
HarError::new(
InvalidField,
"$.limits." + name,
"resource limits cannot be negative",
),
)
}
}
Ok({
max_input_bytes_value: max_input_bytes,
max_pages_value: max_pages,
max_entries_value: max_entries,
max_headers_per_message_value: max_headers_per_message,
max_cookies_per_message_value: max_cookies_per_message,
max_query_parameters_value: max_query_parameters,
})
}
///|
pub fn HarLimits::max_input_bytes(self : HarLimits) -> Int {
self.max_input_bytes_value
}
///|
pub fn HarLimits::max_pages(self : HarLimits) -> Int {
self.max_pages_value
}
///|
pub fn HarLimits::max_entries(self : HarLimits) -> Int {
self.max_entries_value
}
///|
pub fn HarLimits::max_headers_per_message(self : HarLimits) -> Int {
self.max_headers_per_message_value
}
///|
pub fn HarLimits::max_cookies_per_message(self : HarLimits) -> Int {
self.max_cookies_per_message_value
}
///|
pub fn HarLimits::max_query_parameters(self : HarLimits) -> Int {
self.max_query_parameters_value
}
///|
fn limit_error(path : String, actual : Int, maximum : Int) -> HarError {
HarError::new(
LimitExceeded,
path,
"observed " +
actual.to_string() +
" items; maximum is " +
maximum.to_string(),
)
}
///|
fn enforce_document_limits(
document : HarDocument,
limits : HarLimits,
) -> Result[Unit, HarError] {
if document.page_count() > limits.max_pages_value {
return Err(
limit_error("$.log.pages", document.page_count(), limits.max_pages_value),
)
}
if document.entry_count() > limits.max_entries_value {
return Err(
limit_error(
"$.log.entries",
document.entry_count(),
limits.max_entries_value,
),
)
}
for index, entry in document.entries_value {
let base = append_json_index("$.log.entries", index)
let request = entry.request_value
let response = entry.response_value
if request.headers_value.length() > limits.max_headers_per_message_value {
return Err(
limit_error(
append_json_field(append_json_field(base, "request"), "headers"),
request.headers_value.length(),
limits.max_headers_per_message_value,
),
)
}
if response.headers_value.length() > limits.max_headers_per_message_value {
return Err(
limit_error(
append_json_field(append_json_field(base, "response"), "headers"),
response.headers_value.length(),
limits.max_headers_per_message_value,
),
)
}
if request.cookies_value.length() > limits.max_cookies_per_message_value {
return Err(
limit_error(
append_json_field(append_json_field(base, "request"), "cookies"),
request.cookies_value.length(),
limits.max_cookies_per_message_value,
),
)
}
if response.cookies_value.length() > limits.max_cookies_per_message_value {
return Err(
limit_error(
append_json_field(append_json_field(base, "response"), "cookies"),
response.cookies_value.length(),
limits.max_cookies_per_message_value,
),
)
}
if request.query_value.length() > limits.max_query_parameters_value {
return Err(
limit_error(
append_json_field(append_json_field(base, "request"), "queryString"),
request.query_value.length(),
limits.max_query_parameters_value,
),
)
}
}
Ok(())
}
///|
pub fn parse_har_with_limits(
input : String,
limits : HarLimits,
) -> Result[HarDocument, HarError] {
if input.length() > limits.max_input_bytes_value {
return Err(limit_error("$", input.length(), limits.max_input_bytes_value))
}
let document = match parse_har(input) {
Ok(value) => value
Err(error) => return Err(error)
}
match enforce_document_limits(document, limits) {
Ok(_) => Ok(document)
Err(error) => Err(error)
}
}
///|
pub struct HarValidationPolicy {
timing_tolerance_value : Double
require_absolute_urls_value : Bool
require_page_references_value : Bool
} derive(Eq, Debug)
///|
pub fn HarValidationPolicy::standard() -> HarValidationPolicy {
{
timing_tolerance_value: 1.0,
require_absolute_urls_value: true,
require_page_references_value: true,
}
}
///|
pub fn HarValidationPolicy::new(
timing_tolerance : Double,
require_absolute_urls : Bool,
require_page_references : Bool,
) -> Result[HarValidationPolicy, HarError] {
if timing_tolerance < 0.0 {
return Err(
HarError::new(
InvalidField,
"$.policy.timing_tolerance",
"timing tolerance cannot be negative",
),
)
}
Ok({
timing_tolerance_value: timing_tolerance,
require_absolute_urls_value: require_absolute_urls,
require_page_references_value: require_page_references,
})
}
///|
pub fn HarValidationPolicy::timing_tolerance(
self : HarValidationPolicy,
) -> Double {
self.timing_tolerance_value
}
///|
pub fn HarValidationPolicy::require_absolute_urls(
self : HarValidationPolicy,
) -> Bool {
self.require_absolute_urls_value
}
///|
pub fn HarValidationPolicy::require_page_references(
self : HarValidationPolicy,
) -> Bool {
self.require_page_references_value
}
///|
fn page_id_occurrences(document : HarDocument, id : String) -> Int {
let mut count = 0
for page in document.pages_value {
if page.id_value == id {
count += 1
}
}
count
}
///|
fn page_exists(document : HarDocument, id : String) -> Bool {
for page in document.pages_value {
if page.id_value == id {
return true
}
}
false
}
///|
fn absolute_http_url(url : String) -> Bool {
let lower = url.to_lower()
lower.has_prefix("http://") || lower.has_prefix("https://")
}
///|
pub fn validate_har(
document : HarDocument,
policy? : HarValidationPolicy = HarValidationPolicy::standard(),
) -> ValidationResult {
let issues : Array[ValidationIssue] = []
for page_index, page in document.pages_value {
if page_id_occurrences(document, page.id_value) > 1 {
issues.push(
ValidationIssue::error(
DuplicateIdentifier.code(),
append_json_field(append_json_index("$.log.pages", page_index), "id"),
"page id is not unique",
),
)
}
}
for entry_index, entry in document.entries_value {
let base = append_json_index("$.log.entries", entry_index)
match entry.page_ref_value {
Some(page_ref) =>
if !page_exists(document, page_ref) {
let issue = if policy.require_page_references_value {
ValidationIssue::error(
MissingPageReference.code(),
append_json_field(base, "pageref"),
"entry references a page id that does not exist",
)
} else {
ValidationIssue::warning(
MissingPageReference.code(),
append_json_field(base, "pageref"),
"entry references a page id that does not exist",
)
}
issues.push(issue)
}
None => ()
}
if policy.require_absolute_urls_value &&
!absolute_http_url(entry.request_value.url_value) {
issues.push(
ValidationIssue::error(
InvalidUrl.code(),
append_json_field(append_json_field(base, "request"), "url"),
"request URL must be absolute HTTP or HTTPS",
),
)
}
let delta = entry.timing_delta()
let absolute_delta = if delta < 0.0 { -delta } else { delta }
if absolute_delta > policy.timing_tolerance_value {
issues.push(
ValidationIssue::warning(
"entry.timing_mismatch",
append_json_field(base, "time"),
"entry time differs from the sum of known timing phases",
),
)
}
match
(
entry.response_value.known_body_size(),
entry.response_value.content_value.known_size(),
) {
(Some(transferred), Some(decoded)) =>
if transferred > decoded {
issues.push(
ValidationIssue::warning(
"response.body_larger_than_content",
append_json_field(append_json_field(base, "response"), "bodySize"),
"transferred body size is larger than decoded content size",
),
)
}
_ => ()
}
if entry.response_value.status_value >= 400.0 &&
entry.response_value.status_text_value.trim().is_empty() {
issues.push(
ValidationIssue::warning(
"response.empty_status_text",
append_json_field(append_json_field(base, "response"), "statusText"),
"error response has an empty status text",
),
)
}
}
ValidationResult::new(issues)
}