Skip to content

Add R language SDK#177

Merged
clemensv merged 2 commits into
masterfrom
feature/r-sdk
Jul 13, 2026
Merged

Add R language SDK#177
clemensv merged 2 commits into
masterfrom
feature/r-sdk

Conversation

@clemensv

Copy link
Copy Markdown
Contributor

Summary

Adds an official R language SDK (r/) alongside the existing SDKs. It mirrors the Ruby SDK's architecture: because interpreted recursive validation is slow, the R package reuses the proven C validation engine through a thin compiled .Call shim rather than reimplementing the validator in R.

The shim dlopens a prebuilt json_structure shared library downloaded from GitHub Releases on first use (or pointed at by JSONSTRUCTURE_LIB_PATH). Nothing is vendored, and the package is intentionally not on CRAN (CRAN forbids downloading binaries at runtime).

What's included

  • r/src/shim.c, r/src/init.c — dynamic loading (dlopen/dlsym on Unix, LoadLibraryExW on Windows), the C ABI re-declared to match c/include/json_structure/*.h exactly, and columnar .Call result marshaling. Registers 5 routines with R_useDynamicSymbols(FALSE) / R_forceSymbols(TRUE).
  • r/R/ — idiomatic public API: js_validate_schema[_strict](), js_validate_instance[_strict](), is_valid(), js_error_messages() / js_warning_messages(), as.data.frame(), plus lazy library loading, the binary downloader/cache, and a faithful port of Ruby's R-side extension-keyword checks (units, relations, $uses gating, identifiers, tuple $ref, enum typing) — defensively tryCatch-wrapped so it can never turn a passing base result into a failure.
  • r/tests/testthat/ — pure-R tests (run without the binary), binding tests, and shared-corpus conformance tests (mirroring Ruby's test_assets_spec.rb) that skip gracefully when the native library or test-assets/ are unavailable.
  • r/man/ — documentation for every exported function.
  • .github/workflows/r.yml — new CI: R CMD check + conformance across ubuntu/macos (R release + oldrel-1), a Windows shim-compile job, a lint job, and a release-on-tag job that uploads the R source tarball.

Shared prerequisite: C release assets

js_validate_* downloads the prebuilt library from
releases/download/vX.Y.Z/json_structure-<os>-<arch>.tar.gz. The C release job previously published only versioned workflow artifacts, so this URL would 404 for both Ruby and R. This PR makes an additive fix to .github/workflows/c.yml to also attach installer-named archives (json_structure-linux-x86_64, json_structure-macos-arm64, json_structure-windows-x86_64) to the GitHub Release. No existing behavior is removed.

Validation note

There is no R runtime or C compiler in the authoring environment, so the package was validated by rigorous static review plus an independent code review:

  • The re-declared C ABI (structs, severity enum, function signatures) was checked field-by-field against the real headers — exact match; validator structs are minimal { options }, so the stack allocations cannot overflow.
  • Every NAMESPACE export and S3 method has a matching definition and man/ alias.
  • Reviewer-flagged issues were fixed: Windows non-ASCII paths now use LoadLibraryExW; -ldl is linked only on Linux/Solaris (via a GNU-make conditional, declared in SystemRequirements) so macOS builds cleanly; and the augmentation keyword sets were aligned exactly to the Ruby constants.

End-to-end compile/run is exercised by the new CI.

Website

Documentation for the project website is in the companion PR in json-structure/site.


Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

@clemensv

Copy link
Copy Markdown
Contributor Author

Companion website documentation PR: json-structure/site#5

@clemensv

Copy link
Copy Markdown
Contributor Author

Note: the Build and Test (windows-latest) check (from the pre-existing c.yml workflow) is failing due to environmental CI drift unrelated to this PR — the GitHub-hosted Windows image now ships CMake 4.3 / VS 2026, which rejects the repo's -DCMAKE_POLICY_VERSION_MINIMUM=3.5 handling (Invalid CMAKE_POLICY_VERSION_MINIMUM value \\3\\). This job is not modified by this PR (the only c.yml change here is additive, to the tag-only release job) and it also fails on a fresh master build. The R SDK builds the C library only on Ubuntu/macOS (both green) and compiles just the shim on Windows via Rtools, so it does not exercise the broken path. Fixing the C/Windows CMake incompatibility is out of scope for this SDK addition.

@clemensv

Copy link
Copy Markdown
Contributor Author

C workflow windows-latest failure — fixed ✅

Root cause (environmental, surfaced by the new windows-2025-vs2026 runner image shipping CMake 4.3):

The Configure CMake steps run under PowerShell on Windows, which splits unquoted native-command arguments at the decimal point. So:

  • -DCMAKE_POLICY_VERSION_MINIMUM=3.5 → reached CMake as 3 (with .5 as a separate argument)
  • -DJSON_STRUCTURE_VERSION=0.1.0 → reached CMake as 0

CMake 4.3 then rejected configure with Invalid CMAKE_POLICY_VERSION_MINIMUM value "3". Bash on Linux/macOS never splits these, which is why only windows-latest failed. (The version arg being silently truncated to 0 was a latent bug too.)

Fix (99dfc14): quote the -D arguments in all three Configure CMake steps so PowerShell passes them intact. Verified locally that PowerShell now forwards 3.5/0.1.0 unsplit and that CMake configures cleanly.

Result: the C SDK workflow is now fully green on this branch — Build and Test on ubuntu/macos/windows + Valgrind all pass.

Add an official R SDK (r/) mirroring the Ruby SDK architecture: a thin
compiled shim (src/shim.c) loads the prebuilt json_structure C library at
runtime (downloaded from GitHub Releases on first use, or via
JSONSTRUCTURE_LIB_PATH) and exposes idiomatic R validation functions.

- src/shim.c + init.c: dlopen / LoadLibraryExW dynamic loading, ABI
  re-declared to match c/include headers, columnar .Call result marshaling
- R/: public API (js_validate_schema[_strict], js_validate_instance[_strict],
  is_valid, message and data.frame accessors), lazy library loading, binary
  downloader/cache, and a faithful port of Ruby's extension-keyword checks
- tests/testthat: pure-R, binding, and shared-corpus conformance tests that
  skip gracefully when the native library or assets are absent
- man/: documentation for all exported functions
- .github/workflows/r.yml: multi-OS R CMD check + conformance + release
- README.md, SDK-GUIDELINES.md: document the new SDK

The C release workflow change needed to publish the prebuilt libraries the
R (and Ruby) downloaders consume is handled separately in #178.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@clemensv

Copy link
Copy Markdown
Contributor Author

Reorg: this PR no longer touches c.yml. The Windows CMake configure fix and the release-asset attachment now live in #178, so this is a pure R-package PR. It no longer triggers the C SDK workflow; the R SDK workflow covers it end-to-end.

Addresses the findings from an R-community best-practices review of the
R SDK. No changes to the shared C library (c/), so the C workflow is not
triggered.

Loading & consent
- Never download the native library implicitly. .onLoad no longer loads the
  engine eagerly; loading is deferred to first use and resolves only an
  env-override (JSONSTRUCTURE_LIB_PATH) or the local cache. When absent, the
  user is asked for consent (interactive) or given an actionable error
  (non-interactive/CRAN/CI) instead of a silent network fetch.
- install_jsonstructure_binary() remains the explicit, user-invoked download.

Download integrity
- Verify a SHA-256 checksum before extracting a downloaded archive. The
  expected digest resolves from sha256=, JSONSTRUCTURE_BINARY_SHA256, or a
  shipped inst/checksums.dcf manifest; mismatch aborts, and when no digest is
  available the computed one is reported so it can be pinned.
- Checksums are computed via tools::sha256sum with openssl/digest fallbacks
  (added to Suggests). Hash comparison is attribute-safe across backends.
- Extraction prefers the exact expected library name so archive contents
  cannot silently substitute a differently named library.

Native shim
- Load the engine with reduced symbol scope: RTLD_LOCAL on Unix and
  LOAD_LIBRARY_SEARCH_* (with a safe fallback) on Windows.
- Guarantee js_result_cleanup() via R_UnwindProtect when marshalling results,
  so a failed R allocation cannot leak native memory.
- Add an optional ABI-major version probe and a binding_version() routine.

Error handling & docs
- Replace stopifnot() in the result accessors with explicit stop(call.=FALSE).
- Regenerate NAMESPACE/man with roxygen2 (markdown); export the previously
  missing js_warning_messages plus new jsonstructure_binary_version.
- Convert \dontrun{} examples that need the engine to @examplesIf guards so
  they run when the binding is available and are skipped otherwise.

Testing & tooling
- skip_if_no_binding() is network-free and skip_on_cran(): it only uses an
  already-resolved library and never downloads.
- Add .lintr config; document the lintr CI step as advisory.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@clemensv clemensv merged commit f365d3f into master Jul 13, 2026
11 of 14 checks passed
@clemensv clemensv deleted the feature/r-sdk branch July 13, 2026 14:48
clemensv added a commit to json-structure/site that referenced this pull request Jul 14, 2026
Document the new R language SDK alongside the other SDKs:

- sdks/r.md: dedicated R SDK page (install via remotes::install_github with
  subdir = 'r', not on CRAN, usage snippet, JSONSTRUCTURE_LIB_PATH note)
- _layouts/sdk-index.html: add R to the sidebar nav and an R SDK card
- index.md: add an R row to the SDKs table

Companion to json-structure/sdk#177.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant