diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 73bfb21..86c1b42 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -1,50 +1,50 @@ -# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples -# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help -on: - push: - branches: [main, master] - pull_request: - -name: R-CMD-check.yaml - -permissions: read-all - -jobs: - R-CMD-check: - runs-on: ${{ matrix.config.os }} - - name: ${{ matrix.config.os }} (${{ matrix.config.r }}) - - strategy: - fail-fast: false - matrix: - config: - - {os: macos-latest, r: 'release'} - - {os: windows-latest, r: 'release'} - - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - - {os: ubuntu-latest, r: 'release'} - - {os: ubuntu-latest, r: 'oldrel-1'} - - env: - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - R_KEEP_PKG_SOURCE: yes - - steps: - - uses: actions/checkout@v6 - - - uses: r-lib/actions/setup-pandoc@v2 - - - uses: r-lib/actions/setup-r@v2 - with: - r-version: ${{ matrix.config.r }} - http-user-agent: ${{ matrix.config.http-user-agent }} - - - uses: r-lib/actions/setup-r-dependencies@v2 - with: - extra-packages: any::rcmdcheck - needs: check - - - uses: r-lib/actions/check-r-package@v2 - with: - upload-snapshots: true - build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master, develop] + pull_request: + +name: R-CMD-check.yaml + +permissions: read-all + +jobs: + R-CMD-check: + runs-on: ${{ matrix.config.os }} + + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + + strategy: + fail-fast: false + matrix: + config: + - {os: macos-latest, r: 'release'} + - {os: windows-latest, r: 'release'} + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'oldrel-1'} + + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + R_KEEP_PKG_SOURCE: yes + + steps: + - uses: actions/checkout@v6 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.config.r }} + http-user-agent: ${{ matrix.config.http-user-agent }} + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::rcmdcheck + needs: check + + - uses: r-lib/actions/check-r-package@v2 + with: + upload-snapshots: true + build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' diff --git a/DESCRIPTION b/DESCRIPTION index ed8120d..7272859 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -13,12 +13,15 @@ Description: The main goal is to connect to SPARQL endpoints and bering their st License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.2 LazyData: true Imports: + S7, tools, httr2, stats Suggests: testthat (>= 3.0.0) Config/testthat/edition: 3 +Depends: + R (>= 4.3) +Config/roxygen2/version: 8.0.0 diff --git a/R/endpoint_class.R b/R/endpoint_class.R new file mode 100644 index 0000000..f0c8e06 --- /dev/null +++ b/R/endpoint_class.R @@ -0,0 +1,77 @@ +#' '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/zzz.R b/R/zzz.R new file mode 100644 index 0000000..dfe2495 --- /dev/null +++ b/R/zzz.R @@ -0,0 +1,5 @@ +NULL + +.onLoad <- function(libname, pkgname) { + S7::methods_register() +} diff --git a/man/ep_class_links.Rd b/man/ep_class_links.Rd new file mode 100644 index 0000000..645c6e3 --- /dev/null +++ b/man/ep_class_links.Rd @@ -0,0 +1,19 @@ +% 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 new file mode 100644 index 0000000..a39c005 --- /dev/null +++ b/man/ep_classes.Rd @@ -0,0 +1,19 @@ +% 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 new file mode 100644 index 0000000..a1cd2d5 --- /dev/null +++ b/man/ep_properties.Rd @@ -0,0 +1,19 @@ +% 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 new file mode 100644 index 0000000..8a1c04d --- /dev/null +++ b/man/generic_endpoint.Rd @@ -0,0 +1,20 @@ +% 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/sparql_endpoint.Rd b/man/sparql_endpoint.Rd new file mode 100644 index 0000000..10ba0a1 --- /dev/null +++ b/man/sparql_endpoint.Rd @@ -0,0 +1,30 @@ +% 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/man/sparql_query.Rd b/man/sparql_query.Rd index 5297927..affcf9a 100644 --- a/man/sparql_query.Rd +++ b/man/sparql_query.Rd @@ -1,13 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/transport.R \name{sparql_query} \alias{sparql_query} -\title{Send a SPARQL Query} +\title{Send 'SPARQL' query against an endpoint through HTTP request.} \usage{ sparql_query(url, query, timeout = 60) } \arguments{ \item{url}{Character scalar. The SPARQL endpoint URL.} + \item{query}{Character scalar. A SPARQL query.} + \item{timeout}{Numeric scalar. Timeout in seconds.} } -\value{A data frame containing the parsed SPARQL response.} -\description{Send a SPARQL query against an endpoint through an HTTP request.} +\value{ +The parsed response in Json Format. +} +\description{ +Send 'SPARQL' query against an endpoint through HTTP request. +} diff --git a/tests/testthat/test-endpoint_class.R b/tests/testthat/test-endpoint_class.R new file mode 100644 index 0000000..fdc8877 --- /dev/null +++ b/tests/testthat/test-endpoint_class.R @@ -0,0 +1,4 @@ +test_that("sparql_endpoint validator rejects a bad url", { + expect_error(sparql_endpoint(url = "")) + expect_error(sparql_endpoint(url = c("a", "b"))) +})