///|
/// HTTP response metadata supplied by the caller; this library sends no HTTP.
pub struct ProblemContext {
actual_status : Int?
content_type : String?
base_uri : String?
} derive(Eq, Debug)
///|
pub fn problem_context(
actual_status : Int?,
content_type : String?,
base_uri : String?,
) -> ProblemContext {
{ actual_status, content_type, base_uri }
}
///|
pub(all) enum ContextIssueKind {
InvalidContentType
StatusMismatch
MissingBaseUri
InvalidBaseUri
} derive(Eq, Debug)
///|
pub struct ContextIssue {
kind : ContextIssueKind
context : String
} derive(Eq, Debug)
///|
pub fn ContextIssue::kind(self : ContextIssue) -> ContextIssueKind {
self.kind
}
///|
pub fn ContextIssue::context(self : ContextIssue) -> String {
self.context
}
///|
pub fn ContextIssueKind::name(self : ContextIssueKind) -> String {
match self {
InvalidContentType => "InvalidContentType"
StatusMismatch => "StatusMismatch"
MissingBaseUri => "MissingBaseUri"
InvalidBaseUri => "InvalidBaseUri"
}
}
///|
pub fn validate_context(
problem : ProblemDetails,
context : ProblemContext,
) -> Array[ContextIssue] {
let issues : Array[ContextIssue] = []
match context.content_type {
Some(value) if !is_problem_content_type(value) =>
issues.push({
kind: InvalidContentType,
context: "Content-Type is not application/problem+json",
})
_ => ()
}
match (problem.status(), context.actual_status) {
(Some(declared), Some(actual)) if declared != actual =>
issues.push({
kind: StatusMismatch,
context: "problem status differs from HTTP response status",
})
_ => ()
}
let needs_base = is_relative_optional(problem.type_raw()) ||
is_relative_optional(problem.instance())
if needs_base {
match context.base_uri {
None =>
issues.push({
kind: MissingBaseUri,
context: "relative URI-reference has no base URI",
})
Some(base) =>
match validate_uri_reference(base) {
Ok(info) if info.is_absolute() => ()
_ =>
issues.push({
kind: InvalidBaseUri,
context: "base URI must be an absolute URI-reference",
})
}
}
}
issues
}
///|
fn is_problem_content_type(value : String) -> Bool {
let normalized = ascii_lower(value)
normalized == "application/problem+json" ||
normalized.has_prefix("application/problem+json;")
}
///|
fn is_relative_optional(value : String?) -> Bool {
match value {
None => false
Some(uri) =>
match validate_uri_reference(uri) {
Ok(info) => info.is_relative()
Err(_) => false
}
}
}
///|
fn ascii_lower(value : String) -> String {
let builder = StringBuilder()
for c in value {
if c >= 'A' && c <= 'Z' {
builder.write_char((c.to_int() + 32).unsafe_to_char())
} else {
builder.write_char(c)
}
}
builder.to_string()
}