json_p3 0.4.1 → 1.0.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 (76) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.rubocop.yml +26 -9
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +54 -0
  6. data/README.md +125 -123
  7. data/Rakefile +3 -3
  8. data/certs/jgrp.pem +21 -21
  9. data/lib/json_p3/errors.rb +51 -43
  10. data/lib/json_p3/patch/op.rb +23 -0
  11. data/lib/json_p3/patch/op_add.rb +51 -0
  12. data/lib/json_p3/patch/op_copy.rb +64 -0
  13. data/lib/json_p3/patch/op_move.rb +74 -0
  14. data/lib/json_p3/patch/op_remove.rb +56 -0
  15. data/lib/json_p3/patch/op_replace.rb +54 -0
  16. data/lib/json_p3/patch/op_test.rb +31 -0
  17. data/lib/json_p3/patch.rb +15 -330
  18. data/lib/json_p3/path/environment.rb +113 -0
  19. data/lib/json_p3/path/filter.rb +463 -0
  20. data/lib/json_p3/path/function.rb +12 -0
  21. data/lib/json_p3/path/function_extensions/count.rb +15 -0
  22. data/lib/json_p3/path/function_extensions/length.rb +17 -0
  23. data/lib/json_p3/path/function_extensions/match.rb +62 -0
  24. data/lib/json_p3/path/function_extensions/pattern.rb +42 -0
  25. data/lib/json_p3/path/function_extensions/search.rb +44 -0
  26. data/lib/json_p3/path/function_extensions/value.rb +15 -0
  27. data/lib/json_p3/path/lexer.rb +220 -0
  28. data/lib/json_p3/path/node.rb +48 -0
  29. data/lib/json_p3/path/parser.rb +676 -0
  30. data/lib/json_p3/path/query.rb +74 -0
  31. data/lib/json_p3/path/segment.rb +172 -0
  32. data/lib/json_p3/path/selector.rb +304 -0
  33. data/lib/json_p3/path/serialize.rb +16 -0
  34. data/lib/json_p3/path/unescape.rb +134 -0
  35. data/lib/json_p3/pointer.rb +15 -76
  36. data/lib/json_p3/relative_pointer.rb +69 -0
  37. data/lib/json_p3/version.rb +1 -1
  38. data/lib/json_p3.rb +50 -13
  39. data/sig/json_p3/cache.rbs +21 -0
  40. data/sig/json_p3/errors.rbs +55 -0
  41. data/sig/json_p3/patch.rbs +145 -0
  42. data/sig/json_p3/path/environment.rbs +81 -0
  43. data/sig/json_p3/path/filter.rbs +196 -0
  44. data/sig/json_p3/path/function.rbs +94 -0
  45. data/sig/json_p3/path/lexer.rbs +62 -0
  46. data/sig/json_p3/path/node.rbs +46 -0
  47. data/sig/json_p3/path/parser.rbs +92 -0
  48. data/sig/json_p3/path/query.rbs +47 -0
  49. data/sig/json_p3/path/segment.rbs +54 -0
  50. data/sig/json_p3/path/selector.rbs +100 -0
  51. data/sig/json_p3/path/serialize.rbs +9 -0
  52. data/sig/json_p3/path/unescape.rbs +12 -0
  53. data/sig/json_p3/pointer.rbs +64 -0
  54. data/sig/json_p3/relative_pointer.rbs +30 -0
  55. data/sig/json_p3.rbs +24 -1318
  56. data.tar.gz.sig +0 -0
  57. metadata +66 -46
  58. metadata.gz.sig +0 -0
  59. data/lib/json_p3/environment.rb +0 -111
  60. data/lib/json_p3/filter.rb +0 -459
  61. data/lib/json_p3/function.rb +0 -10
  62. data/lib/json_p3/function_extensions/count.rb +0 -15
  63. data/lib/json_p3/function_extensions/length.rb +0 -17
  64. data/lib/json_p3/function_extensions/match.rb +0 -62
  65. data/lib/json_p3/function_extensions/pattern.rb +0 -39
  66. data/lib/json_p3/function_extensions/search.rb +0 -44
  67. data/lib/json_p3/function_extensions/value.rb +0 -15
  68. data/lib/json_p3/lexer.rb +0 -440
  69. data/lib/json_p3/node.rb +0 -44
  70. data/lib/json_p3/parser.rb +0 -553
  71. data/lib/json_p3/path.rb +0 -72
  72. data/lib/json_p3/segment.rb +0 -158
  73. data/lib/json_p3/selector.rb +0 -306
  74. data/lib/json_p3/serialize.rb +0 -13
  75. data/lib/json_p3/token.rb +0 -36
  76. data/lib/json_p3/unescape.rb +0 -112
@@ -1,57 +1,65 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSONP3
4
- # An exception raised when a JSONPathEnvironment is misconfigured.
5
- class JSONPathEnvironmentError < StandardError; end
4
+ # Base class for all errors raised from this gem.
5
+ class Error < StandardError; end
6
6
 
7
- # Base class for JSONPath exceptions that happen when parsing or evaluating a query.
8
- class JSONPathError < StandardError
9
- FULL_MESSAGE = ((RUBY_VERSION.split(".")&.map(&:to_i) <=> [3, 2, 0]) || -1) < 1
7
+ module Path
8
+ # Base class for all JSONPath errors.
9
+ class Error < JSONP3::Error
10
+ FULL_MESSAGE = ((RUBY_VERSION.split(".")&.map(&:to_i) <=> [3, 2, 0]) || -1) < 1
10
11
 
11
- def initialize(msg, token)
12
- super(msg)
13
- @token = token
14
- end
12
+ def initialize(msg, token, query)
13
+ super(msg)
14
+ @token = token
15
+ @query = query
16
+ end
15
17
 
16
- def detailed_message(highlight: true, **_kwargs)
17
- if @token.query.strip.empty?
18
- "empty query"
19
- else
20
- lines = @token.query[...@token.start]&.lines or [""] # pleasing the type checker
21
- lineno = lines.length
22
- col = lines[-1].length
23
- pad = " " * lineno.to_s.length
24
- pointer = (" " * col) + ("^" * [@token.value.length, 1].max)
25
- <<~ENDOFMESSAGE.strip
26
- #{self.class}: #{message}
27
- #{pad} -> '#{@token.query}' #{lineno}:#{col}
28
- #{pad} |
29
- #{lineno} | #{@token.query}
30
- #{pad} | #{pointer} #{highlight ? "\e[1m#{message}\e[0m" : message}
31
- ENDOFMESSAGE
18
+ def detailed_message(highlight: true, **_kwargs)
19
+ if @query.strip.empty?
20
+ "empty query"
21
+ else
22
+ value = JSONP3::Path.get_token_value(@token, @query)
23
+ lines = @query[...@token[1]]&.lines or [""] # pleasing the type checker
24
+ lineno = lines.length
25
+ col = lines.last.length
26
+ pad = " " * lineno.to_s.length
27
+ pointer = (" " * col) + ("^" * [value.length, 1].max)
28
+ <<~ENDOFMESSAGE.strip
29
+ #{self.class}: #{message}
30
+ #{pad} -> '#{@query}' #{lineno}:#{col}
31
+ #{pad} |
32
+ #{lineno} | #{@query}
33
+ #{pad} | #{pointer} #{highlight ? "\e[1m#{message}\e[0m" : message}
34
+ ENDOFMESSAGE
35
+ end
32
36
  end
33
- end
34
37
 
35
- def full_message(highlight: true, order: :top)
36
- if FULL_MESSAGE
37
- # For Ruby < 3.2.0
38
- "#{super}\n#{detailed_message(highlight: highlight, order: order)}"
39
- else
40
- super
38
+ def full_message(highlight: true, order: :top)
39
+ if FULL_MESSAGE
40
+ # For Ruby < 3.2.0
41
+ "#{super}\n#{detailed_message(highlight: highlight, order: order)}"
42
+ else
43
+ super
44
+ end
41
45
  end
42
46
  end
43
- end
44
47
 
45
- class JSONPathSyntaxError < JSONPathError; end
46
- class JSONPathTypeError < JSONPathError; end
47
- class JSONPathNameError < JSONPathError; end
48
- class JSONPathRecursionError < JSONPathError; end
48
+ class SyntaxError < Error; end
49
+ class TypeError < Error; end
50
+ class NameError < Error; end
51
+ class RecursionError < Error; end
52
+ end
49
53
 
50
- class JSONPointerError < StandardError; end
51
- class JSONPointerIndexError < JSONPointerError; end
52
- class JSONPointerSyntaxError < JSONPointerError; end
53
- class JSONPointerTypeError < JSONPointerError; end
54
+ class Pointer
55
+ class Error < JSONP3::Error; end
56
+ class IndexError < Error; end
57
+ class SyntaxError < Error; end
58
+ class TypeError < Error; end
59
+ end
54
60
 
55
- class JSONPatchError < StandardError; end
56
- class JSONPatchTestFailure < JSONPatchError; end
61
+ class Patch
62
+ class Error < JSONP3::Error; end
63
+ class TestFailure < Error; end
64
+ end
57
65
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONP3
4
+ class Patch
5
+ # Base class for all JSON Patch operations
6
+ class Op
7
+ # Return the name of the patch operation.
8
+ def name
9
+ raise "JSON Patch operations must implement #name"
10
+ end
11
+
12
+ # Apply the patch operation to _value_.
13
+ def apply!(_value, _index)
14
+ raise "JSON Patch operations must implement apply!(value, index)"
15
+ end
16
+
17
+ # Return a JSON-like representation of this patch operation.
18
+ def to_h
19
+ raise "JSON Patch operations must implement #to_h"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONP3
4
+ class Patch
5
+ # The JSON Patch _add_ operation.
6
+ class OpAdd < Op
7
+ # @param pointer [JSONPointer]
8
+ # @param value [JSON-like value]
9
+ def initialize(pointer, value)
10
+ super()
11
+ @pointer = pointer
12
+ @value = value
13
+ end
14
+
15
+ def name
16
+ "add"
17
+ end
18
+
19
+ def apply!(value, index)
20
+ parent, obj = @pointer.resolve_with_parent(value)
21
+ return @value if parent == JSONP3::Pointer::UNDEFINED && @pointer.tokens.empty?
22
+
23
+ if parent == JSONP3::Pointer::UNDEFINED
24
+ raise JSONP3::Patch::Error,
25
+ "no such property or item '#{@pointer.parent}' (#{name}:#{index})"
26
+ end
27
+
28
+ target = @pointer.tokens.last
29
+ if parent.is_a?(Array)
30
+ if obj == JSONP3::Pointer::UNDEFINED
31
+ raise JSONP3::Patch::Error, "index out of range (#{name}:#{index})" unless target == "-"
32
+
33
+ parent << @value
34
+ else
35
+ parent.insert(target.to_i, @value)
36
+ end
37
+ elsif parent.is_a?(Hash)
38
+ parent[target] = @value
39
+ else
40
+ raise JSONP3::Patch::Error, "unexpected operation on #{parent.class} (#{name}:#{index})"
41
+ end
42
+
43
+ value
44
+ end
45
+
46
+ def to_h
47
+ { "op" => name, "path" => @pointer.to_s, "value" => @value }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONP3
4
+ class Patch
5
+ # The JSON Patch _copy_ operation.
6
+ class OpCopy < Op
7
+ # @param from [JSONPointer]
8
+ # @param pointer [JSONPointer]
9
+ def initialize(from, pointer)
10
+ super()
11
+ @from = from
12
+ @pointer = pointer
13
+ end
14
+
15
+ def name
16
+ "copy"
17
+ end
18
+
19
+ def apply!(value, index)
20
+ # Grab the source value.
21
+ _source_parent, source_obj = @from.resolve_with_parent(value)
22
+ if source_obj == JSONP3::Pointer::UNDEFINED
23
+ raise JSONP3::Patch::Error,
24
+ "source object does not exist (#{name}:#{index})"
25
+ end
26
+
27
+ # Find the parent of the destination pointer.
28
+ dest_parent, _dest_obj = @pointer.resolve_with_parent(value)
29
+ return deep_copy(source_obj) if dest_parent == JSONP3::Pointer::UNDEFINED
30
+
31
+ dest_target = @pointer.tokens.last
32
+ if dest_target == JSONP3::Pointer::UNDEFINED
33
+ raise JSONP3::Patch::Error,
34
+ "unexpected operation (#{name}:#{index})"
35
+ end
36
+
37
+ # Write the source value to the destination.
38
+ if dest_parent.is_a?(Array)
39
+ if dest_target == "-"
40
+ dest_parent << source_obj
41
+ else
42
+ dest_parent.insert(dest_target.to_i, deep_copy(source_obj))
43
+ end
44
+ elsif dest_parent.is_a?(Hash)
45
+ dest_parent[dest_target] = deep_copy(source_obj)
46
+ else
47
+ raise JSONP3::Patch::Error, "unexpected operation on #{dest_parent.class} (#{name}:#{index})"
48
+ end
49
+
50
+ value
51
+ end
52
+
53
+ def to_h
54
+ { "op" => name, "from" => @from.to_s, "path" => @pointer.to_s }
55
+ end
56
+
57
+ private
58
+
59
+ def deep_copy(obj)
60
+ Marshal.load(Marshal.dump(obj))
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONP3
4
+ class Patch
5
+ # The JSON Patch _move_ operation.
6
+ class OpMove < Op
7
+ # @param from [JSONPointer]
8
+ # @param pointer [JSONPointer]
9
+ def initialize(from, pointer)
10
+ super()
11
+ @from = from
12
+ @pointer = pointer
13
+ end
14
+
15
+ def name
16
+ "move"
17
+ end
18
+
19
+ def apply!(value, index)
20
+ if @pointer.relative_to?(@from)
21
+ raise JSONP3::Patch::Error,
22
+ "can't move object to one of its children (#{name}:#{index})"
23
+ end
24
+
25
+ # Grab the source value.
26
+ source_parent, source_obj = @from.resolve_with_parent(value)
27
+ if source_obj == JSONP3::Pointer::UNDEFINED
28
+ raise JSONP3::Patch::Error,
29
+ "source object does not exist (#{name}:#{index})"
30
+ end
31
+
32
+ source_target = @from.tokens.last
33
+ if source_target == JSONP3::Pointer::UNDEFINED
34
+ raise JSONP3::Patch::Error,
35
+ "unexpected operation (#{name}:#{index})"
36
+ end
37
+
38
+ # Delete the target value from the source location.
39
+ if source_parent.is_a?(Array)
40
+ source_parent.delete_at(source_target.to_i)
41
+ elsif source_parent.is_a?(Hash)
42
+ source_parent.delete(source_target)
43
+ end
44
+
45
+ # Find the parent of the destination pointer.
46
+ dest_parent, _dest_obj = @pointer.resolve_with_parent(value)
47
+ return source_obj if dest_parent == JSONP3::Pointer::UNDEFINED
48
+
49
+ dest_target = @pointer.tokens.last
50
+ if dest_target == JSONP3::Pointer::UNDEFINED
51
+ raise JSONP3::Patch::Error,
52
+ "unexpected operation (#{name}:#{index})"
53
+ end
54
+
55
+ # Write the source value to the destination.
56
+ if dest_parent.is_a?(Array)
57
+ if dest_target == "-"
58
+ dest_parent << source_obj
59
+ else
60
+ dest_parent[dest_target.to_i] = source_obj
61
+ end
62
+ elsif dest_parent.is_a?(Hash)
63
+ dest_parent[dest_target] = source_obj
64
+ end
65
+
66
+ value
67
+ end
68
+
69
+ def to_h
70
+ { "op" => name, "from" => @from.to_s, "path" => @pointer.to_s }
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONP3
4
+ class Patch
5
+ # The JSON Patch _remove_ operation.
6
+ class OpRemove < Op
7
+ # @param pointer [JSONPointer]
8
+ def initialize(pointer)
9
+ super()
10
+ @pointer = pointer
11
+ end
12
+
13
+ def name
14
+ "remove"
15
+ end
16
+
17
+ def apply!(value, index)
18
+ parent, obj = @pointer.resolve_with_parent(value)
19
+
20
+ if parent == JSONP3::Pointer::UNDEFINED && @pointer.tokens.empty?
21
+ raise JSONP3::Patch::Error,
22
+ "can't remove root (#{name}:#{index})"
23
+ end
24
+
25
+ if parent == JSONP3::Pointer::UNDEFINED
26
+ raise JSONP3::Patch::Error,
27
+ "no such property or item '#{@pointer.parent}' (#{name}:#{index})"
28
+ end
29
+
30
+ target = @pointer.tokens.last
31
+ if target == JSONP3::Pointer::UNDEFINED
32
+ raise JSONP3::Patch::Error,
33
+ "unexpected operation (#{name}:#{index})"
34
+ end
35
+
36
+ if parent.is_a?(Array)
37
+ raise JSONP3::Patch::Error, "no item to remove (#{name}:#{index})" if obj == JSONP3::Pointer::UNDEFINED
38
+
39
+ parent.delete_at(target.to_i)
40
+ elsif parent.is_a?(Hash)
41
+ raise JSONP3::Patch::Error, "no property to remove (#{name}:#{index})" if obj == JSONP3::Pointer::UNDEFINED
42
+
43
+ parent.delete(target)
44
+ else
45
+ raise JSONP3::Patch::Error, "unexpected operation on #{parent.class} (#{name}:#{index})"
46
+ end
47
+
48
+ value
49
+ end
50
+
51
+ def to_h
52
+ { "op" => name, "path" => @pointer.to_s }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONP3
4
+ class Patch
5
+ # The JSON Patch _replace_ operation.
6
+ class OpReplace < Op
7
+ # @param pointer [JSONPointer]
8
+ # @param value [JSON-like value]
9
+ def initialize(pointer, value)
10
+ super()
11
+ @pointer = pointer
12
+ @value = value
13
+ end
14
+
15
+ def name
16
+ "replace"
17
+ end
18
+
19
+ def apply!(value, index)
20
+ parent, obj = @pointer.resolve_with_parent(value)
21
+ return @value if parent == JSONP3::Pointer::UNDEFINED && @pointer.tokens.empty?
22
+
23
+ if parent == JSONP3::Pointer::UNDEFINED
24
+ raise JSONP3::Patch::Error,
25
+ "no such property or item '#{@pointer.parent}' (#{name}:#{index})"
26
+ end
27
+
28
+ target = @pointer.tokens.last
29
+ if target == JSONP3::Pointer::UNDEFINED
30
+ raise JSONP3::Patch::Error,
31
+ "unexpected operation (#{name}:#{index})"
32
+ end
33
+
34
+ if parent.is_a?(Array)
35
+ raise JSONP3::Patch::Error, "no item to replace (#{name}:#{index})" if obj == JSONP3::Pointer::UNDEFINED
36
+
37
+ parent[target.to_i] = @value
38
+ elsif parent.is_a?(Hash)
39
+ raise JSONP3::Patch::Error, "no property to replace (#{name}:#{index})" if obj == JSONP3::Pointer::UNDEFINED
40
+
41
+ parent[target] = @value
42
+ else
43
+ raise JSONP3::Patch::Error, "unexpected operation on #{parent.class} (#{name}:#{index})"
44
+ end
45
+
46
+ value
47
+ end
48
+
49
+ def to_h
50
+ { "op" => name, "path" => @pointer.to_s, "value" => @value }
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONP3
4
+ class Patch
5
+ # The JSON Patch _test_ operation.
6
+ class OpTest < Op
7
+ # @param pointer [JSONPointer]
8
+ # @param value [JSON-like value]
9
+ def initialize(pointer, value)
10
+ super()
11
+ @pointer = pointer
12
+ @value = value
13
+ end
14
+
15
+ def name
16
+ "test"
17
+ end
18
+
19
+ def apply!(value, index)
20
+ obj = @pointer.resolve(value)
21
+ raise JSONP3::Patch::TestFailure, "test failed (#{name}:#{index})" if obj != @value
22
+
23
+ value
24
+ end
25
+
26
+ def to_h
27
+ { "op" => name, "path" => @pointer.to_s, "value" => @value }
28
+ end
29
+ end
30
+ end
31
+ end