Skip to content

Commit

Permalink
Add origin info. (#67)
Browse files Browse the repository at this point in the history
* Add origin info.

* Add origin to pkgdown.
  • Loading branch information
jonthegeek committed Oct 6, 2023
1 parent 129a52b commit f3f4ed3
Show file tree
Hide file tree
Showing 16 changed files with 384 additions and 23 deletions.
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Collate:
'components.R'
'info-contact.R'
'info-license.R'
'info-origin.R'
'info.R'
'rapid-package.R'
'security_requirements.R'
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export(as_oauth2_authorization_code_flow)
export(as_oauth2_implicit_flow)
export(as_oauth2_security_scheme)
export(as_oauth2_token_flow)
export(as_origin)
export(as_rapid)
export(as_scopes)
export(as_security_requirements)
Expand All @@ -19,6 +20,7 @@ export(as_security_scheme_details)
export(as_server_variables)
export(as_servers)
export(as_string_replacements)
export(class_origin)
export(component_collection)
export(contact)
export(info)
Expand Down
8 changes: 7 additions & 1 deletion R/as.R
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,20 @@
return(NULL)
}

valid_names <- c(S7::prop_names(target_S7_class()), names(extra_names))
valid_names <- snakecase::to_snake_case(
c(S7::prop_names(target_S7_class()), names(extra_names))
)

if (rlang::is_named2(x)) {
force(x_arg)
x <- rlang::set_names(x, snakecase::to_snake_case)
ignored_names <- names(x)[!names(x) %in% valid_names]
x <- as.list(x)[names(x) %in% valid_names]
if (length(extra_names)) {
extra_names <- rlang::set_names(
snakecase::to_snake_case(extra_names),
snakecase::to_snake_case(names(extra_names))
)
to_rename <- names(x) %in% names(extra_names)
names(x)[to_rename] <- extra_names[names(x)[to_rename]]
}
Expand Down
122 changes: 122 additions & 0 deletions R/info-origin.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#' @include properties.R
NULL

#' Source information for the API description
#'
#' A `class_origin` object provides information about the primary source
#' document(s) used to build an API.
#'
#' @inheritParams rlang::args_dots_empty
#' @param url Character (required). The URL(s) where the document(s) can be
#' found.
#' @param format Character scalar (optional). The format of the document.
#' Presently this will likely always be "openapi".
#' @param version Character scalar (optional). The specification version
#' (relative to the `format`) used in defining the document. Not to be
#' confused with the version of the API description itself. Most often this
#' will be "3.0" (as in "OpenAPI Specification version 3.0"), "3.1", or a
#' patch version of those.
#'
#' @return A `class_origin` S7 object describing where to find the API
#' description, with fields `url`, `format`, and `version`.
#' @export
#'
#' @seealso [as_origin()] for coercing objects to `class_origin`.
#'
#' @examples
#' class_origin(
#' "https://api.open.fec.gov/swagger/",
#' format = "openapi",
#' version = "3.0"
#' )
class_origin <- S7::new_class(
"class_origin",
package = "rapid",
properties = list(
url = character_scalar_property("url"),
format = character_scalar_property("format"),
version = character_scalar_property("version")
),
constructor = function(url = character(),
...,
format = character(),
version = character()) {
check_dots_empty()
if (is.list(url) && length(url) == 1) {
url <- unname(unlist(url))
}

S7::new_object(
S7::S7_object(),
url = url %|0|% character(),
format = format %|0|% character(),
version = version %|0|% character()
)
},
validator = function(self) {
validate_parallel(self, "url", optional = c("format", "version"))
}
)

S7::method(length, class_origin) <- function(x) {
length(x@url)
}

#' Coerce lists and character vectors to class_origin
#'
#' `as_origin()` turns an existing object into a `class_origin`. This is in
#' contrast with [class_origin()], which builds a `class_origin` from individual
#' properties.
#'
#' @inheritParams rlang::args_dots_empty
#' @inheritParams rlang::args_error_context
#' @param x The object to coerce. Must be empty or have names "url", "format",
#' and/or "version", or names that can be coerced to those names via
#' [snakecase::to_snake_case()]. Extra names are ignored. This object should
#' describe a single origin for this API description.
#'
#' @return A `class_origin` as returned by [class_origin()].
#' @export
#'
#' @examples
#' as_origin()
#' as_origin(
#' list(
#' list(
#' format = "openapi",
#' url = "https://api.open.fec.gov/swagger/",
#' version = "3.0"
#' )
#' )
#' )
as_origin <- S7::new_generic("as_origin", dispatch_args = "x")

S7::method(as_origin, class_origin) <- function(x) {
x
}

S7::method(as_origin, class_list | class_character) <- function(x) {
# Case 1: Passed in as a simple character vector, or that wrapped in a list.
if (length(x) == 1 && lengths(x) == 1 && is.character(unlist(x))) {
x <- list(url = unname(unlist(x)))
}
# Case 2: apis.guru provides a list of lists, but we currently only support
# the case where that list has 1 entry.
if (length(x) == 1 && lengths(x) > 1) {
x <- x[[1]]
}

.as_class(x, class_origin)
}

S7::method(as_origin, class_missing | NULL) <- function(x) {
class_origin()
}

S7::method(as_origin, class_any) <- function(x,
...,
arg = rlang::caller_arg(x)) {
cli::cli_abort(
"Can't coerce {.arg {arg}} {.cls {class(x)}} to {.cls class_origin}."
)
}
29 changes: 22 additions & 7 deletions R/info.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#' @include info-contact.R
#' @include info-license.R
#' @include info-origin.R
#' @include properties.R
NULL

Expand All @@ -25,6 +26,10 @@ NULL
#' @param summary Character scalar (optional). A short summary of the API.
#' @param terms_of_service Character scalar (optional). A URL to the Terms of
#' Service for the API.
#' @param origin The url and related information about the document used to
#' build the API description. This is used to resolve relative paths in the
#' API description. Note: This is not part of the OpenAPI Specification, but
#' is sometimes supplied as "x-origin".
#'
#' @return An `info` S7 object with metadata describing a single API.
#' @export
Expand All @@ -41,6 +46,11 @@ NULL
#' url = "https://opensource.org/license/apache-2-0/"
#' )
#' )
#' info(
#' title = "My Abbreviated API",
#' version = "2.0.0",
#' origin = "https://root.url"
#' )
info <- S7::new_class(
"info",
package = "rapid",
Expand All @@ -51,7 +61,8 @@ info <- S7::new_class(
description = character_scalar_property("description"),
license = license,
summary = character_scalar_property("summary"),
terms_of_service = character_scalar_property("terms_of_service")
terms_of_service = character_scalar_property("terms_of_service"),
origin = class_origin
),
constructor = function(title = character(),
version = character(),
Expand All @@ -60,7 +71,8 @@ info <- S7::new_class(
description = character(),
license = class_missing,
summary = character(),
terms_of_service = character()) {
terms_of_service = character(),
origin = class_origin()) {
check_dots_empty()
S7::new_object(
S7::S7_object(),
Expand All @@ -70,7 +82,8 @@ info <- S7::new_class(
description = description %||% character(),
license = as_license(license),
summary = summary %||% character(),
terms_of_service = terms_of_service %||% character()
terms_of_service = terms_of_service %||% character(),
origin = as_origin(origin)
)
},
validator = function(self) {
Expand All @@ -83,7 +96,8 @@ info <- S7::new_class(
"description",
"license",
"summary",
"terms_of_service"
"terms_of_service",
"origin"
)
)
}
Expand All @@ -101,8 +115,9 @@ S7::method(length, info) <- function(x) {
#' @inheritParams rlang::args_dots_empty
#' @inheritParams rlang::args_error_context
#' @param x The object to coerce. Must be empty or have names "title",
#' "version", "contact", "description", "license", "summary", and/or
#' "terms_of_service", or names that can be coerced to those names via
#' "version", "contact", "description", "license", "summary",
#' "terms_of_service", and/or "origin" (or "x-origin", which will be coerced
#' to "origin"), or names that can be coerced to those names via
#' [snakecase::to_snake_case()]. Extra names are ignored. This object should
#' describe a single API.
#'
Expand All @@ -119,7 +134,7 @@ S7::method(as_info, info) <- function(x) {
}

S7::method(as_info, class_list | class_character) <- function(x) {
.as_class(x, info)
.as_class(x, info, extra_names = c("x-origin" = "origin"))
}

S7::method(
Expand Down
5 changes: 5 additions & 0 deletions R/zz-rapid.R
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,12 @@ S7::method(as_rapid, class_list) <- function(x) {
}

S7::method(as_rapid, S7::new_S3_class("url")) <- function(x) {
url <- summary(x)$description
x <- yaml::read_yaml(x)
if (!length(x$info$`x-origin`)) {
x$info$`x-origin` <- list(url = url)
}

as_rapid(x)
}

Expand Down
2 changes: 2 additions & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ reference:
- as_contact
- license
- as_license
- class_origin
- as_origin
- title: servers class
contents:
- servers
Expand Down
5 changes: 3 additions & 2 deletions man/as_info.Rd

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

36 changes: 36 additions & 0 deletions man/as_origin.Rd

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

46 changes: 46 additions & 0 deletions man/class_origin.Rd

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

Loading

0 comments on commit f3f4ed3

Please sign in to comment.