diff -Nru r-cran-crul-0.8.4+dfsg/debian/changelog r-cran-crul-0.9.0+dfsg/debian/changelog --- r-cran-crul-0.8.4+dfsg/debian/changelog 2019-08-23 08:03:05.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/debian/changelog 2019-11-12 09:38:06.000000000 +0000 @@ -1,3 +1,11 @@ +r-cran-crul (0.9.0+dfsg-1) unstable; urgency=medium + + * New upstream version + * Standards-Version: 4.4.1 + * autopkgtest: s/ADTTMP/AUTOPKGTEST_TMP/g + + -- Andreas Tille Tue, 12 Nov 2019 10:38:06 +0100 + r-cran-crul (0.8.4+dfsg-1) unstable; urgency=medium * New upstream version diff -Nru r-cran-crul-0.8.4+dfsg/debian/control r-cran-crul-0.9.0+dfsg/debian/control --- r-cran-crul-0.8.4+dfsg/debian/control 2019-08-23 08:03:05.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/debian/control 2019-11-12 09:38:06.000000000 +0000 @@ -12,7 +12,7 @@ r-cran-httpcode, r-cran-jsonlite, r-cran-mime -Standards-Version: 4.4.0 +Standards-Version: 4.4.1 Vcs-Browser: https://salsa.debian.org/r-pkg-team/r-cran-crul Vcs-Git: https://salsa.debian.org/r-pkg-team/r-cran-crul.git Homepage: https://cran.r-project.org/package=crul diff -Nru r-cran-crul-0.8.4+dfsg/debian/tests/run-unit-test r-cran-crul-0.9.0+dfsg/debian/tests/run-unit-test --- r-cran-crul-0.8.4+dfsg/debian/tests/run-unit-test 2019-08-23 08:03:05.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/debian/tests/run-unit-test 2019-11-12 09:38:06.000000000 +0000 @@ -3,12 +3,12 @@ pkgname=crul debname=r-cran-crul -if [ "$ADTTMP" = "" ] ; then - ADTTMP=`mktemp -d /tmp/${debname}-test.XXXXXX` - trap "rm -rf $ADTTMP" 0 INT QUIT ABRT PIPE TERM +if [ "$AUTOPKGTEST_TMP" = "" ] ; then + AUTOPKGTEST_TMP=`mktemp -d /tmp/${debname}-test.XXXXXX` + trap "rm -rf $AUTOPKGTEST_TMP" 0 INT QUIT ABRT PIPE TERM fi -cd $ADTTMP -cp -a /usr/share/doc/$debname/tests/* $ADTTMP +cd $AUTOPKGTEST_TMP +cp -a /usr/share/doc/$debname/tests/* $AUTOPKGTEST_TMP gunzip -r * for testfile in *.R; do echo "BEGIN TEST $testfile" diff -Nru r-cran-crul-0.8.4+dfsg/DESCRIPTION r-cran-crul-0.9.0+dfsg/DESCRIPTION --- r-cran-crul-0.8.4+dfsg/DESCRIPTION 2019-08-02 20:30:03.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/DESCRIPTION 2019-11-06 21:50:02.000000000 +0000 @@ -6,14 +6,15 @@ The package name is a play on curl, the widely used command line tool for HTTP, and this package is built on top of the R package 'curl', an interface to 'libcurl' (). -Version: 0.8.4 +Version: 0.9.0 License: MIT + file LICENSE Authors@R: c( person("Scott", "Chamberlain", role = c("aut", "cre"), email = "myrmecocystus@gmail.com", comment = c(ORCID = "0000-0003-1444-9135")) ) -URL: https://github.com/ropensci/crul (devel) +URL: https://docs.ropensci.org/crul (website) + https://github.com/ropensci/crul (devel) https://ropenscilabs.github.io/http-testing-book/ (user manual) BugReports: https://github.com/ropensci/crul/issues Encoding: UTF-8 @@ -28,8 +29,8 @@ libcurl, async, mocking, caching X-schema.org-isPartOf: https://ropensci.org NeedsCompilation: no -Packaged: 2019-08-02 19:58:21 UTC; sckott +Packaged: 2019-11-06 21:26:54 UTC; sckott Author: Scott Chamberlain [aut, cre] () Maintainer: Scott Chamberlain Repository: CRAN -Date/Publication: 2019-08-02 20:30:02 UTC +Date/Publication: 2019-11-06 21:50:02 UTC diff -Nru r-cran-crul-0.8.4+dfsg/inst/doc/best-practices-api-packages.R r-cran-crul-0.9.0+dfsg/inst/doc/best-practices-api-packages.R --- r-cran-crul-0.8.4+dfsg/inst/doc/best-practices-api-packages.R 2019-08-02 19:58:19.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/inst/doc/best-practices-api-packages.R 2019-11-06 21:26:52.000000000 +0000 @@ -3,6 +3,7 @@ # cli <- crul::HttpClient$new(url, opts = list(...)) # res <- cli$get(path = path, query = args) # res$raise_for_status() +# res$raise_for_ct_json() # res$parse("UTF-8") # } @@ -15,3 +16,43 @@ ## ----eval=FALSE---------------------------------------------------------- # xGET("https://xxx.org", args = list(foo = "bar"), verbose = TRUE) +## ----eval=FALSE---------------------------------------------------------- +# con <- HttpClient$new("https://httpbin.org/status/404") +# res <- con$get() + +## ----eval=FALSE---------------------------------------------------------- +# x <- fauxpas::find_error_class(res$status_code)$new() +# #> +# #> behavior: stop +# #> message_template: {{reason}} (HTTP {{status}}) +# #> message_template_verbose: {{reason}} (HTTP {{status}}).\n - {{message}} + +## ----eval=FALSE---------------------------------------------------------- +# x$do(res) +# #> Error: Not Found (HTTP 404) + +## ----eval=FALSE---------------------------------------------------------- +# x$do(res, template = "{{status}}\n --> {{reason}}") +# #> Error: 404 +# #> --> Not Found + +## ----eval=FALSE---------------------------------------------------------- +# x$do_verbose(res) +# #> Error: Not Found (HTTP 404). +# #> - The server has not found anything matching the Request-URI. No indication +# #> is given of whether the condition is temporary or permanent. The 410 (Gone) +# #> status code SHOULD be used if the server knows, through some internally configurable +# #> mechanism, that an old resource is permanently unavailable and has no forwarding +# #> address. This status code is commonly used when the server does not wish to +# #> reveal exactly why the request has been refused, or when no other response +# #> is applicable. + +## ----eval=FALSE---------------------------------------------------------- +# x$behavior <- "warning" +# x$do(res) +# #> Warning message: +# #> Not Found (HTTP 404) +# x$behavior <- "message" +# x$do(res) +# #> Not Found (HTTP 404) + diff -Nru r-cran-crul-0.8.4+dfsg/inst/doc/best-practices-api-packages.Rmd r-cran-crul-0.9.0+dfsg/inst/doc/best-practices-api-packages.Rmd --- r-cran-crul-0.8.4+dfsg/inst/doc/best-practices-api-packages.Rmd 2019-07-24 22:54:34.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/inst/doc/best-practices-api-packages.Rmd 2019-11-06 17:16:05.000000000 +0000 @@ -33,6 +33,7 @@ cli <- crul::HttpClient$new(url, opts = list(...)) res <- cli$get(path = path, query = args) res$raise_for_status() + res$raise_for_ct_json() res$parse("UTF-8") } ``` @@ -73,7 +74,78 @@ ## Failing with fauxpas -coming soon ... +[fauxpas][] is in Suggests in this package. If you don't have it +installed, no worries, but if you do have it installed, we use +fauxpas. + +There is not much difference with the default `raise_for_status()` +between using fauxpas and not using it. + +However, you can construct your own replacement with fauxpas that +gives you more flexibility in how you deal with HTTP status codes. + +First, make an HTTP request: + +```{r eval=FALSE} +con <- HttpClient$new("https://httpbin.org/status/404") +res <- con$get() +``` + +Then use `fauxpas::find_error_class` to get the correct R6 error +class for the status code, in this case `404` + +```{r eval=FALSE} +x <- fauxpas::find_error_class(res$status_code)$new() +#> +#> behavior: stop +#> message_template: {{reason}} (HTTP {{status}}) +#> message_template_verbose: {{reason}} (HTTP {{status}}).\n - {{message}} +``` + +We can then do one of two things: use `$do()` or `$do_verbose()`. `$do()` +is simpler and gives you thhe same thing `$raise_for_status()` gives, but +allows you to change behavior (stop vs. warning vs. message), and how the +message is formatted. By default we get: + +```{r eval=FALSE} +x$do(res) +#> Error: Not Found (HTTP 404) +``` + +We can change the template using `whisker` templating + +```{r eval=FALSE} +x$do(res, template = "{{status}}\n --> {{reason}}") +#> Error: 404 +#> --> Not Found +``` + +`$do_verbose()` gives you a lot more detail about the status code, possibly more +than you want: + +```{r eval=FALSE} +x$do_verbose(res) +#> Error: Not Found (HTTP 404). +#> - The server has not found anything matching the Request-URI. No indication +#> is given of whether the condition is temporary or permanent. The 410 (Gone) +#> status code SHOULD be used if the server knows, through some internally configurable +#> mechanism, that an old resource is permanently unavailable and has no forwarding +#> address. This status code is commonly used when the server does not wish to +#> reveal exactly why the request has been refused, or when no other response +#> is applicable. +``` + +You can change behavior to either `warning` or `message`: + +```{r eval=FALSE} +x$behavior <- "warning" +x$do(res) +#> Warning message: +#> Not Found (HTTP 404) +x$behavior <- "message" +x$do(res) +#> Not Found (HTTP 404) +``` ## Mocking with webmockr @@ -146,3 +218,4 @@ [webmockr]: https://github.com/ropensci/webmockr [vcr]: https://github.com/ropensci/vcr [httr]: https://github.com/r-lib/httr +[fauxpas]: https://github.com/ropensci/fauxpas diff -Nru r-cran-crul-0.8.4+dfsg/man/content-types.Rd r-cran-crul-0.9.0+dfsg/man/content-types.Rd --- r-cran-crul-0.8.4+dfsg/man/content-types.Rd 1970-01-01 00:00:00.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/man/content-types.Rd 2019-11-04 20:29:37.000000000 +0000 @@ -0,0 +1,71 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/content-types.R +\name{content-types} +\alias{content-types} +\title{Working with content types} +\description{ +The \link{HttpResponse} class holds all the responses elements for an HTTP +request. This document details how to work specifically with the +content-type of the response headers +} +\section{Content types}{ + +The "Content-Type" header in HTTP responses gives the media type of the +response. The media type is both the data format and how the data is +intended to be processed by a recipient. (modified from rfc7231) +} + +\section{Behavior of the parameters HttpResponse raise_for_ct* methods}{ + +\itemize{ +\item type: (only applicable for the \code{raise_for_ct()} method): instead of +using one of the three other content type methods for html, json, or xml, +you can specify a mime type to check, any of those in \link[mime:mimemap]{mime::mimemap} +\item charset: if you don't give a value to this parameter, we only +check that the content type is what you expect; that is, the charset, +if given, is ignored. +\item behavior: by default when you call this method, and the content type +does not match what the method expects, then we run \code{stop()} with a +message. Instead of stopping, you can choose \code{behavior="warning"} +and we'll throw a warning instead, allowing any downstream processing +to proceed. +} +} + +\examples{ +\dontrun{ +(x <- HttpClient$new(url = "https://httpbin.org")) +(res <- x$get()) + +## get the content type +res$response_headers$`content-type` + +## check that the content type is text/html +res$raise_for_ct_html() + +## it's def. not json +# res$raise_for_ct_json() + +## give custom content type +res$raise_for_ct("text/html") +# res$raise_for_ct("application/json") +# res$raise_for_ct("foo/bar") + +## check charset in addition to the media type +res$raise_for_ct_html(charset = "utf-8") +# res$raise_for_ct_html(charset = "utf-16") + +# warn instead of stop +res$raise_for_ct_json(behavior = "warning") +} +} +\references{ +spec for content types: +\url{https://tools.ietf.org/html/rfc7231#section-3.1.1.5} + +spec for media types: +\url{https://tools.ietf.org/html/rfc7231#section-3.1.1.1} +} +\seealso{ +\link{HttpResponse} +} diff -Nru r-cran-crul-0.8.4+dfsg/man/crul-package.Rd r-cran-crul-0.9.0+dfsg/man/crul-package.Rd --- r-cran-crul-0.8.4+dfsg/man/crul-package.Rd 2019-06-13 21:08:44.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/man/crul-package.Rd 2019-11-04 20:46:19.000000000 +0000 @@ -47,6 +47,24 @@ } } +\section{Checking HTTP responses}{ + + +\code{\link[=HttpResponse]{HttpResponse()}} has helpers for checking and raising warnings/errors. +\itemize{ +\item \link{content-types} details the various options for checking content +types and throwing a warning or error if the response content +type doesn't match what you expect. Mis-matched content-types are +typically a good sign of a bad response. There's methods built +in for json, xml and html, with the ability to set any +custom content type +\item \code{raise_for_status()} is a method on \code{\link[=HttpResponse]{HttpResponse()}} that checks +the HTTP status code, and errors with the appropriate message for +the HTTP status code, optionally using the package \code{fauxpas} +if it's installed. +} +} + \section{HTTP conditions}{ We use \code{fauxpas} if you have it installed for handling HTTP diff -Nru r-cran-crul-0.8.4+dfsg/man/HttpResponse.Rd r-cran-crul-0.9.0+dfsg/man/HttpResponse.Rd --- r-cran-crul-0.8.4+dfsg/man/HttpResponse.Rd 2019-06-13 21:08:44.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/man/HttpResponse.Rd 2019-11-04 20:29:37.000000000 +0000 @@ -5,14 +5,14 @@ \alias{HttpResponse} \title{Base response object} \arguments{ +\item{method}{(character) HTTP method} + \item{url}{(character) A url, required} \item{opts}{(list) curl options} \item{handle}{A handle} -\item{method}{(character) HTTP method} - \item{status_code}{(integer) status code} \item{request_headers}{(list) request headers, named list} @@ -33,32 +33,73 @@ \description{ Base response object } -\details{ -\strong{Methods} +\section{Usage}{ +\preformatted{HttpResponse$new(method, url, opts, handle, status_code, request_headers, + response_headers, response_headers_all, times, content, + request) +} +} + +\section{Methods}{ + \describe{ \item{\code{parse(encoding = NULL, ...)}}{ Parse the raw response content to text -- encoding: A character string describing the current encoding. -If left as \code{NULL}, we attempt to guess the encoding. Passed to -\code{from} parameter in \code{iconv} -- ...: additional parameters passed on to \code{iconv} (options: sub, mark, toRaw). -See \code{?iconv} for help +\itemize{ +\item encoding: (character) A character string describing the +current encoding. If left as \code{NULL}, we attempt to guess the +encoding. Passed to \code{from} parameter in \code{iconv} +\item ...: additional parameters passed on to \code{iconv} +(options: sub, mark, toRaw). See \code{?iconv} for help +\item returns: character +} } \item{\code{success()}}{ Was status code less than or equal to 201. -returns boolean +\itemize{ +\item returns: boolean +} } -\item{\code{status_http()}}{ +\item{\code{status_http(verbose = FALSE)}}{ Get HTTP status code, message, and explanation +\itemize{ +\item returns: object of class "http_code", a list with slots +for status_code, message, and explanation +} } \item{\code{raise_for_status()}}{ Check HTTP status and stop with appropriate -HTTP error code and message if >= 300. -- If you have \code{fauxpas} installed we use that, -otherwise use \pkg{httpcode} +HTTP error code and message if >= 300. otherwise use \pkg{httpcode}. +If you have \code{fauxpas} installed we use that. +\itemize{ +\item returns: stop or warn with message +} +} +\item{\code{raise_for_ct(type, charset = NULL, behavior = "stop")}}{ +Check response content-type; stop or warn if not matched. Parameters: +\itemize{ +\item type: (character) a mime type to match against; see +\link[mime:mimemap]{mime::mimemap} for allowed values +\item charset: (character) if a charset string given, we check that +it matches the charset in the content type header. default: NULL +\item behavior: (character) one of stop (default) or warning +} +} +\item{\code{raise_for_ct_html(charset = NULL, behavior = "stop")}}{ +Check that the response content-type is \code{text/html}; stop or warn if +not matched. Parameters: see \code{raise_for_ct()} +} +\item{\code{raise_for_ct_json(charset = NULL, behavior = "stop")}}{ +Check that the response content-type is \code{application/json}; stop or +warn if not matched. Parameters: see \code{raise_for_ct()} +} +\item{\code{raise_for_ct_xml(charset = NULL, behavior = "stop")}}{ +Check that the response content-type is \code{application/xml}; stop or warn if +not matched. Parameters: see \code{raise_for_ct()} } } } + \examples{ \dontrun{ x <- HttpResponse$new(method = "get", url = "https://httpbin.org") @@ -86,4 +127,7 @@ # res$raise_for_status() } } +\seealso{ +\link{content-types} +} \keyword{datasets} diff -Nru r-cran-crul-0.8.4+dfsg/MD5 r-cran-crul-0.9.0+dfsg/MD5 --- r-cran-crul-0.8.4+dfsg/MD5 2019-08-02 20:30:03.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/MD5 2019-11-06 21:50:02.000000000 +0000 @@ -1,14 +1,15 @@ -9cf8390f2fcbeb0b2014cede3e852f8e *DESCRIPTION +16779691700f2aaec589d4fd5a6fe707 *DESCRIPTION de9532c5aeb8082a5fc9cbee02186d6f *LICENSE -95c7378e8213180deb945fcb64212d99 *NAMESPACE -5235b766142e74bd87e4fe8e69b2d17a *NEWS.md +d6e80689934ea80e4ad079899aed641f *NAMESPACE +8b941c5473365a8cc0e2ad5989558135 *NEWS.md 45ba5247259fa3fe8f98b1e4ead5f391 *R/async.R ab3d848b5b694871eb11af60a80c861e *R/asyncvaried.R 0d3cedbe18c0401f95307215f1bdc312 *R/auth.R -1b68e4e783a279114a03ca706febccca *R/body.R -03a24640a94b4c5ad6d870bee9280fc0 *R/client.R +e43e1313cab9bd7ffe6f2ed6ca34231b *R/body.R +76df23f3bbe2210ff4add2628a17feea *R/client.R +a9bc899207b3329199830be7139e05f8 *R/content-types.R e66320a324ca355793ca5fe482290003 *R/cookies.R -a881d0dcea814616f5749c3b651c251b *R/crul-package.r +611c93db925b234ef069df024653941f *R/crul-package.r e7a085da372381d4c954a5d8ee112885 *R/curl-options.R ce31dc346613d071e58f07d49bdb14eb *R/curl_options.R a5a267b97adb284475f7535a9cc465b3 *R/delete-requests.R @@ -27,20 +28,20 @@ 34512e8bacf64556c2873640126f1357 *R/progress.R 6c7a291b477a02c9154855492c455381 *R/proxies.R ff10b92e14e29e606593ff962f732b7a *R/query.R -04655e56963cfb2d97e381895ae6ac4e *R/response.R +1820fae2ee944bc26ab82792ef89f3f2 *R/response.R e61c00e683899a2acaa67e8b05af1532 *R/set.R -4fadc12ffde03e7588d4f0317c8bc5a2 *R/upload.R +9801ebfed5371685923888d54707479f *R/upload.R 14c11771d130c3cc4ba9e9f475a2951d *R/use_agent.R e0bf20af942d7f06d12d2d49a086b5ab *R/verbs.R 8ac99ac5b38e5a0c38be1bd16b2101f7 *R/writing-options.R 393c44f88f08f6f4c547aaee0d796029 *R/zzz.R -49025a32e48233bec5f9de86ce084ae7 *README.md +ffa6590621ce2c8edc93986509e53d77 *README.md 61f3ae1017e6bcb676b95d3a5e894545 *build/vignette.rds 52ca5681e18151fcc9e90b036892e330 *inst/doc/async.Rmd 7e59e2a3d14a95a09e929fa20fc451d9 *inst/doc/async.html -26350bdcdea4656b242fa75df8d69385 *inst/doc/best-practices-api-packages.R -6072507e2051ed1d46572ea6166f9d0d *inst/doc/best-practices-api-packages.Rmd -983b0462d6c9feeb63214ec5e53069c7 *inst/doc/best-practices-api-packages.html +00b8f847688379cfd76de0986452b1af *inst/doc/best-practices-api-packages.R +c31ad563c8dbb9f82e206b43faa5348e *inst/doc/best-practices-api-packages.Rmd +a9a2acd12cab75960bc72f7a9bf9acb0 *inst/doc/best-practices-api-packages.html eba2ea331f3beb253f064a7de2c64323 *inst/doc/crul.Rmd baadef74610af80be5086a5ca00a36de *inst/doc/crul.html f4a21bd94f323157927ece0d1681f559 *inst/doc/curl-options.Rmd @@ -51,12 +52,13 @@ b50c44b3a029524eb9fb7126a13863e2 *man/AsyncVaried.Rd 9b61a7c0e998ad43216aebdbbbfcda3e *man/HttpClient.Rd c36a0ce52523ef45425dfb256238c484 *man/HttpRequest.Rd -e551526a15bf7d2eda5a25507cb88879 *man/HttpResponse.Rd +3b2bcdae2a724e4f18b96ebdd8e2c731 *man/HttpResponse.Rd 49d28ffbddab650d1ac473829a251e18 *man/Paginator.Rd 55c3fed958ec743c2a98bd885987d0cf *man/auth.Rd +f3c0ef3453456c95d013e161b02ab443 *man/content-types.Rd e1c835263bca884f291a5b471f48cceb *man/cookies.Rd 21ce05727bf6cd811f5706191f45392e *man/crul-options.Rd -604aea171e7bce3471cc86eeb4347bce *man/crul-package.Rd +08a8b189829f452e4e905a8124896eaa *man/crul-package.Rd 3827c9412646c3916a83019bacd09177 *man/curl-options.Rd ff3f74fe778be00c394b730822124f45 *man/delete-requests.Rd 7cb18db56a943383941c5e2b1a223259 *man/handle.Rd @@ -83,16 +85,17 @@ 0849bed9ba9a5849ff55e99107410aa5 *tests/testthat/test-asyncvaried.R 32f26c86402873b19114a788b63c6af6 *tests/testthat/test-auth.R 5a316e36757121541fa616dd5630a76e *tests/testthat/test-client-delete.R -8078f7a703e328202007fe4a80c1bfce *tests/testthat/test-client-get.R -3f31cf7ab889e5adf10d15b94b68d0db *tests/testthat/test-client-head.R +993fb83f6da57a64231d2c0d18e5b584 *tests/testthat/test-client-get.R +c1dfff9048e89f9970337c3ed0166431 *tests/testthat/test-client-head.R 0ad92625ad837fc71ced31fb5bb88aac *tests/testthat/test-client-hooks.R 97e6ad729842dbba9bb092436fa9c238 *tests/testthat/test-client-patch.R 594adc6559319e14e59e515eb07c8d05 *tests/testthat/test-client-post.R a9df544878988bbb6dbdd4549c3d4f82 *tests/testthat/test-client-put.R 69a1da5481e5e9fd713129b671c839ae *tests/testthat/test-client-query.R 4d8007529dee1aac5de4e4c38dd08b38 *tests/testthat/test-client-status.R -7aae0b0af5d8cceb19d86c880987af68 *tests/testthat/test-client-verb.R +f2ccf3cd4a0fe14df5ffb1b984fbaa1f *tests/testthat/test-client-verb.R 3c9019f8d555a6de6faf5b379843b593 *tests/testthat/test-client.R +a6dc76274d379eb00695f829a4ff3935 *tests/testthat/test-content-type.R 82bc5c2fdc82f5fc7d4b7389b5666b5a *tests/testthat/test-handle.R 4f074aae8c6466ae8d647aac59513ad1 *tests/testthat/test-headers.R 3fbb4e1d37f81f8ed9a61ffb863a3654 *tests/testthat/test-mocking.R @@ -109,7 +112,7 @@ 312b4ca269d13bd1b0d7d36f2e63f5ef *tests/testthat/test-user-agent.R 11807caff7a89ebc264d38dbdaf2cac3 *tests/testthat/test-utils.R 52ca5681e18151fcc9e90b036892e330 *vignettes/async.Rmd -6072507e2051ed1d46572ea6166f9d0d *vignettes/best-practices-api-packages.Rmd +c31ad563c8dbb9f82e206b43faa5348e *vignettes/best-practices-api-packages.Rmd eba2ea331f3beb253f064a7de2c64323 *vignettes/crul.Rmd f4a21bd94f323157927ece0d1681f559 *vignettes/curl-options.Rmd 329edf42851dc94cb5506d5719bc6fb6 *vignettes/how-to-use-crul.Rmd diff -Nru r-cran-crul-0.8.4+dfsg/NAMESPACE r-cran-crul-0.9.0+dfsg/NAMESPACE --- r-cran-crul-0.8.4+dfsg/NAMESPACE 2019-08-02 19:18:21.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/NAMESPACE 2019-11-06 20:09:17.000000000 +0000 @@ -1,6 +1,8 @@ # Generated by roxygen2: do not edit by hand -S3method(as.character,form_file) +S3method(field_as,default) +S3method(field_as,logical) +S3method(field_as,numeric) S3method(ok,HttpClient) S3method(ok,character) S3method(ok,default) diff -Nru r-cran-crul-0.8.4+dfsg/NEWS.md r-cran-crul-0.9.0+dfsg/NEWS.md --- r-cran-crul-0.8.4+dfsg/NEWS.md 2019-08-02 19:16:05.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/NEWS.md 2019-11-06 20:12:41.000000000 +0000 @@ -1,3 +1,20 @@ +crul 0.9.0 +========== + +### NEW FEATURES + +* `HttpResponse` response object gains new methods for checking response content types, includes: `raise_for_ct`, `raise_for_ct_html`, `raise_for_ct_json`, `raise_for_ct_xml`. these behave similarly to `raise_for_status`, and can behave as a warning or raise an error through stop (#119) (#120) + +### MINOR IMPROVEMENTS + +* fix to prep_body internal function to handle various body inputs; now avoids warning about `as.character.form_file` when both httr and crul are loaded (#112) +* finish off "Failing with fauxpas" section of the "API package best practices" vignette (#121) + +### BUG FIXES + +* the `head()` verb on `HttpClient` was no capturing `auth` when set on initialization (#122) + + crul 0.8.4 ========== diff -Nru r-cran-crul-0.8.4+dfsg/R/body.R r-cran-crul-0.9.0+dfsg/R/body.R --- r-cran-crul-0.8.4+dfsg/R/body.R 2019-06-13 21:08:44.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/R/body.R 2019-11-04 22:34:02.000000000 +0000 @@ -80,10 +80,16 @@ stop("All components of body must be named", call. = FALSE) } list( - opts = list( - post = TRUE - ), - fields = lapply(body, as.character) + opts = list(post = TRUE), + fields = lapply(body, field_as) ) } } + +field_as <- function(x) UseMethod("field_as") +#' @export +field_as.numeric <- function(x) as.character(x) +#' @export +field_as.logical <- function(x) as.character(x) +#' @export +field_as.default <- function(x) x diff -Nru r-cran-crul-0.8.4+dfsg/R/client.R r-cran-crul-0.9.0+dfsg/R/client.R --- r-cran-crul-0.8.4+dfsg/R/client.R 2019-08-02 18:46:13.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/R/client.R 2019-11-05 20:30:09.000000000 +0000 @@ -386,7 +386,7 @@ } rr$options <- utils::modifyList( rr$options, - c(self$opts, self$proxies, ...)) + c(self$opts, self$proxies, self$auth, ...)) private$make_request(rr) }, diff -Nru r-cran-crul-0.8.4+dfsg/R/content-types.R r-cran-crul-0.9.0+dfsg/R/content-types.R --- r-cran-crul-0.8.4+dfsg/R/content-types.R 1970-01-01 00:00:00.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/R/content-types.R 2019-11-04 20:29:37.000000000 +0000 @@ -0,0 +1,60 @@ +#' Working with content types +#' +#' The [HttpResponse] class holds all the responses elements for an HTTP +#' request. This document details how to work specifically with the +#' content-type of the response headers +#' +#' @name content-types +#' @section Content types: +#' The "Content-Type" header in HTTP responses gives the media type of the +#' response. The media type is both the data format and how the data is +#' intended to be processed by a recipient. (modified from rfc7231) +#' +#' @section Behavior of the parameters HttpResponse raise_for_ct* methods: +#' +#' - type: (only applicable for the `raise_for_ct()` method): instead of +#' using one of the three other content type methods for html, json, or xml, +#' you can specify a mime type to check, any of those in [mime::mimemap] +#' - charset: if you don't give a value to this parameter, we only +#' check that the content type is what you expect; that is, the charset, +#' if given, is ignored. +#' - behavior: by default when you call this method, and the content type +#' does not match what the method expects, then we run `stop()` with a +#' message. Instead of stopping, you can choose `behavior="warning"` +#' and we'll throw a warning instead, allowing any downstream processing +#' to proceed. +#' +#' @references +#' spec for content types: +#' +#' +#' spec for media types: +#' +#' +#' @seealso [HttpResponse] +#' @examples \dontrun{ +#' (x <- HttpClient$new(url = "https://httpbin.org")) +#' (res <- x$get()) +#' +#' ## get the content type +#' res$response_headers$`content-type` +#' +#' ## check that the content type is text/html +#' res$raise_for_ct_html() +#' +#' ## it's def. not json +#' # res$raise_for_ct_json() +#' +#' ## give custom content type +#' res$raise_for_ct("text/html") +#' # res$raise_for_ct("application/json") +#' # res$raise_for_ct("foo/bar") +#' +#' ## check charset in addition to the media type +#' res$raise_for_ct_html(charset = "utf-8") +#' # res$raise_for_ct_html(charset = "utf-16") +#' +#' # warn instead of stop +#' res$raise_for_ct_json(behavior = "warning") +#' } +NULL diff -Nru r-cran-crul-0.8.4+dfsg/R/crul-package.r r-cran-crul-0.9.0+dfsg/R/crul-package.r --- r-cran-crul-0.8.4+dfsg/R/crul-package.r 2019-06-13 21:08:44.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/R/crul-package.r 2019-11-04 20:46:08.000000000 +0000 @@ -32,6 +32,21 @@ #' at a time #' - the `verb()` method can be used on all the above to request #' a specific HTTP verb +#' +#' @section Checking HTTP responses: +#' +#' [HttpResponse()] has helpers for checking and raising warnings/errors. +#' +#' - [content-types] details the various options for checking content +#' types and throwing a warning or error if the response content +#' type doesn't match what you expect. Mis-matched content-types are +#' typically a good sign of a bad response. There's methods built +#' in for json, xml and html, with the ability to set any +#' custom content type +#' - `raise_for_status()` is a method on [HttpResponse()] that checks +#' the HTTP status code, and errors with the appropriate message for +#' the HTTP status code, optionally using the package `fauxpas` +#' if it's installed. #' #' @section HTTP conditions: #' We use `fauxpas` if you have it installed for handling HTTP diff -Nru r-cran-crul-0.8.4+dfsg/R/response.R r-cran-crul-0.9.0+dfsg/R/response.R --- r-cran-crul-0.8.4+dfsg/R/response.R 2019-07-30 15:22:01.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/R/response.R 2019-11-06 17:16:19.000000000 +0000 @@ -1,10 +1,16 @@ #' Base response object #' #' @export +#' @section Usage: +#' ``` +#' HttpResponse$new(method, url, opts, handle, status_code, request_headers, +#' response_headers, response_headers_all, times, content, +#' request) +#' ``` +#' @param method (character) HTTP method #' @param url (character) A url, required #' @param opts (list) curl options #' @param handle A handle -#' @param method (character) HTTP method #' @param status_code (integer) status code #' @param request_headers (list) request headers, named list #' @param response_headers (list) response headers, named list @@ -14,33 +20,66 @@ #' @param times (vector) named vector #' @param content (raw) raw binary content response #' @param request request object, with all details -#' @details -#' **Methods** +#' @section Methods: #' \describe{ #' \item{`parse(encoding = NULL, ...)`}{ #' Parse the raw response content to text -#' - encoding: A character string describing the current encoding. -#' If left as `NULL`, we attempt to guess the encoding. Passed to -#' `from` parameter in `iconv` -#' - ...: additional parameters passed on to `iconv` (options: sub, mark, toRaw). -#' See `?iconv` for help +#' \itemize{ +#' \item encoding: (character) A character string describing the +#' current encoding. If left as `NULL`, we attempt to guess the +#' encoding. Passed to `from` parameter in `iconv` +#' \item ...: additional parameters passed on to `iconv` +#' (options: sub, mark, toRaw). See `?iconv` for help +#' \item returns: character +#' } #' } #' \item{`success()`}{ #' Was status code less than or equal to 201. -#' returns boolean +#' \itemize{ +#' \item returns: boolean +#' } #' } -#' \item{`status_http()`}{ +#' \item{`status_http(verbose = FALSE)`}{ #' Get HTTP status code, message, and explanation +#' \itemize{ +#' \item returns: object of class "http_code", a list with slots +#' for status_code, message, and explanation +#' } #' } #' \item{`raise_for_status()`}{ #' Check HTTP status and stop with appropriate -#' HTTP error code and message if >= 300. -#' - If you have `fauxpas` installed we use that, -#' otherwise use \pkg{httpcode} +#' HTTP error code and message if >= 300. otherwise use \pkg{httpcode}. +#' If you have `fauxpas` installed we use that. +#' \itemize{ +#' \item returns: stop or warn with message +#' } +#' } +#' \item{`raise_for_ct(type, charset = NULL, behavior = "stop")`}{ +#' Check response content-type; stop or warn if not matched. Parameters: +#' \itemize{ +#' \item type: (character) a mime type to match against; see +#' [mime::mimemap] for allowed values +#' \item charset: (character) if a charset string given, we check that +#' it matches the charset in the content type header. default: NULL +#' \item behavior: (character) one of stop (default) or warning +#' } +#' } +#' \item{`raise_for_ct_html(charset = NULL, behavior = "stop")`}{ +#' Check that the response content-type is `text/html`; stop or warn if +#' not matched. Parameters: see `raise_for_ct()` +#' } +#' \item{`raise_for_ct_json(charset = NULL, behavior = "stop")`}{ +#' Check that the response content-type is `application/json`; stop or +#' warn if not matched. Parameters: see `raise_for_ct()` +#' } +#' \item{`raise_for_ct_xml(charset = NULL, behavior = "stop")`}{ +#' Check that the response content-type is `application/xml`; stop or warn if +#' not matched. Parameters: see `raise_for_ct()` #' } #' } #' @format NULL #' @usage NULL +#' @seealso [content-types] #' @examples \dontrun{ #' x <- HttpResponse$new(method = "get", url = "https://httpbin.org") #' x$url @@ -81,6 +120,10 @@ times = NULL, content = NULL, request = NULL, + raise_for_ct = NULL, + raise_for_ct_html = NULL, + raise_for_ct_json = NULL, + raise_for_ct_xml = NULL, print = function(x, ...) { cat(" ", sep = "\n") @@ -132,12 +175,17 @@ if (!missing(times)) self$times <- times if (!missing(content)) self$content <- content if (!missing(request)) self$request <- request + + self$raise_for_ct = private$raise_for_ct_user() + self$raise_for_ct_html = private$raise_for_ct_factory(type = "html") + self$raise_for_ct_json = private$raise_for_ct_factory(type = "json") + self$raise_for_ct_xml = private$raise_for_ct_factory(type = "xml") }, parse = function(encoding = NULL, ...) { if ( "disk" %in% names(self$request) || - (inherits(self$request, "HttpRequest") && + (inherits(self$request, "HttpRequest") && "disk" %in% names(self$request$payload)) ) { if ( @@ -179,9 +227,57 @@ } } } + ), + + private = list( + raise_for_ct_user = function() { + function(type, charset = NULL, behavior = "stop") { + if (!type %in% mime::mimemap) + stop("type not in allowed set, see ?mime::mimemap") + type <- names(mime::mimemap[type == mime::mimemap])[1] + private$raise_for_ct_factory(type)( + charset = charset, behavior = behavior + ) + } + }, + raise_for_ct_factory = function(type) { + function(charset = NULL, behavior = "stop") { + behaviors <- c("stop", "warning") + assert(behavior, "character") + if (!behavior %in% behaviors) + stop("'behavior' must be one of ", paste(behaviors, collapse = ", ")) + ctype <- mime::mimemap[[type]] + if (is.null(self$response_headers$`content-type`)) + stop("content-type header is missing") + rtype <- self$response_headers$`content-type` + if (!is.null(charset)) { + if (!grepl(";\\s?[A-Za-z0-9]+|;\\s?charset=[A-Za-z0-9]+", rtype)) { + warning("no charset detected in response content-type", + call. = FALSE) + } else if ( + !grepl(ctype, rtype) || + !grepl(norm(charset), norm(rtype)) + ) { + get(behavior)(sprintf("response content-type (%s) did not match expected type (%s)\nor character set (%s)", rtype, ctype, charset), call. = FALSE) + } + } else { + if (!grepl(ctype, rtype)) { + get(behavior)(sprintf("response content-type (%s) did not match expected type (%s)", + rtype, ctype), call. = FALSE) + } + } + } + } ) ) +# remove spaces; lowercase everything +norm <- function(x) { + x <- gsub("\\s", "", x) + x <- tolower(x) + return(x) +} + guess_encoding <- function(encoding = NULL) { if (!is.null(encoding)) { return(check_encoding(encoding)) diff -Nru r-cran-crul-0.8.4+dfsg/R/upload.R r-cran-crul-0.9.0+dfsg/R/upload.R --- r-cran-crul-0.8.4+dfsg/R/upload.R 2019-08-02 19:16:10.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/R/upload.R 2019-11-04 22:31:16.000000000 +0000 @@ -9,6 +9,3 @@ if (is.null(type)) type <- mime::guess_type(path) curl::form_file(path, type) } - -#' @export -as.character.form_file <- function(x, ...) x diff -Nru r-cran-crul-0.8.4+dfsg/README.md r-cran-crul-0.9.0+dfsg/README.md --- r-cran-crul-0.8.4+dfsg/README.md 2019-08-02 17:24:06.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/README.md 2019-11-06 21:23:01.000000000 +0000 @@ -11,7 +11,7 @@ [![rstudio mirror downloads](http://cranlogs.r-pkg.org/badges/crul)](https://github.com/metacran/cranlogs.app) [![cran version](https://www.r-pkg.org/badges/version/crul)](https://cran.r-project.org/package=crul) -An HTTP client, taking inspiration from Ruby's [faraday](https://rubygems.org/gems/faraday) and Python's [requests](http://docs.python-requests.org/en/master/) +An HTTP client, taking inspiration from Ruby's [faraday](https://rubygems.org/gems/faraday) and Python's `requests` Package API: @@ -166,11 +166,11 @@ #> [162] 22 2c 20 0a 20 20 20 20 22 48 6f 73 74 22 3a 20 22 68 74 74 70 62 69 #> [185] 6e 2e 6f 72 67 22 2c 20 0a 20 20 20 20 22 55 73 65 72 2d 41 67 65 6e #> [208] 74 22 3a 20 22 6c 69 62 63 75 72 6c 2f 37 2e 35 34 2e 30 20 72 2d 63 -#> [231] 75 72 6c 2f 34 2e 30 20 63 72 75 6c 2f 30 2e 38 2e 31 2e 39 31 32 33 -#> [254] 22 0a 20 20 7d 2c 20 0a 20 20 22 6f 72 69 67 69 6e 22 3a 20 22 32 34 -#> [277] 2e 32 31 2e 32 32 39 2e 35 39 2c 20 32 34 2e 32 31 2e 32 32 39 2e 35 -#> [300] 39 22 2c 20 0a 20 20 22 75 72 6c 22 3a 20 22 68 74 74 70 73 3a 2f 2f -#> [323] 68 74 74 70 62 69 6e 2e 6f 72 67 2f 67 65 74 22 0a 7d 0a +#> [231] 75 72 6c 2f 34 2e 32 20 63 72 75 6c 2f 30 2e 39 2e 30 22 0a 20 20 7d +#> [254] 2c 20 0a 20 20 22 6f 72 69 67 69 6e 22 3a 20 22 31 39 32 2e 31 33 32 +#> [277] 2e 36 31 2e 33 35 2c 20 31 39 32 2e 31 33 32 2e 36 31 2e 33 35 22 2c +#> [300] 20 0a 20 20 22 75 72 6c 22 3a 20 22 68 74 74 70 73 3a 2f 2f 68 74 74 +#> [323] 70 62 69 6e 2e 6f 72 67 2f 67 65 74 22 0a 7d 0a ``` HTTP method @@ -187,7 +187,7 @@ ```r res$request_headers #> $`User-Agent` -#> [1] "libcurl/7.54.0 r-curl/4.0 crul/0.8.1.9123" +#> [1] "libcurl/7.54.0 r-curl/4.2 crul/0.9.0" #> #> $`Accept-Encoding` #> [1] "gzip, deflate" @@ -220,7 +220,7 @@ #> [1] "application/json" #> #> $date -#> [1] "Fri, 02 Aug 2019 17:24:06 GMT" +#> [1] "Wed, 06 Nov 2019 20:52:22 GMT" #> #> $`referrer-policy` #> [1] "no-referrer-when-downgrade" @@ -238,7 +238,7 @@ #> [1] "1; mode=block" #> #> $`content-length` -#> [1] "229" +#> [1] "228" #> #> $connection #> [1] "keep-alive" @@ -257,7 +257,7 @@ ```r res$parse() #> No encoding supplied: defaulting to UTF-8. -#> [1] "{\n \"args\": {}, \n \"headers\": {\n \"A\": \"hello world\", \n \"Accept\": \"application/json, text/xml, application/xml, */*\", \n \"Accept-Encoding\": \"gzip, deflate\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"libcurl/7.54.0 r-curl/4.0 crul/0.8.1.9123\"\n }, \n \"origin\": \"24.21.229.59, 24.21.229.59\", \n \"url\": \"https://httpbin.org/get\"\n}\n" +#> [1] "{\n \"args\": {}, \n \"headers\": {\n \"A\": \"hello world\", \n \"Accept\": \"application/json, text/xml, application/xml, */*\", \n \"Accept-Encoding\": \"gzip, deflate\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"libcurl/7.54.0 r-curl/4.2 crul/0.9.0\"\n }, \n \"origin\": \"192.132.61.35, 192.132.61.35\", \n \"url\": \"https://httpbin.org/get\"\n}\n" jsonlite::fromJSON(res$parse()) #> No encoding supplied: defaulting to UTF-8. #> $args @@ -277,11 +277,11 @@ #> [1] "httpbin.org" #> #> $headers$`User-Agent` -#> [1] "libcurl/7.54.0 r-curl/4.0 crul/0.8.1.9123" +#> [1] "libcurl/7.54.0 r-curl/4.2 crul/0.9.0" #> #> #> $origin -#> [1] "24.21.229.59, 24.21.229.59" +#> [1] "192.132.61.35, 192.132.61.35" #> #> $url #> [1] "https://httpbin.org/get" @@ -351,8 +351,8 @@ #> Message: OK #> Explanation: Request fulfilled, document follows out$parse() -#> [1] "{\n \"args\": {}, \n \"headers\": {\n \"Accept\": \"application/json, text/xml, application/xml, */*\", \n \"Accept-Encoding\": \"gzip, deflate\", \n \"Foo\": \"bar\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"R (3.6.1 x86_64-apple-darwin15.6.0 x86_64 darwin15.6.0)\"\n }, \n \"origin\": \"24.21.229.59, 24.21.229.59\", \n \"url\": \"https://httpbin.org/get\"\n}\n" -#> [2] "{\n \"args\": {}, \n \"data\": \"\", \n \"files\": {}, \n \"form\": {}, \n \"headers\": {\n \"Accept\": \"application/json, text/xml, application/xml, */*\", \n \"Accept-Encoding\": \"gzip, deflate\", \n \"Content-Length\": \"0\", \n \"Content-Type\": \"application/x-www-form-urlencoded\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"libcurl/7.54.0 r-curl/4.0 crul/0.8.1.9123\"\n }, \n \"json\": null, \n \"origin\": \"24.21.229.59, 24.21.229.59\", \n \"url\": \"https://httpbin.org/post\"\n}\n" +#> [1] "{\n \"args\": {}, \n \"headers\": {\n \"Accept\": \"application/json, text/xml, application/xml, */*\", \n \"Accept-Encoding\": \"gzip, deflate\", \n \"Foo\": \"bar\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"R (3.6.1 x86_64-apple-darwin15.6.0 x86_64 darwin15.6.0)\"\n }, \n \"origin\": \"192.132.61.35, 192.132.61.35\", \n \"url\": \"https://httpbin.org/get\"\n}\n" +#> [2] "{\n \"args\": {}, \n \"data\": \"\", \n \"files\": {}, \n \"form\": {}, \n \"headers\": {\n \"Accept\": \"application/json, text/xml, application/xml, */*\", \n \"Accept-Encoding\": \"gzip, deflate\", \n \"Content-Length\": \"0\", \n \"Content-Type\": \"application/x-www-form-urlencoded\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"libcurl/7.54.0 r-curl/4.2 crul/0.9.0\"\n }, \n \"json\": null, \n \"origin\": \"192.132.61.35, 192.132.61.35\", \n \"url\": \"https://httpbin.org/post\"\n}\n" ``` ## Progress bars diff -Nru r-cran-crul-0.8.4+dfsg/tests/testthat/test-client-get.R r-cran-crul-0.9.0+dfsg/tests/testthat/test-client-get.R --- r-cran-crul-0.8.4+dfsg/tests/testthat/test-client-get.R 2019-06-13 21:08:44.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/tests/testthat/test-client-get.R 2019-11-05 22:02:24.000000000 +0000 @@ -49,3 +49,16 @@ ), FALSE) expect_equal(params, querya) }) + +test_that("with auth works", { + skip_on_cran() + + cli <- HttpClient$new(url = hb(), auth = auth("foo", "bar")) + aa <- cli$get("/basic-auth/foo/bar") + + expect_is(aa, "HttpResponse") + expect_equal(aa$method, "get") + expect_true(aa$success()) + expect_equal(aa$status_code, 200) + expect_equal(aa$request$options$userpwd, "foo:bar") +}) diff -Nru r-cran-crul-0.8.4+dfsg/tests/testthat/test-client-head.R r-cran-crul-0.9.0+dfsg/tests/testthat/test-client-head.R --- r-cran-crul-0.8.4+dfsg/tests/testthat/test-client-head.R 2019-06-13 21:08:44.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/tests/testthat/test-client-head.R 2019-11-05 22:02:20.000000000 +0000 @@ -39,3 +39,17 @@ # content is empty expect_equal(aa$content, raw(0)) }) + + +test_that("with auth works", { + skip_on_cran() + + cli <- HttpClient$new(url = hb(), auth = auth("foo", "bar")) + aa <- cli$head("/basic-auth/foo/bar") + + expect_is(aa, "HttpResponse") + expect_equal(aa$method, "head") + expect_true(aa$success()) + expect_equal(aa$status_code, 200) + expect_equal(aa$request$options$userpwd, "foo:bar") +}) diff -Nru r-cran-crul-0.8.4+dfsg/tests/testthat/test-client-verb.R r-cran-crul-0.9.0+dfsg/tests/testthat/test-client-verb.R --- r-cran-crul-0.8.4+dfsg/tests/testthat/test-client-verb.R 2019-06-13 21:08:44.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/tests/testthat/test-client-verb.R 2019-11-05 22:04:17.000000000 +0000 @@ -51,3 +51,16 @@ # fails correctly when unsupported verb passed expect_error(x$verb("foo"), "'verb' must be one of") }) + +test_that("verb: with auth works", { + skip_on_cran() + + cli <- HttpClient$new(url = hb(), auth = auth("foo", "bar")) + aa <- cli$verb("get", "/basic-auth/foo/bar") + + expect_is(aa, "HttpResponse") + expect_equal(aa$method, "get") + expect_true(aa$success()) + expect_equal(aa$status_code, 200) + expect_equal(aa$request$options$userpwd, "foo:bar") +}) diff -Nru r-cran-crul-0.8.4+dfsg/tests/testthat/test-content-type.R r-cran-crul-0.9.0+dfsg/tests/testthat/test-content-type.R --- r-cran-crul-0.8.4+dfsg/tests/testthat/test-content-type.R 1970-01-01 00:00:00.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/tests/testthat/test-content-type.R 2019-11-04 20:29:37.000000000 +0000 @@ -0,0 +1,99 @@ +skip_on_cran() + +res_html <- HttpClient$new(url = "https://httpbin.org")$get() +res_json <- HttpClient$new(url = "https://httpbin.org/json")$get() +res_xml <- HttpClient$new(url = "https://httpbin.org/xml")$get() + +test_that("html", { + ## get the content type + expect_match(res_html$response_headers$`content-type`, + "text/html; charset=utf-8") + + ## check that the content type is text/html + expect_null(res_html$raise_for_ct_html()) + + ## it's def. not json or xml + expect_error(res_html$raise_for_ct_json(), "did not match") + expect_error(res_html$raise_for_ct_xml(), "did not match") + ### behavior: warning + expect_warning(res_html$raise_for_ct_json(behavior = "warning"), + "did not match") + + ## give custom content type + expect_null(res_html$raise_for_ct("text/html")) + expect_error(res_html$raise_for_ct("application/json"), + "did not match") + ### behavior: warning + expect_warning(res_html$raise_for_ct("application/json", behavior = "warning"), + "did not match") + expect_error(res_html$raise_for_ct("foo/bar"), + "type not in allowed set") +}) + +test_that("json", { + ## get the content type + expect_match(res_json$response_headers$`content-type`, + "application/json") + + ## check that the content type is text/html + expect_null(res_json$raise_for_ct_json()) + + ## it's def. not xml + expect_error(res_json$raise_for_ct_xml(), "did not match") + ### behavior: warning + expect_warning(res_json$raise_for_ct_xml(behavior = "warning"), + "did not match") + + ## give custom content type + expect_null(res_json$raise_for_ct("application/json")) + expect_error(res_json$raise_for_ct("application/xml"), + "did not match") + ### behavior: warning + expect_warning(res_json$raise_for_ct("application/xml", behavior = "warning"), + "did not match") +}) + +test_that("xml", { + ## get the content type + expect_match(res_xml$response_headers$`content-type`, "application/xml") + + ## check that the content type is text/html + expect_null(res_xml$raise_for_ct_xml()) + + ## it's def. not json + expect_error(res_xml$raise_for_ct_json(), "did not match") + ### behavior: warning + expect_warning(res_xml$raise_for_ct_json(behavior = "warning"), + "did not match") + + ## give custom content type + expect_null(res_xml$raise_for_ct("application/xml")) + expect_error(res_xml$raise_for_ct("application/json"), + "did not match") + ### behavior: warning + expect_warning(res_xml$raise_for_ct("application/json", behavior = "warning"), + "did not match") + expect_error(res_xml$raise_for_ct("foo/bar"), + "type not in allowed set") +}) + + +test_that("charset works", { + ## check charset in addition to the media type + + ### warning thrown that no charset detected - don't want to fail if no charset given by server + expect_warning(res_json$raise_for_ct_json(charset = "utf-8"), + "no charset detected") + + ### charset should be given with text/html response + #### nothing happens if a match + expect_null(res_html$raise_for_ct_html(charset = "utf-8")) + #### error if not matched + expect_error(res_html$raise_for_ct_html(charset = "utf-16"), + "did not match") + + ### w/ custom type + expect_null(res_html$raise_for_ct("text/html", charset = "utf-8")) + expect_error(res_html$raise_for_ct("text/html", charset = "utf-16"), + "did not match") +}) diff -Nru r-cran-crul-0.8.4+dfsg/vignettes/best-practices-api-packages.Rmd r-cran-crul-0.9.0+dfsg/vignettes/best-practices-api-packages.Rmd --- r-cran-crul-0.8.4+dfsg/vignettes/best-practices-api-packages.Rmd 2019-07-24 22:54:34.000000000 +0000 +++ r-cran-crul-0.9.0+dfsg/vignettes/best-practices-api-packages.Rmd 2019-11-06 17:16:05.000000000 +0000 @@ -33,6 +33,7 @@ cli <- crul::HttpClient$new(url, opts = list(...)) res <- cli$get(path = path, query = args) res$raise_for_status() + res$raise_for_ct_json() res$parse("UTF-8") } ``` @@ -73,7 +74,78 @@ ## Failing with fauxpas -coming soon ... +[fauxpas][] is in Suggests in this package. If you don't have it +installed, no worries, but if you do have it installed, we use +fauxpas. + +There is not much difference with the default `raise_for_status()` +between using fauxpas and not using it. + +However, you can construct your own replacement with fauxpas that +gives you more flexibility in how you deal with HTTP status codes. + +First, make an HTTP request: + +```{r eval=FALSE} +con <- HttpClient$new("https://httpbin.org/status/404") +res <- con$get() +``` + +Then use `fauxpas::find_error_class` to get the correct R6 error +class for the status code, in this case `404` + +```{r eval=FALSE} +x <- fauxpas::find_error_class(res$status_code)$new() +#> +#> behavior: stop +#> message_template: {{reason}} (HTTP {{status}}) +#> message_template_verbose: {{reason}} (HTTP {{status}}).\n - {{message}} +``` + +We can then do one of two things: use `$do()` or `$do_verbose()`. `$do()` +is simpler and gives you thhe same thing `$raise_for_status()` gives, but +allows you to change behavior (stop vs. warning vs. message), and how the +message is formatted. By default we get: + +```{r eval=FALSE} +x$do(res) +#> Error: Not Found (HTTP 404) +``` + +We can change the template using `whisker` templating + +```{r eval=FALSE} +x$do(res, template = "{{status}}\n --> {{reason}}") +#> Error: 404 +#> --> Not Found +``` + +`$do_verbose()` gives you a lot more detail about the status code, possibly more +than you want: + +```{r eval=FALSE} +x$do_verbose(res) +#> Error: Not Found (HTTP 404). +#> - The server has not found anything matching the Request-URI. No indication +#> is given of whether the condition is temporary or permanent. The 410 (Gone) +#> status code SHOULD be used if the server knows, through some internally configurable +#> mechanism, that an old resource is permanently unavailable and has no forwarding +#> address. This status code is commonly used when the server does not wish to +#> reveal exactly why the request has been refused, or when no other response +#> is applicable. +``` + +You can change behavior to either `warning` or `message`: + +```{r eval=FALSE} +x$behavior <- "warning" +x$do(res) +#> Warning message: +#> Not Found (HTTP 404) +x$behavior <- "message" +x$do(res) +#> Not Found (HTTP 404) +``` ## Mocking with webmockr @@ -146,3 +218,4 @@ [webmockr]: https://github.com/ropensci/webmockr [vcr]: https://github.com/ropensci/vcr [httr]: https://github.com/r-lib/httr +[fauxpas]: https://github.com/ropensci/fauxpas