From 13339629d559ab6b677b2ce72067bd35f4ad15df Mon Sep 17 00:00:00 2001 From: Ata B Barzegar Date: Mon, 10 Aug 2026 22:26:54 +0300 Subject: [PATCH 1/2] feat: implement tab-completable ontology builder for SPARQL endpoints - Add `build_ont()` to convert class and property URIs into browsable, tab-completable list structures, facilitating an ORM-like experience in R. - Implement `make_display_list()` and its internal helper to map full URIs to short, human-readable local names or labels. - Introduce `.solve_possible_duplicate_display()` to automatically disambiguate local name collisions by appending the namespace or full URI. - Ensure resulting lists are deduplicated and alphabetically sorted for easier navigation during interactive endpoint exploration. - Add tests related to ontology builder system - Add updated version of Man files. --- R/endpoint_class.R | 77 ---------------------- R/schema_ont.R | 110 +++++++++++++++++++++++++++++++ R/utils.R | 44 +++++++++++++ man/build_ont.Rd | 32 +++++++++ man/ep_class_links.Rd | 19 ------ man/ep_classes.Rd | 19 ------ man/ep_properties.Rd | 19 ------ man/generic_endpoint.Rd | 20 ------ man/local_name.Rd | 22 +++++++ man/make_display_list.Rd | 27 ++++++++ man/namespace_of.Rd | 23 +++++++ man/normalize_scheme.Rd | 22 +++++++ man/sparql_endpoint.Rd | 30 --------- tests/testthat/test-schema_ont.R | 77 ++++++++++++++++++++++ 14 files changed, 357 insertions(+), 184 deletions(-) delete mode 100644 R/endpoint_class.R create mode 100644 R/schema_ont.R create mode 100644 R/utils.R create mode 100644 man/build_ont.Rd delete mode 100644 man/ep_class_links.Rd delete mode 100644 man/ep_classes.Rd delete mode 100644 man/ep_properties.Rd delete mode 100644 man/generic_endpoint.Rd create mode 100644 man/local_name.Rd create mode 100644 man/make_display_list.Rd create mode 100644 man/namespace_of.Rd create mode 100644 man/normalize_scheme.Rd delete mode 100644 man/sparql_endpoint.Rd create mode 100644 tests/testthat/test-schema_ont.R diff --git a/R/endpoint_class.R b/R/endpoint_class.R deleted file mode 100644 index f0c8e06..0000000 --- a/R/endpoint_class.R +++ /dev/null @@ -1,77 +0,0 @@ -#' 'SPARQL' endpoint (base class) -#' -#' The base S7 class every endpoint backend inherits from. You will -#' not normally call `sparql_endpoint()` directly -- use a specific -#' constructor like `new_generic_endpoint()`. -#' -#' @param url Character. The SPARQL endpoint URL (the address a query -#' is sent to). -#' @param name Character. A short label for the endpoint, used for -#' caching and messages. -#' @param headers A named list of extra HTTP headers sent with every -#' request (for example a custom `User-Agent`). -#' @param timeout Numeric. Request timeout in seconds. -#' -#' @export -sparql_endpoint <- S7::new_class( - "sparql_endpoint", - properties = list( - url = S7::new_property( - S7::class_character, - validator = function(value) { - if (length(value) != 1 || !nzchar(value)) "must be a single non-empty string" - } - ), - name = S7::new_property( - S7::class_character, - default = quote(NA_character_), - validator = function(value) { - if (length(value) != 1) "must be a single string (or NA)" - } - ), - headers = S7::new_property( - S7::class_list, - default = quote(list()), - validator = function(value) { - if (length(value) > 0 && (is.null(names(value)) || any(!nzchar(names(value))))) { - "must be a fully named list" - } - } - ), - timeout = S7::new_property( - S7::class_numeric, - default = 60, - validator = function(value) { - if (length(value) != 1 || is.na(value) || value <= 0) "must be a single positive number" - } - ) - ) -) - -#' Generic 'SPARQL' 1.1 backend -#' -#' The fallback backend used for any endpoint that isn't registered -#' under a more specific class. -#' -#' @param url,name,headers,timeout See [sparql_endpoint()]. -#' @export -generic_endpoint <- S7::new_class("generic_endpoint", parent = sparql_endpoint) - -#' Discover the classes used by an endpoint -#' @param endpoint A `sparql_endpoint` object. -#' @param ... Passed on to methods (commonly `limit`). -#' @return A data frame with columns `class` and `n`. -#' @export -ep_classes <- S7::new_generic("ep_classes", "endpoint") - -#' Discover the properties (predicates) used by an endpoint -#' @inheritParams ep_classes -#' @return A data frame with columns `property` and `n`. -#' @export -ep_properties <- S7::new_generic("ep_properties", "endpoint") - -#' Discover which classes are linked by which properties -#' @inheritParams ep_classes -#' @return A data frame with columns `from_class`, `property`, `to_class`, `n`. -#' @export -ep_class_links <- S7::new_generic("ep_class_links", "endpoint") diff --git a/R/schema_ont.R b/R/schema_ont.R new file mode 100644 index 0000000..5fe703b --- /dev/null +++ b/R/schema_ont.R @@ -0,0 +1,110 @@ +#' Build a tab-completable "ont" object +#' +#' Converts the class/property tables discovered from a SPARQL endpoint +#' into short, unique, alphabetically-sorted names so they can be browsed +#' with regular R auto-completion, e.g. `ont$classes$Person`. +#' +#' @param classes A data frame with a `class` column of URIs, such as +#' returned by ep_classes(). May be `NULL`. +#' @param properties A data frame with a `property` column of URIs, such as +#' returned by ep_properties(). May be `NULL`. +#' @return A list with elements `classes` and `properties`. Each is a named +#' list mapping a short display name to the full URI. +#' @examples +#' classes <- data.frame(class = c( +#' "http://xmlns.com/foaf/0.1/Person", +#' "http://schema.org/Person" +#' )) +#' ont <- build_ont(classes) +#' names(ont$classes) +#' @export +build_ont <- function(classes = NULL, properties = NULL) { + class_uris <- if (is.null(classes)) character() else classes$class + property_uris <- if (is.null(properties)) character() else properties$property + + list( + classes = make_display_list(class_uris), + properties = make_display_list(property_uris) + ) +} + +#' Build a display-name -> URI lookup, disambiguating collisions +#' +#' When two different URIs share the same local name (e.g. `foaf:Person` +#' and `schema:Person`), the namespace is appended to disambiguate. In the +#' rare case that is still not enough to make names unique, the full URI is +#' appended instead. +#' +#' @param uris Character vector of URIs. +#' @return A named list mapping a unique display name to each URI in +#' `uris` (duplicated/`NA`/empty URIs are dropped). +#' @examples +#' make_display_list(c( +#' "http://xmlns.com/foaf/0.1/Person", +#' "http://schema.org/Person" +#' )) +#' @export +make_display_list <- function(uris) { + .make_display_list_impl(uris) +} + +#' Build a display-name -> URI lookup, optionally preferring given labels +#' +#' The shared engine behind [make_display_list()] When `labels` is supplied (and non-`NA`/ +#' non-empty for a given id), it is used as the display name instead of the +#' id's local name -- this is what lets ontology terms show up under their +#' `rdfs:label` (e.g. `"Person"`) rather than a bare URI fragment. +#' +#' @param ids Character vector of identifiers (URIs). +#' @param labels Character vector the same length as `ids` (or a single +#' value, recycled), or `NA_character_` (default) to always fall back to +#' [local_name()]. +#' @return A named list mapping a unique display name to each non-missing, +#' non-empty, de-duplicated id in `ids`. +#' @noRd +.make_display_list_impl <- function(ids, labels = NA_character_) { + ids <- as.character(ids) + if (length(labels) != length(ids)) labels <- rep(NA_character_, length(ids)) + + keep <- !is.na(ids) & nzchar(ids) + ids <- ids[keep] + labels <- labels[keep] + if (length(ids) == 0) return(list()) + + dup_id <- duplicated(ids) + ids <- ids[!dup_id] + labels <- labels[!dup_id] + + display <- ifelse(!is.na(labels) & nzchar(labels), labels, local_name(ids)) + display <- .solve_possible_duplicate_display(display, ids) + + ord <- order(display) + stats::setNames(as.list(ids[ord]), display[ord]) +} + + + +#' This function makes sure the final output to the user has unique items. +#' In case there are duplication, the function make them unique by adding their +#' namespace. If there are equal namespaces, the function then brackets are added. +#' +#' @param display A character vector of display names to be checked for duplicates. +#' @param ids A character vector of full URIs corresponding to the display names. +#' +#' @returns display +#' @export +#' +#' @noRd +.solve_possible_duplicate_display <- function(display, ids){ + dup <- display %in% display[duplicated(display)] + if (any(dup)) { + display[dup] <- paste0(display[dup], " <", namespace_of(ids[dup]), ">") + } + dup2 <- display %in% display[duplicated(display)] + if (any(dup2)) { + display[dup2] <- paste0(display[dup2], " [", ids[dup2], "]") + } + display +} + + diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 0000000..b846d9c --- /dev/null +++ b/R/utils.R @@ -0,0 +1,44 @@ +#' Namespace portion of a URI +#' +#' Extracts the "namespace" portion of a URI, keeping whichever delimiter +#' (`/` or `#`) was actually used, so it can be compared directly against +#' known vocabulary namespace strings. +#' +#' @param uri Character vector of URIs. +#' @return A character vector the same length as `uri`. +#' @examples +#' namespace_of("http://xmlns.com/foaf/0.1/Person") +#' namespace_of("http://www.w3.org/2002/07/owl#Class") +#' @export +namespace_of <- function(uri) { + ifelse(is.na(uri), NA_character_, sub("([/#])[^/#]*$", "\\1", uri)) +} + +#' Short display name for a URI +#' +#' Returns the fragment or last path segment of a URI, falling back to the +#' full URI when no `/` or `#` delimiter is present. +#' +#' @param uri Character vector of URIs. +#' @return A character vector the same length as `uri`. +#' @examples +#' local_name("http://xmlns.com/foaf/0.1/Person") +#' local_name("http://www.w3.org/2002/07/owl#Class") +#' @export +local_name <- function(uri) { + nm <- sub(".*[/#]", "", uri) + ifelse(is.na(uri), NA_character_, ifelse(nzchar(nm), nm, uri)) +} + +#' Normalise a URI's scheme +#' +#' Rewrites `https://` to `http://` so namespace comparisons aren't +#' scheme-sensitive (many vocabularies are inconsistently referenced with +#' either scheme). +#' +#' @param x Character vector of URIs. +#' @return A character vector the same length as `x`. +#' @examples +#' normalize_scheme("https://schema.org/") +#' @export +normalize_scheme <- function(x) sub("^https://", "http://", x) diff --git a/man/build_ont.Rd b/man/build_ont.Rd new file mode 100644 index 0000000..3fa3f5f --- /dev/null +++ b/man/build_ont.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/schema_ont.R +\name{build_ont} +\alias{build_ont} +\title{Build a tab-completable "ont" object} +\usage{ +build_ont(classes = NULL, properties = NULL) +} +\arguments{ +\item{classes}{A data frame with a \code{class} column of URIs, such as +returned by ep_classes(). May be \code{NULL}.} + +\item{properties}{A data frame with a \code{property} column of URIs, such as +returned by ep_properties(). May be \code{NULL}.} +} +\value{ +A list with elements \code{classes} and \code{properties}. Each is a named +list mapping a short display name to the full URI. +} +\description{ +Converts the class/property tables discovered from a SPARQL endpoint +into short, unique, alphabetically-sorted names so they can be browsed +with regular R auto-completion, e.g. \code{ont$classes$Person}. +} +\examples{ +classes <- data.frame(class = c( + "http://xmlns.com/foaf/0.1/Person", + "http://schema.org/Person" +)) +ont <- build_ont(classes) +names(ont$classes) +} diff --git a/man/ep_class_links.Rd b/man/ep_class_links.Rd deleted file mode 100644 index 645c6e3..0000000 --- a/man/ep_class_links.Rd +++ /dev/null @@ -1,19 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/endpoint_class.R -\name{ep_class_links} -\alias{ep_class_links} -\title{Discover which classes are linked by which properties} -\usage{ -ep_class_links(endpoint, ...) -} -\arguments{ -\item{endpoint}{A \code{sparql_endpoint} object.} - -\item{...}{Passed on to methods (commonly \code{limit}).} -} -\value{ -A data frame with columns \code{from_class}, \code{property}, \code{to_class}, \code{n}. -} -\description{ -Discover which classes are linked by which properties -} diff --git a/man/ep_classes.Rd b/man/ep_classes.Rd deleted file mode 100644 index a39c005..0000000 --- a/man/ep_classes.Rd +++ /dev/null @@ -1,19 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/endpoint_class.R -\name{ep_classes} -\alias{ep_classes} -\title{Discover the classes used by an endpoint} -\usage{ -ep_classes(endpoint, ...) -} -\arguments{ -\item{endpoint}{A \code{sparql_endpoint} object.} - -\item{...}{Passed on to methods (commonly \code{limit}).} -} -\value{ -A data frame with columns \code{class} and \code{n}. -} -\description{ -Discover the classes used by an endpoint -} diff --git a/man/ep_properties.Rd b/man/ep_properties.Rd deleted file mode 100644 index a1cd2d5..0000000 --- a/man/ep_properties.Rd +++ /dev/null @@ -1,19 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/endpoint_class.R -\name{ep_properties} -\alias{ep_properties} -\title{Discover the properties (predicates) used by an endpoint} -\usage{ -ep_properties(endpoint, ...) -} -\arguments{ -\item{endpoint}{A \code{sparql_endpoint} object.} - -\item{...}{Passed on to methods (commonly \code{limit}).} -} -\value{ -A data frame with columns \code{property} and \code{n}. -} -\description{ -Discover the properties (predicates) used by an endpoint -} diff --git a/man/generic_endpoint.Rd b/man/generic_endpoint.Rd deleted file mode 100644 index 8a1c04d..0000000 --- a/man/generic_endpoint.Rd +++ /dev/null @@ -1,20 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/endpoint_class.R -\name{generic_endpoint} -\alias{generic_endpoint} -\title{Generic 'SPARQL' 1.1 backend} -\usage{ -generic_endpoint( - url = character(0), - name = NA_character_, - headers = list(), - timeout = 60 -) -} -\arguments{ -\item{url, name, headers, timeout}{See \code{\link[=sparql_endpoint]{sparql_endpoint()}}.} -} -\description{ -The fallback backend used for any endpoint that isn't registered -under a more specific class. -} diff --git a/man/local_name.Rd b/man/local_name.Rd new file mode 100644 index 0000000..4972f69 --- /dev/null +++ b/man/local_name.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{local_name} +\alias{local_name} +\title{Short display name for a URI} +\usage{ +local_name(uri) +} +\arguments{ +\item{uri}{Character vector of URIs.} +} +\value{ +A character vector the same length as \code{uri}. +} +\description{ +Returns the fragment or last path segment of a URI, falling back to the +full URI when no \code{/} or \verb{#} delimiter is present. +} +\examples{ +local_name("http://xmlns.com/foaf/0.1/Person") +local_name("http://www.w3.org/2002/07/owl#Class") +} diff --git a/man/make_display_list.Rd b/man/make_display_list.Rd new file mode 100644 index 0000000..dc1a5de --- /dev/null +++ b/man/make_display_list.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/schema_ont.R +\name{make_display_list} +\alias{make_display_list} +\title{Build a display-name -> URI lookup, disambiguating collisions} +\usage{ +make_display_list(uris) +} +\arguments{ +\item{uris}{Character vector of URIs.} +} +\value{ +A named list mapping a unique display name to each URI in +\code{uris} (duplicated/\code{NA}/empty URIs are dropped). +} +\description{ +When two different URIs share the same local name (e.g. \code{foaf:Person} +and \code{schema:Person}), the namespace is appended to disambiguate. In the +rare case that is still not enough to make names unique, the full URI is +appended instead. +} +\examples{ +make_display_list(c( + "http://xmlns.com/foaf/0.1/Person", + "http://schema.org/Person" +)) +} diff --git a/man/namespace_of.Rd b/man/namespace_of.Rd new file mode 100644 index 0000000..7ee060e --- /dev/null +++ b/man/namespace_of.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{namespace_of} +\alias{namespace_of} +\title{Namespace portion of a URI} +\usage{ +namespace_of(uri) +} +\arguments{ +\item{uri}{Character vector of URIs.} +} +\value{ +A character vector the same length as \code{uri}. +} +\description{ +Extracts the "namespace" portion of a URI, keeping whichever delimiter +(\code{/} or \verb{#}) was actually used, so it can be compared directly against +known vocabulary namespace strings. +} +\examples{ +namespace_of("http://xmlns.com/foaf/0.1/Person") +namespace_of("http://www.w3.org/2002/07/owl#Class") +} diff --git a/man/normalize_scheme.Rd b/man/normalize_scheme.Rd new file mode 100644 index 0000000..23fe6d4 --- /dev/null +++ b/man/normalize_scheme.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{normalize_scheme} +\alias{normalize_scheme} +\title{Normalise a URI's scheme} +\usage{ +normalize_scheme(x) +} +\arguments{ +\item{x}{Character vector of URIs.} +} +\value{ +A character vector the same length as \code{x}. +} +\description{ +Rewrites \verb{https://} to \verb{http://} so namespace comparisons aren't +scheme-sensitive (many vocabularies are inconsistently referenced with +either scheme). +} +\examples{ +normalize_scheme("https://schema.org/") +} diff --git a/man/sparql_endpoint.Rd b/man/sparql_endpoint.Rd deleted file mode 100644 index 10ba0a1..0000000 --- a/man/sparql_endpoint.Rd +++ /dev/null @@ -1,30 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/endpoint_class.R -\name{sparql_endpoint} -\alias{sparql_endpoint} -\title{'SPARQL' endpoint (base class)} -\usage{ -sparql_endpoint( - url = character(0), - name = NA_character_, - headers = list(), - timeout = 60 -) -} -\arguments{ -\item{url}{Character. The SPARQL endpoint URL (the address a query -is sent to).} - -\item{name}{Character. A short label for the endpoint, used for -caching and messages.} - -\item{headers}{A named list of extra HTTP headers sent with every -request (for example a custom \code{User-Agent}).} - -\item{timeout}{Numeric. Request timeout in seconds.} -} -\description{ -The base S7 class every endpoint backend inherits from. You will -not normally call \code{sparql_endpoint()} directly -- use a specific -constructor like \code{new_generic_endpoint()}. -} diff --git a/tests/testthat/test-schema_ont.R b/tests/testthat/test-schema_ont.R new file mode 100644 index 0000000..54e5cfa --- /dev/null +++ b/tests/testthat/test-schema_ont.R @@ -0,0 +1,77 @@ +test_that("make_display_list maps local names to URIs", { + result <- make_display_list(c( + "http://ex.org/Book", + "http://ex.org/Author" + )) + + expect_equal(result$Author, "http://ex.org/Author") + expect_equal(result$Book, "http://ex.org/Book") +}) + +test_that("make_display_list disambiguates colliding local names with the namespace", { + result <- make_display_list(c( + "http://xmlns.com/foaf/0.1/Person", + "http://schema.org/Person" + )) + + expect_length(result, 2) + expect_equal(length(unique(names(result))), 2) + expect_true(all(grepl("Person", names(result)))) +}) + +test_that("make_display_list drops NA and empty values", { + result <- make_display_list(c("http://ex.org/Book", NA, "")) + + expect_length(result, 1) +}) + +test_that("make_display_list returns an empty list for no input", { + expect_equal(make_display_list(character()), list()) +}) + +test_that("build_ont builds both classes and properties lists", { + classes <- data.frame(class = "http://ex.org/Book", stringsAsFactors = FALSE) + properties <- data.frame(property = "http://ex.org/title", stringsAsFactors = FALSE) + + ont <- build_ont(classes, properties) + + expect_equal(ont$classes$Book, "http://ex.org/Book") + expect_equal(ont$properties$title, "http://ex.org/title") +}) + +test_that("build_ont handles NULL inputs", { + ont <- build_ont(NULL, NULL) + + expect_equal(ont$classes, list()) + expect_equal(ont$properties, list()) +}) + +test_that(".make_display_list_impl prefers a supplied label over the local name", { + result <- .make_display_list_impl( + c("http://ex.org/Book", "http://ex.org/Author"), + c("A Book", NA_character_) + ) + + expect_equal(result[["A Book"]], "http://ex.org/Book") + expect_equal(result$Author, "http://ex.org/Author") +}) + +test_that(".make_display_list_impl still disambiguates when labels collide", { + result <- .make_display_list_impl( + c("http://xmlns.com/foaf/0.1/Person", "http://schema.org/Person"), + c("Person", "Person") + ) + + expect_length(result, 2) + expect_equal(length(unique(names(result))), 2) +}) + +test_that(".make_display_list_impl de-duplicates repeated ids, keeping the first label", { + result <- .make_display_list_impl( + c("http://ex.org/Book", "http://ex.org/Book"), + c("First", "Second") + ) + + expect_length(result, 1) + expect_equal(result[["First"]], "http://ex.org/Book") +}) From 2d54fa97dd79945ea8e16a4065c794de2284ad2f Mon Sep 17 00:00:00 2001 From: Ata B Barzegar Date: Mon, 10 Aug 2026 23:41:14 +0300 Subject: [PATCH 2/2] refactor: remove test file for the rmoved file. --- tests/testthat/test-endpoint_class.R | 63 ---------------------------- 1 file changed, 63 deletions(-) delete mode 100644 tests/testthat/test-endpoint_class.R diff --git a/tests/testthat/test-endpoint_class.R b/tests/testthat/test-endpoint_class.R deleted file mode 100644 index 53ce144..0000000 --- a/tests/testthat/test-endpoint_class.R +++ /dev/null @@ -1,63 +0,0 @@ -test_that("sparql_endpoint stores valid custom property values", { - example_url <- "https://example.org/sparql" - example_name <- "example" - example_headers <- list(Accept = "application/json") - example_timeout <- 30 - - ep <- sparql_endpoint( - url = example_url, - name = example_name, - headers = example_headers, - timeout = example_timeout - ) - - expect_equal(ep@url, example_url) - expect_equal(ep@name, example_name) - expect_equal(ep@headers, example_headers) - expect_equal(ep@timeout, example_timeout) -}) - -test_that("sparql_endpoint has the right defaults", { - ep <- sparql_endpoint(url = "https://example.org/sparql") - - expect_true(is.na(ep@name)) - expect_equal(ep@headers, list()) - expect_equal(ep@timeout, 60) -}) - -test_that("sparql_endpoint@url validator rejects bad values", { - expect_error(sparql_endpoint(url = ""), regexp = "non-empty") - expect_error(sparql_endpoint(url = c("a", "b")), regexp = "single") -}) - -test_that("sparql_endpoint@name validator rejects non-scalar values", { - expect_error( - sparql_endpoint(url = "https://example.org", name = c("a", "b")), - regexp = "single" - ) -}) - -test_that("sparql_endpoint@headers validator rejects unnamed lists", { - expect_error( - sparql_endpoint(url = "https://example.org", headers = list("application/json")), - regexp = "named" - ) - expect_error( - sparql_endpoint(url = "https://example.org", headers = list(Accept = "a", "b")), - regexp = "named" - ) -}) - -test_that("sparql_endpoint@timeout validator rejects non-positive or NA values", { - expect_error(sparql_endpoint(url = "https://example.org", timeout = 0), regexp = "positive") - expect_error(sparql_endpoint(url = "https://example.org", timeout = -5), regexp = "positive") - expect_error(sparql_endpoint(url = "https://example.org", timeout = NA_real_), regexp = "positive") - expect_error(sparql_endpoint(url = "https://example.org", timeout = c(1, 2)), regexp = "positive") -}) - -test_that("generic_endpoint inherits from sparql_endpoint", { - ep <- generic_endpoint(url = "https://example.org/sparql") - - expect_true(S7::S7_inherits(ep, generic_endpoint)) - expect_true(S7::S7_inherits(ep, sparql_endpoint)) -})