///|
fn validate_field_schema(
  field : Field,
  depth : Int,
  seen : Array[(Int64, DataType, Bool)],
) -> Unit raise ArrowError {
  if depth > 64 {
    raise LimitExceeded("schema nesting depth")
  }
  match field.data_type {
    List | LargeList =>
      if field.children.length() != 1 {
        raise Invalid("List must have one child field")
      }
    FixedSizeList(size) =>
      if size <= 0 || field.children.length() != 1 {
        raise Invalid("FixedSizeList requires positive size and one child")
      }
    Map(_) =>
      if field.children.length() != 1 ||
        field.children[0].data_type != Struct ||
        field.children[0].nullable ||
        field.children[0].children.length() != 2 ||
        field.children[0].children[0].nullable {
        raise Invalid("Map requires nonnullable entries and keys")
      }
    Struct => ()
    Dictionary(id, inner, ordered) => {
      if depth != 0 {
        raise Unsupported("nested dictionary fields")
      }
      if inner != Utf8 && inner != Binary {
        raise Unsupported("dictionary values must be Utf8 or Binary")
      }
      if !field.children.is_empty() {
        raise Invalid("dictionary field has children")
      }
      for entry in seen {
        let (found, found_type, found_ordered) = entry
        if found == id {
          if found_type != inner || found_ordered != ordered {
            raise Invalid("dictionary ID used for incompatible fields")
          }
          return
        }
      }
      seen.push((id, inner, ordered))
    }
    FixedSizeBinary(width) =>
      if width <= 0 || !field.children.is_empty() {
        raise Invalid("invalid fixed-size binary field")
      }
    _ =>
      if !field.children.is_empty() {
        raise Invalid("primitive field has children")
      }
  }
  for child in field.children {
    validate_field_schema(child, depth + 1, seen)
  }
}

///|
fn validate_schema(schema : Schema) -> Unit raise ArrowError {
  let seen : Array[(Int64, DataType, Bool)] = []
  for field in schema.fields {
    validate_field_schema(field, 0, seen)
  }
}