braintree 4.32.0 → 4.33.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b982c20d1a239e4a623a802fb0c0d6ce66a5be501e767c22a33a7b06a3c09a59
4
- data.tar.gz: 9d515e47f8d7e5908b21f7ed5ccd98b02f2ce712298082fd67cbc3d88d7fd13d
3
+ metadata.gz: 4e90519d70f62c557f0f25c54d7c481854711c5206ae20a24d09ca3c7ea77970
4
+ data.tar.gz: f07cc386365888000e3e44f6083b24f1d8fec0e25bba465f0e157a97c9d54e51
5
5
  SHA512:
6
- metadata.gz: 1459fb667fdbb3d3f52feb2082394f347dbe0c3347eef68c87cbc31a8c915fcc96ff8623bbd98f8766e2391801423514fa2db4a4c90f511ea9d2c1c3778f9b3d
7
- data.tar.gz: 36af58c2cc626136cfaa8bc1f28d2acb8fa47e9e747e0e9eb641acabcbf4e0afadf34556e5467928c4e091a66279b505f952a1fb9e5476e19531f0c81b91a59d
6
+ metadata.gz: c801cf89c35d0f835fa9f8513e3cffb39e19cd5e3aee3e004db2047a129bf3519ee2f00396ae47ef4ce01bf95f2abc56719ead396733cf5977eb4d9ad8323d80
7
+ data.tar.gz: 57112741f710b8e08c8d03ebba98e1e385be46a6dcac917c52f717f6e1007a3bfeda9a329c7973ed32d3f4e9a1da5669a1ca7b518491cf643d643c88cc81f7ed
@@ -1,8 +1,8 @@
1
1
  module Braintree
2
2
  module Version
3
3
  Major = 4
4
- Minor = 32
5
- Tiny = 0
4
+ Minor = 33
5
+ Tiny = 1
6
6
 
7
7
  String = "#{Major}.#{Minor}.#{Tiny}"
8
8
  end
@@ -2,24 +2,24 @@
2
2
  # under the MIT license, copyright (c) 2005-2009 David Heinemeier Hansson
3
3
  module Braintree
4
4
  module Xml
5
- module Libxml
6
- LIB_XML_LIMIT = 30000000
5
+ module Nokogiri
6
+ NOKOGIRI_XML_LIMIT = 30000000
7
7
 
8
8
  def self.parse(xml_string)
9
- old_keep_blanks_setting = ::LibXML::XML.default_keep_blanks
10
- ::LibXML::XML.default_keep_blanks = false
11
- root_node = LibXML::XML::Parser.string(xml_string.strip).parse.root
12
- _node_to_hash(root_node)
13
- ensure
14
- ::LibXML::XML.default_keep_blanks = old_keep_blanks_setting
9
+ require "nokogiri" unless defined?(::Nokogiri)
10
+ doc = ::Nokogiri::XML(xml_string.strip)
11
+ _node_to_hash(doc.root)
15
12
  end
16
13
 
17
14
  def self._node_to_hash(node, hash = {})
18
- if node.text?
19
- raise ::LibXML::XML::Error if node.content.length >= LIB_XML_LIMIT
20
- hash[CONTENT_ROOT] = node.content
15
+ sub_hash = node.text? ? hash : _build_sub_hash(hash, node.name)
16
+
17
+ if node.text? || (node.children.size == 1 && node.children.first.text?)
18
+ content = node.text? ? node.content : node.children.first.content
19
+ raise "Content too large" if content.length >= NOKOGIRI_XML_LIMIT
20
+ sub_hash[CONTENT_ROOT] = content
21
+ _attributes_to_hash(node, sub_hash) unless node.text?
21
22
  else
22
- sub_hash = _build_sub_hash(hash, node.name)
23
23
  _attributes_to_hash(node, sub_hash)
24
24
  if _array?(node)
25
25
  _children_array_to_hash(node, sub_hash)
@@ -44,25 +44,27 @@ module Braintree
44
44
  end
45
45
 
46
46
  def self._children_to_hash(node, hash={})
47
- node.each { |child| _node_to_hash(child, hash) }
47
+ node.children.each { |child| _node_to_hash(child, hash) unless child.text? && child.content.strip.empty? }
48
48
  _attributes_to_hash(node, hash)
49
49
  hash
50
50
  end
51
51
 
52
52
  def self._attributes_to_hash(node, hash={})
53
- node.each_attr { |attr| hash[attr.name] = attr.value }
53
+ node.attributes.each { |name, attr| hash[name] = attr.value }
54
54
  hash
55
55
  end
56
56
 
57
57
  def self._children_array_to_hash(node, hash={})
58
- hash[node.child.name] = node.map do |child|
58
+ first_child = node.children.find { |child| !child.text? }
59
+ hash[first_child.name] = node.children.select { |child| !child.text? }.map do |child|
59
60
  _children_to_hash(child, {})
60
61
  end
61
62
  hash
62
63
  end
63
64
 
64
65
  def self._array?(node)
65
- node.child? && node.child.next? && node.child.name == node.child.next.name
66
+ non_text_children = node.children.select { |child| !child.text? }
67
+ non_text_children.size > 1 && non_text_children.first.name == non_text_children[1].name
66
68
  end
67
69
  end
68
70
  end
@@ -18,10 +18,10 @@ module Braintree
18
18
  end
19
19
 
20
20
  def self._determine_parser
21
- # If LibXML is not available, we fall back to REXML
22
- # This allows us to be compatible with JRuby, which LibXML does not support
23
- if defined?(::LibXML::XML) && ::LibXML::XML.respond_to?(:default_keep_blanks=)
24
- ::Braintree::Xml::Libxml
21
+ # If Nokogiri is available, use it for better performance
22
+ # Otherwise fall back to REXML for JRuby compatibility
23
+ if defined?(::Nokogiri)
24
+ ::Braintree::Xml::Nokogiri
25
25
  else
26
26
  ::Braintree::Xml::Rexml
27
27
  end
data/lib/braintree.rb CHANGED
@@ -203,6 +203,6 @@ require "braintree/webhook_testing"
203
203
  require "braintree/webhook_testing_gateway"
204
204
  require "braintree/xml"
205
205
  require "braintree/xml/generator"
206
- require "braintree/xml/libxml"
206
+ require "braintree/xml/nokogiri"
207
207
  require "braintree/xml/rexml"
208
208
  require "braintree/xml/parser"
data/spec/spec_helper.rb CHANGED
@@ -3,7 +3,6 @@ unless defined?(SPEC_HELPER_LOADED)
3
3
  project_root = File.expand_path(File.dirname(__FILE__) + "/..")
4
4
  require "rubygems"
5
5
  require "bundler/setup"
6
- require "libxml"
7
6
  require "rspec"
8
7
  require "pry"
9
8
 
@@ -195,10 +194,10 @@ unless defined?(SPEC_HELPER_LOADED)
195
194
  end
196
195
 
197
196
  def matches?(xml_string)
198
- @libxml_parse = Braintree::Xml::Parser.hash_from_xml(xml_string)
199
- if @libxml_parse != @expected_hash
200
- @results = @libxml_parse
201
- @failed_parser = "libxml"
197
+ @xml_parse = Braintree::Xml::Parser.hash_from_xml(xml_string)
198
+ if @xml_parse != @expected_hash
199
+ @results = @xml_parse
200
+ @failed_parser = "xml"
202
201
  false
203
202
  else
204
203
  true
@@ -33,5 +33,16 @@ module Braintree
33
33
  end
34
34
  end
35
35
  end
36
+
37
+ describe "error response handling" do
38
+ it "correctly parses error response with nested structure" do
39
+ error_xml = "<api-error-response><message>Invalid request</message><errors><errors type=\"array\"></errors></errors></api-error-response>"
40
+ result = Braintree::Xml::Parser.hash_from_xml(error_xml)
41
+
42
+ expect(result[:api_error_response]).to be_a(Hash)
43
+ expect(result[:api_error_response][:message]).to eq("Invalid request")
44
+ expect(result[:api_error_response][:errors]).to be_a(Hash)
45
+ end
46
+ end
36
47
  end
37
48
  end
@@ -14,6 +14,34 @@ describe Braintree::ErrorResult do
14
14
  )
15
15
  end.to_not raise_error
16
16
  end
17
+
18
+ it "handles parsed XML error response structure correctly" do
19
+ data = {
20
+ :message => "Validation failed",
21
+ :errors => {
22
+ :errors => [{:code => "81234", :message => "Field is required"}]
23
+ }
24
+ }
25
+
26
+ expect do
27
+ result = Braintree::ErrorResult.new(:gateway, data)
28
+ expect(result.message).to eq("Validation failed")
29
+ expect(result.errors.inspect).to eq("#<Braintree::Errors :[(81234) Field is required]>")
30
+ end.to_not raise_error
31
+ end
32
+
33
+ it "handles empty error array in parsed XML response" do
34
+ data = {
35
+ :message => "Invalid request",
36
+ :errors => {:errors => []}
37
+ }
38
+
39
+ expect do
40
+ result = Braintree::ErrorResult.new(:gateway, data)
41
+ expect(result.message).to eq("Invalid request")
42
+ expect(result.errors).to be_a(Braintree::Errors)
43
+ end.to_not raise_error
44
+ end
17
45
  end
18
46
 
19
47
  describe "inspect" do
@@ -1,10 +1,10 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
2
2
 
3
- describe Braintree::Xml::Libxml do
3
+ describe Braintree::Xml::Nokogiri do
4
4
  describe "self.parse" do
5
5
  it "typecasts integers" do
6
6
  xml = "<root><foo type=\"integer\">123</foo></root>"
7
- expect(Braintree::Xml::Libxml.parse(xml)).to eq({"root"=>{"foo"=>{"__content__"=>"123", "type"=>"integer"}}})
7
+ expect(Braintree::Xml::Nokogiri.parse(xml)).to eq({"root"=>{"foo"=>{"__content__"=>"123", "type"=>"integer"}}})
8
8
  end
9
9
 
10
10
  it "works with dashes or underscores" do
@@ -14,7 +14,7 @@ describe Braintree::Xml::Libxml do
14
14
  <under_scores />
15
15
  </root>
16
16
  END
17
- expect(Braintree::Xml::Libxml.parse(xml)).to eq({"root"=>{"dash-es"=>{}, "under_scores"=>{}}})
17
+ expect(Braintree::Xml::Nokogiri.parse(xml)).to eq({"root"=>{"dash-es"=>{}, "under_scores"=>{}}})
18
18
  end
19
19
 
20
20
  it "uses nil if nil=true, otherwise uses empty string" do
@@ -24,7 +24,7 @@ describe Braintree::Xml::Libxml do
24
24
  <an_empty_string></an_empty_string>
25
25
  </root>
26
26
  END
27
- expect(Braintree::Xml::Libxml.parse(xml)).to eq({"root"=>{"a_nil_value"=>{"nil"=>"true"}, "an_empty_string"=>{}}})
27
+ expect(Braintree::Xml::Nokogiri.parse(xml)).to eq({"root"=>{"a_nil_value"=>{"nil"=>"true"}, "an_empty_string"=>{}}})
28
28
  end
29
29
 
30
30
  it "typecasts dates and times" do
@@ -33,7 +33,7 @@ describe Braintree::Xml::Libxml do
33
33
  <created-at type="datetime">2009-10-28T10:19:49Z</created-at>
34
34
  </root>
35
35
  END
36
- expect(Braintree::Xml::Libxml.parse(xml)).to eq({"root"=>{"created-at"=>{"__content__"=>"2009-10-28T10:19:49Z", "type"=>"datetime"}}})
36
+ expect(Braintree::Xml::Nokogiri.parse(xml)).to eq({"root"=>{"created-at"=>{"__content__"=>"2009-10-28T10:19:49Z", "type"=>"datetime"}}})
37
37
  end
38
38
 
39
39
  it "builds an array if type=array" do
@@ -45,7 +45,7 @@ describe Braintree::Xml::Libxml do
45
45
  </customers>
46
46
  </root>
47
47
  END
48
- expect(Braintree::Xml::Libxml.parse(xml)).to eq({"root"=>{"customers"=>{"type"=>"array", "customer"=>[{"name"=>{"__content__"=>"Adam"}}, {"name"=>{"__content__"=>"Ben"}}]}}})
48
+ expect(Braintree::Xml::Nokogiri.parse(xml)).to eq({"root"=>{"customers"=>{"type"=>"array", "customer"=>[{"name"=>{"__content__"=>"Adam"}}, {"name"=>{"__content__"=>"Ben"}}]}}})
49
49
  end
50
50
  end
51
- end
51
+ end
@@ -81,5 +81,62 @@ describe Braintree::Xml::Parser do
81
81
  END
82
82
  expect(xml).to parse_to(:root => {:paypal_details => {:deets => [{:secret_code => "1234"}], :payer_email => "abc@test.com", :payment_id => "1234567890"}})
83
83
  end
84
+
85
+ it "does not collapse nested structures with single-child elements" do
86
+ xml = "<api-error-response><message>Test</message><errors><errors type=\"array\"></errors></errors></api-error-response>"
87
+ expect(xml).to parse_to({:api_error_response=>{:message=>"Test", :errors=>{:errors=>[]}}})
88
+ end
89
+
90
+ it "preserves hash structure when one key has array value" do
91
+ xml = <<-END
92
+ <root>
93
+ <message>Error message</message>
94
+ <items type="array">
95
+ <item>Value1</item>
96
+ </items>
97
+ </root>
98
+ END
99
+ expect(xml).to parse_to({:root => {:message => "Error message", :items => ["Value1"]}})
100
+ end
101
+
102
+ it "handles error response with nested errors correctly" do
103
+ xml = <<-END
104
+ <api-error-response>
105
+ <message>Validation failed</message>
106
+ <errors>
107
+ <errors type="array">
108
+ <error>
109
+ <code>81234</code>
110
+ <message>Field is required</message>
111
+ </error>
112
+ </errors>
113
+ </errors>
114
+ </api-error-response>
115
+ END
116
+ expect(xml).to parse_to({
117
+ :api_error_response => {
118
+ :message => "Validation failed",
119
+ :errors => {
120
+ :errors => [{:code => "81234", :message => "Field is required"}]
121
+ }
122
+ }
123
+ })
124
+ end
125
+
126
+ it "handles client token error response structure" do
127
+ xml = <<-END
128
+ <api-error-response>
129
+ <message>Invalid request</message>
130
+ <errors>
131
+ <errors type="array"></errors>
132
+ </errors>
133
+ </api-error-response>
134
+ END
135
+ result = Braintree::Xml::Parser.hash_from_xml(xml)
136
+ expect(result[:api_error_response]).to be_a(Hash)
137
+ expect(result[:api_error_response][:message]).to eq("Invalid request")
138
+ expect(result[:api_error_response][:errors]).to be_a(Hash)
139
+ expect(result[:api_error_response][:errors][:errors]).to eq([])
140
+ end
84
141
  end
85
142
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: braintree
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.32.0
4
+ version: 4.33.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Braintree
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-10-23 00:00:00.000000000 Z
11
+ date: 2025-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: builder
@@ -236,7 +236,7 @@ files:
236
236
  - lib/braintree/webhook_testing_gateway.rb
237
237
  - lib/braintree/xml.rb
238
238
  - lib/braintree/xml/generator.rb
239
- - lib/braintree/xml/libxml.rb
239
+ - lib/braintree/xml/nokogiri.rb
240
240
  - lib/braintree/xml/parser.rb
241
241
  - lib/braintree/xml/rexml.rb
242
242
  - lib/ssl/api_braintreegateway_com.ca.crt
@@ -390,7 +390,7 @@ files:
390
390
  - spec/unit/braintree/venmo_profile_data_spec.rb
391
391
  - spec/unit/braintree/visa_checkout_card_spec.rb
392
392
  - spec/unit/braintree/webhook_notification_spec.rb
393
- - spec/unit/braintree/xml/libxml_spec.rb
393
+ - spec/unit/braintree/xml/nokogiri_spec.rb
394
394
  - spec/unit/braintree/xml/parser_spec.rb
395
395
  - spec/unit/braintree/xml/rexml_spec.rb
396
396
  - spec/unit/braintree/xml_spec.rb
@@ -405,7 +405,7 @@ metadata:
405
405
  changelog_uri: https://github.com/braintree/braintree_ruby/blob/master/CHANGELOG.md
406
406
  source_code_uri: https://github.com/braintree/braintree_ruby
407
407
  documentation_uri: https://developer.paypal.com/braintree/docs
408
- post_install_message:
408
+ post_install_message:
409
409
  rdoc_options: []
410
410
  require_paths:
411
411
  - lib
@@ -420,8 +420,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
420
420
  - !ruby/object:Gem::Version
421
421
  version: '0'
422
422
  requirements: []
423
- rubygems_version: 3.2.5
424
- signing_key:
423
+ rubygems_version: 3.3.15
424
+ signing_key:
425
425
  specification_version: 4
426
426
  summary: Braintree Ruby Server SDK
427
427
  test_files: []