Skip to content

Commit be9f984

Browse files
committed
Merge branch 'main' into rc-v1.3.0
* main: bug: Validate APIs using `@redocly/cli` (#986)
2 parents 8c3cbd8 + bd555c4 commit be9f984

File tree

9 files changed

+58
-79
lines changed

9 files changed

+58
-79
lines changed

NAMESPACE

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export(validate_api_spec)
110110
import(R6)
111111
import(promises)
112112
import(stringi)
113+
importFrom(crayon,silver)
113114
importFrom(grDevices,dev.cur)
114115
importFrom(grDevices,dev.set)
115116
importFrom(jsonlite,parse_json)

NEWS.md

+6
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,18 @@
1414

1515
* Mounts now have a dynamic `req$PATH_INFO` instead of a pre-computed value. (#888)
1616

17+
* `validate_api_spec()` now uses `@redocly/cli` to validate the API spec. (#986)
18+
19+
* Added `operationId` to each operation within the auto-generated OpenAPI output. The value is similar to the `PATH-VERB`, e.g. `/users/create-POST`. (#986)
20+
21+
1722
# plumber 1.2.2
1823

1924
* Allow to set plumber options using environment variables `?options_plumber`. (@meztez #934)
2025
* Add support for quoted boundary for multipart request parsing. (@meztez #924)
2126
* Fix #916, related to `parseUTF8` return value attribute `srcfile` on Windows. (#930)
2227

28+
2329
# plumber 1.2.1
2430

2531
* Update docs for CRAN (#878)

R/openapi-spec.R

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ endpointSpecification <- function(routerEndpointEntry, path = routerEndpointEntr
2626
resps <- responsesSpecification(routerEndpointEntry)
2727

2828
endptSpec <- list(
29+
operationId = paste0(cleanedPath, "-", verb),
2930
summary = routerEndpointEntry$comments,
3031
description = routerEndpointEntry$description,
3132
responses = resps,

R/utils.R

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#' @importFrom crayon silver
2+
NULL
3+
14
#' HTTP Date String
25
#'
36
#' Given a POSIXct object, return a date string in the format required for a

R/validate_api_spec.R

+31-66
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,34 @@
1-
2-
#' @include globals.R
3-
validate_api_spec_folder <- function() {
4-
file.path(tempdir(), "plumber_validate_api_spec")
5-
}
6-
7-
8-
validate_api_spec__install_node_modules <- function() {
9-
10-
if (!nzchar(Sys.which("node"))) {
11-
stop("node not installed")
12-
}
13-
if (!nzchar(Sys.which("npm"))) {
14-
stop("npm not installed")
15-
}
16-
17-
if (dir.exists(validate_api_spec_folder())) {
18-
# assume npm install has completed
19-
return(invisible(TRUE))
20-
}
21-
22-
dir.create(validate_api_spec_folder(), recursive = TRUE, showWarnings = FALSE)
23-
24-
file.copy(
25-
system.file(file.path("validate_api_spec", "package.json"), package = "plumber"),
26-
file.path(validate_api_spec_folder(), "package.json")
27-
)
28-
29-
old_wd <- setwd(validate_api_spec_folder())
30-
on.exit({
31-
setwd(old_wd)
32-
}, add = TRUE)
33-
34-
# install everything. Ignore regular output
35-
status <- system2("npm", c("install", "--loglevel", "warn"), stdout = FALSE)
36-
if (status != 0) {
37-
# delete the folder if it didn't work
38-
unlink(validate_api_spec_folder(), recursive = TRUE)
39-
stop("Could not install npm dependencies required for plumber::validate_api_spec()")
40-
}
41-
42-
invisible(TRUE)
43-
}
44-
45-
461
#' Validate OpenAPI Spec
472
#'
48-
#' Validate an OpenAPI Spec using [Swagger CLI](https://github.com/APIDevTools/swagger-cli) which calls [Swagger Parser](https://github.com/APIDevTools/swagger-parser).
3+
#' Validate an OpenAPI Spec using [`@redocly/cli`](https://redocly.com/docs/cli/commands/lint).
494
#'
50-
#' If the api is deemed invalid, an error will be thrown.
5+
#' If any warning or error is presented, an error will be thrown.
516
#'
52-
#' This function is VERY `r lifecycle::badge("experimental")` and may be altered, changed, or removed in the future.
7+
#' This function is `r lifecycle::badge("experimental")` and may be altered, changed, or removed in the future.
538
#'
549
#' @param pr A Plumber API
10+
#' @param ... Ignored
11+
#' @param ruleset Character that determines the ruleset to use for validation. Can be one of "minimal", "recommended",
12+
#' or "recommended-strict". Defaults to "minimal". See [`@redocly/cli`
13+
#' options](https://redocly.com/docs/cli/commands/lint#options) for more details.
5514
#' @param verbose Logical that determines if a "is valid" statement is displayed. Defaults to `TRUE`
5615
#' @export
5716
#' @examples
5817
#' \dontrun{
5918
#' pr <- plumb_api("plumber", "01-append")
6019
#' validate_api_spec(pr)
6120
#' }
62-
validate_api_spec <- function(pr, verbose = TRUE) {
63-
64-
validate_api_spec__install_node_modules()
21+
validate_api_spec <- function(pr, ..., ruleset = c("minimal", "recommended", "recommended-strict"), verbose = TRUE) {
6522

23+
ruleset <- match.arg(ruleset, several.ok = FALSE)
24+
if (!nzchar(Sys.which("node"))) {
25+
stop("`node` command not found. Please install Node.js")
26+
}
27+
if (!nzchar(Sys.which("npx"))) {
28+
stop("`npx` not installed. Please install Node.js w/ `npx` command available.")
29+
}
30+
6631
spec <- jsonlite::toJSON(pr$getApiSpec(), auto_unbox = TRUE, pretty = TRUE)
67-
old_wd <- setwd(validate_api_spec_folder())
68-
on.exit({
69-
setwd(old_wd)
70-
}, add = TRUE)
7132

7233
tmpfile <- tempfile(fileext = ".json")
7334
on.exit({
@@ -76,27 +37,31 @@ validate_api_spec <- function(pr, verbose = TRUE) {
7637
cat(spec, file = tmpfile)
7738

7839
output <- system2(
79-
"node_modules/.bin/swagger-cli",
40+
"npx",
8041
c(
81-
"validate",
42+
"--yes", # auto install `@redocly/cli`
43+
"-p", "@redocly/cli",
44+
"redocly",
45+
"lint",
46+
"--extends", ruleset,
47+
"--skip-rule", "no-empty-servers", # We don't know the end servers by default
48+
"--skip-rule", "security-defined", # We don't know the security by default
49+
"--skip-rule", "operation-summary", # operation summary values are optional. Not required
50+
"--skip-rule", "operation-operationId-url-safe", # By default, it wants to have all operationId values be URL safe. This does not work with path parameters that want to use `{``}`.
51+
"--skip-rule", "no-path-trailing-slash", # This is OK
8252
tmpfile
8353
),
8454
stdout = TRUE,
8555
stderr = TRUE
8656
)
87-
57+
8858
output <- paste0(output, collapse = "\n")
8959

90-
# using expect_equal vs a regex test to have a better error message
91-
is_equal <- sub(tmpfile, "", output, fixed = TRUE) == " is valid"
92-
if (!isTRUE(is_equal)) {
93-
cat("Plumber Spec: \n", as.character(spec), "\nOutput:\n", output)
60+
has_warn_or_error <- grepl("\n[1] ", output, fixed = TRUE)
61+
if (has_warn_or_error) {
62+
cat("Plumber Spec: \n", as.character(spec), "\n\nOutput:\n", output, sep = "")
9463
stop("Plumber OpenAPI Spec is not valid")
9564
}
9665

97-
if (isTRUE(verbose)) {
98-
cat(crayon::green("\u2714"), crayon::silver(": Plumber OpenAPI Spec is valid"), "\n", sep = "")
99-
}
100-
10166
invisible(TRUE)
10267
}

inst/WORDLIST

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ API's
22
Api
33
BMP
44
BUGFIX
5-
CLI
65
CORS
76
CentOS
87
Crypto
@@ -125,6 +124,7 @@ repo
125124
req
126125
retransmitted
127126
rstudio
127+
ruleset
128128
runnable
129129
scalability
130130
scalable

inst/validate_api_spec/package.json

-7
This file was deleted.

man/validate_api_spec.Rd

+14-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vignettes/annotations.Rmd

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Annotation | Argument | Description/References
7272
`@get`, `@post`, `@put`, `@use`, `@delete`, `@head`, `@options`, `@patch` | `Path` | [Endpoints](./routing-and-input.html#endpoints), [Dynamic Routes](./routing-and-input.html#dynamic-routes), [Typed Dynamic Routes](./routing-and-input.html#typed-dynamic-routes)
7373
`@serializer` | `Alias` [`Args list]`] | Some serializers accept arguments. See [serializers article](./rendering-output.html#serializers) and [serializers reference](https://www.rplumber.io/reference/serializers.html). Aliases : `r paste0("<code>", registered_serializers(), "</code>", collapse = ", ")` from [`registered_serializers()`](https://www.rplumber.io/reference/register_serializer.html).
7474
`@parser` | `Alias` `[Args list]` | Some parsers accept arguments. See [parsers reference](https://www.rplumber.io/reference/parsers.html). Can be repeated to allow multiple parsers on the same endpoint. Aliases : `r paste0("<code>", registered_parsers(), "</code>", collapse = ", ")` from [`registered_parsers()`](https://www.rplumber.io/reference/register_parser.html).
75-
`@param` | `Name`[`:Type`(`*`) `Description`] | Enclose `Type` between square brackets `[]` to indicate it is an array. Adding an asterix indicates that the parameter is required. Can be repeated to define different parameters.
75+
`@param` | `Name`[`:Type`(`*`) `Description`] | Enclose `Type` between square brackets `[]` to indicate it is an array. Adding an asterisk indicates that the parameter is required. Can be repeated to define different parameters.
7676
`@response` | `Name` `Description` | Simple [Response object](http://spec.openapis.org/oas/v3.0.3#response-object). Can be repeated to define different responses.
7777
`@tag` | `Tag` | Can be repeated to add multiple tags. Quote with " or ' to use non word character (like spaces) in `Tag`. [Tag field](http://spec.openapis.org/oas/v3.0.3#operation-object)
7878
`@preempt` | `Filter` | Specify that this endpoint has to execute before `Filter`. [Filters](./programmatic-usage.html#defining-filters)

0 commit comments

Comments
 (0)