multi_json 1.15.0 → 1.21.1
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 +4 -4
- data/LICENSE.md +1 -1
- data/README.md +207 -47
- data/lib/multi_json/adapter.rb +205 -20
- data/lib/multi_json/adapter_error.rb +36 -9
- data/lib/multi_json/adapter_selector.rb +214 -0
- data/lib/multi_json/adapters/fast_jsonparser.rb +74 -0
- data/lib/multi_json/adapters/json_gem.rb +65 -4
- data/lib/multi_json/adapters/oj.rb +72 -46
- data/lib/multi_json/adapters/oj_common.rb +44 -0
- data/lib/multi_json/adapters/yajl.rb +25 -4
- data/lib/multi_json/concurrency.rb +57 -0
- data/lib/multi_json/deprecated.rb +113 -0
- data/lib/multi_json/options.rb +162 -15
- data/lib/multi_json/options_cache/mutex_store.rb +65 -0
- data/lib/multi_json/options_cache.rb +77 -20
- data/lib/multi_json/parse_error.rb +96 -10
- data/lib/multi_json/version.rb +20 -7
- data/lib/multi_json.rb +283 -124
- metadata +22 -60
- data/CHANGELOG.md +0 -275
- data/CONTRIBUTING.md +0 -46
- data/lib/multi_json/adapters/gson.rb +0 -20
- data/lib/multi_json/adapters/jr_jackson.rb +0 -25
- data/lib/multi_json/adapters/json_common.rb +0 -23
- data/lib/multi_json/adapters/json_pure.rb +0 -11
- data/lib/multi_json/adapters/nsjsonserialization.rb +0 -35
- data/lib/multi_json/adapters/ok_json.rb +0 -23
- data/lib/multi_json/convertible_hash_keys.rb +0 -43
- data/lib/multi_json/vendor/okjson.rb +0 -606
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Deprecated public API kept around for one major release
|
|
4
|
+
#
|
|
5
|
+
# Each method here emits a one-time deprecation warning on first call and
|
|
6
|
+
# delegates to its current-API counterpart. The whole file is loaded by
|
|
7
|
+
# {MultiJSON} so the deprecation surface stays out of the main module
|
|
8
|
+
# definition.
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
module MultiJSON
|
|
12
|
+
class << self
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# Define a deprecated alias that delegates to a new method name
|
|
16
|
+
#
|
|
17
|
+
# The generated singleton method emits a one-time deprecation
|
|
18
|
+
# warning naming the replacement, then forwards all positional and
|
|
19
|
+
# keyword arguments plus any block to ``replacement``. Used for the
|
|
20
|
+
# ``load`` / ``dump`` / ``decode`` / ``encode`` / ``engine*`` /
|
|
21
|
+
# ``with_engine`` / ``default_engine`` aliases that are scheduled
|
|
22
|
+
# for removal in v2.0.
|
|
23
|
+
#
|
|
24
|
+
# @api private
|
|
25
|
+
# @param name [Symbol] deprecated method name
|
|
26
|
+
# @param replacement [Symbol] current-API method to delegate to
|
|
27
|
+
# @return [Symbol] the defined method name
|
|
28
|
+
# @example
|
|
29
|
+
# deprecate_alias :load, :parse
|
|
30
|
+
def deprecate_alias(name, replacement)
|
|
31
|
+
message = "MultiJSON.#{name} is deprecated and will be removed in v2.0. Use MultiJSON.#{replacement} instead."
|
|
32
|
+
define_singleton_method(name) do |*args, **kwargs, &block|
|
|
33
|
+
warn_deprecation_once(name, message)
|
|
34
|
+
public_send(replacement, *args, **kwargs, &block)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Define a deprecated method whose body needs custom delegation
|
|
39
|
+
#
|
|
40
|
+
# Used for the ``default_options`` / ``default_options=`` pair
|
|
41
|
+
# whose body fans out to multiple replacement methods, and for the
|
|
42
|
+
# ``cached_options`` / ``reset_cached_options!`` no-op stubs that
|
|
43
|
+
# have no current-API counterpart at all. The block runs in its
|
|
44
|
+
# own lexical ``self``, which is the ``MultiJSON`` module since
|
|
45
|
+
# every call site sits inside ``module MultiJSON`` below.
|
|
46
|
+
#
|
|
47
|
+
# @api private
|
|
48
|
+
# @param name [Symbol] deprecated method name
|
|
49
|
+
# @param message [String] warning to emit on first call
|
|
50
|
+
# @yield body to evaluate after the warning
|
|
51
|
+
# @return [Symbol] the defined method name
|
|
52
|
+
# @example
|
|
53
|
+
# deprecate_method(:cached_options, "...") { nil }
|
|
54
|
+
def deprecate_method(name, message, &body)
|
|
55
|
+
define_singleton_method(name) do |*args, **kwargs|
|
|
56
|
+
warn_deprecation_once(name, message)
|
|
57
|
+
body.call(*args, **kwargs)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
deprecate_alias :load, :parse
|
|
63
|
+
deprecate_alias :dump, :generate
|
|
64
|
+
deprecate_alias :decode, :parse
|
|
65
|
+
deprecate_alias :encode, :generate
|
|
66
|
+
deprecate_alias :engine, :adapter
|
|
67
|
+
deprecate_alias :engine=, :adapter=
|
|
68
|
+
deprecate_alias :default_engine, :default_adapter
|
|
69
|
+
deprecate_alias :with_engine, :with_adapter
|
|
70
|
+
|
|
71
|
+
deprecate_method(
|
|
72
|
+
:default_options=,
|
|
73
|
+
"MultiJSON.default_options setter is deprecated\n" \
|
|
74
|
+
"Use MultiJSON.parse_options and MultiJSON.generate_options instead"
|
|
75
|
+
) { |value| self.parse_options = self.generate_options = value }
|
|
76
|
+
|
|
77
|
+
deprecate_method(
|
|
78
|
+
:default_options,
|
|
79
|
+
"MultiJSON.default_options is deprecated\n" \
|
|
80
|
+
"Use MultiJSON.parse_options or MultiJSON.generate_options instead"
|
|
81
|
+
) { parse_options }
|
|
82
|
+
|
|
83
|
+
%i[cached_options reset_cached_options!].each do |name|
|
|
84
|
+
deprecate_method(name, "MultiJSON.#{name} method is deprecated and no longer used.") { nil }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
# Instance-method delegate for the deprecated default_options setter
|
|
90
|
+
#
|
|
91
|
+
# @api private
|
|
92
|
+
# @deprecated Use {MultiJSON.load_options=} and {MultiJSON.dump_options=} instead
|
|
93
|
+
# @param value [Hash] options hash
|
|
94
|
+
# @return [Hash] the options hash
|
|
95
|
+
# @example
|
|
96
|
+
# class Foo; include MultiJSON; end
|
|
97
|
+
# Foo.new.send(:default_options=, symbolize_keys: true)
|
|
98
|
+
def default_options=(value)
|
|
99
|
+
MultiJSON.default_options = value
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Instance-method delegate for the deprecated default_options getter
|
|
103
|
+
#
|
|
104
|
+
# @api private
|
|
105
|
+
# @deprecated Use {MultiJSON.load_options} or {MultiJSON.dump_options} instead
|
|
106
|
+
# @return [Hash] the current load options
|
|
107
|
+
# @example
|
|
108
|
+
# class Foo; include MultiJSON; end
|
|
109
|
+
# Foo.new.send(:default_options)
|
|
110
|
+
def default_options
|
|
111
|
+
MultiJSON.default_options
|
|
112
|
+
end
|
|
113
|
+
end
|
data/lib/multi_json/options.rb
CHANGED
|
@@ -1,39 +1,186 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MultiJSON
|
|
4
|
+
# Mixin providing configurable parse/generate options
|
|
5
|
+
#
|
|
6
|
+
# Supports static hashes or dynamic callables (procs/lambdas).
|
|
7
|
+
# Extended by both MultiJSON (global options) and Adapter classes.
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
2
10
|
module Options
|
|
3
|
-
|
|
11
|
+
# Steep needs an inline `#:` annotation here because `{}.freeze`
|
|
12
|
+
# would be inferred as `Hash[untyped, untyped]` and trip
|
|
13
|
+
# `UnannotatedEmptyCollection`. The annotation requires
|
|
14
|
+
# `Hash.new.freeze` (not the `{}.freeze` rubocop would prefer)
|
|
15
|
+
# because the `#:` cast only applies to method-call results.
|
|
16
|
+
EMPTY_OPTIONS = Hash.new.freeze #: options # rubocop:disable Style/EmptyLiteral
|
|
17
|
+
|
|
18
|
+
# Set options for parse operations
|
|
19
|
+
#
|
|
20
|
+
# @api public
|
|
21
|
+
# @param options [Hash, Proc] options hash or callable
|
|
22
|
+
# @return [Hash, Proc] the options
|
|
23
|
+
# @example
|
|
24
|
+
# MultiJSON.parse_options = {symbolize_keys: true}
|
|
25
|
+
def parse_options=(options)
|
|
4
26
|
OptionsCache.reset
|
|
5
|
-
@
|
|
27
|
+
@parse_options = options
|
|
6
28
|
end
|
|
7
29
|
|
|
8
|
-
|
|
30
|
+
# Set options for generate operations
|
|
31
|
+
#
|
|
32
|
+
# @api public
|
|
33
|
+
# @param options [Hash, Proc] options hash or callable
|
|
34
|
+
# @return [Hash, Proc] the options
|
|
35
|
+
# @example
|
|
36
|
+
# MultiJSON.generate_options = {pretty: true}
|
|
37
|
+
def generate_options=(options)
|
|
9
38
|
OptionsCache.reset
|
|
10
|
-
@
|
|
39
|
+
@generate_options = options
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Get options for parse operations
|
|
43
|
+
#
|
|
44
|
+
# When `@parse_options` is a callable (proc/lambda), it's invoked
|
|
45
|
+
# with `args` as positional arguments — typically the merged
|
|
46
|
+
# options hash from `Adapter.merged_parse_options`. When it's a
|
|
47
|
+
# plain hash, `args` is ignored.
|
|
48
|
+
#
|
|
49
|
+
# @api public
|
|
50
|
+
# @param args [Array<Object>] forwarded to the callable, ignored otherwise
|
|
51
|
+
# @return [Hash] resolved options hash
|
|
52
|
+
# @example
|
|
53
|
+
# MultiJSON.parse_options #=> {}
|
|
54
|
+
def parse_options(*args)
|
|
55
|
+
resolve_options(@parse_options, *args) || default_parse_options
|
|
11
56
|
end
|
|
12
57
|
|
|
58
|
+
# Get options for generate operations
|
|
59
|
+
#
|
|
60
|
+
# @api public
|
|
61
|
+
# @param args [Array<Object>] forwarded to the callable, ignored otherwise
|
|
62
|
+
# @return [Hash] resolved options hash
|
|
63
|
+
# @example
|
|
64
|
+
# MultiJSON.generate_options #=> {}
|
|
65
|
+
def generate_options(*args)
|
|
66
|
+
resolve_options(@generate_options, *args) || default_generate_options
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Get default parse options
|
|
70
|
+
#
|
|
71
|
+
# @api private
|
|
72
|
+
# @return [Hash] frozen empty hash
|
|
73
|
+
def default_parse_options
|
|
74
|
+
Concurrency.synchronize(:default_options) { @default_parse_options ||= EMPTY_OPTIONS }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Get default generate options
|
|
78
|
+
#
|
|
79
|
+
# @api private
|
|
80
|
+
# @return [Hash] frozen empty hash
|
|
81
|
+
def default_generate_options
|
|
82
|
+
Concurrency.synchronize(:default_options) { @default_generate_options ||= EMPTY_OPTIONS }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Set options for parse operations
|
|
86
|
+
#
|
|
87
|
+
# @api public
|
|
88
|
+
# @deprecated Use {#parse_options=} instead. Will be removed in v2.0.
|
|
89
|
+
# @param options [Hash, Proc] options hash or callable
|
|
90
|
+
# @return [Hash, Proc] the options
|
|
91
|
+
# @example
|
|
92
|
+
# MultiJSON.load_options = {symbolize_keys: true}
|
|
93
|
+
def load_options=(options)
|
|
94
|
+
MultiJSON.warn_deprecation_once(:load_options=,
|
|
95
|
+
"MultiJSON.load_options= is deprecated and will be removed in v2.0. Use MultiJSON.parse_options= instead.")
|
|
96
|
+
self.parse_options = options
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Set options for generate operations
|
|
100
|
+
#
|
|
101
|
+
# @api public
|
|
102
|
+
# @deprecated Use {#generate_options=} instead. Will be removed in v2.0.
|
|
103
|
+
# @param options [Hash, Proc] options hash or callable
|
|
104
|
+
# @return [Hash, Proc] the options
|
|
105
|
+
# @example
|
|
106
|
+
# MultiJSON.dump_options = {pretty: true}
|
|
107
|
+
def dump_options=(options)
|
|
108
|
+
MultiJSON.warn_deprecation_once(:dump_options=,
|
|
109
|
+
"MultiJSON.dump_options= is deprecated and will be removed in v2.0. Use MultiJSON.generate_options= instead.")
|
|
110
|
+
self.generate_options = options
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Get options for parse operations
|
|
114
|
+
#
|
|
115
|
+
# @api public
|
|
116
|
+
# @deprecated Use {#parse_options} instead. Will be removed in v2.0.
|
|
117
|
+
# @param args [Array<Object>] forwarded to the callable, ignored otherwise
|
|
118
|
+
# @return [Hash] resolved options hash
|
|
119
|
+
# @example
|
|
120
|
+
# MultiJSON.load_options #=> {}
|
|
13
121
|
def load_options(*args)
|
|
14
|
-
|
|
122
|
+
MultiJSON.warn_deprecation_once(:load_options,
|
|
123
|
+
"MultiJSON.load_options is deprecated and will be removed in v2.0. Use MultiJSON.parse_options instead.")
|
|
124
|
+
parse_options(*args)
|
|
15
125
|
end
|
|
16
126
|
|
|
127
|
+
# Get options for generate operations
|
|
128
|
+
#
|
|
129
|
+
# @api public
|
|
130
|
+
# @deprecated Use {#generate_options} instead. Will be removed in v2.0.
|
|
131
|
+
# @param args [Array<Object>] forwarded to the callable, ignored otherwise
|
|
132
|
+
# @return [Hash] resolved options hash
|
|
133
|
+
# @example
|
|
134
|
+
# MultiJSON.dump_options #=> {}
|
|
17
135
|
def dump_options(*args)
|
|
18
|
-
|
|
136
|
+
MultiJSON.warn_deprecation_once(:dump_options,
|
|
137
|
+
"MultiJSON.dump_options is deprecated and will be removed in v2.0. Use MultiJSON.generate_options instead.")
|
|
138
|
+
generate_options(*args)
|
|
19
139
|
end
|
|
20
140
|
|
|
141
|
+
# Get default parse options
|
|
142
|
+
#
|
|
143
|
+
# @api private
|
|
144
|
+
# @deprecated Use {#default_parse_options} instead. Will be removed in v2.0.
|
|
145
|
+
# @return [Hash] frozen empty hash
|
|
21
146
|
def default_load_options
|
|
22
|
-
|
|
147
|
+
default_parse_options
|
|
23
148
|
end
|
|
24
149
|
|
|
150
|
+
# Get default generate options
|
|
151
|
+
#
|
|
152
|
+
# @api private
|
|
153
|
+
# @deprecated Use {#default_generate_options} instead. Will be removed in v2.0.
|
|
154
|
+
# @return [Hash] frozen empty hash
|
|
25
155
|
def default_dump_options
|
|
26
|
-
|
|
156
|
+
default_generate_options
|
|
27
157
|
end
|
|
28
158
|
|
|
29
|
-
|
|
159
|
+
private
|
|
30
160
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
161
|
+
# Resolves options from a hash or callable
|
|
162
|
+
#
|
|
163
|
+
# @api private
|
|
164
|
+
# @param options [Hash, Proc, nil] options configuration
|
|
165
|
+
# @param args [Array<Object>] arguments forwarded to a callable provider
|
|
166
|
+
# @return [Hash, nil] resolved options hash
|
|
167
|
+
def resolve_options(options, *args)
|
|
168
|
+
if options.respond_to?(:call)
|
|
169
|
+
# @type var options: options_proc
|
|
170
|
+
return invoke_callable(options, *args)
|
|
36
171
|
end
|
|
172
|
+
|
|
173
|
+
options.to_hash if options.respond_to?(:to_hash)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Invokes a callable options provider
|
|
177
|
+
#
|
|
178
|
+
# @api private
|
|
179
|
+
# @param callable [Proc] options provider
|
|
180
|
+
# @param args [Array<Object>] arguments forwarded when the callable is non-arity-zero
|
|
181
|
+
# @return [Hash] options returned by the callable
|
|
182
|
+
def invoke_callable(callable, *args)
|
|
183
|
+
callable.arity.zero? ? callable.call : callable.call(*args)
|
|
37
184
|
end
|
|
38
185
|
end
|
|
39
186
|
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MultiJSON
|
|
4
|
+
module OptionsCache
|
|
5
|
+
# Thread-safe cache store backed by a Hash guarded by a Mutex
|
|
6
|
+
#
|
|
7
|
+
# Used on MRI and TruffleRuby, where a runtime dependency on
|
|
8
|
+
# concurrent-ruby would be overkill: the GVL on MRI makes single
|
|
9
|
+
# lookups atomic, and locking on both reads and writes keeps the
|
|
10
|
+
# small perf cost predictable across engines without adding a
|
|
11
|
+
# runtime dependency.
|
|
12
|
+
#
|
|
13
|
+
# @api private
|
|
14
|
+
class Store
|
|
15
|
+
# Create a new cache store
|
|
16
|
+
#
|
|
17
|
+
# @api private
|
|
18
|
+
# @return [Store] new store instance
|
|
19
|
+
def initialize
|
|
20
|
+
@cache = {}
|
|
21
|
+
@mutex = Mutex.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Clear all cached entries
|
|
25
|
+
#
|
|
26
|
+
# Held under the mutex because TruffleRuby (which also uses this
|
|
27
|
+
# backend via the ruby-platform gem) has true parallelism: a
|
|
28
|
+
# concurrent ``fetch`` racing with ``Hash#clear`` could corrupt
|
|
29
|
+
# iteration in a way that MRI's GVL would otherwise prevent.
|
|
30
|
+
#
|
|
31
|
+
# @api private
|
|
32
|
+
# @return [void]
|
|
33
|
+
def reset
|
|
34
|
+
@mutex.synchronize { @cache.clear }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Fetch a value from cache or compute it
|
|
38
|
+
#
|
|
39
|
+
# When called with a block, returns the cached value or computes a
|
|
40
|
+
# new one. When called without a block, returns the cached value or
|
|
41
|
+
# the supplied default if the key is missing. Nil cached values are
|
|
42
|
+
# preserved because ``Hash#fetch`` only falls through to the default
|
|
43
|
+
# block when the key is truly missing. The ``block_given?`` check
|
|
44
|
+
# is hoisted out of the mutex so the no-block read path runs the
|
|
45
|
+
# check once per call instead of once inside the critical section.
|
|
46
|
+
#
|
|
47
|
+
# @api private
|
|
48
|
+
# @param key [Object] cache key
|
|
49
|
+
# @param default [Object] value to return when key is missing and no
|
|
50
|
+
# block is given
|
|
51
|
+
# @yield block to compute value if not cached
|
|
52
|
+
# @return [Object] cached, computed, or default value
|
|
53
|
+
def fetch(key, default = nil)
|
|
54
|
+
return @mutex.synchronize { @cache.fetch(key) { default } } unless block_given?
|
|
55
|
+
|
|
56
|
+
@mutex.synchronize do
|
|
57
|
+
@cache.fetch(key) do
|
|
58
|
+
@cache.shift if @cache.size >= OptionsCache.max_cache_size
|
|
59
|
+
@cache[key] = yield
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -1,29 +1,86 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MultiJSON
|
|
4
|
+
# Thread-safe bounded cache for merged options hashes
|
|
5
|
+
#
|
|
6
|
+
# Caches are separated for load and dump operations. Each cache is
|
|
7
|
+
# bounded to prevent unbounded memory growth when options are
|
|
8
|
+
# generated dynamically. The ``Store`` backend is chosen at load time
|
|
9
|
+
# based on ``RUBY_ENGINE``: JRuby uses Concurrent::Map (shipped as a
|
|
10
|
+
# runtime dependency of the java-platform gem); MRI and TruffleRuby
|
|
11
|
+
# use a Hash guarded by a Mutex.
|
|
12
|
+
#
|
|
13
|
+
# @api private
|
|
2
14
|
module OptionsCache
|
|
3
|
-
|
|
15
|
+
# Default bound on the number of cached entries per store. Applications
|
|
16
|
+
# that dynamically generate many distinct option hashes can raise this
|
|
17
|
+
# via {.max_cache_size=}.
|
|
18
|
+
DEFAULT_MAX_CACHE_SIZE = 1000
|
|
4
19
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
20
|
+
class << self
|
|
21
|
+
# Get the dump options cache
|
|
22
|
+
#
|
|
23
|
+
# @api private
|
|
24
|
+
# @return [Store] dump cache store
|
|
25
|
+
attr_reader :dump
|
|
9
26
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
27
|
+
# Get the load options cache
|
|
28
|
+
#
|
|
29
|
+
# @api private
|
|
30
|
+
# @return [Store] load cache store
|
|
31
|
+
attr_reader :load
|
|
14
32
|
|
|
15
|
-
|
|
33
|
+
# Maximum number of entries per cache store
|
|
34
|
+
#
|
|
35
|
+
# Applies to both the dump and load caches. Existing entries are
|
|
36
|
+
# left in place until normal eviction trims them below a lowered
|
|
37
|
+
# limit; call {.reset} if you need to evict immediately.
|
|
38
|
+
#
|
|
39
|
+
# @api public
|
|
40
|
+
# @return [Integer] current cache size limit
|
|
41
|
+
# @example
|
|
42
|
+
# MultiJSON::OptionsCache.max_cache_size = 5000
|
|
43
|
+
# MultiJSON::OptionsCache.max_cache_size #=> 5000
|
|
44
|
+
attr_reader :max_cache_size
|
|
16
45
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
46
|
+
# Set the maximum number of entries per cache store
|
|
47
|
+
#
|
|
48
|
+
# @api public
|
|
49
|
+
# @param value [Integer] positive entry cap
|
|
50
|
+
# @return [Integer] the validated value
|
|
51
|
+
# @raise [ArgumentError] when value is not a positive Integer
|
|
52
|
+
# @example
|
|
53
|
+
# MultiJSON::OptionsCache.max_cache_size = 5000
|
|
54
|
+
def max_cache_size=(value)
|
|
55
|
+
raise ArgumentError, "max_cache_size must be a positive Integer, got #{value.inspect}" unless Integer === value && value.positive? # rubocop:disable Style/CaseEquality
|
|
23
56
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
57
|
+
@max_cache_size = value
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Reset both caches
|
|
61
|
+
#
|
|
62
|
+
# @api private
|
|
63
|
+
# @return [void]
|
|
64
|
+
def reset
|
|
65
|
+
@dump = Store.new
|
|
66
|
+
@load = Store.new
|
|
67
|
+
end
|
|
27
68
|
end
|
|
69
|
+
|
|
70
|
+
self.max_cache_size = DEFAULT_MAX_CACHE_SIZE
|
|
28
71
|
end
|
|
29
72
|
end
|
|
73
|
+
|
|
74
|
+
module MultiJSON
|
|
75
|
+
module OptionsCache
|
|
76
|
+
# Dynamic require path so MRI (mutex_store) and JRuby
|
|
77
|
+
# (concurrent_store) execute the same physical line, avoiding a
|
|
78
|
+
# dead-branch ``require_relative`` that would otherwise drop
|
|
79
|
+
# JRuby's line coverage below 100%.
|
|
80
|
+
BACKENDS = {"jruby" => "concurrent_store"}.freeze
|
|
81
|
+
private_constant :BACKENDS
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
require_relative "options_cache/#{MultiJSON::OptionsCache.send(:const_get, :BACKENDS).fetch(RUBY_ENGINE, "mutex_store")}"
|
|
86
|
+
MultiJSON::OptionsCache.reset
|
|
@@ -1,17 +1,103 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MultiJSON
|
|
4
|
+
# Raised when JSON parsing fails
|
|
5
|
+
#
|
|
6
|
+
# Wraps the underlying adapter's parse error with the original input
|
|
7
|
+
# data, plus best-effort line and column extraction from the adapter's
|
|
8
|
+
# error message. Line/column are populated for adapters that include
|
|
9
|
+
# them in their messages (Oj, the json gem) and remain nil for
|
|
10
|
+
# adapters that don't (Yajl, fast_jsonparser).
|
|
11
|
+
#
|
|
12
|
+
# @api public
|
|
2
13
|
class ParseError < StandardError
|
|
3
|
-
|
|
14
|
+
# Regex that matches the "line N[, ]column M" fragment inside an
|
|
15
|
+
# adapter error message. The separator between line and column is
|
|
16
|
+
# permissive — Oj emits ``"line 1, column 3"`` while the json gem
|
|
17
|
+
# emits ``"line 1 column 2"`` — so ``[,\s]+`` covers both. Column
|
|
18
|
+
# is optional so messages like ``"at line 5"`` still yield a line.
|
|
19
|
+
LOCATION_PATTERN = /line\s+(\d+)(?:[,\s]+column\s+(\d+))?/i
|
|
20
|
+
private_constant :LOCATION_PATTERN
|
|
21
|
+
|
|
22
|
+
# The input string that failed to parse
|
|
23
|
+
#
|
|
24
|
+
# @api public
|
|
25
|
+
# @return [String, nil] the original input data
|
|
26
|
+
# @example
|
|
27
|
+
# error.data #=> "{invalid json}"
|
|
28
|
+
attr_reader :data
|
|
29
|
+
|
|
30
|
+
# The 1-based line number reported by the adapter
|
|
31
|
+
#
|
|
32
|
+
# @api public
|
|
33
|
+
# @return [Integer, nil] line number, or nil if the adapter's message
|
|
34
|
+
# did not include one
|
|
35
|
+
# @example
|
|
36
|
+
# error.line #=> 1
|
|
37
|
+
attr_reader :line
|
|
38
|
+
|
|
39
|
+
# The 1-based column reported by the adapter
|
|
40
|
+
#
|
|
41
|
+
# @api public
|
|
42
|
+
# @return [Integer, nil] column number, or nil if the adapter's message
|
|
43
|
+
# did not include one
|
|
44
|
+
# @example
|
|
45
|
+
# error.column #=> 3
|
|
46
|
+
attr_reader :column
|
|
47
|
+
|
|
48
|
+
# Create a new ParseError
|
|
49
|
+
#
|
|
50
|
+
# @api public
|
|
51
|
+
# @param message [String, nil] error message
|
|
52
|
+
# @param data [String, nil] the input that failed to parse
|
|
53
|
+
# @param cause [Exception, nil] the original exception
|
|
54
|
+
# @return [ParseError] new error instance
|
|
55
|
+
# @example
|
|
56
|
+
# ParseError.new("unexpected token at line 1 column 2", data: "{}")
|
|
57
|
+
def initialize(message = nil, data: nil, cause: nil)
|
|
58
|
+
super(message)
|
|
59
|
+
@data = data
|
|
60
|
+
match = location_match(message)
|
|
61
|
+
@line = match && Integer(match[1])
|
|
62
|
+
@column = match && match[2] && Integer(match[2])
|
|
63
|
+
set_backtrace(cause.backtrace) if cause
|
|
64
|
+
end
|
|
4
65
|
|
|
66
|
+
# Build a ParseError from an original exception
|
|
67
|
+
#
|
|
68
|
+
# @api public
|
|
69
|
+
# @param original_exception [Exception] the adapter's parse error
|
|
70
|
+
# @param data [String] the input that failed to parse
|
|
71
|
+
# @return [ParseError] new error with formatted message
|
|
72
|
+
# @example
|
|
73
|
+
# ParseError.build(JSON::ParserError.new("..."), "{bad json}")
|
|
5
74
|
def self.build(original_exception, data)
|
|
6
|
-
new(original_exception.message
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
75
|
+
new(original_exception.message, data: data, cause: original_exception)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
# Match an adapter error message against the line/column pattern
|
|
81
|
+
#
|
|
82
|
+
# Adapter error messages sometimes embed bytes from the failing
|
|
83
|
+
# input (e.g., the json gem's ``"invalid byte sequence in UTF-8"``
|
|
84
|
+
# error). The pattern is pure ASCII so it's compatible with any
|
|
85
|
+
# encoding, but a UTF-8 string with invalid bytes still trips the
|
|
86
|
+
# regex engine — ``String#scrub`` replaces those bytes so the
|
|
87
|
+
# match can proceed. Strings in binary (ASCII-8BIT) or any valid
|
|
88
|
+
# encoding pass through scrub untouched.
|
|
89
|
+
#
|
|
90
|
+
# @api private
|
|
91
|
+
# @param message [String, nil] the adapter's error message
|
|
92
|
+
# @return [MatchData, nil] the regex match, or nil if no message or
|
|
93
|
+
# no location fragment was found
|
|
94
|
+
def location_match(message)
|
|
95
|
+
return unless message
|
|
96
|
+
|
|
97
|
+
LOCATION_PATTERN.match(message.scrub)
|
|
13
98
|
end
|
|
14
99
|
end
|
|
15
100
|
|
|
16
|
-
|
|
101
|
+
# Legacy aliases for backward compatibility
|
|
102
|
+
DecodeError = LoadError = ParseError
|
|
17
103
|
end
|
data/lib/multi_json/version.rb
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MultiJSON
|
|
4
|
+
# Version information for MultiJSON
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
2
7
|
class Version
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
# Major version number
|
|
9
|
+
MAJOR = 1 unless defined? MultiJSON::Version::MAJOR
|
|
10
|
+
# Minor version number
|
|
11
|
+
MINOR = 21 unless defined? MultiJSON::Version::MINOR
|
|
12
|
+
# Patch version number
|
|
13
|
+
PATCH = 1 unless defined? MultiJSON::Version::PATCH
|
|
14
|
+
# Pre-release version suffix
|
|
15
|
+
PRE = nil unless defined? MultiJSON::Version::PRE
|
|
7
16
|
|
|
8
17
|
class << self
|
|
9
|
-
#
|
|
18
|
+
# Return the version string
|
|
19
|
+
#
|
|
20
|
+
# @api private
|
|
21
|
+
# @return [String] version in semver format
|
|
10
22
|
def to_s
|
|
11
|
-
[MAJOR, MINOR, PATCH, PRE].compact.join(
|
|
23
|
+
[MAJOR, MINOR, PATCH, PRE].compact.join(".")
|
|
12
24
|
end
|
|
13
25
|
end
|
|
14
26
|
end
|
|
15
27
|
|
|
28
|
+
# Current version string in semver format
|
|
16
29
|
VERSION = Version.to_s.freeze
|
|
17
30
|
end
|