Skip to content
Merged
2 changes: 1 addition & 1 deletion R/accessors.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.get_resource_data <- function(x, resource_name) {
check_gldp(x)
frictionless::read_resource(x, resource_name = resource_name)
gldp_read_resource(x, resource_name = resource_name)
}

.set_resource_data <- function(x, value, resource_name, post = identity) {
Expand Down
31 changes: 24 additions & 7 deletions R/add_gldp_resource.R
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,9 @@ add_gldp_resource <- function(

# Retrieve full schema (pkg$resources) does not have schema at first
pkg_schema <- gldp_profile_schema(version)
# The first branch is the one for the fixed tables.
possible_gldp_resources <-
pkg_schema$allOf[[2]]$properties$resources$items$oneOf[[
1
]]$properties$name$enum
gldp_profile_resource_branches(pkg_schema)[[1]]$properties$name$enum

if (!resource_name %in% possible_gldp_resources) {
cli_abort(c(
Expand Down Expand Up @@ -139,17 +138,35 @@ add_gldp_resource <- function(
delim = delim
)

# GeoLocator-DP requires `type: "table"` on its tabular resources from v1.1.
# frictionless only sets it from version 2.0, so set it here to stay correct
# on both. Every profile version declares the property, so this is safe for
# older packages too.
# GeoLocator-DP requires `$schema` and `type: "table"` on its tabular resources
# from v1.1. frictionless only sets them from version 2.0, so set them here to
# stay correct on both. Every profile version declares both properties, so this
# is safe for older packages too.
#
# `profile: "tabular-data-resource"` is deliberately left in place: it is the
# Data Package v1 spelling that `$schema` and `type` replace, but frictionless
# 1.3.0 refuses to read a resource without it.
resource <- gldp_resource(pkg, resource_name)
changed <- FALSE

if (!identical(resource[["$schema"]], .gldp_data_resource_profile)) {
resource <- append(
list(`$schema` = .gldp_data_resource_profile),
resource
)
changed <- TRUE
}

if (!identical(resource$type, "table")) {
resource <- append(
resource,
list(type = "table"),
after = which(names(resource) == "name")
)
changed <- TRUE
}

if (changed) {
gldp_resource(pkg, resource_name) <- resource
}

Expand Down
4 changes: 2 additions & 2 deletions R/merge_gldp.R
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ merge_gldp_prepare_tags <- function(pkg, resources) {
return(NULL)
}

tags <- frictionless::read_resource(pkg, resource_name = "tags")
tags <- gldp_read_resource(pkg, resource_name = "tags")
if (!"datapackage_id" %in% names(tags)) {
tags$datapackage_id <- pkg$id
}
Expand All @@ -246,7 +246,7 @@ merge_gldp_resources <- function(pkgs, resources, resources_by_pkg, tags_by_pkg)
if (!resource_name %in% resources_by_pkg[[idx]]) {
return(NULL)
}
frictionless::read_resource(pkgs[[idx]], resource_name = resource_name)
gldp_read_resource(pkgs[[idx]], resource_name = resource_name)
}
)
data_parts <- purrr::compact(data_parts)
Expand Down
2 changes: 1 addition & 1 deletion R/read_gldp.R
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ read_gldp <- function(x = "datapackage.json", force_read = TRUE, drop_measuremen
has_parsing_warning <- FALSE
df <- tryCatch(
withCallingHandlers(
frictionless::read_resource(pkg, resource_name),
gldp_read_resource(pkg, resource_name),
warning = function(w) {
msg <- conditionMessage(w)
if (grepl("One or more parsing issues", msg, fixed = TRUE)) {
Expand Down
4 changes: 1 addition & 3 deletions R/update_gldp.R
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ update_gldp_order_resources <- function(pkg) {

pkg_schema <- gldp_profile_schema(version)

resource_order <- pkg_schema$allOf[[2]]$properties$resources$items$oneOf |>
purrr::map(~ .x$properties$name$enum %||% .x$properties$name$const %||% character(0)) |>
purrr::flatten_chr()
resource_order <- gldp_profile_resource_names(pkg_schema)

if (length(resource_order) == 0) {
return(pkg)
Expand Down
17 changes: 13 additions & 4 deletions R/upgrade_gldp.R
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ upgrade_gldp <- function(x, to_version = .gldp_default_version) {
"v0.3" = upgrade_gldp_v0_3_to_v0_4(x),
"v0.4" = upgrade_gldp_v0_4_to_v0_5(x),
"v0.5" = upgrade_gldp_v0_5_to_v0_6(x),
"v0.6" = upgrade_gldp_v0_6_to_v1_0(x)
"v0.6" = upgrade_gldp_v0_6_to_v1_0(x),
"v1.0" = upgrade_gldp_v1_0_to_v1_1(x)
)

from_version <- gldp_version(x)
Expand All @@ -62,9 +63,7 @@ normalize_upgraded_resources <- function(x) {
# We only touch resources already present in the package and keep custom resources unchanged.
target_version <- gldp_version(x)
profile <- gldp_profile_schema(target_version)
supported_resources <- profile$allOf[[2]]$properties$resources$items$oneOf |>
purrr::map(~ .x$properties$name$enum %||% .x$properties$name$const %||% character(0)) |>
purrr::flatten_chr()
supported_resources <- gldp_profile_resource_names(profile)

resources <- x$resources %||% list()
for (resource in resources) {
Expand Down Expand Up @@ -345,6 +344,16 @@ upgrade_gldp_v0_6_to_v1_0 <- function(x) {
x
}

#' @noRd
upgrade_gldp_v1_0_to_v1_1 <- function(x) {
# Release: https://github.com/GeoPressure/GeoLocator-DP/releases/tag/v1.1
# The table schema moved from `resource$$schema` to `resource$schema`, and
# `type` became required. Resources are rebuilt against the v1.1 schemas by
# `normalize_upgraded_resources()`, so only the profile URL changes here.
x[["$schema"]] <- gldp_schema_url("v1.1")
x
}

#' @noRd
mutate_resource <- function(pkg, resource_name, fn) {
resource <- gldp_resource(pkg, resource_name)
Expand Down
20 changes: 20 additions & 0 deletions R/validate_gldp.R
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,26 @@ validate_gldp_table <- function(data, schema) {
schema$fieldsMatch[[1]]
)

# `fieldsMatch` is a rule over the whole set of fields and says nothing about
# which ones are required, so every mode GeoLocator-DP uses would let a table
# leave out a column carrying `required`. The standard has no way to say "the
# required columns must be present, the optional ones may be absent"
# (frictionlessdata/datapackage#1126), so it is checked here instead.
required <- names(schema_fields)[vapply(
schema_fields,
\(f) isTRUE(f$constraints$required),
logical(1)
)]
absent_required <- setdiff(required, names(data))

if (length(absent_required) > 0) {
cli_alert_danger(
"{.field {schema$name}} is missing {length(absent_required)} required
column{?s}: {.field {absent_required}}."
)
valid <- FALSE
}

fields <- intersect(names(schema_fields), names(data))

for (field in fields) {
Expand Down
32 changes: 21 additions & 11 deletions R/validate_gldp_schema.R
Original file line number Diff line number Diff line change
Expand Up @@ -374,21 +374,31 @@ validate_gldp_item <- function(item, prop, field) {
}
}

# Check if 'enum' property exists and is specified
if (!is.null(prop$enum)) {
# Check if each item is one of the allowed values in the enum
if (required) {
in_enum <- item %in% unlist(prop$enum)
} else {
# Both `categories` and the `enum` constraint restrict a field to a fixed set
# of values. GeoLocator-DP declares both, `categories` being the Data Package
# v2 spelling that also carries a label per value, so read whichever is there.
allowed <- if (!is.null(prop$categories)) {
vapply(
prop$categories,
\(x) if (is.list(x)) as.character(x$value)[1] else as.character(x)[1],
character(1)
)
} else if (!is.null(prop$enum)) {
as.character(unlist(prop$enum))
}

if (!is.null(allowed)) {
in_allowed <- item %in% allowed
if (!required) {
# Allow NA if not required
in_enum <- (item %in% unlist(prop$enum)) | is.na(item)
in_allowed <- in_allowed | is.na(item)
}

if (!all(in_enum)) {
invalid_values <- item[!in_enum]
if (!all(in_allowed)) {
invalid_values <- item[!in_allowed]
cli_alert_danger(
"{.field {field}} has {sum(!in_enum)} item{?s} that are not in the allowed values:
{.val {glue::glue_collapse(prop$enum, sep = ', ')}}. Invalid value{?s}:
"{.field {field}} has {sum(!in_allowed)} item{?s} that are not in the allowed values:
{.val {glue::glue_collapse(allowed, sep = ', ')}}. Invalid value{?s}:
{.val {glue::glue_collapse(unique(invalid_values), sep = ', ')}}"
)
valid <- FALSE
Expand Down
20 changes: 19 additions & 1 deletion R/versioning.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
# Supported GeoLocator-DP versions.
# This vector is the single source of truth for runtime and schema sync.
.gldp_supported_versions <- c("main", "v0.1", "v0.2", "v0.3", "v0.4", "v0.5", "v0.6", "v1.0")
.gldp_supported_versions <- c(
"main",
"v0.1",
"v0.2",
"v0.3",
"v0.4",
"v0.5",
"v0.6",
"v1.0",
"v1.1"
)

#' Data Package profile of a tabular resource descriptor
#'
#' GeoLocator-DP requires this on its tabular resources from v1.1.
#'
#' @noRd
.gldp_data_resource_profile <- "https://datapackage.org/profiles/2.0/dataresource.json"

.gldp_default_version <- utils::tail(.gldp_supported_versions, 1)

#' Get GeoLocator DP version
Expand Down
Loading
Loading