json-path-builder 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,168 @@
1
+ module JsonPath
2
+ RSpec.describe Builder do
3
+ let(:key) { "some-value" }
4
+ let(:other_key) { "some-other-value" }
5
+ let(:list) { %w[some-list-value-1 some-list-value-2] }
6
+
7
+ let(:input) { { key: key, other_key: other_key, list: list }.as_json }
8
+
9
+ subject(:instance) { described_class.new }
10
+
11
+ describe '.from' do
12
+ it 'handles simple path mapping' do
13
+ %i[key other_key].each { |k| instance.from(k) }
14
+
15
+ expect(instance.build_for(input)).to eql({ key: key, other_key: other_key })
16
+ end
17
+
18
+ it 'handles renaming path name' do
19
+ instance.from(:other_key, to: :another_key)
20
+
21
+ expect(instance.build_for(input)).to eql({ another_key: other_key })
22
+ end
23
+
24
+ it 'handles transformations' do
25
+ instance.from(:key, transform: proc { |v| v.upcase })
26
+
27
+ expect(instance.build_for(input)).to eql({ key: key.upcase })
28
+ end
29
+ end
30
+
31
+ describe '.from_each' do
32
+ it 'handles simple path mapping' do
33
+ instance.from_each(:list)
34
+
35
+ expect(instance.build_for(input)).to eql({ list: list })
36
+ end
37
+
38
+ it 'handles renaming path name' do
39
+ instance.from(:list, to: :another_list_key)
40
+
41
+ expect(instance.build_for(input)).to eql({ another_list_key: list })
42
+ end
43
+
44
+ it 'handles transformations' do
45
+ instance.from_each(:list, transform: proc { |v| v.upcase })
46
+
47
+ expect(instance.build_for(input)).to eql({ list: list.map(&:upcase) })
48
+ end
49
+
50
+ it 'handles skipping items' do
51
+ instance.from_each(:list, skip_if: proc { |v| v == 'some-list-value-1' })
52
+
53
+ expect(instance.build_for(input)).to eql({ list: ['some-list-value-2'] })
54
+ end
55
+ end
56
+
57
+ describe '#within' do
58
+ let(:email) { 'email@domain.com' }
59
+ let(:user_id) { 1 }
60
+ let(:input) { { root: { deep: { profile: { email: email, uid: user_id } } } }.as_json }
61
+
62
+ it 'uses scope to simplify dot notation' do
63
+ builder = described_class.new
64
+ builder.within('root.deep.profile') do |b|
65
+ b.from(:email)
66
+ b.from(:uid, to: :user_id)
67
+ end
68
+
69
+ expect(builder.build_for(input)).to eql({ email: email, user_id: user_id })
70
+ end
71
+ end
72
+
73
+ describe '#with_wrapped_data_class' do
74
+ let(:email) { 'email@domain.com' }
75
+ let(:user_id) { 123 }
76
+ let(:input) { { profile: { email: email } }.as_json }
77
+ let(:user) { double(:user, id: user_id) }
78
+ let(:user_repo) { double(:user_repo, find_by: user) }
79
+
80
+ let(:wrapped_data_class) do
81
+ Class.new(SimpleDelegator) do
82
+ class << self
83
+ attr_accessor :user_repo
84
+ end
85
+
86
+ def user
87
+ self.class.user_repo.find_by(email: dig('profile', 'email'))
88
+ end
89
+ end
90
+ end
91
+
92
+ before do
93
+ wrapped_data_class.user_repo = user_repo
94
+ end
95
+
96
+ it 'wraps input data in wrapped class' do
97
+ builder = described_class.new
98
+ builder.with_wrapped_data_class(wrapped_data_class)
99
+ transform = proc do |_email, path_context|
100
+ path_context.wrapped_source_data.user.id
101
+ end
102
+
103
+ builder.from('profile.email', to: :user_id, transform: transform)
104
+ expect(builder.build_for(input)).to eql({ user_id: user_id })
105
+ end
106
+ end
107
+
108
+ describe '#wrapped_data_class' do
109
+ context 'with custom data class' do
110
+ let(:wrapper_class) { Class.new(DefaultDataWrapper) }
111
+
112
+ it 'returns custom class' do
113
+ instance.with_wrapped_data_class(wrapper_class)
114
+
115
+ expect(instance.data_wrapper_class).to eql(wrapper_class)
116
+ end
117
+ end
118
+
119
+ context 'without custom data class' do
120
+ let(:custom_wrapper) { nil }
121
+
122
+ it 'returns default class' do
123
+ expect(instance.data_wrapper_class).to eql(DefaultDataWrapper)
124
+ end
125
+ end
126
+ end
127
+
128
+ # describe PathsBuilder::PathContext do
129
+ # let(:key) { "some-value" }
130
+ # let(:other_key) { "some-other-value" }
131
+ # let(:list) { %w[some-list-value-1 some-list-value-2] }
132
+ #
133
+ # let(:input) { { key: key, other_key: other_key, list: list }.as_json }
134
+ # let(:builder) { PathsBuilder.new }
135
+ # let(:path_context) { builder.path_context_collection.first }
136
+ #
137
+ # before(:each) do
138
+ # builder.from(:key, to: :another_key, transform: proc { |val| val.upcase })
139
+ # builder.with_source_data(input)
140
+ # end
141
+ #
142
+ #
143
+ # it 'returns path context instance' do
144
+ # expect(path_context).to be_instance_of(described_class)
145
+ # end
146
+ #
147
+ # describe '#wrapped_source_data' do
148
+ # subject { path_context.wrapped_source_data }
149
+ #
150
+ # context 'with custom data class' do
151
+ # let(:wrapper_class) { Sharplaunch::PropertyListingDataWrapper }
152
+ #
153
+ # before(:each) { builder.with_wrapped_data_class(wrapper_class) }
154
+ #
155
+ # it 'returns custom class' do
156
+ # expect(subject).to be_an_instance_of(wrapper_class)
157
+ # end
158
+ # end
159
+ #
160
+ # context 'without custom data wrapper class' do
161
+ # it 'returns default wrapper instance' do
162
+ # expect(subject).to be_instance_of(PathsBuilder::DefaultDataWrapper)
163
+ # end
164
+ # end
165
+ # end
166
+ # end
167
+ end
168
+ end
@@ -0,0 +1,33 @@
1
+ module JsonPath
2
+ RSpec.describe DefaultDataWrapper do
3
+ describe '#[]' do
4
+ context 'when the wrapped object is a hash with string keys' do
5
+ let(:data) { { 'name' => 'John', 'age' => 30 } }
6
+ let(:wrapper) { described_class.new(data) }
7
+
8
+ it 'returns the value associated with the symbol key' do
9
+ expect(wrapper[:name]).to eq('John')
10
+ expect(wrapper[:age]).to eq(30)
11
+ end
12
+
13
+ it 'does not modify the original hash' do
14
+ expect { wrapper[:name] }.not_to(change { data })
15
+ end
16
+ end
17
+
18
+ context 'when the wrapped object is a hash with symbol keys' do
19
+ let(:data) { { name: 'John', age: 30 } }
20
+ let(:wrapper) { described_class.new(data) }
21
+
22
+ it 'returns the value associated with the symbol key' do
23
+ expect(wrapper[:name]).to eq('John')
24
+ expect(wrapper[:age]).to eq(30)
25
+ end
26
+
27
+ it 'does not modify the original hash' do
28
+ expect { wrapper[:name] }.not_to(change { data })
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,46 @@
1
+ module JsonPath
2
+ # rubocop:disable Layout/LineLength,RSpec/MessageSpies,RSpec/StubbedMock
3
+ RSpec.describe PathContextCollection do
4
+ describe '#reject_from_paths!' do
5
+ let(:path_context1) { instance_double(PathContext, from: 'path1') }
6
+ let(:path_context2) { instance_double(PathContext, from: 'path2') }
7
+ let(:path_context3) { instance_double(PathContext, from: 'path3') }
8
+
9
+ it 'removes elements from the collection with matching from paths' do
10
+ collection = described_class.new([])
11
+ collection.push(path_context1, path_context2, path_context3)
12
+
13
+ collection.reject_from_paths!(%w[path1 path3])
14
+
15
+ expect(collection).to include(path_context2)
16
+ expect(collection).not_to include(path_context1, path_context3)
17
+ end
18
+ end
19
+
20
+ describe '#add_path' do
21
+ let(:paths_builder) { instance_double(Builder) }
22
+ let(:iterable_data) { true }
23
+ let(:transform) { proc { |data| data.upcase } }
24
+ let(:defaults) { { foo: 'bar' } }
25
+ let(:fallback_proc) { proc { 'fallback' } }
26
+ let(:skip_if_proc) { proc { |data| data.nil? } }
27
+ let(:path_context) { instance_double(PathContext) }
28
+
29
+ before do
30
+ allow(PathContext).to receive(:new).and_return(path_context)
31
+ end
32
+
33
+ it 'adds a new PathContext object to the collection' do
34
+ collection = described_class.new([])
35
+ expect(PathContext).to receive(:new).with('path', paths_builder, to: nil, iterable_data: iterable_data,
36
+ transform: transform, use_builder: true, defaults: defaults, fallback_proc: fallback_proc, skip_if_proc: skip_if_proc).and_return(path_context)
37
+
38
+ result = collection.add_path('path', paths_builder, iterable_data: iterable_data, transform: transform,
39
+ defaults: defaults, fallback_proc: fallback_proc, skip_if_proc: skip_if_proc)
40
+
41
+ expect(result).to include(path_context)
42
+ end
43
+ end
44
+ end
45
+ # rubocop:enable Layout/LineLength,RSpec/MessageSpies,RSpec/StubbedMock
46
+ end
@@ -0,0 +1,90 @@
1
+ module JsonPath
2
+ RSpec.describe PathContext do
3
+ let(:builder) { double("PathsBuilder", nested_paths: []) }
4
+ let(:data) { { foo: "bar" } }
5
+ let(:transform) { :iso8601 }
6
+ let(:json_path) { "foo" }
7
+ let(:defaults) { nil }
8
+ let(:path_context) do
9
+ described_class.new(json_path, builder, iterable_data: false, transform: transform, use_builder: false,
10
+ defaults: defaults, fallback_proc: nil, skip_if_proc: nil)
11
+ end
12
+
13
+ describe "#initialize" do
14
+ context "when json_path is blank" do
15
+ let(:json_path) { ' ' }
16
+
17
+ it "raises an ArgumentError" do
18
+ expect { path_context }.to raise_error(ArgumentError, "`from` must be filled")
19
+ end
20
+ end
21
+
22
+ context "when transform is not a symbol or a valid transform key" do
23
+ let(:transform) { "not_a_symbol_or_valid_transform" }
24
+
25
+ it "raises an ArgumentError" do
26
+ expect do
27
+ path_context
28
+ end.to raise_error(ArgumentError,
29
+ "`transform`: 'not_a_symbol_or_valid_transform' must be one of [:iso8601, :date]")
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "#parent" do
35
+ context "when the builder has a parent_path_context" do
36
+ let(:parent_path_context) { double("PathContext") }
37
+
38
+ before { allow(builder).to receive(:parent_path_context).and_return(parent_path_context) }
39
+
40
+ it "returns the parent_path_context" do
41
+ expect(path_context.parent).to eq(parent_path_context)
42
+ end
43
+ end
44
+
45
+ context "when the builder does not have a parent_path_context" do
46
+ before { allow(builder).to receive(:parent_path_context).and_return(nil) }
47
+
48
+ it "returns nil" do
49
+ expect(path_context.parent).to be_nil
50
+ end
51
+ end
52
+ end
53
+
54
+ describe "#defaults?" do
55
+ context "when defaults is an empty hash" do
56
+ let(:defaults) { {} }
57
+
58
+ it "returns false" do
59
+ expect(path_context.defaults?).to be false
60
+ end
61
+ end
62
+
63
+ context "when defaults is a non-empty hash" do
64
+ let(:defaults) { { baz: "qux" } }
65
+
66
+ it "returns true" do
67
+ expect(path_context.defaults?).to be true
68
+ end
69
+ end
70
+ end
71
+
72
+ describe "#transformable?" do
73
+ context "when transform is a Proc" do
74
+ let(:transform) { ->(val) { val.to_s.upcase } }
75
+
76
+ it "returns true" do
77
+ expect(path_context.transformable?).to be true
78
+ end
79
+ end
80
+
81
+ context "when transform is not a Proc" do
82
+ let(:transform) { nil }
83
+
84
+ it "returns false" do
85
+ expect(path_context.transformable?).to be false
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe JsonPathBuilder do
4
+ it "has a version number" do
5
+ expect(JsonPathBuilder::VERSION).not_to be_nil
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.join(__dir__, "..", 'dev', 'setup')
4
+ require Pathname.new(__dir__).realpath.join('coverage_helper').to_s
5
+
6
+ RSpec.configure do |config|
7
+ # Enable flags like --only-failures and --next-failure
8
+ config.example_status_persistence_file_path = ".rspec_status"
9
+
10
+ # Disable RSpec exposing methods globally on `Module` and `main`
11
+ config.disable_monkey_patching!
12
+
13
+ config.expect_with :rspec do |c|
14
+ c.syntax = :expect
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: json-path-builder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Desmond O'Leary
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-02-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rordash
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.1.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.1.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: codecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.6.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.6.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 13.0.6
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 13.0.6
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 3.12.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 3.12.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.21.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.21.2
97
+ description: Declarative mapping JSON/Hash data structures
98
+ email:
99
+ - desoleary@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".github/workflows/main.yml"
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - ".rubocop.yml"
108
+ - CHANGELOG.md
109
+ - CODE_OF_CONDUCT.md
110
+ - Gemfile
111
+ - LICENSE.txt
112
+ - README.md
113
+ - Rakefile
114
+ - bin/console
115
+ - bin/rspec
116
+ - bin/rubocop
117
+ - bin/setup
118
+ - dev/setup.rb
119
+ - json_path_builder.gemspec
120
+ - lib/json-path/builder.rb
121
+ - lib/json-path/default_data_wrapper.rb
122
+ - lib/json-path/path_context.rb
123
+ - lib/json-path/path_context_collection.rb
124
+ - lib/json-path/version.rb
125
+ - lib/json_path.rb
126
+ - sig/json_path_builder.rbs
127
+ - spec/coverage_helper.rb
128
+ - spec/json-path/builder_spec.rb
129
+ - spec/json-path/default_data_wrapper_spec.rb
130
+ - spec/json-path/path_context_collection_spec.rb
131
+ - spec/json-path/path_context_spec.rb
132
+ - spec/json_path_builder_spec.rb
133
+ - spec/spec_helper.rb
134
+ homepage: https://github.com/omnitech-solutions/json-path-builder
135
+ licenses:
136
+ - MIT
137
+ metadata:
138
+ homepage_uri: https://github.com/omnitech-solutions/json-path-builder
139
+ source_code_uri: https://github.com/omnitech-solutions/json-path-builder
140
+ changelog_uri: https://github.com/omnitech-solutions/json-path-builder/CHANGELOG.md
141
+ rubygems_mfa_required: 'true'
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '2.7'
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ requirements: []
157
+ rubygems_version: 3.1.6
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: Declarative mapping JSON/Hash data structures
161
+ test_files: []