406 lines
12 KiB
JavaScript
406 lines
12 KiB
JavaScript
|
var Collection = require('./collection');
|
||
|
|
||
|
var util = require('../util');
|
||
|
|
||
|
function property(obj, name, value) {
|
||
|
if (value !== null && value !== undefined) {
|
||
|
util.property.apply(this, arguments);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function memoizedProperty(obj, name) {
|
||
|
if (!obj.constructor.prototype[name]) {
|
||
|
util.memoizedProperty.apply(this, arguments);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function Shape(shape, options, memberName) {
|
||
|
options = options || {};
|
||
|
|
||
|
property(this, 'shape', shape.shape);
|
||
|
property(this, 'api', options.api, false);
|
||
|
property(this, 'type', shape.type);
|
||
|
property(this, 'enum', shape.enum);
|
||
|
property(this, 'min', shape.min);
|
||
|
property(this, 'max', shape.max);
|
||
|
property(this, 'pattern', shape.pattern);
|
||
|
property(this, 'location', shape.location || this.location || 'body');
|
||
|
property(this, 'name', this.name || shape.xmlName || shape.queryName ||
|
||
|
shape.locationName || memberName);
|
||
|
property(this, 'isStreaming', shape.streaming || this.isStreaming || false);
|
||
|
property(this, 'isComposite', shape.isComposite || false);
|
||
|
property(this, 'isShape', true, false);
|
||
|
property(this, 'isQueryName', Boolean(shape.queryName), false);
|
||
|
property(this, 'isLocationName', Boolean(shape.locationName), false);
|
||
|
property(this, 'isIdempotent', shape.idempotencyToken === true);
|
||
|
property(this, 'isJsonValue', shape.jsonvalue === true);
|
||
|
property(this, 'isSensitive', shape.sensitive === true || shape.prototype && shape.prototype.sensitive === true);
|
||
|
property(this, 'isEventStream', Boolean(shape.eventstream), false);
|
||
|
property(this, 'isEvent', Boolean(shape.event), false);
|
||
|
property(this, 'isEventPayload', Boolean(shape.eventpayload), false);
|
||
|
property(this, 'isEventHeader', Boolean(shape.eventheader), false);
|
||
|
property(this, 'isTimestampFormatSet', Boolean(shape.timestampFormat) || shape.prototype && shape.prototype.isTimestampFormatSet === true, false);
|
||
|
property(this, 'endpointDiscoveryId', Boolean(shape.endpointdiscoveryid), false);
|
||
|
property(this, 'hostLabel', Boolean(shape.hostLabel), false);
|
||
|
|
||
|
if (options.documentation) {
|
||
|
property(this, 'documentation', shape.documentation);
|
||
|
property(this, 'documentationUrl', shape.documentationUrl);
|
||
|
}
|
||
|
|
||
|
if (shape.xmlAttribute) {
|
||
|
property(this, 'isXmlAttribute', shape.xmlAttribute || false);
|
||
|
}
|
||
|
|
||
|
// type conversion and parsing
|
||
|
property(this, 'defaultValue', null);
|
||
|
this.toWireFormat = function(value) {
|
||
|
if (value === null || value === undefined) return '';
|
||
|
return value;
|
||
|
};
|
||
|
this.toType = function(value) { return value; };
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @api private
|
||
|
*/
|
||
|
Shape.normalizedTypes = {
|
||
|
character: 'string',
|
||
|
double: 'float',
|
||
|
long: 'integer',
|
||
|
short: 'integer',
|
||
|
biginteger: 'integer',
|
||
|
bigdecimal: 'float',
|
||
|
blob: 'binary'
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @api private
|
||
|
*/
|
||
|
Shape.types = {
|
||
|
'structure': StructureShape,
|
||
|
'list': ListShape,
|
||
|
'map': MapShape,
|
||
|
'boolean': BooleanShape,
|
||
|
'timestamp': TimestampShape,
|
||
|
'float': FloatShape,
|
||
|
'integer': IntegerShape,
|
||
|
'string': StringShape,
|
||
|
'base64': Base64Shape,
|
||
|
'binary': BinaryShape
|
||
|
};
|
||
|
|
||
|
Shape.resolve = function resolve(shape, options) {
|
||
|
if (shape.shape) {
|
||
|
var refShape = options.api.shapes[shape.shape];
|
||
|
if (!refShape) {
|
||
|
throw new Error('Cannot find shape reference: ' + shape.shape);
|
||
|
}
|
||
|
|
||
|
return refShape;
|
||
|
} else {
|
||
|
return null;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Shape.create = function create(shape, options, memberName) {
|
||
|
if (shape.isShape) return shape;
|
||
|
|
||
|
var refShape = Shape.resolve(shape, options);
|
||
|
if (refShape) {
|
||
|
var filteredKeys = Object.keys(shape);
|
||
|
if (!options.documentation) {
|
||
|
filteredKeys = filteredKeys.filter(function(name) {
|
||
|
return !name.match(/documentation/);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// create an inline shape with extra members
|
||
|
var InlineShape = function() {
|
||
|
refShape.constructor.call(this, shape, options, memberName);
|
||
|
};
|
||
|
InlineShape.prototype = refShape;
|
||
|
return new InlineShape();
|
||
|
} else {
|
||
|
// set type if not set
|
||
|
if (!shape.type) {
|
||
|
if (shape.members) shape.type = 'structure';
|
||
|
else if (shape.member) shape.type = 'list';
|
||
|
else if (shape.key) shape.type = 'map';
|
||
|
else shape.type = 'string';
|
||
|
}
|
||
|
|
||
|
// normalize types
|
||
|
var origType = shape.type;
|
||
|
if (Shape.normalizedTypes[shape.type]) {
|
||
|
shape.type = Shape.normalizedTypes[shape.type];
|
||
|
}
|
||
|
|
||
|
if (Shape.types[shape.type]) {
|
||
|
return new Shape.types[shape.type](shape, options, memberName);
|
||
|
} else {
|
||
|
throw new Error('Unrecognized shape type: ' + origType);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
function CompositeShape(shape) {
|
||
|
Shape.apply(this, arguments);
|
||
|
property(this, 'isComposite', true);
|
||
|
|
||
|
if (shape.flattened) {
|
||
|
property(this, 'flattened', shape.flattened || false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function StructureShape(shape, options) {
|
||
|
var self = this;
|
||
|
var requiredMap = null, firstInit = !this.isShape;
|
||
|
|
||
|
CompositeShape.apply(this, arguments);
|
||
|
|
||
|
if (firstInit) {
|
||
|
property(this, 'defaultValue', function() { return {}; });
|
||
|
property(this, 'members', {});
|
||
|
property(this, 'memberNames', []);
|
||
|
property(this, 'required', []);
|
||
|
property(this, 'isRequired', function() { return false; });
|
||
|
}
|
||
|
|
||
|
if (shape.members) {
|
||
|
property(this, 'members', new Collection(shape.members, options, function(name, member) {
|
||
|
return Shape.create(member, options, name);
|
||
|
}));
|
||
|
memoizedProperty(this, 'memberNames', function() {
|
||
|
return shape.xmlOrder || Object.keys(shape.members);
|
||
|
});
|
||
|
|
||
|
if (shape.event) {
|
||
|
memoizedProperty(this, 'eventPayloadMemberName', function() {
|
||
|
var members = self.members;
|
||
|
var memberNames = self.memberNames;
|
||
|
// iterate over members to find ones that are event payloads
|
||
|
for (var i = 0, iLen = memberNames.length; i < iLen; i++) {
|
||
|
if (members[memberNames[i]].isEventPayload) {
|
||
|
return memberNames[i];
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
memoizedProperty(this, 'eventHeaderMemberNames', function() {
|
||
|
var members = self.members;
|
||
|
var memberNames = self.memberNames;
|
||
|
var eventHeaderMemberNames = [];
|
||
|
// iterate over members to find ones that are event headers
|
||
|
for (var i = 0, iLen = memberNames.length; i < iLen; i++) {
|
||
|
if (members[memberNames[i]].isEventHeader) {
|
||
|
eventHeaderMemberNames.push(memberNames[i]);
|
||
|
}
|
||
|
}
|
||
|
return eventHeaderMemberNames;
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (shape.required) {
|
||
|
property(this, 'required', shape.required);
|
||
|
property(this, 'isRequired', function(name) {
|
||
|
if (!requiredMap) {
|
||
|
requiredMap = {};
|
||
|
for (var i = 0; i < shape.required.length; i++) {
|
||
|
requiredMap[shape.required[i]] = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return requiredMap[name];
|
||
|
}, false, true);
|
||
|
}
|
||
|
|
||
|
property(this, 'resultWrapper', shape.resultWrapper || null);
|
||
|
|
||
|
if (shape.payload) {
|
||
|
property(this, 'payload', shape.payload);
|
||
|
}
|
||
|
|
||
|
if (typeof shape.xmlNamespace === 'string') {
|
||
|
property(this, 'xmlNamespaceUri', shape.xmlNamespace);
|
||
|
} else if (typeof shape.xmlNamespace === 'object') {
|
||
|
property(this, 'xmlNamespacePrefix', shape.xmlNamespace.prefix);
|
||
|
property(this, 'xmlNamespaceUri', shape.xmlNamespace.uri);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function ListShape(shape, options) {
|
||
|
var self = this, firstInit = !this.isShape;
|
||
|
CompositeShape.apply(this, arguments);
|
||
|
|
||
|
if (firstInit) {
|
||
|
property(this, 'defaultValue', function() { return []; });
|
||
|
}
|
||
|
|
||
|
if (shape.member) {
|
||
|
memoizedProperty(this, 'member', function() {
|
||
|
return Shape.create(shape.member, options);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (this.flattened) {
|
||
|
var oldName = this.name;
|
||
|
memoizedProperty(this, 'name', function() {
|
||
|
return self.member.name || oldName;
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function MapShape(shape, options) {
|
||
|
var firstInit = !this.isShape;
|
||
|
CompositeShape.apply(this, arguments);
|
||
|
|
||
|
if (firstInit) {
|
||
|
property(this, 'defaultValue', function() { return {}; });
|
||
|
property(this, 'key', Shape.create({type: 'string'}, options));
|
||
|
property(this, 'value', Shape.create({type: 'string'}, options));
|
||
|
}
|
||
|
|
||
|
if (shape.key) {
|
||
|
memoizedProperty(this, 'key', function() {
|
||
|
return Shape.create(shape.key, options);
|
||
|
});
|
||
|
}
|
||
|
if (shape.value) {
|
||
|
memoizedProperty(this, 'value', function() {
|
||
|
return Shape.create(shape.value, options);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function TimestampShape(shape) {
|
||
|
var self = this;
|
||
|
Shape.apply(this, arguments);
|
||
|
|
||
|
if (shape.timestampFormat) {
|
||
|
property(this, 'timestampFormat', shape.timestampFormat);
|
||
|
} else if (self.isTimestampFormatSet && this.timestampFormat) {
|
||
|
property(this, 'timestampFormat', this.timestampFormat);
|
||
|
} else if (this.location === 'header') {
|
||
|
property(this, 'timestampFormat', 'rfc822');
|
||
|
} else if (this.location === 'querystring') {
|
||
|
property(this, 'timestampFormat', 'iso8601');
|
||
|
} else if (this.api) {
|
||
|
switch (this.api.protocol) {
|
||
|
case 'json':
|
||
|
case 'rest-json':
|
||
|
property(this, 'timestampFormat', 'unixTimestamp');
|
||
|
break;
|
||
|
case 'rest-xml':
|
||
|
case 'query':
|
||
|
case 'ec2':
|
||
|
property(this, 'timestampFormat', 'iso8601');
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.toType = function(value) {
|
||
|
if (value === null || value === undefined) return null;
|
||
|
if (typeof value.toUTCString === 'function') return value;
|
||
|
return typeof value === 'string' || typeof value === 'number' ?
|
||
|
util.date.parseTimestamp(value) : null;
|
||
|
};
|
||
|
|
||
|
this.toWireFormat = function(value) {
|
||
|
return util.date.format(value, self.timestampFormat);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function StringShape() {
|
||
|
Shape.apply(this, arguments);
|
||
|
|
||
|
var nullLessProtocols = ['rest-xml', 'query', 'ec2'];
|
||
|
this.toType = function(value) {
|
||
|
value = this.api && nullLessProtocols.indexOf(this.api.protocol) > -1 ?
|
||
|
value || '' : value;
|
||
|
if (this.isJsonValue) {
|
||
|
return JSON.parse(value);
|
||
|
}
|
||
|
|
||
|
return value && typeof value.toString === 'function' ?
|
||
|
value.toString() : value;
|
||
|
};
|
||
|
|
||
|
this.toWireFormat = function(value) {
|
||
|
return this.isJsonValue ? JSON.stringify(value) : value;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function FloatShape() {
|
||
|
Shape.apply(this, arguments);
|
||
|
|
||
|
this.toType = function(value) {
|
||
|
if (value === null || value === undefined) return null;
|
||
|
return parseFloat(value);
|
||
|
};
|
||
|
this.toWireFormat = this.toType;
|
||
|
}
|
||
|
|
||
|
function IntegerShape() {
|
||
|
Shape.apply(this, arguments);
|
||
|
|
||
|
this.toType = function(value) {
|
||
|
if (value === null || value === undefined) return null;
|
||
|
return parseInt(value, 10);
|
||
|
};
|
||
|
this.toWireFormat = this.toType;
|
||
|
}
|
||
|
|
||
|
function BinaryShape() {
|
||
|
Shape.apply(this, arguments);
|
||
|
this.toType = function(value) {
|
||
|
var buf = util.base64.decode(value);
|
||
|
if (this.isSensitive && util.isNode() && typeof util.Buffer.alloc === 'function') {
|
||
|
/* Node.js can create a Buffer that is not isolated.
|
||
|
* i.e. buf.byteLength !== buf.buffer.byteLength
|
||
|
* This means that the sensitive data is accessible to anyone with access to buf.buffer.
|
||
|
* If this is the node shared Buffer, then other code within this process _could_ find this secret.
|
||
|
* Copy sensitive data to an isolated Buffer and zero the sensitive data.
|
||
|
* While this is safe to do here, copying this code somewhere else may produce unexpected results.
|
||
|
*/
|
||
|
var secureBuf = util.Buffer.alloc(buf.length, buf);
|
||
|
buf.fill(0);
|
||
|
buf = secureBuf;
|
||
|
}
|
||
|
return buf;
|
||
|
};
|
||
|
this.toWireFormat = util.base64.encode;
|
||
|
}
|
||
|
|
||
|
function Base64Shape() {
|
||
|
BinaryShape.apply(this, arguments);
|
||
|
}
|
||
|
|
||
|
function BooleanShape() {
|
||
|
Shape.apply(this, arguments);
|
||
|
|
||
|
this.toType = function(value) {
|
||
|
if (typeof value === 'boolean') return value;
|
||
|
if (value === null || value === undefined) return null;
|
||
|
return value === 'true';
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @api private
|
||
|
*/
|
||
|
Shape.shapes = {
|
||
|
StructureShape: StructureShape,
|
||
|
ListShape: ListShape,
|
||
|
MapShape: MapShape,
|
||
|
StringShape: StringShape,
|
||
|
BooleanShape: BooleanShape,
|
||
|
Base64Shape: Base64Shape
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @api private
|
||
|
*/
|
||
|
module.exports = Shape;
|