///|
/// Validate fields needed to use an Item safely as a search result.
/// This is intentionally smaller than the full STAC JSON Schemas.
fn validate_item_shape(v : Json, collection : String?) -> Unit raise StacError {
validate_extensions(v)
let has_collection_link = validate_links(v, "item")
match collection {
Some(name) =>
if name == "" || !has_collection_link {
raise Invalid("collection requires collection link")
}
None =>
if has_collection_link {
raise Invalid("collection link requires collection id")
}
}
let geometry = match field(v, "geometry") {
Some(value) => value
None => raise Invalid("missing geometry")
}
match geometry {
Null =>
if field(v, "bbox") != None {
raise Invalid("bbox must be absent when geometry is null")
}
Object(_) => {
validate_geojson_geometry(geometry, 0)
validate_bbox(v)
}
_ => raise Invalid("invalid geometry")
}
let properties = match field(v, "properties") {
Some(Object(m)) => Json::object(m)
_ => raise Invalid("invalid properties")
}
validate_item_time(properties)
validate_assets(v)
}
///|
fn validate_extensions(v : Json) -> Unit raise StacError {
match field(v, "stac_extensions") {
None => ()
Some(Array(xs)) => {
let seen : Map[String, Bool] = Map([])
for value in xs {
match value {
String(name) if name != "" => {
if seen.contains(name) {
raise Invalid("duplicate stac_extension")
}
seen[name] = true
}
_ => raise Invalid("invalid stac_extensions")
}
}
}
_ => raise Invalid("invalid stac_extensions")
}
}
///|
fn validate_links(v : Json, context : String) -> Bool raise StacError {
let links = match field(v, "links") {
Some(Array(xs)) => xs
_ => raise Invalid("invalid " + context + " links")
}
let mut has_collection = false
for link in links {
let rel = string_field(link, "rel")
ignore(string_field(link, "href"))
if rel == "collection" {
has_collection = true
}
}
has_collection
}
///|
fn validate_bbox(v : Json) -> Unit raise StacError {
let bbox = match field(v, "bbox") {
Some(Array(xs)) => xs
_ => raise Invalid("missing bbox")
}
if bbox.length() != 4 && bbox.length() != 6 {
raise Invalid("invalid bbox length")
}
for value in bbox {
match value {
Number(_, ..) => ()
_ => raise Invalid("invalid bbox value")
}
}
}
///|
fn validate_item_time(properties : Json) -> Unit raise StacError {
let datetime = optional_string(properties, "datetime")
match datetime {
Some(s) if s != "" => validate_timestamp(s)
_ => {
if !(field(properties, "datetime") is Some(Null)) {
raise Invalid("missing datetime or interval")
}
let start = optional_string(properties, "start_datetime")
let end = optional_string(properties, "end_datetime")
match (start, end) {
(Some(s), Some(e)) => {
validate_timestamp(s)
validate_timestamp(e)
}
_ => raise Invalid("missing datetime or interval")
}
}
}
}
///|
fn validate_assets(v : Json) -> Unit raise StacError {
let assets = match field(v, "assets") {
Some(Object(m)) => m
_ => raise Invalid("invalid assets")
}
for key, asset in assets {
if key == "" {
raise Invalid("empty asset key")
}
ignore(string_field(asset, "href"))
validate_asset_string(asset, "title")
validate_asset_string(asset, "description")
validate_asset_string(asset, "type")
match field(asset, "roles") {
None => ()
Some(Array(roles)) =>
for role in roles {
match role {
String(s) if s != "" => ()
_ => raise Invalid("invalid asset role")
}
}
_ => raise Invalid("invalid asset roles")
}
}
}
///|
fn validate_asset_string(asset : Json, name : String) -> Unit raise StacError {
match field(asset, name) {
None | Some(String(_)) => ()
_ => raise Invalid("invalid asset " + name)
}
}