Document KorAPConnection() as a function to fix CRAN Rd NOTE

CRAN's r-devel-linux-x86_64-debian-gcc and -clang checks report

  Rd files without \usage:
    'KorAPConnection-class.Rd'
  \arguments should not be documented without \usage.

because the user facing documentation, with one @param per constructor
argument, was attached to the setClass() call. The object exported under
the name KorAPConnection was the class generator, whose formals are just
`function(...)`, so roxygen2 could not derive a \usage section from it
and the documented arguments ended up in an Rd file without one. Adding
a hand written \usage is not an option either: it contradicts the
generator's `...` signature and turns the NOTE into a much worse
"code/documentation mismatches" WARNING.

Replace the generator with a real KorAPConnection() function that
forwards its arguments to new("KorAPConnection", ...) and attach the
documentation to it. roxygen2 now derives \usage from the actual
formals, so the manual page states how the constructor is really called,
and the NOTE is gone.

The constructor forwards supplied arguments only, so that the defaults
and the missing() based KORAP_VERBOSE / rkorap.verbose overrides of the
initialize method keep working unchanged. Its own defaults merely mirror
those of the initialize method in order to document them; the new tests
asserts that the documented defaults are the ones a connection actually
ends up with, so that the two cannot drift apart silently. The KORAP_URL
default is now shared by both signatures as defaultKorAPUrl().

NAMESPACE is unchanged: KorAPConnection remains both an exported
function and an exported S4 class, and new("KorAPConnection", ...) keeps
working.

R CMD check --as-cran: Status OK, and no Rd file in man/ has \arguments
without \usage any more.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Change-Id: I789d42c01e1be98a989f8f4d9a5b14865ca48daa
diff --git a/R/KorAPConnection.R b/R/KorAPConnection.R
index f7b6af8..967feaa 100644
--- a/R/KorAPConnection.R
+++ b/R/KorAPConnection.R
@@ -5,6 +5,28 @@
 setClassUnion("listOrNULL", c("list", "NULL"))
 # setOldClass("httr2_oauth_client")
 
+#' @rdname KorAPConnection
+#' @export
+setClass("KorAPConnection", slots = c(KorAPUrl = "character", apiVersion = "character", indexRevision = "characterOrNULL", apiUrl = "character", accessToken = "characterOrNULL", oauthClient = "ANY", oauthScope = "characterOrNULL", authorizationSupported = "logical", userAgent = "character", timeout = "numeric", verbose = "logical", cache = "logical", welcome = "listOrNULL"))
+
+generic_kor_app_id <- "99FbPHH7RrN36hbndF7b6f"
+
+kustvakt_redirect_uri <- "http://localhost:1410/"
+kustvakt_auth_path <- "settings/oauth/authorize"
+
+#' Default KorAP server URL
+#'
+#' Returns the KorAP instance URL to connect to if none is given explicitly:
+#' the environment variable `KORAP_URL` if it is set and non-empty, and the
+#' IDS Mannheim KorAP main instance otherwise.
+#'
+#' @return URL of the KorAP instance to connect to by default
+#' @keywords internal
+defaultKorAPUrl <- function() {
+  url <- Sys.getenv("KORAP_URL", unset = "")
+  if (nzchar(url)) url else "https://korap.ids-mannheim.de/"
+}
+
 #' Connect to KorAP Server
 #'
 #' `KorAPConnection()` creates a connection to a KorAP server for corpus queries.
@@ -101,27 +123,32 @@
 #' @import utils
 #' @import methods
 #' @include logging.R
-
 #' @export
-KorAPConnection <- setClass("KorAPConnection", slots = c(KorAPUrl = "character", apiVersion = "character", indexRevision = "characterOrNULL", apiUrl = "character", accessToken = "characterOrNULL", oauthClient = "ANY", oauthScope = "characterOrNULL", authorizationSupported = "logical", userAgent = "character", timeout = "numeric", verbose = "logical", cache = "logical", welcome = "listOrNULL"))
-
-generic_kor_app_id <- "99FbPHH7RrN36hbndF7b6f"
-
-kustvakt_redirect_uri <- "http://localhost:1410/"
-kustvakt_auth_path <- "settings/oauth/authorize"
-
+KorAPConnection <- function(KorAPUrl = defaultKorAPUrl(),
+                            apiVersion = "v1.0",
+                            apiUrl,
+                            accessToken = getAccessToken(KorAPUrl),
+                            oauthClient = NULL,
+                            oauthScope = "search match_info",
+                            authorizationSupported = TRUE,
+                            userAgent = "R-KorAP-Client",
+                            timeout = 240,
+                            verbose = FALSE,
+                            cache = TRUE) {
+  # Forward only the arguments that were actually supplied, so that the
+  # defaults and the `missing()` based overrides of the initialize method
+  # (see below) keep working. The defaults above merely mirror those of the
+  # initialize method to document them (see test-korapconnection-signature.R).
+  args <- as.list(match.call())[-1L]
+  do.call(methods::new, c("KorAPConnection", args), envir = parent.frame())
+}
 
 #' Initialize KorAPConnection object
 #' @keywords internal
 #' @export
 #'
 setMethod("initialize", "KorAPConnection", function(.Object,
-                                                    KorAPUrl = if (is.null(Sys.getenv("KORAP_URL")) |
-                                                      Sys.getenv("KORAP_URL") == "") {
-                                                      "https://korap.ids-mannheim.de/"
-                                                    } else {
-                                                      Sys.getenv("KORAP_URL")
-                                                    },
+                                                    KorAPUrl = defaultKorAPUrl(),
                                                     apiVersion = "v1.0",
                                                     apiUrl,
                                                     accessToken = getAccessToken(KorAPUrl),
diff --git a/man/KorAPConnection-class.Rd b/man/KorAPConnection.Rd
similarity index 93%
rename from man/KorAPConnection-class.Rd
rename to man/KorAPConnection.Rd
index fd306a4..018573f 100644
--- a/man/KorAPConnection-class.Rd
+++ b/man/KorAPConnection.Rd
@@ -5,6 +5,21 @@
 \alias{KorAPConnection-class}
 \alias{KorAPConnection}
 \title{Connect to KorAP Server}
+\usage{
+KorAPConnection(
+  KorAPUrl = defaultKorAPUrl(),
+  apiVersion = "v1.0",
+  apiUrl,
+  accessToken = getAccessToken(KorAPUrl),
+  oauthClient = NULL,
+  oauthScope = "search match_info",
+  authorizationSupported = TRUE,
+  userAgent = "R-KorAP-Client",
+  timeout = 240,
+  verbose = FALSE,
+  cache = TRUE
+)
+}
 \arguments{
 \item{KorAPUrl}{URL of the web user interface of the KorAP server instance you want to access.
 Defaults to the environment variable \code{KORAP_URL} if set and to the IDS Mannheim KorAP main instance
diff --git a/man/defaultKorAPUrl.Rd b/man/defaultKorAPUrl.Rd
new file mode 100644
index 0000000..5da4915
--- /dev/null
+++ b/man/defaultKorAPUrl.Rd
@@ -0,0 +1,17 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/KorAPConnection.R
+\name{defaultKorAPUrl}
+\alias{defaultKorAPUrl}
+\title{Default KorAP server URL}
+\usage{
+defaultKorAPUrl()
+}
+\value{
+URL of the KorAP instance to connect to by default
+}
+\description{
+Returns the KorAP instance URL to connect to if none is given explicitly:
+the environment variable \code{KORAP_URL} if it is set and non-empty, and the
+IDS Mannheim KorAP main instance otherwise.
+}
+\keyword{internal}
diff --git a/man/initialize-KorAPConnection-method.Rd b/man/initialize-KorAPConnection-method.Rd
index 35a92a0..8399c3f 100644
--- a/man/initialize-KorAPConnection-method.Rd
+++ b/man/initialize-KorAPConnection-method.Rd
@@ -6,12 +6,7 @@
 \usage{
 \S4method{initialize}{KorAPConnection}(
   .Object,
-  KorAPUrl = if (is.null(Sys.getenv("KORAP_URL")) | Sys.getenv("KORAP_URL") == "") {
-    
-    "https://korap.ids-mannheim.de/"
- } else {
-     Sys.getenv("KORAP_URL")
- },
+  KorAPUrl = defaultKorAPUrl(),
   apiVersion = "v1.0",
   apiUrl,
   accessToken = getAccessToken(KorAPUrl),
diff --git a/tests/testthat/test-korapconnection-signature.R b/tests/testthat/test-korapconnection-signature.R
new file mode 100644
index 0000000..e8356fe
--- /dev/null
+++ b/tests/testthat/test-korapconnection-signature.R
@@ -0,0 +1,96 @@
+withOfflineKorAPEnv <- function(code, verboseEnv = NA_character_) {
+  oldUrl <- Sys.getenv("KORAP_URL", unset = NA_character_)
+  oldVerbose <- Sys.getenv("KORAP_VERBOSE", unset = NA_character_)
+  on.exit(
+    {
+      if (is.na(oldUrl)) Sys.unsetenv("KORAP_URL") else Sys.setenv(KORAP_URL = oldUrl)
+      if (is.na(oldVerbose)) {
+        Sys.unsetenv("KORAP_VERBOSE")
+      } else {
+        Sys.setenv(KORAP_VERBOSE = oldVerbose)
+      }
+    },
+    add = TRUE
+  )
+  Sys.setenv(KORAP_URL = "https://example.invalid/")
+  if (is.na(verboseEnv)) Sys.unsetenv("KORAP_VERBOSE") else Sys.setenv(KORAP_VERBOSE = verboseEnv)
+  force(code)
+}
+
+test_that("the documented KorAPConnection() defaults are the ones actually used", {
+  # KorAPConnection() only forwards the arguments that were supplied, so its
+  # own defaults are never evaluated. They mirror those of the initialize
+  # method to document them, and this test makes sure that the two cannot
+  # drift apart silently.
+  withOfflineKorAPEnv({
+    kco <- KorAPConnection(accessToken = NULL)
+    documented <- formals(KorAPConnection)
+
+    # accessToken (keyring), apiUrl (derived) and authorizationSupported
+    # (answered by the server) are covered by the tests below instead
+    for (parameter in c(
+      "KorAPUrl", "apiVersion", "oauthClient", "oauthScope",
+      "userAgent", "timeout", "verbose", "cache"
+    )) {
+      expect_equal(
+        slot(kco, parameter),
+        eval(documented[[parameter]]),
+        info = parameter
+      )
+    }
+  })
+})
+
+test_that("KorAPConnection() forwards only the arguments that were supplied", {
+  # `missing()` based overrides in the initialize method must keep working
+  withOfflineKorAPEnv(
+    {
+      expect_true(KorAPConnection(accessToken = NULL)@verbose)
+      # an explicitly passed verbose argument still wins over the env var
+      expect_false(KorAPConnection(accessToken = NULL, verbose = FALSE)@verbose)
+    },
+    verboseEnv = "true"
+  )
+})
+
+test_that("KorAPConnection() derives apiUrl unless it is given explicitly", {
+  kco <- KorAPConnection(KorAPUrl = "https://example.invalid/", accessToken = NULL)
+  expect_equal(kco@apiUrl, "https://example.invalid/api/v1.0/")
+
+  kco <- KorAPConnection(
+    KorAPUrl = "https://example.invalid/",
+    apiUrl = "https://other.invalid/api/",
+    accessToken = NULL
+  )
+  expect_equal(kco@apiUrl, "https://other.invalid/api/")
+})
+
+test_that("KorAPConnection objects can still be created with new()", {
+  kco <- methods::new(
+    "KorAPConnection",
+    KorAPUrl = "https://example.invalid/",
+    apiUrl = "https://example.invalid/api/v1.0/",
+    accessToken = NULL,
+    verbose = FALSE
+  )
+  expect_s4_class(kco, "KorAPConnection")
+  expect_equal(kco@apiUrl, "https://example.invalid/api/v1.0/")
+})
+
+test_that("defaultKorAPUrl() honours KORAP_URL", {
+  old <- Sys.getenv("KORAP_URL", unset = NA_character_)
+  on.exit(
+    if (is.na(old)) Sys.unsetenv("KORAP_URL") else Sys.setenv(KORAP_URL = old),
+    add = TRUE
+  )
+
+  Sys.setenv(KORAP_URL = "https://example.invalid/")
+  expect_equal(defaultKorAPUrl(), "https://example.invalid/")
+
+  # an empty KORAP_URL counts as unset
+  Sys.setenv(KORAP_URL = "")
+  expect_equal(defaultKorAPUrl(), "https://korap.ids-mannheim.de/")
+
+  Sys.unsetenv("KORAP_URL")
+  expect_equal(defaultKorAPUrl(), "https://korap.ids-mannheim.de/")
+})