multi_json 1.19.1 → 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.
@@ -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
@@ -1,64 +1,159 @@
1
- module MultiJson
2
- # Mixin providing configurable load/dump options
1
+ # frozen_string_literal: true
2
+
3
+ module MultiJSON
4
+ # Mixin providing configurable parse/generate options
3
5
  #
4
6
  # Supports static hashes or dynamic callables (procs/lambdas).
5
- # Extended by both MultiJson (global options) and Adapter classes.
7
+ # Extended by both MultiJSON (global options) and Adapter classes.
6
8
  #
7
9
  # @api private
8
10
  module Options
9
- EMPTY_OPTIONS = {}.freeze
10
- private_constant :EMPTY_OPTIONS
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
11
17
 
12
- # Set options for load operations
18
+ # Set options for parse operations
13
19
  #
14
- # @api private
20
+ # @api public
15
21
  # @param options [Hash, Proc] options hash or callable
16
22
  # @return [Hash, Proc] the options
17
- def load_options=(options)
23
+ # @example
24
+ # MultiJSON.parse_options = {symbolize_keys: true}
25
+ def parse_options=(options)
18
26
  OptionsCache.reset
19
- @load_options = options
27
+ @parse_options = options
20
28
  end
21
29
 
22
- # Set options for dump operations
30
+ # Set options for generate operations
23
31
  #
24
- # @api private
32
+ # @api public
25
33
  # @param options [Hash, Proc] options hash or callable
26
34
  # @return [Hash, Proc] the options
27
- def dump_options=(options)
35
+ # @example
36
+ # MultiJSON.generate_options = {pretty: true}
37
+ def generate_options=(options)
28
38
  OptionsCache.reset
29
- @dump_options = options
39
+ @generate_options = options
30
40
  end
31
41
 
32
- # Get options for load operations
42
+ # Get options for parse operations
33
43
  #
34
- # @api private
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
56
+ end
57
+
58
+ # Get options for generate operations
59
+ #
60
+ # @api public
61
+ # @param args [Array<Object>] forwarded to the callable, ignored otherwise
35
62
  # @return [Hash] resolved options hash
36
- def load_options(...)
37
- resolve_options(@load_options, ...) || default_load_options
63
+ # @example
64
+ # MultiJSON.generate_options #=> {}
65
+ def generate_options(*args)
66
+ resolve_options(@generate_options, *args) || default_generate_options
38
67
  end
39
68
 
40
- # Get options for dump operations
69
+ # Get default parse options
41
70
  #
42
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 #=> {}
121
+ def load_options(*args)
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)
125
+ end
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
43
132
  # @return [Hash] resolved options hash
44
- def dump_options(...)
45
- resolve_options(@dump_options, ...) || default_dump_options
133
+ # @example
134
+ # MultiJSON.dump_options #=> {}
135
+ def dump_options(*args)
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)
46
139
  end
47
140
 
48
- # Get default load options
141
+ # Get default parse options
49
142
  #
50
143
  # @api private
144
+ # @deprecated Use {#default_parse_options} instead. Will be removed in v2.0.
51
145
  # @return [Hash] frozen empty hash
52
146
  def default_load_options
53
- @default_load_options ||= EMPTY_OPTIONS
147
+ default_parse_options
54
148
  end
55
149
 
56
- # Get default dump options
150
+ # Get default generate options
57
151
  #
58
152
  # @api private
153
+ # @deprecated Use {#default_generate_options} instead. Will be removed in v2.0.
59
154
  # @return [Hash] frozen empty hash
60
155
  def default_dump_options
61
- @default_dump_options ||= EMPTY_OPTIONS
156
+ default_generate_options
62
157
  end
63
158
 
64
159
  private
@@ -67,9 +162,13 @@ module MultiJson
67
162
  #
68
163
  # @api private
69
164
  # @param options [Hash, Proc, nil] options configuration
165
+ # @param args [Array<Object>] arguments forwarded to a callable provider
70
166
  # @return [Hash, nil] resolved options hash
71
- def resolve_options(options, ...)
72
- return invoke_callable(options, ...) if options.respond_to?(:call)
167
+ def resolve_options(options, *args)
168
+ if options.respond_to?(:call)
169
+ # @type var options: options_proc
170
+ return invoke_callable(options, *args)
171
+ end
73
172
 
74
173
  options.to_hash if options.respond_to?(:to_hash)
75
174
  end
@@ -78,9 +177,10 @@ module MultiJson
78
177
  #
79
178
  # @api private
80
179
  # @param callable [Proc] options provider
180
+ # @param args [Array<Object>] arguments forwarded when the callable is non-arity-zero
81
181
  # @return [Hash] options returned by the callable
82
- def invoke_callable(callable, ...)
83
- callable.arity.zero? ? callable.call : callable.call(...)
182
+ def invoke_callable(callable, *args)
183
+ callable.arity.zero? ? callable.call : callable.call(*args)
84
184
  end
85
185
  end
86
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,74 +1,21 @@
1
- module MultiJson
2
- # Thread-safe LRU-like cache for merged options hashes
1
+ # frozen_string_literal: true
2
+
3
+ module MultiJSON
4
+ # Thread-safe bounded cache for merged options hashes
3
5
  #
4
6
  # Caches are separated for load and dump operations. Each cache is
5
7
  # bounded to prevent unbounded memory growth when options are
6
- # generated dynamically.
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.
7
12
  #
8
13
  # @api private
9
14
  module OptionsCache
10
- # Maximum entries before oldest entry is evicted
11
- MAX_CACHE_SIZE = 1000
12
-
13
- # Thread-safe cache store using double-checked locking pattern
14
- #
15
- # @api private
16
- class Store
17
- # Sentinel value to detect cache misses (unique object identity)
18
- NOT_FOUND = Object.new
19
-
20
- # Create a new cache store
21
- #
22
- # @api private
23
- # @return [Store] new store instance
24
- def initialize
25
- @cache = {}
26
- @mutex = Mutex.new
27
- end
28
-
29
- # Clear all cached entries
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
- # @api private
40
- # @param key [Object] cache key
41
- # @param default [Object] default value if key not found
42
- # @yield block to compute value if not cached
43
- # @return [Object] cached or computed value
44
- def fetch(key, default = nil)
45
- # Fast path: check cache without lock (safe for reads)
46
- value = @cache.fetch(key, NOT_FOUND)
47
- return value unless value.equal?(NOT_FOUND)
48
-
49
- # Slow path: acquire lock and compute value
50
- @mutex.synchronize do
51
- @cache.fetch(key) { block_given? ? store(key, yield) : default }
52
- end
53
- end
54
-
55
- private
56
-
57
- # Stores a value in the cache with LRU eviction
58
- #
59
- # @api private
60
- # @param key [Object] cache key
61
- # @param value [Object] value to store
62
- # @return [Object] the stored value
63
- def store(key, value)
64
- # Double-check in case another thread computed while we waited
65
- @cache.fetch(key) do
66
- # Evict oldest entry if at capacity (Hash maintains insertion order)
67
- @cache.shift if @cache.size >= MAX_CACHE_SIZE
68
- @cache[key] = value
69
- end
70
- end
71
- end
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
72
19
 
73
20
  class << self
74
21
  # Get the dump options cache
@@ -83,6 +30,33 @@ module MultiJson
83
30
  # @return [Store] load cache store
84
31
  attr_reader :load
85
32
 
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
45
+
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
56
+
57
+ @max_cache_size = value
58
+ end
59
+
86
60
  # Reset both caches
87
61
  #
88
62
  # @api private
@@ -93,6 +67,20 @@ module MultiJson
93
67
  end
94
68
  end
95
69
 
96
- reset
70
+ self.max_cache_size = DEFAULT_MAX_CACHE_SIZE
71
+ end
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
97
82
  end
98
83
  end
84
+
85
+ require_relative "options_cache/#{MultiJSON::OptionsCache.send(:const_get, :BACKENDS).fetch(RUBY_ENGINE, "mutex_store")}"
86
+ MultiJSON::OptionsCache.reset
@@ -1,10 +1,24 @@
1
- module MultiJson
1
+ # frozen_string_literal: true
2
+
3
+ module MultiJSON
2
4
  # Raised when JSON parsing fails
3
5
  #
4
- # Wraps the underlying adapter's parse error with the original input data.
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).
5
11
  #
6
12
  # @api public
7
13
  class ParseError < StandardError
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
+
8
22
  # The input string that failed to parse
9
23
  #
10
24
  # @api public
@@ -13,6 +27,24 @@ module MultiJson
13
27
  # error.data #=> "{invalid json}"
14
28
  attr_reader :data
15
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
+
16
48
  # Create a new ParseError
17
49
  #
18
50
  # @api public
@@ -21,10 +53,13 @@ module MultiJson
21
53
  # @param cause [Exception, nil] the original exception
22
54
  # @return [ParseError] new error instance
23
55
  # @example
24
- # ParseError.new("unexpected token", data: "{invalid}", cause: err)
56
+ # ParseError.new("unexpected token at line 1 column 2", data: "{}")
25
57
  def initialize(message = nil, data: nil, cause: nil)
26
58
  super(message)
27
59
  @data = data
60
+ match = location_match(message)
61
+ @line = match && Integer(match[1])
62
+ @column = match && match[2] && Integer(match[2])
28
63
  set_backtrace(cause.backtrace) if cause
29
64
  end
30
65
 
@@ -39,6 +74,28 @@ module MultiJson
39
74
  def self.build(original_exception, data)
40
75
  new(original_exception.message, data: data, cause: original_exception)
41
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)
98
+ end
42
99
  end
43
100
 
44
101
  # Legacy aliases for backward compatibility
@@ -1,16 +1,18 @@
1
- module MultiJson
2
- # Version information for MultiJson
1
+ # frozen_string_literal: true
2
+
3
+ module MultiJSON
4
+ # Version information for MultiJSON
3
5
  #
4
6
  # @api private
5
7
  class Version
6
8
  # Major version number
7
- MAJOR = 1 unless defined? MultiJson::Version::MAJOR
9
+ MAJOR = 1 unless defined? MultiJSON::Version::MAJOR
8
10
  # Minor version number
9
- MINOR = 19 unless defined? MultiJson::Version::MINOR
11
+ MINOR = 21 unless defined? MultiJSON::Version::MINOR
10
12
  # Patch version number
11
- PATCH = 1 unless defined? MultiJson::Version::PATCH
13
+ PATCH = 1 unless defined? MultiJSON::Version::PATCH
12
14
  # Pre-release version suffix
13
- PRE = nil unless defined? MultiJson::Version::PRE
15
+ PRE = nil unless defined? MultiJSON::Version::PRE
14
16
 
15
17
  class << self
16
18
  # Return the version string