2 * Copyright (C) 2015 CMCC, Inc. and others. All rights reserved. (CMCC)
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
17 // The purpose of the `Content` object is to abstract away the data conversions
\r
18 // to and from raw content entities as strings. For example, you want to be able
\r
19 // to pass in a Javascript object and have it be automatically converted into a
\r
20 // JSON string if the `content-type` is set to a JSON-based media type.
\r
21 // Conversely, you want to be able to transparently get back a Javascript object
\r
22 // in the response if the `content-type` is a JSON-based media-type.
\r
24 // One limitation of the current implementation is that it [assumes the `charset` is UTF-8](https://github.com/spire-io/shred/issues/5).
\r
26 // The `Content` constructor takes an options object, which *must* have either a
\r
27 // `body` or `data` property and *may* have a `type` property indicating the
\r
28 // media type. If there is no `type` attribute, a default will be inferred.
\r
29 var Content = function(options) {
\r
30 this.body = options.body;
\r
31 this.data = options.data;
\r
32 this.type = options.type;
\r
35 Content.prototype = {
\r
36 // Treat `toString()` as asking for the `content.body`. That is, the raw content entity.
\r
38 // toString: function() { return this.body; }
\r
40 // Commented out, but I've forgotten why. :/
\r
44 // `Content` objects have the following attributes:
\r
45 Object.defineProperties(Content.prototype,{
\r
47 // - **type**. Typically accessed as `content.type`, reflects the `content-type`
\r
48 // header associated with the request or response. If not passed as an options
\r
49 // to the constructor or set explicitly, it will infer the type the `data`
\r
50 // attribute, if possible, and, failing that, will default to `text/plain`.
\r
57 switch(typeof this._data) {
\r
58 case "string": return "text/plain";
\r
59 case "object": return "application/json";
\r
63 return "text/plain";
\r
65 set: function(value) {
\r
72 // - **data**. Typically accessed as `content.data`, reflects the content entity
\r
73 // converted into Javascript data. This can be a string, if the `type` is, say,
\r
74 // `text/plain`, but can also be a Javascript object. The conversion applied is
\r
75 // based on the `processor` attribute. The `data` attribute can also be set
\r
76 // directly, in which case the conversion will be done the other way, to infer
\r
77 // the `body` attribute.
\r
81 return this.processor.parser(this._body);
\r
86 set: function(data) {
\r
87 if (this._body&&data) Errors.setDataWithBody(this);
\r
94 // - **body**. Typically accessed as `content.body`, reflects the content entity
\r
95 // as a UTF-8 string. It is the mirror of the `data` attribute. If you set the
\r
96 // `data` attribute, the `body` attribute will be inferred and vice-versa. If
\r
97 // you attempt to set both, an exception is raised.
\r
101 return this.processor.stringify(this._data);
\r
103 return this._body.toString();
\r
106 set: function(body) {
\r
107 if (this._data&&body) Errors.setBodyWithData(this);
\r
114 // - **processor**. The functions that will be used to convert to/from `data` and
\r
115 // `body` attributes. You can add processors. The two that are built-in are for
\r
116 // `text/plain`, which is basically an identity transformation and
\r
117 // `application/json` and other JSON-based media types (including custom media
\r
118 // types with `+json`). You can add your own processors. See below.
\r
121 var processor = Content.processors[this.type];
\r
125 // Return the first processor that matches any part of the
\r
126 // content type. ex: application/vnd.foobar.baz+json will match json.
\r
127 var main = this.type.split(";")[0];
\r
128 var parts = main.split(/\+|\//);
\r
129 for (var i=0, l=parts.length; i < l; i++) {
\r
130 processor = Content.processors[parts[i]]
\r
132 return processor || {parser:identity,stringify:toString};
\r
138 // - **length**. Typically accessed as `content.length`, returns the length in
\r
139 // bytes of the raw content entity.
\r
142 if (typeof Buffer !== 'undefined') {
\r
143 return Buffer.byteLength(this.body);
\r
145 return this.body.length;
\r
150 Content.processors = {};
\r
152 // The `registerProcessor` function allows you to add your own processors to
\r
153 // convert content entities. Each processor consists of a Javascript object with
\r
155 // - **parser**. The function used to parse a raw content entity and convert it
\r
156 // into a Javascript data type.
\r
157 // - **stringify**. The function used to convert a Javascript data type into a
\r
158 // raw content entity.
\r
159 Content.registerProcessor = function(types,processor) {
\r
161 // You can pass an array of types that will trigger this processor, or just one.
\r
162 // We determine the array via duck-typing here.
\r
163 if (types.forEach) {
\r
164 types.forEach(function(type) {
\r
165 Content.processors[type] = processor;
\r
168 // If you didn't pass an array, we just use what you pass in.
\r
169 Content.processors[types] = processor;
\r
173 // Register the identity processor, which is used for text-based media types.
\r
174 var identity = function(x) { return x; }
\r
175 , toString = function(x) { return x.toString(); }
\r
176 Content.registerProcessor(
\r
177 ["text/html","text/plain","text"],
\r
178 { parser: identity, stringify: toString });
\r
180 // Register the JSON processor, which is used for JSON-based media types.
\r
181 Content.registerProcessor(
\r
182 ["application/json; charset=utf-8","application/json","json"],
\r
184 parser: function(string) {
\r
185 return JSON.parse(string);
\r
187 stringify: function(data) {
\r
188 return JSON.stringify(data); }});
\r
190 var qs = require('querystring');
\r
191 // Register the post processor, which is used for JSON-based media types.
\r
192 Content.registerProcessor(
\r
193 ["application/x-www-form-urlencoded"],
\r
194 { parser : qs.parse, stringify : qs.stringify });
\r
196 // Error functions are defined separately here in an attempt to make the code
\r
199 setDataWithBody: function(object) {
\r
200 throw new Error("Attempt to set data attribute of a content object " +
\r
201 "when the body attributes was already set.");
\r
203 setBodyWithData: function(object) {
\r
204 throw new Error("Attempt to set body attribute of a content object " +
\r
205 "when the data attributes was already set.");
\r
208 module.exports = Content;