jsonrpc-middleware 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.aiignore +6 -1
  3. data/.claude/agents/entire-search.md +25 -0
  4. data/.claude/agents/rbs-specialist.md +89 -0
  5. data/.claude/settings.json +84 -0
  6. data/.devcontainer/devcontainer.json +17 -0
  7. data/.dockerignore +16 -0
  8. data/.entire/.gitignore +5 -0
  9. data/.entire/settings.json +4 -0
  10. data/.rubocop.yml +26 -1
  11. data/.tool-versions +1 -1
  12. data/.yard-lint.yml +283 -0
  13. data/AGENTS.md +142 -0
  14. data/CHANGELOG.md +19 -0
  15. data/CLAUDE.md +2 -113
  16. data/Dockerfile +144 -0
  17. data/README.md +9 -17
  18. data/Rakefile +62 -11
  19. data/examples/procedures.rb +3 -1
  20. data/examples/rack/Gemfile.lock +4 -4
  21. data/examples/rack-echo/Gemfile.lock +4 -4
  22. data/examples/rails/Gemfile.lock +12 -5
  23. data/examples/rails/config/initializers/jsonrpc.rb +1 -1
  24. data/examples/rails-routing-dsl/config.ru +5 -5
  25. data/examples/rails-single-file/config.ru +1 -1
  26. data/examples/rails-single-file-routing/config.ru +1 -1
  27. data/examples/sinatra-classic/Gemfile.lock +11 -4
  28. data/examples/sinatra-modular/Gemfile.lock +11 -4
  29. data/lib/jsonrpc/batch_request.rb +8 -11
  30. data/lib/jsonrpc/batch_response.rb +6 -8
  31. data/lib/jsonrpc/configuration.rb +30 -4
  32. data/lib/jsonrpc/error.rb +7 -8
  33. data/lib/jsonrpc/errors/internal_error.rb +2 -0
  34. data/lib/jsonrpc/errors/invalid_params_error.rb +2 -0
  35. data/lib/jsonrpc/errors/invalid_request_error.rb +2 -0
  36. data/lib/jsonrpc/errors/method_not_found_error.rb +2 -0
  37. data/lib/jsonrpc/errors/parse_error.rb +2 -0
  38. data/lib/jsonrpc/helpers.rb +6 -0
  39. data/lib/jsonrpc/middleware.rb +12 -11
  40. data/lib/jsonrpc/notification.rb +7 -8
  41. data/lib/jsonrpc/parser.rb +13 -12
  42. data/lib/jsonrpc/railtie/batch_constraint.rb +1 -0
  43. data/lib/jsonrpc/railtie/mapper_extension.rb +2 -2
  44. data/lib/jsonrpc/railtie/method_constraint.rb +9 -0
  45. data/lib/jsonrpc/railtie/routes_dsl.rb +10 -15
  46. data/lib/jsonrpc/railtie.rb +2 -0
  47. data/lib/jsonrpc/response.rb +2 -2
  48. data/lib/jsonrpc/types.rb +1 -1
  49. data/lib/jsonrpc/validator.rb +14 -4
  50. data/lib/jsonrpc/version.rb +1 -1
  51. data/lib/jsonrpc.rb +3 -0
  52. data/rbs_collection.lock.yaml +476 -0
  53. data/rbs_collection.yaml +21 -0
  54. data/sig/jsonrpc/batch_request.rbs +17 -0
  55. data/sig/jsonrpc/batch_response.rbs +17 -0
  56. data/sig/jsonrpc/configuration.rbs +18 -0
  57. data/sig/jsonrpc/error.rbs +17 -0
  58. data/sig/jsonrpc/errors/internal_error.rbs +5 -0
  59. data/sig/jsonrpc/errors/invalid_params_error.rbs +5 -0
  60. data/sig/jsonrpc/errors/invalid_request_error.rbs +5 -0
  61. data/sig/jsonrpc/errors/method_not_found_error.rbs +5 -0
  62. data/sig/jsonrpc/errors/parse_error.rbs +5 -0
  63. data/sig/jsonrpc/middleware.rbs +20 -3
  64. data/sig/jsonrpc/notification.rbs +15 -0
  65. data/sig/jsonrpc/parser.rbs +7 -1
  66. data/sig/jsonrpc/request.rbs +18 -0
  67. data/sig/jsonrpc/response.rbs +19 -0
  68. data/sig/jsonrpc/validator.rbs +8 -0
  69. data/sig/jsonrpc.rbs +3 -156
  70. data/sig/multi_json.rbs +17 -0
  71. data/sig/type_definitions.rbs +11 -0
  72. data/sig/zeitwerk.rbs +10 -0
  73. metadata +34 -12
  74. data/.claude/commands/document.md +0 -105
  75. data/.claude/commands/gemfile/update.md +0 -52
  76. data/.claude/commands/test.md +0 -561
  77. data/.claude/docs/yard.md +0 -602
  78. data/.claude/settings.local.json +0 -15
  79. data/.yardstick.yml +0 -22
data/AGENTS.md ADDED
@@ -0,0 +1,142 @@
1
+ # AGENTS.md
2
+
3
+ This file provides guidance to AI agents when working with code in this repository.
4
+
5
+ ## Development Commands
6
+
7
+ ### Testing
8
+ - `bundle exec rspec` - Run all tests
9
+ - `bundle exec rspec spec/path/to/spec.rb` - Run specific test file
10
+ - `bundle exec rake coverage` - Run tests with test coverage and write a LLM-friendly report in `coverage/report.md`
11
+
12
+ ### Code Quality
13
+ - `bundle exec rake qa` - Complete quality check (tests, linting, security, docs)
14
+ - `bundle exec rubocop` - Run Ruby linter
15
+ - `bundle exec rubocop --autocorrect` - Auto-fix safe Ruby style issues
16
+ - `bundle exec rubocop --autocorrect-all` - Auto-fix all Ruby style issues
17
+ - `bundle exec steep check` - Run type checking with Steep/RBS
18
+
19
+ ### Security & Dependencies
20
+ - `bundle exec rake bundle:audit:check` - Check for vulnerable dependencies
21
+ - `bundle exec rake bundle:audit:update` - Update vulnerability database
22
+
23
+ ### Documentation
24
+ - `bundle exec rake yard` - Generate YARD documentation
25
+ - `bundle exec rake yard:format` - Format YARD comments in code
26
+ - `bundle exec rake yard:lint` - Lint YARD comments for issues
27
+
28
+ ### Build & Release
29
+ - `bundle exec rake build` - Build gem package
30
+ - `bundle exec rake install` - Install gem locally
31
+ - `bin/console` - Interactive console with gem loaded
32
+
33
+ ### Containerized development (cloud agents)
34
+
35
+ A ready-to-develop container is defined by `Dockerfile` and `.devcontainer/devcontainer.json`
36
+ (Ruby 4.0.5 + the full dev toolchain). Cloud agent platforms / Codespaces consume the
37
+ devcontainer directly. Locally:
38
+
39
+ - `docker build -t jsonrpc-middleware-dev .`
40
+ - `docker run --rm -it -v "$PWD":/app jsonrpc-middleware-dev bash`
41
+ - Inside the container: `bundle exec rake qa`, `bundle exec rspec`, `bundle exec steep check`.
42
+
43
+ To run an example app on Linux, first add Linux platforms to that example's lockfile:
44
+ `cd examples/<name> && bundle lock --add-platform x86_64-linux aarch64-linux && bundle install`.
45
+
46
+ ## Architecture Overview
47
+
48
+ ### Core Components
49
+
50
+ **JSONRPC Module** (`lib/jsonrpc.rb`)
51
+ - Main entry point with configuration DSL
52
+ - Uses Zeitwerk for autoloading with specific inflection rules
53
+ - Singleton Configuration pattern for procedure definitions
54
+
55
+ **Middleware** (`lib/jsonrpc/middleware.rb`)
56
+ - Rack middleware implementing JSON-RPC 2.0 specification
57
+ - Handles parsing, validation, and error responses
58
+ - Supports single requests, notifications, and batch operations
59
+ - Complex batch processing with mixed error/success handling
60
+
61
+ **Parser** (`lib/jsonrpc/parser.rb`)
62
+ - Converts JSON strings into Request/Notification/BatchRequest objects
63
+ - Handles malformed JSON and invalid request structures
64
+
65
+ **Validator** (`lib/jsonrpc/validator.rb`)
66
+ - Validates requests against procedure definitions using dry-validation
67
+ - Supports both positional and named parameters
68
+ - Handles method existence and parameter validation
69
+
70
+ **Helpers** (`lib/jsonrpc/helpers.rb`)
71
+ - Framework-agnostic helper methods for apps
72
+ - Provides convenience methods for checking request types
73
+ - Response generation helpers for different scenarios
74
+
75
+ ### Request/Response Objects
76
+ - `Request` - Standard JSON-RPC request with id
77
+ - `Notification` - Request without id (no response expected)
78
+ - `BatchRequest` - Collection of requests/notifications
79
+ - `Response` - JSON-RPC response with result or error
80
+ - `BatchResponse` - Collection of responses
81
+
82
+ ### Error System
83
+ Structured error hierarchy in `lib/jsonrpc/errors/`:
84
+ - `ParseError` (-32700) - Invalid JSON
85
+ - `InvalidRequestError` (-32600) - Invalid request object
86
+ - `MethodNotFoundError` (-32601) - Method not found
87
+ - `InvalidParamsError` (-32602) - Invalid parameters
88
+ - `InternalError` (-32603) - Server error
89
+
90
+ ### Configuration DSL
91
+ ```ruby
92
+ JSONRPC.configure do
93
+ procedure('method_name') do
94
+ params do
95
+ required(:param).value(:type)
96
+ end
97
+
98
+ rule(:param) do
99
+ # Custom validation rules
100
+ end
101
+ end
102
+ end
103
+ ```
104
+
105
+ ## Type System
106
+
107
+ The project uses RBS (Ruby Type Signatures) with Steep for type checking:
108
+ - Type definitions in `sig/` directory
109
+ - Run `bundle exec steep check` for type validation
110
+ - Generate types with `bundle exec typeprof FILENAME`
111
+
112
+ ### Third-party RBS signatures (RBS collection)
113
+
114
+ Signatures for third-party gems are managed with the RBS collection:
115
+ - `rbs_collection.yaml` declares the requested gems (activesupport, dry-struct,
116
+ dry-validation, multi_json, zeitwerk) and the `ruby/gem_rbs_collection` source.
117
+ - `rbs_collection.lock.yaml` pins the resolved versions and revisions (committed).
118
+ - `bundle exec rbs collection install` downloads them into `.gem_rbs_collection/`
119
+ (gitignored). Run this after cloning, before `bundle exec steep check`.
120
+ - `bundle exec rbs collection update` re-resolves and refreshes the lock file when
121
+ adding a gem to `rbs_collection.yaml` or bumping sources.
122
+
123
+ Steep loads the collection automatically via the lock file. A few gems still have
124
+ hand-written stubs in `sig/` (e.g. `sig/zeitwerk.rbs`, `sig/multi_json.rbs`) where the
125
+ collection's coverage is incomplete for this project's usage.
126
+
127
+ ## Examples
128
+
129
+ Comprehensive examples in `examples/` directory showing integration with:
130
+ - Pure Rack applications
131
+ - Sinatra (classic and modular)
132
+ - Single-file applications with bundler/inline
133
+
134
+ Each example implements calculator operations (add, subtract, multiply, divide) demonstrating different usage patterns.
135
+
136
+ ## Testing Strategy
137
+
138
+ - RSpec with comprehensive test coverage
139
+ - Factory Bot for test data generation
140
+ - Rack::Test for middleware testing
141
+ - SimpleCov for coverage reporting with multiple formatters
142
+ - Examples include both unit and integration tests
data/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.7.0] - 2026-06-01
9
+
10
+ ### Added
11
+ - Configurable `Logger` support for JSON-RPC components
12
+ - `JSONRPC.configuration.logger` (defaults to `Logger.new($stdout, progname: 'JSONRPC')`)
13
+ - `JSONRPC::Middleware` accepts a `:logger` option and uses it for internal-error and validation logging
14
+ - Development container support (`.devcontainer`, `Dockerfile`, `.dockerignore`)
15
+
16
+ ### Changed
17
+ - Updated Ruby to v4.0.5
18
+ - Updated Bundler to v4.0.8
19
+ - Updated runtime dependencies
20
+ - `multi_json` to `~> 1.21` (removed the local `JSON::Ext::Generator::State#except` workaround now that the upstream fix has shipped)
21
+ - `zeitwerk` to `~> 2.8`
22
+ - Replaced the README architecture diagram (Mermaid) with an inline SVG
23
+ - Replaced yardstick/yard-junk with yard-lint for documentation linting
24
+ - Achieved 100% test coverage
25
+
8
26
  ## [0.6.0] - 2025-09-17
9
27
 
10
28
  ### Added
@@ -158,6 +176,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
158
176
  - Helper methods for request and response processing
159
177
  - Examples for basic and advanced usage scenarios
160
178
 
179
+ [0.7.0]: https://github.com/wilsonsilva/jsonrpc-middleware/compare/v0.6.0...v0.7.0
161
180
  [0.6.0]: https://github.com/wilsonsilva/jsonrpc-middleware/compare/v0.5.0...v0.6.0
162
181
  [0.5.0]: https://github.com/wilsonsilva/jsonrpc-middleware/compare/v0.4.0...v0.5.0
163
182
  [0.4.0]: https://github.com/wilsonsilva/jsonrpc-middleware/compare/v0.3.0...v0.4.0
data/CLAUDE.md CHANGED
@@ -1,114 +1,3 @@
1
- # CLAUDE.md
1
+ # Claude Code Instructions
2
2
 
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## Development Commands
6
-
7
- ### Testing
8
- - `bundle exec rspec` - Run all tests
9
- - `bundle exec rspec spec/path/to/spec.rb` - Run specific test file
10
- - `rake coverage` - Run tests with coverage report (opens in browser)
11
-
12
- ### Code Quality
13
- - `bundle exec rake qa` - Complete quality check (tests, linting, security, docs)
14
- - `bundle exec rubocop` - Run Ruby linter
15
- - `bundle exec rubocop --autocorrect` - Auto-fix safe Ruby style issues
16
- - `bundle exec rubocop --autocorrect-all` - Auto-fix all Ruby style issues
17
- - `bundle exec steep check` - Run type checking with Steep/RBS
18
-
19
- ### Security & Dependencies
20
- - `bundle exec rake bundle:audit:check` - Check for vulnerable dependencies
21
- - `bundle exec rake bundle:audit:update` - Update vulnerability database
22
-
23
- ### Documentation
24
- - `bundle exec rake yard` - Generate YARD documentation
25
- - `bundle exec rake yard:format` - Format YARD comments in code
26
- - `bundle exec rake verify_measurements` - Verify 100% documentation coverage
27
-
28
- ### Build & Release
29
- - `bundle exec rake build` - Build gem package
30
- - `bundle exec rake install` - Install gem locally
31
- - `bin/console` - Interactive console with gem loaded
32
-
33
- ## Architecture Overview
34
-
35
- ### Core Components
36
-
37
- **JSONRPC Module** (`lib/jsonrpc.rb`)
38
- - Main entry point with configuration DSL
39
- - Uses Zeitwerk for autoloading with specific inflection rules
40
- - Singleton Configuration pattern for procedure definitions
41
-
42
- **Middleware** (`lib/jsonrpc/middleware.rb`)
43
- - Rack middleware implementing JSON-RPC 2.0 specification
44
- - Handles parsing, validation, and error responses
45
- - Supports single requests, notifications, and batch operations
46
- - Complex batch processing with mixed error/success handling
47
-
48
- **Parser** (`lib/jsonrpc/parser.rb`)
49
- - Converts JSON strings into Request/Notification/BatchRequest objects
50
- - Handles malformed JSON and invalid request structures
51
-
52
- **Validator** (`lib/jsonrpc/validator.rb`)
53
- - Validates requests against procedure definitions using dry-validation
54
- - Supports both positional and named parameters
55
- - Handles method existence and parameter validation
56
-
57
- **Helpers** (`lib/jsonrpc/helpers.rb`)
58
- - Framework-agnostic helper methods for apps
59
- - Provides convenience methods for checking request types
60
- - Response generation helpers for different scenarios
61
-
62
- ### Request/Response Objects
63
- - `Request` - Standard JSON-RPC request with id
64
- - `Notification` - Request without id (no response expected)
65
- - `BatchRequest` - Collection of requests/notifications
66
- - `Response` - JSON-RPC response with result or error
67
- - `BatchResponse` - Collection of responses
68
-
69
- ### Error System
70
- Structured error hierarchy in `lib/jsonrpc/errors/`:
71
- - `ParseError` (-32700) - Invalid JSON
72
- - `InvalidRequestError` (-32600) - Invalid request object
73
- - `MethodNotFoundError` (-32601) - Method not found
74
- - `InvalidParamsError` (-32602) - Invalid parameters
75
- - `InternalError` (-32603) - Server error
76
-
77
- ### Configuration DSL
78
- ```ruby
79
- JSONRPC.configure do
80
- procedure('method_name') do
81
- params do
82
- required(:param).value(:type)
83
- end
84
-
85
- rule(:param) do
86
- # Custom validation rules
87
- end
88
- end
89
- end
90
- ```
91
-
92
- ## Type System
93
-
94
- The project uses RBS (Ruby Type Signatures) with Steep for type checking:
95
- - Type definitions in `sig/` directory
96
- - Run `bundle exec steep check` for type validation
97
- - Generate types with `bundle exec typeprof FILENAME`
98
-
99
- ## Examples
100
-
101
- Comprehensive examples in `examples/` directory showing integration with:
102
- - Pure Rack applications
103
- - Sinatra (classic and modular)
104
- - Single-file applications with bundler/inline
105
-
106
- Each example implements calculator operations (add, subtract, multiply, divide) demonstrating different usage patterns.
107
-
108
- ## Testing Strategy
109
-
110
- - RSpec with comprehensive test coverage
111
- - Factory Bot for test data generation
112
- - Rack::Test for middleware testing
113
- - SimpleCov for coverage reporting with multiple formatters
114
- - Examples include both unit and integration tests
3
+ See [AGENTS.md](./AGENTS.md) for all contribution guidelines.
data/Dockerfile ADDED
@@ -0,0 +1,144 @@
1
+ # syntax=docker/dockerfile:1
2
+ #
3
+ # Development image for the jsonrpc-middleware gem.
4
+ # ============================================================================
5
+ # This image is a *development environment* for AI agents (and humans) who work
6
+ # ON the gem — not a *production runtime* that ships the gem to end users.
7
+ #
8
+ # That single fact inverts most of the usual Docker advice:
9
+ # - We do NOT use a multi-stage build or a distroless final stage. The whole
10
+ # point is to keep the full toolchain available — compilers, git, and every
11
+ # development gem (RSpec, RuboCop, Steep, YARD, bundler-audit, Guard, ...) —
12
+ # so an agent can run the test/lint/type-check/audit suite and rebuild or
13
+ # install native gems on demand. Slimming those away would defeat the image.
14
+ # - Optimising for a tiny final image is therefore a non-goal; optimising for
15
+ # "everything an agent needs is already here and reproducible" is the goal.
16
+ # ============================================================================
17
+
18
+ # Why this exact base:
19
+ # - ruby:4.0.5 -> `.tool-versions` pins Ruby 4.0.5 as the repo's canonical
20
+ # version (CI runs on it too). The gemspec only requires `>= 3.4.0`, but the
21
+ # container must match the pinned version to faithfully reproduce CI results
22
+ # — otherwise an agent could see green/red locally that disagrees with CI.
23
+ # - -slim -> Debian-slim: small, but still glibc + `apt`. We deliberately
24
+ # avoid Alpine here: Alpine's musl libc forces many native gems to recompile
25
+ # from source (slower builds) and occasionally behaves differently from the
26
+ # glibc CI environment. Debian-slim keeps us byte-for-byte closer to CI while
27
+ # letting us add only the few build packages we actually need.
28
+ FROM ruby:4.0.5-slim
29
+
30
+ # System packages. Each line below earns its place — nothing here is "just in
31
+ # case", because every package widens the image and the attack surface:
32
+ # build-essential, pkg-config Compile native gem extensions when a gem has no
33
+ # precompiled platform build (or an agent adds one
34
+ # later). Most deps resolve to precompiled gems,
35
+ # but the toolchain must be present so `bundle
36
+ # install` never dead-ends.
37
+ # git Required for THREE distinct reasons:
38
+ # 1. `yard-lint` is sourced from GitHub in the
39
+ # Gemfile (`github: 'mensfeld/yard-lint'`),
40
+ # so bundler git-clones it during install.
41
+ # 2. `overcommit --install` (run by bin/setup in
42
+ # the devcontainer) writes git hooks.
43
+ # 3. The gemspec calls `git ls-files` to build
44
+ # its file list.
45
+ # libyaml-dev Psych (Ruby's YAML) links against libyaml; tools
46
+ # and configs throughout the stack parse YAML.
47
+ # curl, ca-certificates TLS roots + a fetch tool for rubygems.org and
48
+ # the bundler-audit advisory database.
49
+ # `--no-install-recommends` skips suggested extras; deleting the apt lists in the
50
+ # same layer keeps this layer from carrying ~tens of MB of package indexes.
51
+ RUN apt-get update \
52
+ && apt-get install -y --no-install-recommends \
53
+ build-essential \
54
+ ca-certificates \
55
+ curl \
56
+ git \
57
+ libyaml-dev \
58
+ pkg-config \
59
+ && rm -rf /var/lib/apt/lists/*
60
+
61
+ # Pin bundler to the version recorded in Gemfile.lock's `BUNDLED WITH` (4.0.8).
62
+ # Bundler is sensitive to this: a mismatched bundler can re-resolve or warn, which
63
+ # undermines the whole point of a locked, reproducible dependency set. Matching it
64
+ # keeps installs identical to what a contributor (and CI) get.
65
+ RUN gem install bundler -v 4.0.8
66
+
67
+ # Build/runtime environment:
68
+ # BUNDLE_JOBS=4 Install gems in parallel — meaningfully faster on multi-core
69
+ # cloud builders.
70
+ # BUNDLE_RETRY=3 Retry a gem fetch up to 3x; absorbs transient network blips
71
+ # that would otherwise fail an unattended cloud build.
72
+ # LANG=C.UTF-8 Slim images ship with no locale, so Ruby's default external
73
+ # encoding would be US-ASCII and choke on non-ASCII I/O. Force
74
+ # UTF-8 to avoid Encoding errors in tooling and specs.
75
+ ENV BUNDLE_JOBS=4 \
76
+ BUNDLE_RETRY=3 \
77
+ LANG=C.UTF-8
78
+
79
+ # Conventional, stable workdir. The devcontainer bind-mounts the live repo here
80
+ # (see .devcontainer/devcontainer.json -> workspaceFolder/workspaceMount).
81
+ WORKDIR /app
82
+
83
+ # Run as a non-root user (security: containers should not run as root). UID/GID
84
+ # 1000 is chosen on purpose — it's the first non-root user on most Linux hosts
85
+ # and CI runners, so files created in a bind-mounted workspace get sane, non-root
86
+ # ownership on the host instead of being owned by root.
87
+ RUN groupadd --gid 1000 dev \
88
+ && useradd --uid 1000 --gid 1000 --create-home --shell /bin/bash dev
89
+
90
+ # --- Dependency layer --------------------------------------------------------
91
+ # Copy ONLY the dependency manifests before the source so this (expensive) layer
92
+ # is cached and re-runs only when dependencies change — not on every code edit.
93
+ #
94
+ # Why the gemspec AND lib/jsonrpc/version.rb are needed here: the Gemfile's
95
+ # `gemspec` directive evaluates jsonrpc-middleware.gemspec at install time, and
96
+ # that file `require_relative`s lib/jsonrpc/version.rb (for `spec.version`). Both
97
+ # must be present or `bundle install` fails before it ever reads the lock.
98
+ # (The gemspec also runs `git ls-files`; with .git excluded via .dockerignore it
99
+ # simply returns an empty file list — harmless, because the *dependencies* come
100
+ # from the `add_dependency` lines, not from `spec.files`.)
101
+ COPY Gemfile Gemfile.lock jsonrpc-middleware.gemspec ./
102
+ COPY lib/jsonrpc/version.rb lib/jsonrpc/version.rb
103
+
104
+ # Why add Linux platforms: the committed Gemfile.lock was generated on macOS and
105
+ # historically listed only `arm64-darwin`. Bundler refuses to install for a
106
+ # platform the lock doesn't name, so a Linux build would fail outright. Adding
107
+ # x86_64-linux + aarch64-linux makes it resolve on both Intel and ARM Linux
108
+ # (cloud runners and Docker on Apple silicon). These platforms are now also
109
+ # committed to the repo's lockfile, so in normal builds this command is a no-op
110
+ # — it stays here defensively so the image still builds from an older checkout.
111
+ #
112
+ # Gems install into the base image's GEM_HOME (/usr/local/bundle), which lives
113
+ # OUTSIDE /app. That is deliberate and important: the devcontainer bind-mounts
114
+ # the host repo over /app at runtime, which would shadow anything installed under
115
+ # /app — but gems in /usr/local/bundle survive the mount, so the container is
116
+ # ready to run the toolchain the instant it starts.
117
+ RUN bundle lock --add-platform x86_64-linux aarch64-linux \
118
+ && bundle install
119
+
120
+ # --- Source layer ------------------------------------------------------------
121
+ # Bake a full copy of the repo so the image is usable standalone (e.g. plain
122
+ # `docker run ... bundle exec rspec`). When used as a devcontainer, the live
123
+ # bind-mount overlays this copy with the contributor's working tree.
124
+ COPY . .
125
+
126
+ # Hand ownership of both the workspace and the gem cache to the non-root user.
127
+ # Chowning /usr/local/bundle is essential, not cosmetic: gems were installed as
128
+ # root above, and without this the `dev` user could not install additional gems
129
+ # at runtime (e.g. `bundle exec rake examples:bundle_install`) without sudo.
130
+ RUN chown -R dev:dev /app /usr/local/bundle
131
+ USER dev
132
+
133
+ # Documentation only — EXPOSE does not publish ports. It records the ports the
134
+ # example apps use, so an agent that boots one to manually verify behaviour knows
135
+ # where to look: rack/sinatra examples listen on 9292, the Rails example on 3000.
136
+ EXPOSE 9292 3000
137
+
138
+ # Deliberately no HEALTHCHECK: this container runs dev commands, it does not serve
139
+ # traffic, so there is no endpoint or process to health-check.
140
+
141
+ # Default to an interactive shell for ad-hoc development. The devcontainer
142
+ # overrides this with its own keep-alive command, so this CMD only matters for
143
+ # direct `docker run` usage.
144
+ CMD ["bash"]
data/README.md CHANGED
@@ -47,19 +47,13 @@ A Rack middleware implementing the JSON-RPC 2.0 protocol that integrates easily
47
47
 
48
48
  The gem integrates seamlessly into your Rack-based application:
49
49
 
50
- ```mermaid
51
- block-beta
52
- columns 4
53
-
54
- App["Your app"]:4
55
- Rails:1 Sinatra:1 RackApp["Other Rack-compatible framework"]:2
56
- Middleware["JSON-RPC Middleware"]:4
57
- Rack["Rack"]:4
58
- HTTP["HTTP"]:4
59
-
60
- classDef middlewareStyle fill:#ff6b6b,stroke:#d63031,stroke-width:2px,color:#fff
61
- class Middleware middlewareStyle
62
- ```
50
+ <p align="center">
51
+ <picture>
52
+ <source media="(prefers-color-scheme: dark)" srcset="./.github/images/architecture-diagram-dark.svg">
53
+ <source media="(prefers-color-scheme: light)" srcset="./.github/images/architecture-diagram-light.svg">
54
+ <img alt="Architecture Diagram" src="./.github/images/architecture-diagram-light.svg" width="800" height="400" style="max-width: 100%;">
55
+ </picture>
56
+ </p>
63
57
 
64
58
  ## 📦 Installation
65
59
 
@@ -97,7 +91,7 @@ JSONRPC.configure do
97
91
  end
98
92
 
99
93
  rule(:addends) do
100
- key.failure('must contain at least one addend') if value.empty?
94
+ key.failure('must contain at least two addends') if value.size < 2
101
95
  end
102
96
  end
103
97
  end
@@ -185,11 +179,9 @@ rake rubocop # Run RuboCop
185
179
  rake rubocop:autocorrect # Autocorrect RuboCop offenses (only when it's safe)
186
180
  rake rubocop:autocorrect_all # Autocorrect RuboCop offenses (safe and unsafe)
187
181
  rake spec # Run RSpec code examples
188
- rake verify_measurements # Verify that yardstick coverage is at least 100%
189
182
  rake yard # Generate YARD Documentation
190
183
  rake yard:format # Format YARD documentation
191
- rake yard:junk # Check the junk in your YARD Documentation
192
- rake yardstick_measure # Measure docs in lib/**/*.rb with yardstick
184
+ rake yard:lint # Lint YARD documentation
193
185
  ```
194
186
 
195
187
  ### 🧪 Type checking
data/Rakefile CHANGED
@@ -6,25 +6,18 @@ require 'bundler/gem_tasks'
6
6
  require 'rspec/core/rake_task'
7
7
  require 'rubocop/rake_task'
8
8
  require 'yaml'
9
+ require 'yard'
9
10
  require 'yard/rake/yardoc_task'
10
- require 'yard-junk/rake'
11
- require 'yardstick/rake/measurement'
12
- require 'yardstick/rake/verify'
13
-
14
- yardstick_options = YAML.load_file('.yardstick.yml')
15
11
 
16
12
  Bundler::Audit::Task.new
17
13
  RSpec::Core::RakeTask.new(:spec)
18
14
  RuboCop::RakeTask.new
19
15
  YARD::Rake::YardocTask.new
20
- YardJunk::Rake.define_task
21
- Yardstick::Rake::Measurement.new(:yardstick_measure, yardstick_options)
22
- Yardstick::Rake::Verify.new(:verify_measurements, yardstick_options)
23
16
 
24
17
  task default: %i[spec rubocop]
25
18
 
26
19
  # Remove the report on rake clobber
27
- CLEAN.include('measurements', 'doc', '.yardoc', 'tmp')
20
+ CLEAN.include('doc', '.yardoc', 'tmp')
28
21
 
29
22
  # Delete these files and folders when running rake clobber.
30
23
  CLOBBER.include('coverage', '.rspec_status')
@@ -33,11 +26,10 @@ desc 'Run spec with coverage'
33
26
  task :coverage do
34
27
  ENV['COVERAGE'] = 'true'
35
28
  Rake::Task['spec'].execute
36
- `open coverage/index.html`
37
29
  end
38
30
 
39
31
  desc 'Test, lint and perform security and documentation audits'
40
- task qa: %w[spec rubocop yard:junk verify_measurements bundle:audit]
32
+ task qa: %w[spec rubocop yard:lint bundle:audit]
41
33
 
42
34
  namespace :yard do
43
35
  desc 'Format YARD documentation'
@@ -95,6 +87,11 @@ namespace :yard do
95
87
  puts
96
88
  puts 'Done!'
97
89
  end
90
+
91
+ desc 'Lint YARD documentation'
92
+ task :lint do
93
+ system 'bundle exec yard-lint lib/'
94
+ end
98
95
  end
99
96
 
100
97
  namespace :examples do
@@ -151,4 +148,58 @@ namespace :examples do
151
148
  exit 1
152
149
  end
153
150
  end
151
+
152
+ desc 'Run bundle update on all example folders'
153
+ task :bundle_update do
154
+ examples_dir = File.join(Dir.pwd, 'examples')
155
+
156
+ unless Dir.exist?(examples_dir)
157
+ puts 'Examples directory not found'
158
+ exit 1
159
+ end
160
+
161
+ example_folders = Dir.glob(File.join(examples_dir, '*')).select { |path| Dir.exist?(path) }
162
+
163
+ if example_folders.empty?
164
+ puts 'No example folders found'
165
+ return
166
+ end
167
+
168
+ puts "Found #{example_folders.length} example folders:"
169
+ example_folders.each { |folder| puts " - #{File.basename(folder)}" }
170
+ puts
171
+
172
+ failed_folders = []
173
+
174
+ example_folders.each do |folder|
175
+ gemfile_path = File.join(folder, 'Gemfile')
176
+
177
+ unless File.exist?(gemfile_path)
178
+ puts "Skipping #{File.basename(folder)} - no Gemfile found"
179
+ next
180
+ end
181
+
182
+ puts "Running bundle update in #{File.basename(folder)}..."
183
+
184
+ Dir.chdir(folder) do
185
+ system('bundle update --all')
186
+
187
+ if $CHILD_STATUS.success?
188
+ puts " ✓ Successfully updated gems in #{File.basename(folder)}"
189
+ else
190
+ failed_folders << File.basename(folder)
191
+ puts " ✗ Failed to bundle update in #{File.basename(folder)}"
192
+ end
193
+ end
194
+
195
+ puts
196
+ end
197
+
198
+ if failed_folders.empty?
199
+ puts 'All example folders processed successfully!'
200
+ else
201
+ puts "Failed to process #{failed_folders.length} folders: #{failed_folders.join(", ")}"
202
+ exit 1
203
+ end
204
+ end
154
205
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  JSONRPC.configure do |config|
4
+ # Logger instance (default: Logger.new($stdout, progname: 'JSONRPC'))
5
+ config.logger = Logger.new($stdout, progname: 'JSONRPC')
4
6
  config.log_internal_errors = true # Log internal error backtraces (default: true)
5
7
  config.log_request_validation_errors = true # Log JSON-RPC request validation errors (default: false)
6
8
  config.render_internal_errors = true # Render detailed internal error information in responses (default: true)
@@ -14,7 +16,7 @@ JSONRPC.configure do |config|
14
16
  end
15
17
 
16
18
  rule(:addends) do
17
- key.failure('must contain at least one addend') if value.empty?
19
+ key.failure('must contain at least two addends') if value.size < 2
18
20
  end
19
21
  end
20
22
 
@@ -1,10 +1,10 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- jsonrpc-middleware (0.5.0)
4
+ jsonrpc-middleware (0.6.0)
5
5
  dry-struct (~> 1.8)
6
6
  dry-validation (~> 1.11)
7
- multi_json (~> 1.17)
7
+ multi_json (~> 1.20)
8
8
  zeitwerk (~> 2.7)
9
9
 
10
10
  GEM
@@ -54,7 +54,7 @@ GEM
54
54
  zeitwerk (~> 2.6)
55
55
  ice_nine (0.11.2)
56
56
  logger (1.7.0)
57
- multi_json (1.17.0)
57
+ multi_json (1.21.1)
58
58
  nio4r (2.7.4)
59
59
  puma (6.6.0)
60
60
  nio4r (~> 2.0)
@@ -74,4 +74,4 @@ DEPENDENCIES
74
74
  rackup
75
75
 
76
76
  BUNDLED WITH
77
- 2.7.0
77
+ 4.0.8
@@ -1,10 +1,10 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- jsonrpc-middleware (0.5.0)
4
+ jsonrpc-middleware (0.6.0)
5
5
  dry-struct (~> 1.8)
6
6
  dry-validation (~> 1.11)
7
- multi_json (~> 1.17)
7
+ multi_json (~> 1.20)
8
8
  zeitwerk (~> 2.7)
9
9
 
10
10
  GEM
@@ -54,7 +54,7 @@ GEM
54
54
  zeitwerk (~> 2.6)
55
55
  ice_nine (0.11.2)
56
56
  logger (1.7.0)
57
- multi_json (1.17.0)
57
+ multi_json (1.21.1)
58
58
  nio4r (2.7.4)
59
59
  puma (6.6.0)
60
60
  nio4r (~> 2.0)
@@ -74,4 +74,4 @@ DEPENDENCIES
74
74
  rackup
75
75
 
76
76
  BUNDLED WITH
77
- 2.7.0
77
+ 4.0.8