// test_invalid.mbt — Malformed input handling. Nothing ever panics, and
// every malformed input surfaces as a structured `LinkError` (never a raw
// exception, never a silent accept).

///|
/// Whether a result is an `Err`.
fn[T] is_err(r : Result[T, LinkError]) -> Bool {
  match r {
    Ok(_) => false
    Err(_) => true
  }
}

///|
/// Evaluates a result, discarding it (used to force parser execution).
fn[T] swallow(r : Result[T, LinkError]) -> Unit {
  match r {
    Ok(_) => ()
    Err(_) => ()
  }
}

///|
test "garbage and truncated inputs never panic" {
  let dflt = Limits::default()
  let garbage : Array[String] = [
    "", "   ", "\"", ">", "<>", "<>;", ";", "; rel", "; rel=", "; rel=\"",
    "; rel=\"next", "; rel=\"next\";", "; rel=\"next\"; title=\"", "rel=\"next\"",
    "; rel=\"next\" x", "a", "ab", "1a:b", "; x=\"\\", "; x='v'", "; =v",
    "", "", "", "http://", "[]",
    "{}", "true", "null", "NaN", "{\"linkset\":", "{\"linkset\": [}", "{\"linkset\": 5}",
    "not json", "; title*=UTF-8'", "; title*=UTF-8''%", "; hreflang=\"1e\"",
    "; type=\"", "\u{1}\u{2}", "\u{7f}", "café", "\u{ff}",
  ]
  assert_true(garbage.length() >= 40)

  // Every parser runs on every garbage input and never panics.
  let mut link_rejected = 0
  for g in garbage {
    if is_err(parse_link_header(g, dflt)) {
      link_rejected = link_rejected + 1
    }
    swallow(parse_linkset_text(g, dflt))
    swallow(parse_linkset_json(g, dflt))
    swallow(parse_uri_reference(g))
    swallow(parse_relation_type(g))
    swallow(parse_extended_value_string(g))
    swallow(canonicalize_link_header(g))
  }
  // Almost all of these are rejected by the link-header parser. (A couple of
  // entries such as IRI targets are accepted by design.)
  assert_true(link_rejected >= garbage.length() - 4)
}

///|
test "malformed constructs map to the documented error kinds" {
  let dflt = Limits::default()
  // Missing angle bracket.
  expect_err_kind(parse_link_header("a", dflt), ExpectedAngleBracket)
  // Unterminated target.
  expect_err_kind(parse_link_header("", dflt), InvalidTarget)
  // Control characters in the target are rejected.
  expect_err_kind(parse_link_header("", dflt), InvalidTarget)
  // A parameter with no name.
  expect_err_kind(parse_link_header("; =v", dflt), MissingParameterName)
  // An unterminated quoted-string value.
  expect_err_kind(
    parse_link_header("; x=\"oops", dflt),
    UnterminatedQuotedString,
  )
  // A relation value that is not a valid relation type.
  expect_err_kind(parse_link_header("; rel=\"a/b\"", dflt), InvalidRelation)
  // Trailing bytes after the last link-value.
  expect_err_kind(parse_link_header("; rel=\"next\" x", dflt), TrailingInput)
  // Malformed extended values.
  expect_err_kind(
    parse_link_header("; title*=UTF-8'", dflt),
    UnexpectedCharacter,
  )
  expect_err_kind(
    parse_link_header("; title*=UTF-8''%ZZ", dflt),
    InvalidPercentEncoding,
  )
  // URI parser rejects non-ASCII and malformed percent-encoding.
  expect_err_kind(parse_uri_reference("café"), InvalidToken)
  expect_err_kind(parse_uri_reference(""), InvalidToken)
  expect_err_kind(parse_uri_reference("%GG"), InvalidPercentEncoding)

  // Non-ASCII bytes are legal in a link target (IRI, RFC 8288 Section 3.1)
  // but forbidden in the application/linkset text format.
  let iri = unwrap_ok(parse_link_header("; rel=\"next\"", dflt))
  assert_str_eq(iri[0].target(), "café")
  expect_err_kind(parse_linkset_text("", dflt), UnexpectedCharacter)
}