shape.js (9074B)
1 var Collection = require('./collection'); 2 3 var util = require('../util'); 4 5 function property(obj, name, value) { 6 if (value !== null && value !== undefined) { 7 util.property.apply(this, arguments); 8 } 9 } 10 11 function memoizedProperty(obj, name) { 12 if (!obj.constructor.prototype[name]) { 13 util.memoizedProperty.apply(this, arguments); 14 } 15 } 16 17 function Shape(shape, options, memberName) { 18 options = options || {}; 19 20 property(this, 'shape', shape.shape); 21 property(this, 'api', options.api, false); 22 property(this, 'type', shape.type); 23 property(this, 'enum', shape.enum); 24 property(this, 'min', shape.min); 25 property(this, 'max', shape.max); 26 property(this, 'pattern', shape.pattern); 27 property(this, 'location', shape.location || this.location || 'body'); 28 property(this, 'name', this.name || shape.xmlName || shape.queryName || 29 shape.locationName || memberName); 30 property(this, 'isStreaming', shape.streaming || this.isStreaming || false); 31 property(this, 'isComposite', shape.isComposite || false); 32 property(this, 'isShape', true, false); 33 property(this, 'isQueryName', shape.queryName ? true : false, false); 34 property(this, 'isLocationName', shape.locationName ? true : false, false); 35 36 if (options.documentation) { 37 property(this, 'documentation', shape.documentation); 38 property(this, 'documentationUrl', shape.documentationUrl); 39 } 40 41 if (shape.xmlAttribute) { 42 property(this, 'isXmlAttribute', shape.xmlAttribute || false); 43 } 44 45 // type conversion and parsing 46 property(this, 'defaultValue', null); 47 this.toWireFormat = function(value) { 48 if (value === null || value === undefined) return ''; 49 return value; 50 }; 51 this.toType = function(value) { return value; }; 52 } 53 54 /** 55 * @api private 56 */ 57 Shape.normalizedTypes = { 58 character: 'string', 59 double: 'float', 60 long: 'integer', 61 short: 'integer', 62 biginteger: 'integer', 63 bigdecimal: 'float', 64 blob: 'binary' 65 }; 66 67 /** 68 * @api private 69 */ 70 Shape.types = { 71 'structure': StructureShape, 72 'list': ListShape, 73 'map': MapShape, 74 'boolean': BooleanShape, 75 'timestamp': TimestampShape, 76 'float': FloatShape, 77 'integer': IntegerShape, 78 'string': StringShape, 79 'base64': Base64Shape, 80 'binary': BinaryShape 81 }; 82 83 Shape.resolve = function resolve(shape, options) { 84 if (shape.shape) { 85 var refShape = options.api.shapes[shape.shape]; 86 if (!refShape) { 87 throw new Error('Cannot find shape reference: ' + shape.shape); 88 } 89 90 return refShape; 91 } else { 92 return null; 93 } 94 }; 95 96 Shape.create = function create(shape, options, memberName) { 97 if (shape.isShape) return shape; 98 99 var refShape = Shape.resolve(shape, options); 100 if (refShape) { 101 var filteredKeys = Object.keys(shape); 102 if (!options.documentation) { 103 filteredKeys = filteredKeys.filter(function(name) { 104 return !name.match(/documentation/); 105 }); 106 } 107 if (filteredKeys === ['shape']) { // no inline customizations 108 return refShape; 109 } 110 111 // create an inline shape with extra members 112 var InlineShape = function() { 113 refShape.constructor.call(this, shape, options, memberName); 114 }; 115 InlineShape.prototype = refShape; 116 return new InlineShape(); 117 } else { 118 // set type if not set 119 if (!shape.type) { 120 if (shape.members) shape.type = 'structure'; 121 else if (shape.member) shape.type = 'list'; 122 else if (shape.key) shape.type = 'map'; 123 else shape.type = 'string'; 124 } 125 126 // normalize types 127 var origType = shape.type; 128 if (Shape.normalizedTypes[shape.type]) { 129 shape.type = Shape.normalizedTypes[shape.type]; 130 } 131 132 if (Shape.types[shape.type]) { 133 return new Shape.types[shape.type](shape, options, memberName); 134 } else { 135 throw new Error('Unrecognized shape type: ' + origType); 136 } 137 } 138 }; 139 140 function CompositeShape(shape) { 141 Shape.apply(this, arguments); 142 property(this, 'isComposite', true); 143 144 if (shape.flattened) { 145 property(this, 'flattened', shape.flattened || false); 146 } 147 } 148 149 function StructureShape(shape, options) { 150 var requiredMap = null, firstInit = !this.isShape; 151 152 CompositeShape.apply(this, arguments); 153 154 if (firstInit) { 155 property(this, 'defaultValue', function() { return {}; }); 156 property(this, 'members', {}); 157 property(this, 'memberNames', []); 158 property(this, 'required', []); 159 property(this, 'isRequired', function() { return false; }); 160 } 161 162 if (shape.members) { 163 property(this, 'members', new Collection(shape.members, options, function(name, member) { 164 return Shape.create(member, options, name); 165 })); 166 memoizedProperty(this, 'memberNames', function() { 167 return shape.xmlOrder || Object.keys(shape.members); 168 }); 169 } 170 171 if (shape.required) { 172 property(this, 'required', shape.required); 173 property(this, 'isRequired', function(name) { 174 if (!requiredMap) { 175 requiredMap = {}; 176 for (var i = 0; i < shape.required.length; i++) { 177 requiredMap[shape.required[i]] = true; 178 } 179 } 180 181 return requiredMap[name]; 182 }, false, true); 183 } 184 185 property(this, 'resultWrapper', shape.resultWrapper || null); 186 187 if (shape.payload) { 188 property(this, 'payload', shape.payload); 189 } 190 191 if (typeof shape.xmlNamespace === 'string') { 192 property(this, 'xmlNamespaceUri', shape.xmlNamespace); 193 } else if (typeof shape.xmlNamespace === 'object') { 194 property(this, 'xmlNamespacePrefix', shape.xmlNamespace.prefix); 195 property(this, 'xmlNamespaceUri', shape.xmlNamespace.uri); 196 } 197 } 198 199 function ListShape(shape, options) { 200 var self = this, firstInit = !this.isShape; 201 CompositeShape.apply(this, arguments); 202 203 if (firstInit) { 204 property(this, 'defaultValue', function() { return []; }); 205 } 206 207 if (shape.member) { 208 memoizedProperty(this, 'member', function() { 209 return Shape.create(shape.member, options); 210 }); 211 } 212 213 if (this.flattened) { 214 var oldName = this.name; 215 memoizedProperty(this, 'name', function() { 216 return self.member.name || oldName; 217 }); 218 } 219 } 220 221 function MapShape(shape, options) { 222 var firstInit = !this.isShape; 223 CompositeShape.apply(this, arguments); 224 225 if (firstInit) { 226 property(this, 'defaultValue', function() { return {}; }); 227 property(this, 'key', Shape.create({type: 'string'}, options)); 228 property(this, 'value', Shape.create({type: 'string'}, options)); 229 } 230 231 if (shape.key) { 232 memoizedProperty(this, 'key', function() { 233 return Shape.create(shape.key, options); 234 }); 235 } 236 if (shape.value) { 237 memoizedProperty(this, 'value', function() { 238 return Shape.create(shape.value, options); 239 }); 240 } 241 } 242 243 function TimestampShape(shape) { 244 var self = this; 245 Shape.apply(this, arguments); 246 247 if (this.location === 'header') { 248 property(this, 'timestampFormat', 'rfc822'); 249 } else if (shape.timestampFormat) { 250 property(this, 'timestampFormat', shape.timestampFormat); 251 } else if (this.api) { 252 if (this.api.timestampFormat) { 253 property(this, 'timestampFormat', this.api.timestampFormat); 254 } else { 255 switch (this.api.protocol) { 256 case 'json': 257 case 'rest-json': 258 property(this, 'timestampFormat', 'unixTimestamp'); 259 break; 260 case 'rest-xml': 261 case 'query': 262 case 'ec2': 263 property(this, 'timestampFormat', 'iso8601'); 264 break; 265 } 266 } 267 } 268 269 this.toType = function(value) { 270 if (value === null || value === undefined) return null; 271 if (typeof value.toUTCString === 'function') return value; 272 return typeof value === 'string' || typeof value === 'number' ? 273 util.date.parseTimestamp(value) : null; 274 }; 275 276 this.toWireFormat = function(value) { 277 return util.date.format(value, self.timestampFormat); 278 }; 279 } 280 281 function StringShape() { 282 Shape.apply(this, arguments); 283 284 if (this.api) { 285 switch (this.api.protocol) { 286 case 'rest-xml': 287 case 'query': 288 case 'ec2': 289 this.toType = function(value) { return value || ''; }; 290 } 291 } 292 } 293 294 function FloatShape() { 295 Shape.apply(this, arguments); 296 297 this.toType = function(value) { 298 if (value === null || value === undefined) return null; 299 return parseFloat(value); 300 }; 301 this.toWireFormat = this.toType; 302 } 303 304 function IntegerShape() { 305 Shape.apply(this, arguments); 306 307 this.toType = function(value) { 308 if (value === null || value === undefined) return null; 309 return parseInt(value, 10); 310 }; 311 this.toWireFormat = this.toType; 312 } 313 314 function BinaryShape() { 315 Shape.apply(this, arguments); 316 this.toType = util.base64.decode; 317 this.toWireFormat = util.base64.encode; 318 } 319 320 function Base64Shape() { 321 BinaryShape.apply(this, arguments); 322 } 323 324 function BooleanShape() { 325 Shape.apply(this, arguments); 326 327 this.toType = function(value) { 328 if (typeof value === 'boolean') return value; 329 if (value === null || value === undefined) return null; 330 return value === 'true'; 331 }; 332 } 333 334 /** 335 * @api private 336 */ 337 Shape.shapes = { 338 StructureShape: StructureShape, 339 ListShape: ListShape, 340 MapShape: MapShape, 341 StringShape: StringShape, 342 BooleanShape: BooleanShape, 343 Base64Shape: Base64Shape 344 }; 345 346 module.exports = Shape;