--- /dev/null
+/*\r
+ * Copyright 2016 ZTE Corporation.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+// The purpose of the `Content` object is to abstract away the data conversions\r
+// to and from raw content entities as strings. For example, you want to be able\r
+// to pass in a Javascript object and have it be automatically converted into a\r
+// JSON string if the `content-type` is set to a JSON-based media type.\r
+// Conversely, you want to be able to transparently get back a Javascript object\r
+// in the response if the `content-type` is a JSON-based media-type.\r
+\r
+// One limitation of the current implementation is that it [assumes the `charset` is UTF-8](https://github.com/spire-io/shred/issues/5).\r
+\r
+// The `Content` constructor takes an options object, which *must* have either a\r
+// `body` or `data` property and *may* have a `type` property indicating the\r
+// media type. If there is no `type` attribute, a default will be inferred.\r
+var Content = function(options) {\r
+ this.body = options.body;\r
+ this.data = options.data;\r
+ this.type = options.type;\r
+};\r
+\r
+Content.prototype = {\r
+ // Treat `toString()` as asking for the `content.body`. That is, the raw content entity.\r
+ //\r
+ // toString: function() { return this.body; }\r
+ //\r
+ // Commented out, but I've forgotten why. :/\r
+};\r
+\r
+\r
+// `Content` objects have the following attributes:\r
+Object.defineProperties(Content.prototype,{\r
+ \r
+// - **type**. Typically accessed as `content.type`, reflects the `content-type`\r
+// header associated with the request or response. If not passed as an options\r
+// to the constructor or set explicitly, it will infer the type the `data`\r
+// attribute, if possible, and, failing that, will default to `text/plain`.\r
+ type: {\r
+ get: function() {\r
+ if (this._type) {\r
+ return this._type;\r
+ } else {\r
+ if (this._data) {\r
+ switch(typeof this._data) {\r
+ case "string": return "text/plain";\r
+ case "object": return "application/json";\r
+ }\r
+ }\r
+ }\r
+ return "text/plain";\r
+ },\r
+ set: function(value) {\r
+ this._type = value;\r
+ return this;\r
+ },\r
+ enumerable: true\r
+ },\r
+\r
+// - **data**. Typically accessed as `content.data`, reflects the content entity\r
+// converted into Javascript data. This can be a string, if the `type` is, say,\r
+// `text/plain`, but can also be a Javascript object. The conversion applied is\r
+// based on the `processor` attribute. The `data` attribute can also be set\r
+// directly, in which case the conversion will be done the other way, to infer\r
+// the `body` attribute.\r
+ data: {\r
+ get: function() {\r
+ if (this._body) {\r
+ return this.processor.parser(this._body);\r
+ } else {\r
+ return this._data;\r
+ }\r
+ },\r
+ set: function(data) {\r
+ if (this._body&&data) Errors.setDataWithBody(this);\r
+ this._data = data;\r
+ return this;\r
+ },\r
+ enumerable: true\r
+ },\r
+\r
+// - **body**. Typically accessed as `content.body`, reflects the content entity\r
+// as a UTF-8 string. It is the mirror of the `data` attribute. If you set the\r
+// `data` attribute, the `body` attribute will be inferred and vice-versa. If\r
+// you attempt to set both, an exception is raised.\r
+ body: {\r
+ get: function() {\r
+ if (this._data) {\r
+ return this.processor.stringify(this._data);\r
+ } else {\r
+ return this._body.toString();\r
+ }\r
+ },\r
+ set: function(body) {\r
+ if (this._data&&body) Errors.setBodyWithData(this);\r
+ this._body = body;\r
+ return this;\r
+ },\r
+ enumerable: true\r
+ },\r
+\r
+// - **processor**. The functions that will be used to convert to/from `data` and\r
+// `body` attributes. You can add processors. The two that are built-in are for\r
+// `text/plain`, which is basically an identity transformation and\r
+// `application/json` and other JSON-based media types (including custom media\r
+// types with `+json`). You can add your own processors. See below.\r
+ processor: {\r
+ get: function() {\r
+ var processor = Content.processors[this.type];\r
+ if (processor) {\r
+ return processor;\r
+ } else {\r
+ // Return the first processor that matches any part of the\r
+ // content type. ex: application/vnd.foobar.baz+json will match json.\r
+ var main = this.type.split(";")[0];\r
+ var parts = main.split(/\+|\//);\r
+ for (var i=0, l=parts.length; i < l; i++) {\r
+ processor = Content.processors[parts[i]]\r
+ }\r
+ return processor || {parser:identity,stringify:toString};\r
+ }\r
+ },\r
+ enumerable: true\r
+ },\r
+\r
+// - **length**. Typically accessed as `content.length`, returns the length in\r
+// bytes of the raw content entity.\r
+ length: {\r
+ get: function() {\r
+ if (typeof Buffer !== 'undefined') {\r
+ return Buffer.byteLength(this.body);\r
+ }\r
+ return this.body.length;\r
+ }\r
+ }\r
+});\r
+\r
+Content.processors = {};\r
+\r
+// The `registerProcessor` function allows you to add your own processors to\r
+// convert content entities. Each processor consists of a Javascript object with\r
+// two properties:\r
+// - **parser**. The function used to parse a raw content entity and convert it\r
+// into a Javascript data type.\r
+// - **stringify**. The function used to convert a Javascript data type into a\r
+// raw content entity.\r
+Content.registerProcessor = function(types,processor) {\r
+ \r
+// You can pass an array of types that will trigger this processor, or just one.\r
+// We determine the array via duck-typing here.\r
+ if (types.forEach) {\r
+ types.forEach(function(type) {\r
+ Content.processors[type] = processor;\r
+ });\r
+ } else {\r
+ // If you didn't pass an array, we just use what you pass in.\r
+ Content.processors[types] = processor;\r
+ }\r
+};\r
+\r
+// Register the identity processor, which is used for text-based media types.\r
+var identity = function(x) { return x; }\r
+ , toString = function(x) { return x.toString(); }\r
+Content.registerProcessor(\r
+ ["text/html","text/plain","text"],\r
+ { parser: identity, stringify: toString });\r
+\r
+// Register the JSON processor, which is used for JSON-based media types.\r
+Content.registerProcessor(\r
+ ["application/json; charset=utf-8","application/json","json"],\r
+ {\r
+ parser: function(string) {\r
+ return JSON.parse(string);\r
+ },\r
+ stringify: function(data) {\r
+ return JSON.stringify(data); }});\r
+\r
+var qs = require('querystring');\r
+// Register the post processor, which is used for JSON-based media types.\r
+Content.registerProcessor(\r
+ ["application/x-www-form-urlencoded"],\r
+ { parser : qs.parse, stringify : qs.stringify });\r
+\r
+// Error functions are defined separately here in an attempt to make the code\r
+// easier to read.\r
+var Errors = {\r
+ setDataWithBody: function(object) {\r
+ throw new Error("Attempt to set data attribute of a content object " +\r
+ "when the body attributes was already set.");\r
+ },\r
+ setBodyWithData: function(object) {\r
+ throw new Error("Attempt to set body attribute of a content object " +\r
+ "when the data attributes was already set.");\r
+ }\r
+}\r
+module.exports = Content;
\ No newline at end of file