helpers = require('../helpers'); AWS = helpers.AWS describe 'AWS.XML.Parser', -> parse = (xml, rules, callback) -> if rules shape = AWS.Model.Shape.create(rules, api: {}) else shape = {} callback.call(this, new AWS.XML.Parser().parse(xml, shape)) describe 'default behavior', -> rules = null # no rules, rely on default parsing behavior it 'returns empty object when string is empty', -> parse '', null, (data) -> expect(data).toEqual({}) it 'returns an empty object from an empty document', -> xml = '' parse xml, rules, (data) -> expect(data).toEqual({}) it 'returns empty elements as empty string', -> xml = '' parse xml, rules, (data) -> expect(data).toEqual({element:''}) it 'converts string elements to properties', -> xml = 'abcxyz' parse xml, rules, (data) -> expect(data).toEqual({foo:'abc', bar:'xyz'}) it 'converts nested elements into objects', -> xml = 'yuck' parse xml, rules, (data) -> expect(data).toEqual({foo:{bar:'yuck'}}) it 'returns everything as a string (even numbers)', -> xml = '123' parse xml, rules, (data) -> expect(data).toEqual({count:'123'}) it 'ignores xmlns on the root element', -> xml = 'xyz' parse xml, rules, (data) -> expect(data).toEqual({Abc:'xyz'}) describe 'structures', -> it 'returns empty objects as {}', -> xml = '' rules = type: 'structure' members: Item: type: 'structure' members: Name: type: 'string' parse xml, rules, (data) -> expect(data).toEqual({Item:{}}) it 'parses attributes from tags', -> xml = ' ' rules = type: 'structure' members: Item: type: 'structure' members: Name: type: 'string' xmlAttribute: true locationName: 'xsi:name' parse xml, rules, (data) -> expect(data).toEqual({Item:{Name: 'name'}}) describe 'lists', -> it 'returns empty lists as []', -> xml = '' rules = type: 'structure' members: items: type: 'list' member: type: 'string' parse xml, rules, (data) -> expect(data).toEqual({items:[]}) it 'returns missing lists as []', -> xml = '' rules = type: 'structure' members: items: type: 'list' member: type: 'string' parse xml, rules, (data) -> expect(data).toEqual({items:[]}) it 'Converts xml lists of strings into arrays of strings', -> xml = """ abc xyz """ rules = type: 'structure' members: items: type: 'list' member: {} parse xml, rules, (data) -> expect(data).toEqual({items:['abc','xyz']}) it 'observes list member names when present', -> xml = """ abc xyz """ rules = type: 'structure' members: items: type: 'list' member: locationName: 'item' parse xml, rules, (data) -> expect(data).toEqual({items:['abc','xyz']}) it 'can parse lists of strucures', -> xml = """ abc> xyz> """ rules = type: 'structure' members: People: type: 'list' member: type: 'structure' members: Name: type: 'string' parse xml, rules, (data) -> expect(data).toEqual({People:[{Name:'abc'},{Name:'xyz'}]}) it 'can parse lists of strucures with renames', -> xml = """ abc> xyz> """ rules = type: 'structure' members: People: type: 'list' member: type: 'structure' locationName: 'Person' members: Name: type: 'string' parse xml, rules, (data) -> expect(data).toEqual({People:[{Name:'abc'},{Name:'xyz'}]}) describe 'flattened lists', -> xml = """ Unknown John Doe Jane Doe """ it 'collects sibling elements of the same name', -> rules = type: 'structure' members: person: type: 'structure' members: name: {} aka: type: 'list' flattened: true member: locationName: 'alias' parse xml, rules, (data) -> expect(data).toEqual({person:{name:'Unknown',aka:['John Doe', 'Jane Doe']}}) it 'flattened lists can be composed of complex obects', -> xml = """ Name 1 2 3 4 """ rules = type: 'structure' members: name: type: 'string' values: type: 'list' flattened: true member: locationName: 'complexValue' type: 'structure' members: a: type: 'integer' b: type: 'integer' values = {name:'Name',values:[{a:1,b:2},{a:3,b:4}]} parse xml, rules, (data) -> expect(data).toEqual(values) it 'can parse flattened lists of complex objects', -> xml = """ 2 abc xyz """ rules = type: 'structure' members: Count: type: 'integer' People: type: 'list' flattened: true member: type: 'structure' locationName: 'Person' members: Name: {} parse xml, rules, (data) -> expect(data).toEqual({Count:2,People:[{Name:'abc'},{Name:'xyz'}]}) describe 'maps', -> describe 'non-flattened', -> it 'expects entry, key, and value elements by default', -> # example from IAM GetAccountSummary (output) xml = """ Groups 31 GroupsQuota 50 UsersQuota 150 """ rules = type: 'structure' members: SummaryMap: type: 'map' value: type: 'integer' parse xml, rules, (data) -> expect(data).toEqual(SummaryMap:{Groups:31,GroupsQuota:50,UsersQuota:150}) it 'can use alternate names for key and value elements', -> # using Property/Count instead of key/value, also applied a name # trait to the Summary map to rename it xml = """ Groups 31 GroupsQuota 50 UsersQuota 150 """ rules = type: 'structure' members: Summary: type: 'map' locationName: 'SummaryMap', key: locationName: 'Property' value: type: 'integer' locationName: 'Count' parse xml, rules, (data) -> expect(data).toEqual(Summary:{Groups:31,GroupsQuota:50,UsersQuota:150}) describe 'flattened', -> it 'expects key and value elements by default', -> xml = """ color red size large """ rules = type: 'structure' members: Attributes: type: 'map' flattened: true parse xml, rules, (data) -> expect(data).toEqual({Attributes:{color:'red',size:'large'}}) it 'can use alternate names for key and value elements', -> # using AttrName/AttrValue instead of key/value, also applied a name # trait to the Attributes map xml = """ age 35 height 72 """ rules = type: 'structure' members: Attributes: locationName: 'Attribute' type: 'map' flattened: true key: locationName: 'AttrName' value: locationName: 'AttrValue' type: 'integer' parse xml, rules, (data) -> expect(data).toEqual({Attributes:{age:35,height:72}}) describe 'booleans', -> rules = type: 'structure' members: enabled: type: 'boolean' it 'converts the string "true" in to the boolean value true', -> xml = "true" parse xml, rules, (data) -> expect(data).toEqual({enabled:true}) it 'converts the string "false" in to the boolean value false', -> xml = "false" parse xml, rules, (data) -> expect(data).toEqual({enabled:false}) it 'converts the empty elements into null', -> xml = "" parse xml, rules, (data) -> expect(data).toEqual({enabled:null}) describe 'timestamp', -> rules = type: 'structure' members: CreatedAt: type: 'timestamp' it 'returns an empty element as null', -> xml = "" parse xml, rules, (data) -> expect(data).toEqual({CreatedAt:null}) it 'understands unix timestamps', -> timestamp = 1349908100 date = new Date(timestamp * 1000) xml = "#{timestamp}" parse xml, rules, (data) -> expect(data).toEqual({CreatedAt:date}) it 'understands basic iso8601 strings', -> timestamp = '2012-10-10T15:47:10.001Z' date = new Date(timestamp) xml = "#{timestamp}" parse xml, rules, (data) -> expect(data).toEqual({CreatedAt:date}) it 'understands basic rfc822 strings', -> timestamp = 'Wed, 10 Oct 2012 15:59:55 UTC' date = new Date(timestamp) xml = "#{timestamp}" parse xml, rules, (data) -> expect(data).toEqual({CreatedAt:date}) it 'throws an error when unable to determine the format', -> timestamp = 'bad-date-format' xml = "#{timestamp}" message = 'unhandled timestamp format: ' + timestamp error = {} try parse(xml, rules, ->) catch e error = e expect(error.message).toEqual(message) describe 'numbers', -> rules = type: 'structure' members: decimal: type: 'float' it 'float parses elements types as integer', -> xml = "123.456" parse xml, rules, (data) -> expect(data).toEqual({decimal:123.456}) it 'returns null for empty elements', -> xml = "" parse xml, rules, (data) -> expect(data).toEqual({decimal:null}) describe 'integers', -> rules = type: 'structure' members: count: type: 'integer' it 'integer parses elements types as integer', -> xml = "123" parse xml, rules, (data) -> expect(data).toEqual({count:123}) it 'returns null for empty elements', -> xml = "" parse xml, rules, (data) -> expect(data).toEqual({count:null}) describe 'renaming elements', -> it 'can rename scalar elements', -> rules = type: 'structure' members: aka: locationName: 'alias' xml = "John Doe" parse xml, rules, (data) -> expect(data).toEqual({aka:'John Doe'}) it 'can rename nested elements', -> rules = type: 'structure' members: person: members: name: {} aka: locationName: 'alias' xml = "JoeJohn Doe" parse xml, rules, (data) -> expect(data).toEqual({person:{name:'Joe',aka:'John Doe'}}) describe 'base64 encoded strings', -> it 'base64 decodes string elements with encoding="base64"', -> rules = type: 'structure' members: Value: type: 'string' xml = """ Zm9v """ parse xml, rules, (data) -> expect(data.Value.toString()).toEqual('foo') rules = type: 'structure' members: Value: {} xml = """ Zm9v """ parse xml, rules, (data) -> expect(data.Value.toString()).toEqual('foo') describe 'elements with XML namespaces', -> it 'strips the xmlns element', -> rules = type: 'structure' members: List: type: 'list' member: type: 'structure' members: Attr1: {} Attr2: type: 'structure' members: Foo: {} xml = """ abc bar """ parse xml, rules, (data) -> expect(data).toEqual({List:[{Attr1:'abc',Attr2:{Foo:'bar'}}]}) describe 'parsing errors', -> it 'throws an error when unable to parse the xml', -> xml = 'asdf' rules = {} error = {} try new AWS.XML.Parser().parse(xml, rules) catch e error = e expect(error.code).toEqual('XMLParserError')