shape.js 12 KB

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