From 7def399d4b1ea8760b6f73c622d263df39ee5696 Mon Sep 17 00:00:00 2001 From: "dyfan.jones" Date: Mon, 1 Dec 2025 13:34:52 +0000 Subject: [PATCH 1/4] chore(paws.common): bump version to 0.8.8 and add C++14 requirement - Add explicit C++14 standard requirement via Makevars files - Update DESCRIPTION with R >= 4.1.0 dependency and C++14 system requirement - Expand CI matrix to test against older R versions (oldrel-1 through oldrel-4) - Update NEWS.md with version 0.8.8 release notes --- .github/workflows/unit-tests.yml | 16 +++++++++++++--- paws.common/DESCRIPTION | 5 +++-- paws.common/NEWS.md | 3 +++ paws.common/src/Makevars | 1 + paws.common/src/Makevars.win | 1 + 5 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 paws.common/src/Makevars create mode 100644 paws.common/src/Makevars.win diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index fe0a2568c..7df511a69 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -18,12 +18,22 @@ jobs: fail-fast: false matrix: config: - - { os: "ubuntu-latest", R: "latest" } - - { os: "macOS-latest", R: "latest" } + + # Check latest R versions + - { os: "ubuntu-latest", R: "latest" } + - { os: "macOS-latest", R: "latest" } - { os: "windows-latest", R: "latest" } - - { os: "ubuntu-latest", R: "devel" } + - { os: "ubuntu-latest", R: "devel" } - { os: "windows-latest", R: "devel" } + # Check older versions of R + - { os: "ubuntu-latest", R: 'oldrel-1'} + - { os: "ubuntu-latest", R: 'oldrel-2'} + - { os: "ubuntu-latest", R: 'oldrel-3'} + - { os: "ubuntu-latest", R: 'oldrel-4'} + + - { os: "windows-latest", R: "oldrel-4" } + env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/paws.common/DESCRIPTION b/paws.common/DESCRIPTION index 28316a776..f69348a76 100644 --- a/paws.common/DESCRIPTION +++ b/paws.common/DESCRIPTION @@ -1,7 +1,7 @@ Package: paws.common Type: Package Title: Paws Low-Level Amazon Web Services API -Version: 0.8.7 +Version: 0.8.8 Authors@R: c( person("David", "Kretch", email = "david.kretch@gmail.com", role = "aut"), person("Adam", "Banker", email = "adam.banker39@gmail.com", role = "aut"), @@ -14,6 +14,7 @@ Description: Functions for making low-level API requests to Amazon Web Services higher-level interfaces to individual services, such as Simple Storage Service (S3). License: Apache License (>= 2.0) +Depends: R (>= 4.1.0) URL: https://github.com/paws-r/paws, https://paws-r.r-universe.dev/paws.common, https://www.paws-r-sdk.com BugReports: https://github.com/paws-r/paws/issues Encoding: UTF-8 @@ -37,7 +38,7 @@ Suggests: withr, rstudioapi, testthat (>= 3.0.0) -SystemRequirements: pandoc (>= 1.12.3) - http://pandoc.org +SystemRequirements: C++14, pandoc (>= 1.12.3) - http://pandoc.org Roxygen: list(markdown = TRUE, roclets = c("rd", "namespace", "collate")) RoxygenNote: 7.3.3 Collate: diff --git a/paws.common/NEWS.md b/paws.common/NEWS.md index 3378a4c5e..1628c2311 100644 --- a/paws.common/NEWS.md +++ b/paws.common/NEWS.md @@ -1,3 +1,6 @@ +# paws.common 0.8.8 +* fix C++ standard compatibility for older R versions by explicitly requiring C++14 and R >= 4.1.0 (#957). Thanks to @detule for raising the issue. + # paws.common 0.8.7 * fix timezone handling in bearer token tests (#955). Thanks to @kyleam for raising issue. diff --git a/paws.common/src/Makevars b/paws.common/src/Makevars new file mode 100644 index 000000000..f684e9a96 --- /dev/null +++ b/paws.common/src/Makevars @@ -0,0 +1 @@ +CXX_STD = CXX14 diff --git a/paws.common/src/Makevars.win b/paws.common/src/Makevars.win new file mode 100644 index 000000000..f684e9a96 --- /dev/null +++ b/paws.common/src/Makevars.win @@ -0,0 +1 @@ +CXX_STD = CXX14 From 4a45b5f18cb79ad0259dfc5133f8423fe99c85a6 Mon Sep 17 00:00:00 2001 From: "dyfan.jones" Date: Mon, 1 Dec 2025 13:53:39 +0000 Subject: [PATCH 2/4] chore(paws.common): refactor C++ code to use C++11 standard and remove explicit C++14 requirement --- paws.common/DESCRIPTION | 2 +- paws.common/NEWS.md | 2 +- paws.common/src/Makevars | 1 - paws.common/src/Makevars.win | 1 - paws.common/src/json_builder.cpp | 2 +- 5 files changed, 3 insertions(+), 5 deletions(-) delete mode 100644 paws.common/src/Makevars delete mode 100644 paws.common/src/Makevars.win diff --git a/paws.common/DESCRIPTION b/paws.common/DESCRIPTION index f69348a76..45a13b7a9 100644 --- a/paws.common/DESCRIPTION +++ b/paws.common/DESCRIPTION @@ -38,7 +38,7 @@ Suggests: withr, rstudioapi, testthat (>= 3.0.0) -SystemRequirements: C++14, pandoc (>= 1.12.3) - http://pandoc.org +SystemRequirements: pandoc (>= 1.12.3) - http://pandoc.org Roxygen: list(markdown = TRUE, roclets = c("rd", "namespace", "collate")) RoxygenNote: 7.3.3 Collate: diff --git a/paws.common/NEWS.md b/paws.common/NEWS.md index 1628c2311..1c012898a 100644 --- a/paws.common/NEWS.md +++ b/paws.common/NEWS.md @@ -1,5 +1,5 @@ # paws.common 0.8.8 -* fix C++ standard compatibility for older R versions by explicitly requiring C++14 and R >= 4.1.0 (#957). Thanks to @detule for raising the issue. +* fix C++ compilation issues on older R versions by refactoring code to use C++11 standard and requiring R >= 4.1.0 (#957). Thanks to @detule for raising the issue. # paws.common 0.8.7 * fix timezone handling in bearer token tests (#955). Thanks to @kyleam for raising issue. diff --git a/paws.common/src/Makevars b/paws.common/src/Makevars deleted file mode 100644 index f684e9a96..000000000 --- a/paws.common/src/Makevars +++ /dev/null @@ -1 +0,0 @@ -CXX_STD = CXX14 diff --git a/paws.common/src/Makevars.win b/paws.common/src/Makevars.win deleted file mode 100644 index f684e9a96..000000000 --- a/paws.common/src/Makevars.win +++ /dev/null @@ -1 +0,0 @@ -CXX_STD = CXX14 diff --git a/paws.common/src/json_builder.cpp b/paws.common/src/json_builder.cpp index 19c86372a..9062c3824 100644 --- a/paws.common/src/json_builder.cpp +++ b/paws.common/src/json_builder.cpp @@ -427,7 +427,7 @@ inline std::string json_build_map(SEXP values) // Sort the vector based on the string key in each pair std::sort(named_elements.begin(), named_elements.end(), - [](const auto &a, const auto &b) + [](const std::pair &a, const std::pair &b) { return a.first < b.first; }); From 0b1eed450a88f091698e04950b2ab51e3e918971 Mon Sep 17 00:00:00 2001 From: "dyfan.jones" Date: Fri, 5 Dec 2025 16:52:01 +0000 Subject: [PATCH 3/4] fix(paws.common): ensure UTF-8 encoding in JSON string handling Replace CHAR() with Rf_translateCharUTF8() for proper UTF-8 support in json_builder.cpp and add explicit UTF-8 encoding in json_escape.cpp --- paws.common/src/json_builder.cpp | 14 +++++++------- paws.common/src/json_escape.cpp | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/paws.common/src/json_builder.cpp b/paws.common/src/json_builder.cpp index 9062c3824..d2ea08ca1 100644 --- a/paws.common/src/json_builder.cpp +++ b/paws.common/src/json_builder.cpp @@ -67,7 +67,7 @@ inline std::string tag_get(SEXP object, const char *tag) case STRSXP: if (Rf_length(val) > 0) { - return std::string(CHAR(STRING_ELT(val, 0))); + return std::string(Rf_translateCharUTF8(STRING_ELT(val, 0))); } break; case LGLSXP: @@ -151,17 +151,17 @@ std::string safe_as_string(SEXP x) switch (type) { case STRSXP: - // Directly return string for character SEXPs - return std::string(CHAR(STRING_ELT(x, 0))); + // Directly return string for character SEXPs, ensuring UTF-8 encoding + return std::string(Rf_translateCharUTF8(STRING_ELT(x, 0))); case INTSXP: case REALSXP: case LGLSXP: case RAWSXP: // Coerce to character and then return string for numeric, logical, raw - return std::string(CHAR(STRING_ELT(Rf_coerceVector(x, STRSXP), 0))); + return std::string(Rf_translateCharUTF8(STRING_ELT(Rf_coerceVector(x, STRSXP), 0))); default: // Coerce any other type to character as a fallback - return std::string(CHAR(STRING_ELT(Rf_coerceVector(x, STRSXP), 0))); + return std::string(Rf_translateCharUTF8(STRING_ELT(Rf_coerceVector(x, STRSXP), 0))); } } @@ -360,7 +360,7 @@ std::string json_build_structure(SEXP values) val = uuid_val; // Use the newly created UUID SEXP } - std::string key = CHAR(STRING_ELT(names, i)); // Get the field name + std::string key = Rf_translateCharUTF8(STRING_ELT(names, i)); // Get the field name std::string loc_name = tag_get(val, "locationName"); // Check for locationName tag std::string name = loc_name.empty() ? key : loc_name; // Use locationName if present @@ -420,7 +420,7 @@ inline std::string json_build_map(SEXP values) // Populate the vector with key-value pairs from the R object for (R_xlen_t i = 0; i < n; ++i) { - std::string key = CHAR(STRING_ELT(names_sexp, i)); + std::string key = Rf_translateCharUTF8(STRING_ELT(names_sexp, i)); SEXP val = VECTOR_ELT(values, i); named_elements.push_back({key, val}); } diff --git a/paws.common/src/json_escape.cpp b/paws.common/src/json_escape.cpp index fbc1d9ba4..a90cf4cfd 100644 --- a/paws.common/src/json_escape.cpp +++ b/paws.common/src/json_escape.cpp @@ -143,6 +143,7 @@ CharacterVector json_convert_string(CharacterVector x) for (int i = 0; i < n; ++i) { String cur = x[i]; + cur.set_encoding(CE_UTF8); // Ensure UTF-8 encoding out[i] = json_escape(cur); } return out; From 3a570ee07b3696cb3146c51050a6a24b3f8fc03b Mon Sep 17 00:00:00 2001 From: "dyfan.jones" Date: Sat, 6 Dec 2025 11:56:19 +0000 Subject: [PATCH 4/4] refactor(paws.common): split json_build_any into internal and exported functions with UTF-8 encoding --- paws.common/src/RcppExports.cpp | 2 +- paws.common/src/json_builder.cpp | 42 +++++++++++++++++++------------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/paws.common/src/RcppExports.cpp b/paws.common/src/RcppExports.cpp index 3c7a0ca97..7eee77425 100644 --- a/paws.common/src/RcppExports.cpp +++ b/paws.common/src/RcppExports.cpp @@ -68,7 +68,7 @@ BEGIN_RCPP END_RCPP } // json_build_any -std::string json_build_any(SEXP values); +CharacterVector json_build_any(SEXP values); RcppExport SEXP _paws_common_json_build_any(SEXP valuesSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; diff --git a/paws.common/src/json_builder.cpp b/paws.common/src/json_builder.cpp index d2ea08ca1..969e19ebd 100644 --- a/paws.common/src/json_builder.cpp +++ b/paws.common/src/json_builder.cpp @@ -135,8 +135,8 @@ std::string type(SEXP object) } // -------------------- Forward declaration -------------------- -// This function will be defined later and called recursively -std::string json_build_any(SEXP values); +// Internal function that builds JSON and returns std::string (used internally) +std::string json_build_internal(SEXP values); // -------------------- Helper function to safely convert to string -------------------- // This function converts an SEXP to a string representation, handling various R types. @@ -255,7 +255,7 @@ std::string json_build_list(SEXP values) for (R_xlen_t i = 0; i < n; i++) { // Recursively build JSON for each element - std::string elem_json = json_build_any(VECTOR_ELT(values, i)); + std::string elem_json = json_build_internal(VECTOR_ELT(values, i)); // Only add non-empty elements, excluding "empty" JSON array/object strings if (!elem_json.empty() && elem_json != "[]" && elem_json != "{}") { @@ -325,7 +325,7 @@ std::string json_build_structure(SEXP values) } // Recursively build JSON for the payload's value - return json_build_any(payload_val); + return json_build_internal(payload_val); } SEXP names = Rf_getAttrib(values, symbol_cache.names_sym); @@ -364,7 +364,7 @@ std::string json_build_structure(SEXP values) std::string loc_name = tag_get(val, "locationName"); // Check for locationName tag std::string name = loc_name.empty() ? key : loc_name; // Use locationName if present - std::string json_val = json_build_any(val); // Recursively build JSON for field value + std::string json_val = json_build_internal(val); // Recursively build JSON for field value // Only add non-empty fields, excluding "empty" JSON array/object strings if (!json_val.empty() && json_val != "[]" && json_val != "{}") { @@ -442,7 +442,7 @@ inline std::string json_build_map(SEXP values) SEXP val = element.second; // Recursively build JSON for the value - std::string json_val = json_build_any(val); + std::string json_val = json_build_internal(val); // Only add non-empty pairs to the final JSON string if (!json_val.empty() && json_val != "[]" && json_val != "{}") @@ -469,17 +469,8 @@ inline std::string json_build_map(SEXP values) return result; } -/** - * @brief Build Json Strings Using AWS Attributes for JSON Template - * - * @param object A list to be parsed into JSON string - * - * @return a JSON String - */ -//' @useDynLib paws.common _paws_common_json_build_any -//' @importFrom Rcpp evalCpp -// [[Rcpp::export]] -std::string json_build_any(SEXP values) +// Internal implementation that returns std::string (used by recursive calls) +std::string json_build_internal(SEXP values) { // Determine the effective type of the R object for JSON building std::string t = type(values); @@ -494,3 +485,20 @@ std::string json_build_any(SEXP values) // Default to scalar if none of the above, or if `type` returns "scalar" return json_build_scalar(values); } + +/** + * @brief Build Json Strings Using AWS Attributes for JSON Template + * + * @param object A list to be parsed into JSON string + * + * @return a JSON String + */ +//' @useDynLib paws.common _paws_common_json_build_any +//' @importFrom Rcpp evalCpp +// [[Rcpp::export]] +CharacterVector json_build_any(SEXP values) +{ + // Call internal implementation and UTF-8 encode result + std::string result = json_build_internal(values); + return CharacterVector::create(String(result, CE_UTF8)); +}