json_rpc_handler 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4e714ed8007b1495db68aef79e5a649c63b8209e1b7600c405f8611ecadd16bd
4
+ data.tar.gz: 3b27a986d66f12f2919e8439b3e645e3c3b2e7136d5175e11f9c1e498f07b28c
5
+ SHA512:
6
+ metadata.gz: d385890032cb7d7bb6791292aa42392dbad2d320904364c3651a55f0c6f99a396200d481c72d30d7b29786ac6f456a9d33a4a5dcd66bce0b4768b0171eba16b8
7
+ data.tar.gz: 25034b8fe5653f04e692fd555323e61ac44af0b7e90eab7c2ef6947288e89fb37ac874b177980d4c76ee143c64be08056f0317cdb94a128d7b3b96d2c5781a9f
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025-present, Shopify Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # json_rpc_handler
2
+
3
+ A lightweight, fully spec-compliant [JSON-RPC 2.0][1] handler.
4
+
5
+ It only deals with the parsing and handling of JSON-RPC requests, producing
6
+ appropriate JSON-RPC responses.
7
+
8
+ It does not deal with the transport layer (e.g. HTTP, WebSockets, etc.)
9
+
10
+ [1]: https://www.jsonrpc.org/specification
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'json_rpc_handler'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ ```sh
23
+ bundle
24
+ ```
25
+
26
+ Or install it yourself as:
27
+
28
+ ```sh
29
+ gem install json_rpc_handler
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ If you already have a request object parsed from JSON, you can use the `handle`
35
+ method. Pass a block to the `handle` call to look up methods by name:
36
+
37
+ ```rb
38
+ require 'json_rpc_handler'
39
+
40
+ request = {
41
+ jsonrpc: '2.0',
42
+ id: 1,
43
+ method: 'add',
44
+ params: {a: 1, b: 2},
45
+ }
46
+
47
+ result = JsonRpcHandler.handle(request) do |method_name|
48
+ case method_name
49
+ when 'add'
50
+ ->(params) { params[:a] + params[:b] }
51
+ end
52
+ end
53
+
54
+ puts result.to_json
55
+ # {"jsonrpc":"2.0","id":1,"result":3}
56
+ ```
57
+
58
+ Returning a `nil` from the method lookup block will result in a method not found
59
+ error.
60
+
61
+ If the request JSON is not already parsed, you can use the `handle_json` method:
62
+
63
+ ```rb
64
+ request_json = '{"jsonrpc": "2.0","id":1,"method":"add","params":{"a":1,"b":2}}'
65
+
66
+ result_json = JsonRpcHandler.handle_json(request_json) do |method_name|
67
+ case method_name
68
+ when 'add'
69
+ ->(params) { params[:a] + params[:b] }
70
+ end
71
+ end
72
+
73
+ puts result_json
74
+ # {"jsonrpc":"2.0","id":1,"result":3}
75
+ ```
76
+
77
+ A `nil` from `handle` or `handle_json` means "no content" - either the
78
+ method returned `nil`, or all method calls of a batch request returned `nil`. It
79
+ is up to the integration to apply the appropriate transport-layer semantics
80
+ (e.g. returning a 204 No Content).
81
+
82
+ ## Development
83
+
84
+ After checking out the repo:
85
+
86
+ 1. Run `bundle` to install dependencies.
87
+ 2. Run `rake test` to run the tests, including code coverage.
88
+
89
+ To install this gem onto your local machine, run `bundle exec rake install`.
90
+
91
+ ## Contributing
92
+
93
+ Bug reports and pull requests are welcome on GitHub at
94
+ https://github.com/Shopify/json-rpc-handler. This project is intended to be a
95
+ safe, welcoming space for collaboration, and contributors are expected to adhere
96
+ to the [Contributor Covenant][2] code of conduct. Read more about contributing
97
+ [here][3].
98
+
99
+ [2]: https://contributor-covenant.org
100
+ [3]: https://github.com/Shopify/json-rpc-handler/blob/main/CONTRIBUTING.md
101
+
102
+ ## License
103
+
104
+ The gem is available as open source under the terms of the [MIT License][4].
105
+
106
+ [4]: https://opensource.org/licenses/MIT
107
+
108
+ ## Code of Conduct
109
+
110
+ Everyone interacting in this repository is expected to follow the
111
+ [Code of Conduct][5].
112
+
113
+ [5]: https://github.com/Shopify/json-rpc-handler/blob/main/CODE_OF_CONDUCT.md
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonRpcHandler
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json_rpc_handler/version'
4
+
5
+ module JsonRpcHandler
6
+ class Version
7
+ V1_0 = '1.0'
8
+ V2_0 = '2.0'
9
+ end
10
+
11
+ class ErrorCode
12
+ InvalidRequest = -32600
13
+ MethodNotFound = -32601
14
+ InvalidParams = -32602
15
+ InternalError = -32603
16
+ ParseError = -32700
17
+ end
18
+
19
+ module_function
20
+
21
+ def handle(request, &method_finder)
22
+ if request.is_a? Array
23
+ return error_response id: :unknown_id, error: {
24
+ code: ErrorCode::InvalidRequest,
25
+ message: 'Invalid Request',
26
+ data: 'Request is an empty array',
27
+ } if request.empty?
28
+
29
+ # Handle batch requests
30
+ responses = request.map { |req| process_request req, &method_finder }.compact
31
+
32
+ # A single item is hoisted out of the array
33
+ return responses.first if responses.one?
34
+
35
+ # An empty array yields nil
36
+ responses if responses.any?
37
+ elsif request.is_a? Hash
38
+ # Handle single request
39
+ process_request request, &method_finder
40
+ else
41
+ error_response id: :unknown_id, error: {
42
+ code: ErrorCode::InvalidRequest,
43
+ message: 'Invalid Request',
44
+ data: 'Request must be an array or a hash',
45
+ }
46
+ end
47
+ end
48
+
49
+ def handle_json(request_json, &method_finder)
50
+ begin
51
+ request = JSON.parse request_json, symbolize_names: true
52
+ response = handle request, &method_finder
53
+ rescue JSON::ParserError
54
+ response =error_response id: :unknown_id, error: {
55
+ code: ErrorCode::ParseError,
56
+ message: 'Parse error',
57
+ data: 'Invalid JSON',
58
+ }
59
+ end
60
+
61
+ response.to_json if response
62
+ end
63
+
64
+ def process_request(request, &method_finder)
65
+ id = request[:id]
66
+
67
+ error = case
68
+ when !valid_version?(request[:jsonrpc]) then 'JSON-RPC version must be 2.0'
69
+ when !valid_id?(request[:id]) then 'Request ID must be a string or an integer or null'
70
+ when !valid_method_name?(request[:method]) then 'Method name must be a string and not start with "rpc."'
71
+ end
72
+
73
+ return error_response id: :unknown_id, error: {
74
+ code: ErrorCode::InvalidRequest,
75
+ message: 'Invalid Request',
76
+ data: error,
77
+ } if error
78
+
79
+ method_name = request[:method]
80
+ params = request[:params]
81
+
82
+ unless valid_params? params
83
+ return error_response id:, error: {
84
+ code: ErrorCode::InvalidParams,
85
+ message: 'Invalid params',
86
+ data: 'Method parameters must be an array or an object or null',
87
+ }
88
+ end
89
+
90
+ begin
91
+ method = method_finder.call method_name
92
+
93
+ if method.nil?
94
+ return error_response id:, error: {
95
+ code: ErrorCode::MethodNotFound,
96
+ message: 'Method not found',
97
+ data: method_name,
98
+ }
99
+ end
100
+
101
+ result = method.call params
102
+
103
+ success_response id:, result:
104
+ rescue StandardError => e
105
+ error_response id:, error: {
106
+ code: ErrorCode::InternalError,
107
+ message: 'Internal error',
108
+ data: e.message,
109
+ }
110
+ end
111
+ end
112
+
113
+ def valid_version?(version)
114
+ version == Version::V2_0
115
+ end
116
+
117
+ def valid_id?(id)
118
+ id.is_a?(String) || id.is_a?(Integer) || id.nil?
119
+ end
120
+
121
+ def valid_method_name?(method)
122
+ method.is_a?(String) && !method.start_with?('rpc.')
123
+ end
124
+
125
+ def valid_params?(params)
126
+ params.nil? || params.is_a?(Array) || params.is_a?(Hash)
127
+ end
128
+
129
+ def success_response(id:, result:)
130
+ {
131
+ jsonrpc: Version::V2_0,
132
+ id:,
133
+ result:,
134
+ } unless id.nil?
135
+ end
136
+
137
+ def error_response(id:, error:)
138
+ {
139
+ jsonrpc: Version::V2_0,
140
+ id: valid_id?(id) ? id : nil,
141
+ error: error.compact,
142
+ } unless id.nil?
143
+ end
144
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: json_rpc_handler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ates Goral
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-03-12 00:00:00.000000000 Z
11
+ dependencies: []
12
+ email:
13
+ - ates.goral@shopify.com
14
+ executables: []
15
+ extensions: []
16
+ extra_rdoc_files:
17
+ - README.md
18
+ files:
19
+ - LICENSE.md
20
+ - README.md
21
+ - lib/json_rpc_handler.rb
22
+ - lib/json_rpc_handler/version.rb
23
+ homepage: https://github.com/Shopify/json_rpc_handler
24
+ licenses:
25
+ - MIT
26
+ metadata:
27
+ bug_tracker_uri: https://github.com/Shopify/json_rpc_handler/issues
28
+ changelog_uri: https://github.com/Shopify/json_rpc_handler/blob/main/CHANGELOG.md
29
+ source_code_uri: https://github.com/Shopify/json_rpc_handler
30
+ allowed_push_host: https://rubygems.org
31
+ rubygems_mfa_required: 'true'
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 3.1.0
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: 1.3.7
45
+ requirements: []
46
+ rubygems_version: 3.6.5
47
+ specification_version: 4
48
+ summary: A spec-compliant JSON-RPC 2.0 handler
49
+ test_files: []