///|
fn json_field(j : Json, key : String) -> Json {
  match j {
    Object(m) => m.get(key).unwrap_or(Json::null())
    _ => Json::null()
  }
}

///|
fn object(j : Json, path : String) -> Map[String, Json] raise IceError {
  match j {
    Object(m) => m
    _ => raise Invalid("INVALID_METADATA", path, "Expected an object")
  }
}

///|
fn text_value(j : Json, path : String) -> String raise IceError {
  match j {
    String(s) => s
    _ => raise Invalid("INVALID_METADATA", path, "Expected a string")
  }
}

///|
fn array_value(j : Json, path : String) -> Array[Json] raise IceError {
  match j {
    Array(a) => a
    _ => raise Invalid("INVALID_METADATA", path, "Expected an array")
  }
}

///|
fn int_value(j : Json, path : String) -> Int raise IceError {
  match j {
    Number(n, ..) if n >= -2147483648.0 &&
      n <= 2147483647.0 &&
      n.to_int().to_double() == n => n.to_int()
    _ =>
      raise Invalid("INVALID_INTEGER", path, "Expected a signed 32-bit integer")
  }
}

///|
fn long_value(j : Json, path : String) -> Int64 raise IceError {
  match j {
    Number(_, repr=Some(s)) =>
      @json.from_json(Json::string(s)) catch {
        _ =>
          raise Invalid(
            "INVALID_INTEGER", path, "Expected a signed 64-bit integer literal",
          )
      }
    Number(n, repr=None) if n >= -9007199254740991.0 &&
      n <= 9007199254740991.0 &&
      n.to_int64().to_double() == n => n.to_int64()
    _ =>
      raise Invalid(
        "INVALID_INTEGER", path, "Expected an exact signed 64-bit integer",
      )
  }
}

///|
fn opt_long(j : Json, path : String) -> Int64? raise IceError {
  match j {
    Null => None
    _ => Some(long_value(j, path))
  }
}

///|
// UI/CLI arguments allow decimal strings without weakening metadata validation.
fn argument_long(j : Json, path : String) -> Int64 raise IceError {
  match j {
    String(s) => {
      let value = @json.parse(s) catch {
        _ =>
          raise Invalid(
            "INVALID_INTEGER", path, "Expected a decimal integer string",
          )
      }
      long_value(value, path)
    }
    _ => long_value(j, path)
  }
}

///|
fn opt_int(j : Json, path : String) -> Int? raise IceError {
  match j {
    Null => None
    _ => Some(int_value(j, path))
  }
}

///|
fn bool_value(j : Json, path : String) -> Bool raise IceError {
  match j {
    True => true
    False => false
    _ => raise Invalid("INVALID_METADATA", path, "Expected a boolean")
  }
}

///|
fn nonnegative(n : Int64, path : String) -> Int64 raise IceError {
  if n < 0 {
    raise Invalid("INVALID_INTEGER", path, "Expected a nonnegative integer")
  }
  n
}