///|
fn check_strings(
  value : @exact.Value,
  path : Array[String],
  names : Array[String],
) -> Unit raise DecodeError {
  for name in names {
    if value.get(name) is Some(v) {
      text(v, child(path, name)) |> ignore
    }
  }
}

///|
fn check_bools(
  value : @exact.Value,
  path : Array[String],
  names : Array[String],
) -> Unit raise DecodeError {
  for name in names {
    if value.get(name) is Some(v) && !(v.kind() is @exact.Boolean(_)) {
      raise DecodeError(
        @exact.Diagnostic::new(
          @exact.WrongType,
          "Expected a boolean",
          path=child(path, name),
        ),
      )
    }
  }
}

///|
fn uri_(
  value : @exact.Value,
  path : Array[String],
  limits : @exact.Limits,
) -> @reference.UriReference raise DecodeError {
  match @reference.UriReference::parse(text(value, path), limits~) {
    Ok(uri) => uri
    Err(d) =>
      raise DecodeError(@exact.Diagnostic::new(d.code(), d.message(), path~))
  }
}

///|
fn check_uris(
  value : @exact.Value,
  path : Array[String],
  names : Array[String],
  limits : @exact.Limits,
) -> Unit raise DecodeError {
  for name in names {
    if value.get(name) is Some(v) {
      uri_(v, child(path, name), limits) |> ignore
    }
  }
}

///|
fn contact_(
  value : @exact.Value,
  path : Array[String],
  limits : @exact.Limits,
) -> Contact raise DecodeError {
  object(value, path) |> ignore
  check_strings(value, path, ["name", "email"])
  check_uris(value, path, ["url"], limits)
  { value, }
}

///|
fn license_(
  value : @exact.Value,
  path : Array[String],
  limits : @exact.Limits,
) -> License raise DecodeError {
  object(value, path) |> ignore
  check_strings(value, path, ["name"])
  check_uris(value, path, ["url"], limits)
  { value, }
}

///|
fn documentation_(
  value : @exact.Value,
  path : Array[String],
  limits : @exact.Limits,
) -> ExternalDocumentation raise DecodeError {
  object(value, path) |> ignore
  uri_(field(value, "url", path), child(path, "url"), limits) |> ignore
  check_strings(value, path, ["description"])
  { value, }
}

///|
fn variable_(
  value : @exact.Value,
  path : Array[String],
  _limits : @exact.Limits,
) -> ServerVariable raise DecodeError {
  object(value, path) |> ignore
  text(field(value, "default", path), child(path, "default")) |> ignore
  check_strings(value, path, ["description"])
  if value.get("enum") is Some(v) {
    for i, item in array(v, child(path, "enum")) {
      text(item, child(child(path, "enum"), i.to_string())) |> ignore
    }
  }
  { value, }
}

///|
fn server_(
  value : @exact.Value,
  path : Array[String],
  limits : @exact.Limits,
) -> Server raise DecodeError {
  object(value, path) |> ignore
  text(field(value, "url", path), child(path, "url")) |> ignore
  check_strings(value, path, ["name", "summary", "description"])
  if value.get("variables") is Some(v) {
    for (name, variable) in object(v, child(path, "variables")) {
      variable_(variable, child(child(path, "variables"), name), limits)
      |> ignore
    }
  }
  { value, }
}

///|
fn tag_(
  value : @exact.Value,
  path : Array[String],
  limits : @exact.Limits,
) -> Tag raise DecodeError {
  object(value, path) |> ignore
  text(field(value, "name", path), child(path, "name"), nonempty=true) |> ignore
  check_strings(value, path, ["summary", "description"])
  if value.get("externalDocs") is Some(v) {
    documentation_(v, child(path, "externalDocs"), limits) |> ignore
  }
  { value, }
}

///|
fn error_object_(
  value : @exact.Value,
  path : Array[String],
  _limits : @exact.Limits,
) -> ErrorObject raise DecodeError {
  object(value, path) |> ignore
  match field(value, "code", path).kind() {
    @exact.Numeric(n) =>
      match ErrorCode::from_number(n) {
        Ok(_) => ()
        Err(d) =>
          raise DecodeError(
            @exact.Diagnostic::new(
              d.code(),
              d.message(),
              path=child(path, "code"),
            ),
          )
      }
    _ =>
      raise DecodeError(
        @exact.Diagnostic::new(
          @exact.WrongType,
          "Expected an integer error code",
          path=child(path, "code"),
        ),
      )
  }
  text(field(value, "message", path), child(path, "message")) |> ignore
  { value, }
}

///|
fn example_(
  value : @exact.Value,
  path : Array[String],
  limits : @exact.Limits,
) -> Example raise DecodeError {
  object(value, path) |> ignore
  check_strings(value, path, ["name", "summary", "description"])
  if value.get("value") is Some(_) && value.get("externalValue") is Some(_) {
    raise DecodeError(
      @exact.Diagnostic::new(
        @exact.WrongType,
        "value and externalValue are mutually exclusive",
        path=child(path, "externalValue"),
      ),
    )
  }
  check_uris(value, path, ["externalValue"], limits)
  { value, }
}

///|
fn example_ref(
  value : @exact.Value,
  path : Array[String],
  limits : @exact.Limits,
) -> InlineOrReference[Example] raise DecodeError {
  match reference(value, path, limits~) {
    Some(ref_) => Reference(ref_)
    None => Inline(example_(value, path, limits))
  }
}

///|
fn pairing_(
  value : @exact.Value,
  path : Array[String],
  limits : @exact.Limits,
) -> ExamplePairing raise DecodeError {
  object(value, path) |> ignore
  text(field(value, "name", path), child(path, "name"), nonempty=true) |> ignore
  check_strings(value, path, ["summary", "description"])
  for i, param in array(field(value, "params", path), child(path, "params")) {
    example_ref(param, child(child(path, "params"), i.to_string()), limits)
    |> ignore
  }
  if value.get("result") is Some(v) {
    example_ref(v, child(path, "result"), limits) |> ignore
  }
  { value, }
}

///|
fn link_(
  value : @exact.Value,
  path : Array[String],
  limits : @exact.Limits,
) -> Link raise DecodeError {
  object(value, path) |> ignore
  if value.get("name") is Some(v) {
    text(v, child(path, "name"), nonempty=true) |> ignore
  }
  check_strings(value, path, ["summary", "description"])
  if value.get("method") is Some(v) {
    match MethodName::parse(text(v, child(path, "method"))) {
      Ok(_) => ()
      Err(d) =>
        raise DecodeError(
          @exact.Diagnostic::new(
            d.code(),
            d.message(),
            path=child(path, "method"),
          ),
        )
    }
  }
  if value.get("params") is Some(v) {
    object(v, child(path, "params")) |> ignore
  }
  if value.get("server") is Some(v) {
    server_(v, child(path, "server"), limits) |> ignore
  }
  { value, }
}

///|
fn info_(
  value : @exact.Value,
  path : Array[String],
  limits : @exact.Limits,
) -> Info raise DecodeError {
  object(value, path) |> ignore
  let title = text(field(value, "title", path), child(path, "title"))
  let version = text(field(value, "version", path), child(path, "version"))
  check_strings(value, path, ["description"])
  check_uris(value, path, ["termsOfService"], limits)
  if value.get("contact") is Some(v) {
    contact_(v, child(path, "contact"), limits) |> ignore
  }
  if value.get("license") is Some(v) {
    license_(v, child(path, "license"), limits) |> ignore
  }
  { title, version, value, }
}