Skip to content

Commit

Permalink
Tibblify paths (#84)
Browse files Browse the repository at this point in the history
* Better read.

* Implement tibblified paths.

* Toward a real class_paths.

* Add pkgdown info.
  • Loading branch information
jonthegeek authored Mar 26, 2024
1 parent 99886d5 commit 0bb15ec
Show file tree
Hide file tree
Showing 16 changed files with 549 additions and 143 deletions.
16 changes: 11 additions & 5 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
Package: rapid
Title: R 'API' Descriptions
Version: 0.0.0.9000
Version: 0.0.0.9003
Authors@R: c(
person("Jon", "Harmon", , "[email protected]", role = c("aut", "cre"),
comment = c(ORCID = "0000-0003-4781-4346")),
person("The Linux Foundation", role = "cph",
comment = "OpenAPI Specification")
)
Description: Convert an 'API' description ('APID'), such as one that follows
the 'OpenAPI Specification', to an R 'API' description object (a
"rapid"). The rapid object follows the 'OpenAPI Specification' to
Description: Convert an 'API' description ('APID'), such as one that
follows the 'OpenAPI Specification', to an R 'API' description object
(a "rapid"). The rapid object follows the 'OpenAPI Specification' to
make it easy to convert to and from 'API' documents.
License: MIT + file LICENSE
URL: https://jonthegeek.github.io/rapid/,
https://github.com/jonthegeek/rapid
BugReports: https://github.com/jonthegeek/rapid/issues
Depends:
R (>= 3.5.0)
Imports:
cli,
glue,
Expand All @@ -24,12 +26,15 @@ Imports:
S7 (>= 0.1.1),
snakecase,
stbl,
tibble,
tibblify,
xml2,
yaml
Suggests:
testthat (>= 3.0.0)
Remotes:
jonthegeek/stbl
jonthegeek/stbl,
mgirlich/tibblify#191
Config/testthat/edition: 3
Config/testthat/parallel: true
Encoding: UTF-8
Expand All @@ -38,6 +43,7 @@ RoxygenNote: 7.3.1
Collate:
'properties.R'
'security.R'
'paths.R'
'components-security_scheme_details.R'
'components-security_schemes.R'
'components.R'
Expand Down
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export(as_oauth2_implicit_flow)
export(as_oauth2_security_scheme)
export(as_oauth2_token_flow)
export(as_origin)
export(as_paths)
export(as_rapid)
export(as_reference)
export(as_schema)
Expand All @@ -34,6 +35,7 @@ export(class_oauth2_implicit_flow)
export(class_oauth2_security_scheme)
export(class_oauth2_token_flow)
export(class_origin)
export(class_paths)
export(class_rapid)
export(class_reference)
export(class_schema)
Expand All @@ -50,6 +52,7 @@ importFrom(S7,"prop<-")
importFrom(S7,S7_inherits)
importFrom(S7,class_any)
importFrom(S7,class_character)
importFrom(S7,class_data.frame)
importFrom(S7,class_factor)
importFrom(S7,class_list)
importFrom(S7,class_logical)
Expand Down
5 changes: 1 addition & 4 deletions R/components-security_schemes.R
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,7 @@ S7::method(length, class_security_schemes) <- function(x) {
#' )
#' )
#' )
as_security_schemes <- S7::new_generic(
"as_security_schemes",
"x"
)
as_security_schemes <- S7::new_generic("as_security_schemes", "x")

S7::method(
as_security_schemes,
Expand Down
105 changes: 105 additions & 0 deletions R/paths.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#' The available paths and operations for the API
#'
#' Holds the relative paths to the individual endpoints and their operations.
#' The path is appended to the URL from the [class_servers()] object in order to
#' construct the full URL. The paths may be empty.
#'
#' @param ... A data.frame, or arguments to pass to [tibble::tibble()].
#'
#' @return A `paths` S7 object with details about API endpoints.
#' @export
#'
#' @seealso [as_paths()] for coercing objects to `paths`.
#'
#' @examples
#' class_paths()
#' class_paths(
#' tibble::tibble(
#' endpoint = c("/endpoint1", "/endpoint2"),
#' operations = list(
#' tibble::tibble(operation_properties = 1:2),
#' tibble::tibble(operation_properties = 3:5)
#' )
#' )
#' )
class_paths <- S7::new_class(
"paths",
package = "rapid",
parent = class_data.frame,
constructor = function(...) {
if (...length() == 1 && is.data.frame(..1)) {
return(S7::new_object(tibble::as_tibble(..1)))
}
S7::new_object(tibble::tibble(...))
}
)

#' Coerce objects to paths
#'
#' `as_paths()` turns an existing object into a `paths` object. This is in
#' contrast with [class_paths()], which builds a `paths` object from individual
#' properties. In practice, [class_paths()] and `as_paths()` are currently
#' functionally identical. However, in the future, `as_paths()` will coerce
#' other valid objects to the expected shape.
#'
#' @inheritParams rlang::args_dots_empty
#' @inheritParams rlang::args_error_context
#' @param x The object to coerce. Must be empty or be a `data.frame()`.
#'
#' @return A `paths` object as returned by [class_paths()].
#' @export
#'
#' @examples
#' as_paths()
#' as_paths(mtcars)
as_paths <- S7::new_generic("as_paths", "x")

S7::method(as_paths, class_data.frame) <- function(x,
...,
arg = caller_arg(x),
call = caller_env()) {
class_paths(x)
}

S7::method(as_paths, class_any) <- function(x,
...,
arg = caller_arg(x),
call = caller_env()) {
as_api_object(x, class_paths, ..., arg = arg, call = call)
}

.parse_paths <- S7::new_generic(".parse_paths", "paths")

S7::method(.parse_paths, class_data.frame | class_paths) <- function(paths,
...) {
paths
}

S7::method(.parse_paths, class_list) <- function(paths,
openapi,
x,
call = caller_env()) {
if (!is.null(openapi) && openapi >= "3") {
return(.parse_openapi_spec(x, call = call))
}
return(tibble::tibble())
}

.parse_openapi_spec <- function(x, call = caller_env()) { # nocov start
rlang::try_fetch(
{
tibblify::parse_openapi_spec(x)
},
error = function(cnd) {
cli::cli_abort(
"Failed to parse paths from OpenAPI spec.",
class = "rapid_error_bad_tibblify",
call = call
)
}
)
} # nocov end

S7::method(.parse_paths, class_any) <- function(paths, ...) {
return(tibble::tibble())
}
1 change: 1 addition & 0 deletions R/rapid-package.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#' @importFrom rlang check_dots_empty
#' @importFrom S7 class_any
#' @importFrom S7 class_character
#' @importFrom S7 class_data.frame
#' @importFrom S7 class_factor
#' @importFrom S7 class_list
#' @importFrom S7 class_logical
Expand Down
9 changes: 7 additions & 2 deletions R/urls.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@

.url_fetch <- function(x) {
rlang::try_fetch(
jsonlite::read_json(x),
jsonlite::read_json(
x,
simplifyVector = TRUE,
simplifyDataFrame = FALSE,
simplifyMatrix = FALSE
),
error = function(e) {
yaml::read_yaml(url(x))
yaml::read_yaml(url(x)) # nocov
}
)
}
38 changes: 32 additions & 6 deletions R/zz-rapid.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#' @include info.R
#' @include servers.R
#' @include components.R
#' @include paths.R
#' @include security.R
NULL

Expand All @@ -12,10 +13,11 @@ NULL
#' @param info An `info` object defined by [class_info()].
#' @param servers A `servers` object defined by [class_servers()].
#' @param components A `components` object defined by [class_components()].
#' @param paths A `paths` object defined by [class_paths()].
#' @param security A `security` object defined by [class_security()].
#'
#' @return A `rapid` S7 object, with properties `info`, `servers`, `components`,
#' and `security`.
#' `paths`, and `security`.
#' @export
#'
#' @seealso [as_rapid()] for coercing objects to `rapid`.
Expand Down Expand Up @@ -58,19 +60,22 @@ class_rapid <- S7::new_class(
info = class_info,
servers = class_servers,
components = class_components,
paths = class_paths,
security = class_security
),
constructor = function(info = class_info(),
...,
servers = class_servers(),
components = class_components(),
paths = class_paths(),
security = class_security()) {
check_dots_empty()
S7::new_object(
S7::S7_object(),
info = as_info(info),
servers = as_servers(servers),
components = as_components(components),
paths = as_paths(paths),
security = as_security(security)
)
},
Expand All @@ -79,7 +84,7 @@ class_rapid <- S7::new_class(
validate_lengths(
self,
key_name = "info",
optional_any = c("components", "security", "servers")
optional_any = c("components", "paths", "security", "servers")
),
validate_in_specific(
values = self@security@name,
Expand All @@ -103,10 +108,11 @@ S7::method(length, class_rapid) <- function(x) {
#'
#' @inheritParams rlang::args_dots_empty
#' @inheritParams rlang::args_error_context
#' @param x The object to coerce. Must be empty or have names "info" and/or
#' "servers", or names that can be coerced to those names via
#' [snakecase::to_snake_case()]. Extra names are ignored. [url()] objects are
#' read with [jsonlite::fromJSON()] or [yaml::read_yaml()] before conversion.
#' @param x The object to coerce. Must be empty or have names "info", "servers",
#' "components", "paths", and/or "security", or names that can be coerced to
#' those names via [snakecase::to_snake_case()]. Extra names are ignored.
#' [url()] objects are read with [jsonlite::fromJSON()] or [yaml::read_yaml()]
#' before conversion.
#'
#' @return A `rapid` object as returned by [class_rapid()].
#' @export
Expand All @@ -127,6 +133,26 @@ S7::method(as_rapid, S7::new_S3_class("url")) <- function(x,
as_rapid(x, ..., arg = arg, call = call)
}

S7::method(as_rapid, class_list) <- function(x,
...,
arg = caller_arg(x),
call = caller_env()) {
x$paths <- .parse_paths(x$paths, x$openapi, x, call)
rlang::try_fetch(
{
x <- as_api_object(x, class_rapid, ..., arg = arg, call = call)
expand_servers(x)
},
rapid_error_missing_names = function(cnd) {
cli::cli_abort(
"{.arg x} must be comprised of properly formed, supported elements.",
class = "rapid_error_unsupported_elements",
parent = cnd
)
}
)
}

S7::method(as_rapid, class_any) <- function(x,
...,
arg = caller_arg(x),
Expand Down
4 changes: 4 additions & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ reference:
- as_oauth2_token_flow
- class_scopes
- as_scopes
- title: paths class
contents:
- class_paths
- as_paths
- title: security class
contents:
- class_security
Expand Down
27 changes: 27 additions & 0 deletions man/as_paths.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions man/as_rapid.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 0bb15ec

Please sign in to comment.