use std::fmt;
pub const EXTENSION_NAME: &str = "gpkg_metadata";
pub const EXTENSION_DEFINITION: &str = "http://www.geopackage.org/spec140/#extension_metadata";
pub const EXTENSION_SCOPE: &str = "read-write";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MetadataRecord {
pub id: i64,
pub scope: MetadataScope,
pub standard_uri: String,
pub mime_type: String,
pub metadata: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MetadataReference {
pub scope: ReferenceScope,
pub table_name: Option<String>,
pub column_name: Option<String>,
pub row_id_value: Option<i64>,
pub timestamp: String,
pub md_file_id: i64,
pub md_parent_id: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum MetadataScope {
Undefined,
FieldSession,
CollectionSession,
Series,
Dataset,
FeatureType,
Feature,
AttributeType,
Attribute,
Tile,
Model,
Catalog,
Schema,
Taxonomy,
Software,
Service,
CollectionHardware,
NonGeographicDataset,
DimensionGroup,
Style,
Other(String),
}
impl MetadataScope {
pub fn parse(value: &str) -> Self {
match value {
"undefined" => Self::Undefined,
"fieldSession" => Self::FieldSession,
"collectionSession" => Self::CollectionSession,
"series" => Self::Series,
"dataset" => Self::Dataset,
"featureType" => Self::FeatureType,
"feature" => Self::Feature,
"attributeType" => Self::AttributeType,
"attribute" => Self::Attribute,
"tile" => Self::Tile,
"model" => Self::Model,
"catalog" => Self::Catalog,
"schema" => Self::Schema,
"taxonomy" => Self::Taxonomy,
"software" => Self::Software,
"service" => Self::Service,
"collectionHardware" => Self::CollectionHardware,
"nonGeographicDataset" => Self::NonGeographicDataset,
"dimensionGroup" => Self::DimensionGroup,
"style" => Self::Style,
other => Self::Other(other.to_owned()),
}
}
pub fn as_str(&self) -> &str {
match self {
Self::Undefined => "undefined",
Self::FieldSession => "fieldSession",
Self::CollectionSession => "collectionSession",
Self::Series => "series",
Self::Dataset => "dataset",
Self::FeatureType => "featureType",
Self::Feature => "feature",
Self::AttributeType => "attributeType",
Self::Attribute => "attribute",
Self::Tile => "tile",
Self::Model => "model",
Self::Catalog => "catalog",
Self::Schema => "schema",
Self::Taxonomy => "taxonomy",
Self::Software => "software",
Self::Service => "service",
Self::CollectionHardware => "collectionHardware",
Self::NonGeographicDataset => "nonGeographicDataset",
Self::DimensionGroup => "dimensionGroup",
Self::Style => "style",
Self::Other(name) => name,
}
}
pub fn is_listed(&self) -> bool {
!matches!(self, Self::Other(_))
}
}
impl fmt::Display for MetadataScope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReferenceScope {
GeoPackage,
Table,
Column,
Row,
RowCol,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ReferenceTargets {
pub table_name: bool,
pub column_name: bool,
pub row_id_value: bool,
}
impl ReferenceScope {
pub fn parse(value: &str) -> Option<Self> {
match value {
"geopackage" => Some(Self::GeoPackage),
"table" => Some(Self::Table),
"column" => Some(Self::Column),
"row" => Some(Self::Row),
"row/col" => Some(Self::RowCol),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::GeoPackage => "geopackage",
Self::Table => "table",
Self::Column => "column",
Self::Row => "row",
Self::RowCol => "row/col",
}
}
pub fn targets(self) -> ReferenceTargets {
ReferenceTargets {
table_name: !matches!(self, Self::GeoPackage),
column_name: matches!(self, Self::Column | Self::RowCol),
row_id_value: matches!(self, Self::Row | Self::RowCol),
}
}
}
impl fmt::Display for ReferenceScope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn metadata_scopes_round_trip() {
for scope in [
MetadataScope::Undefined,
MetadataScope::FieldSession,
MetadataScope::CollectionSession,
MetadataScope::Series,
MetadataScope::Dataset,
MetadataScope::FeatureType,
MetadataScope::Feature,
MetadataScope::AttributeType,
MetadataScope::Attribute,
MetadataScope::Tile,
MetadataScope::Model,
MetadataScope::Catalog,
MetadataScope::Schema,
MetadataScope::Taxonomy,
MetadataScope::Software,
MetadataScope::Service,
MetadataScope::CollectionHardware,
MetadataScope::NonGeographicDataset,
MetadataScope::DimensionGroup,
MetadataScope::Style,
] {
assert_eq!(MetadataScope::parse(scope.as_str()), scope);
assert!(scope.is_listed());
}
}
#[test]
fn an_unlisted_scope_is_kept_rather_than_rejected() {
let scope = MetadataScope::parse("x-vendor_thing");
assert_eq!(scope, MetadataScope::Other("x-vendor_thing".to_owned()));
assert_eq!(scope.as_str(), "x-vendor_thing");
assert!(!scope.is_listed());
}
#[test]
fn reference_scopes_round_trip_and_reject_the_rest() {
for scope in [
ReferenceScope::GeoPackage,
ReferenceScope::Table,
ReferenceScope::Column,
ReferenceScope::Row,
ReferenceScope::RowCol,
] {
assert_eq!(ReferenceScope::parse(scope.as_str()), Some(scope));
}
assert_eq!(ReferenceScope::parse("Table"), None);
assert_eq!(ReferenceScope::parse("rowcol"), None);
assert_eq!(ReferenceScope::parse("cell"), None);
}
#[test]
fn targets_follow_requirements_97_to_99() {
let cases = [
(ReferenceScope::GeoPackage, [false, false, false]),
(ReferenceScope::Table, [true, false, false]),
(ReferenceScope::Column, [true, true, false]),
(ReferenceScope::Row, [true, false, true]),
(ReferenceScope::RowCol, [true, true, true]),
];
for (scope, [table, column, row_id]) in cases {
let targets = scope.targets();
assert_eq!(targets.table_name, table, "{scope} table_name");
assert_eq!(targets.column_name, column, "{scope} column_name");
assert_eq!(targets.row_id_value, row_id, "{scope} row_id_value");
}
}
}