# frozen_string_literal: true require_relative '../../spec_helper' require 'time' module Aws module Xml describe Parser do [:ox, :oga, :nokogiri, :libxml, :rexml].each do |engine| describe("ENGINE: #{engine}") do begin Parser.engine = engine rescue LoadError next end let(:shapes) { ApiHelper.sample_shapes } let(:parser) { api = ApiHelper.sample_api(shapes:shapes) rules = api.operation(:example_operation).output Parser.new(rules) } def parse(xml, to_h = true) data = parser.parse(xml) to_h ? data.to_h : data end it 'does not trap xml parsing errors' do xml = '<xml' expect { parse(xml) }.to raise_error(Parser::ParsingError) end it 'returns an empty hash when the XML is empty' do expect(parse('<xml/>')).to eq({}) end it 'ignores xml namespaces on the root element' do expect(parse('<xml xmlns="http://xmlns.com"/>')).to eq({}) end it 'returns an instance of Struct' do expect(parse('<xml/>', false)).to be_kind_of(Struct) end it 'parses complex and nested documents' do xml = <<-XML.strip <xml> <Nested> <Nested> <Nested> <Integer>3</Integer> </Nested> <Integer>2</Integer> </Nested> <Integer>1</Integer> </Nested> <NestedList> <member> <String>v1</String> </member> <member> <String>v2</String> </member> <member> <Nested> <String>v3</String> </Nested> </member> </NestedList> <NestedMap> <entry> <key>First</key> <value><String>v1</String></value> </entry> <entry> <key>Second</key> <value> <Nested> <String>v2</String> </Nested> </value> </entry> </NestedMap> <NumberList> <member>1</member> <member>2</member> <member>3</member> <member>4</member> <member>5</member> </NumberList> <StringMap> <entry> <key>Size</key> <value>large</value> </entry> <entry> <key>Color</key> <value>red</value> </entry> </StringMap> <Blob>#{Base64.strict_encode64('data')}}</Blob> <Byte>90</Byte> <Boolean>true</Boolean> <Character>b</Character> <Double>123.456</Double> <Float>654.321</Float> <Integer>123</Integer> <Long>321</Long> <String>Hello</String> <StringWithConsecutiveSpaces>foo bar</StringWithConsecutiveSpaces> <StringWithLF>foo\nbar</StringWithLF> <Timestamp>123456789</Timestamp> </xml> XML expect(parse(xml)).to eq({ nested: { nested: { nested: { integer: 3 }, integer: 2 }, integer: 1 }, nested_list: [ { string: "v1" }, { string: "v2" }, { nested: { string: "v3" }} ], nested_map: { "First" => { string: "v1" }, "Second" => { nested: { string: "v2" }}, }, number_list: [1,2,3,4,5], string_map: { "Size" => "large", "Color" => "red", }, blob: "data", byte: 90, boolean: true, character: "b", double: 123.456, float: 654.321, integer: 123, long: 321, string: "Hello", timestamp: Time.at(123456789), string_with_consecutive_spaces: "foo bar", string_with_lf: "foo\nbar", }) end describe 'structures' do it 'observes locationName traits' do ref = shapes['StructureShape']['members']['Integer'] ref['locationName'] = 'IntegerElement' xml = '<xml><IntegerElement>123</IntegerElement></xml>' expect(parse(xml)).to eq(integer: 123) end it 'ignores unknown elements' do xml = '<xml><String>abc</String><Unknown>value</Unknown></xml>' expect(parse(xml)).to eq(string: 'abc') end end describe 'lists' do it 'observes locationName traits' do # list and list member ref = shapes['StructureShape']['members']['NumberList'] ref['locationName'] = 'Numbers' shapes['IntegerList']['member']['locationName'] = 'Value' xml = <<-XML.strip <xml> <Numbers> <Value>1</Value> <Value>2</Value> <Value>3</Value> </Numbers> </xml> XML expect(parse(xml)).to eq(number_list:[1,2,3]) end it 'returns missing lists as a DefaultList' do xml = '<xml/>' expect(parse(xml, false)[:number_list]).to be_kind_of(DefaultList) expect(parse(xml)).to eq({}) end it 'returns empty lists as a []' do xml = '<xml><NumberList/></xml>' expect(parse(xml)).to eq(number_list: []) end it 'supports flattened lists' do shapes['StructureList']['flattened'] = true xml = <<-XML.strip <xml> <NestedList> <String>v1</String> </NestedList> <NestedList> <String>v2</String> </NestedList> <NestedList> <String>v3</String> </NestedList> </xml> XML expect(parse(xml)).to eq(nested_list: [ { string: 'v1' }, { string: 'v2' }, { string: 'v3' }, ]) end it 'supports flattened lists with locationName trait' do shapes['StructureList']['flattened'] = true ref = shapes['StructureShape']['members']['NestedList'] ref['locationName'] = 'List' xml = <<-XML.strip <xml> <List> <String>v1</String> </List> <List> <String>v2</String> </List> <List> <String>v3</String> </List> </xml> XML expect(parse(xml)).to eq(nested_list: [ { string: 'v1' }, { string: 'v2' }, { string: 'v3' }, ]) end it 'supports flattened lists with member locationName trait' do shapes['StructureList']['flattened'] = true shapes['StructureList']['member']['locationName'] = 'ListMember' xml = <<-XML.strip <xml> <ListMember> <String>v1</String> </ListMember> <ListMember> <String>v2</String> </ListMember> <ListMember> <String>v3</String> </ListMember> </xml> XML expect(parse(xml)).to eq(nested_list: [ { string: 'v1' }, { string: 'v2' }, { string: 'v3' }, ]) end end describe 'maps' do it 'returns missing maps as a DefaultMap' do xml = '<xml/>' expect(parse(xml, false)[:string_map]).to be_kind_of(DefaultMap) expect(parse(xml)).to eq({}) end it 'returns empty maps as {}' do xml = '<xml><StringMap/></xml>' expect(parse(xml)).to eq(string_map: {}) end it 'supports maps with locationName traits' do # key and value shapes['StringMap']['key']['locationName'] = 'AttrName' shapes['StringMap']['value']['locationName'] = 'AttrValue' xml = <<-XML.strip <xml> <StringMap> <entry> <AttrName>size</AttrName> <AttrValue>large</AttrValue> </entry> <entry> <AttrName>color</AttrName> <AttrValue>red</AttrValue> </entry> </StringMap> </xml> XML expect(parse(xml)).to eq(string_map: { 'size' => 'large', 'color' => 'red', }) end it 'supports flattened maps' do shapes['StringMap']['flattened'] = true xml = <<-XML.strip <xml> <StringMap> <key>size</key> <value>large</value> </StringMap> <StringMap> <key>color</key> <value>red</value> </StringMap> </xml> XML expect(parse(xml)).to eq(string_map: { 'size' => 'large', 'color' => 'red', }) end it 'supports flattened maps with locationName traits' do shapes['StringMap']['flattened'] = true shapes['StringMap']['key']['locationName'] = 'AttrName' shapes['StringMap']['value']['locationName'] = 'AttrValue' ref = shapes['StructureShape']['members']['StringMap'] ref['locationName'] = 'Attributes' xml = <<-XML.strip <xml> <Attributes> <AttrName>size</AttrName> <AttrValue>large</AttrValue> </Attributes> <Attributes> <AttrName>color</AttrName> <AttrValue>red</AttrValue> </Attributes> </xml> XML expect(parse(xml)).to eq(string_map: { 'size' => 'large', 'color' => 'red', }) end end describe 'booleans' do it 'supports true values' do xml = '<xml><Boolean>true</Boolean></xml>' expect(parse(xml)).to eq(boolean: true) end it 'supports false values' do xml = '<xml><Boolean>false</Boolean></xml>' expect(parse(xml)).to eq(boolean: false) end it 'returns nil for an empty element' do xml = '<xml><Boolean/></xml>' expect(parse(xml)).to eq({}) end end describe 'timestamps' do it 'supports unix timestamps' do now = Time.now.to_i xml = "<xml><Timestamp>#{now}</Timestamp></xml>" expect(parse(xml)).to eq({timestamp: Time.at(now) }) end it 'supports iso8601 strings' do now = Time.now.iso8601 xml = "<xml><Timestamp>#{now}</Timestamp></xml>" expect(parse(xml)).to eq({timestamp: Time.parse(now) }) end it 'supports rfc822 strings' do now = Time.now.rfc822 xml = "<xml><Timestamp>#{now}</Timestamp></xml>" expect(parse(xml)).to eq({timestamp: Time.parse(now) }) end end describe 'strings' do it 'base64 decodes with encoding is set' do xml = <<-XML.strip <xml> <String encoding="base64">#{Base64.encode64('a')}</String> <Nested> <String encoding="base64">#{Base64.encode64('b')}</String> </Nested> </xml> XML expect(parse(xml)).to eq(string: 'a', nested: { string: 'b' }) end end describe 'xml attributes' do it 'supports paring structure members from xml attributes' do ref = shapes['StructureShape']['members']['String'] ref['locationName'] = 'stringAsAttribute' xml = '<xml><Nested stringAsAttribute="value"/></xml>' expect(parse(xml)).to eq(nested: { string: 'value' }) end it 'ignores xml attributes that are not members' do xml = '<xml><Nested ignored="value"/></xml>' expect(parse(xml)).to eq(nested: {}) end it 'supports xml attributes with qnames' do xml = '<xml><Nested xmlns:xsi="http://example.com" xsi:string-attr-value="value"/></xml>' shapes['StructureShape']['members']['String']['xmlAttribute'] = true shapes['StructureShape']['members']['String']['locationName'] = 'xsi:string-attr-value' expect(parse(xml)).to eq(nested: { string: 'value' }) end end end end end end end