Issue-id: OCS-9
[msb/apigateway.git] / msb-core / apiroute / apiroute-service / src / main / resources / api-doc / lib / shred / content.js
1 /*
2  * Copyright 2016 2015-2016 ZTE, Inc. and others. All rights reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  *     Author: Zhaoxing Meng
17  *     email: meng.zhaoxing1@zte.com.cn
18  */
19 // The purpose of the `Content` object is to abstract away the data conversions
20 // to and from raw content entities as strings. For example, you want to be able
21 // to pass in a Javascript object and have it be automatically converted into a
22 // JSON string if the `content-type` is set to a JSON-based media type.
23 // Conversely, you want to be able to transparently get back a Javascript object
24 // in the response if the `content-type` is a JSON-based media-type.
25
26 // One limitation of the current implementation is that it [assumes the `charset` is UTF-8](https://github.com/spire-io/shred/issues/5).
27
28 // The `Content` constructor takes an options object, which *must* have either a
29 // `body` or `data` property and *may* have a `type` property indicating the
30 // media type. If there is no `type` attribute, a default will be inferred.
31 var Content = function(options) {
32   this.body = options.body;
33   this.data = options.data;
34   this.type = options.type;
35 };
36
37 Content.prototype = {
38   // Treat `toString()` as asking for the `content.body`. That is, the raw content entity.
39   //
40   //     toString: function() { return this.body; }
41   //
42   // Commented out, but I've forgotten why. :/
43 };
44
45
46 // `Content` objects have the following attributes:
47 Object.defineProperties(Content.prototype,{
48   
49 // - **type**. Typically accessed as `content.type`, reflects the `content-type`
50 //   header associated with the request or response. If not passed as an options
51 //   to the constructor or set explicitly, it will infer the type the `data`
52 //   attribute, if possible, and, failing that, will default to `text/plain`.
53   type: {
54     get: function() {
55       if (this._type) {
56         return this._type;
57       } else {
58         if (this._data) {
59           switch(typeof this._data) {
60             case "string": return "text/plain";
61             case "object": return "application/json";
62           }
63         }
64       }
65       return "text/plain";
66     },
67     set: function(value) {
68       this._type = value;
69       return this;
70     },
71     enumerable: true
72   },
73
74 // - **data**. Typically accessed as `content.data`, reflects the content entity
75 //   converted into Javascript data. This can be a string, if the `type` is, say,
76 //   `text/plain`, but can also be a Javascript object. The conversion applied is
77 //   based on the `processor` attribute. The `data` attribute can also be set
78 //   directly, in which case the conversion will be done the other way, to infer
79 //   the `body` attribute.
80   data: {
81     get: function() {
82       if (this._body) {
83         return this.processor.parser(this._body);
84       } else {
85         return this._data;
86       }
87     },
88     set: function(data) {
89       if (this._body&&data) Errors.setDataWithBody(this);
90       this._data = data;
91       return this;
92     },
93     enumerable: true
94   },
95
96 // - **body**. Typically accessed as `content.body`, reflects the content entity
97 //   as a UTF-8 string. It is the mirror of the `data` attribute. If you set the
98 //   `data` attribute, the `body` attribute will be inferred and vice-versa. If
99 //   you attempt to set both, an exception is raised.
100   body: {
101     get: function() {
102       if (this._data) {
103         return this.processor.stringify(this._data);
104       } else {
105         return this._body.toString();
106       }
107     },
108     set: function(body) {
109       if (this._data&&body) Errors.setBodyWithData(this);
110       this._body = body;
111       return this;
112     },
113     enumerable: true
114   },
115
116 // - **processor**. The functions that will be used to convert to/from `data` and
117 //   `body` attributes. You can add processors. The two that are built-in are for
118 //   `text/plain`, which is basically an identity transformation and
119 //   `application/json` and other JSON-based media types (including custom media
120 //   types with `+json`). You can add your own processors. See below.
121   processor: {
122     get: function() {
123       var processor = Content.processors[this.type];
124       if (processor) {
125         return processor;
126       } else {
127         // Return the first processor that matches any part of the
128         // content type. ex: application/vnd.foobar.baz+json will match json.
129         var main = this.type.split(";")[0];
130         var parts = main.split(/\+|\//);
131         for (var i=0, l=parts.length; i < l; i++) {
132           processor = Content.processors[parts[i]]
133         }
134         return processor || {parser:identity,stringify:toString};
135       }
136     },
137     enumerable: true
138   },
139
140 // - **length**. Typically accessed as `content.length`, returns the length in
141 //   bytes of the raw content entity.
142   length: {
143     get: function() {
144       if (typeof Buffer !== 'undefined') {
145         return Buffer.byteLength(this.body);
146       }
147       return this.body.length;
148     }
149   }
150 });
151
152 Content.processors = {};
153
154 // The `registerProcessor` function allows you to add your own processors to
155 // convert content entities. Each processor consists of a Javascript object with
156 // two properties:
157 // - **parser**. The function used to parse a raw content entity and convert it
158 //   into a Javascript data type.
159 // - **stringify**. The function used to convert a Javascript data type into a
160 //   raw content entity.
161 Content.registerProcessor = function(types,processor) {
162   
163 // You can pass an array of types that will trigger this processor, or just one.
164 // We determine the array via duck-typing here.
165   if (types.forEach) {
166     types.forEach(function(type) {
167       Content.processors[type] = processor;
168     });
169   } else {
170     // If you didn't pass an array, we just use what you pass in.
171     Content.processors[types] = processor;
172   }
173 };
174
175 // Register the identity processor, which is used for text-based media types.
176 var identity = function(x) { return x; }
177   , toString = function(x) { return x.toString(); }
178 Content.registerProcessor(
179   ["text/html","text/plain","text"],
180   { parser: identity, stringify: toString });
181
182 // Register the JSON processor, which is used for JSON-based media types.
183 Content.registerProcessor(
184   ["application/json; charset=utf-8","application/json","json"],
185   {
186     parser: function(string) {
187       return JSON.parse(string);
188     },
189     stringify: function(data) {
190       return JSON.stringify(data); }});
191
192 var qs = require('querystring');
193 // Register the post processor, which is used for JSON-based media types.
194 Content.registerProcessor(
195   ["application/x-www-form-urlencoded"],
196   { parser : qs.parse, stringify : qs.stringify });
197
198 // Error functions are defined separately here in an attempt to make the code
199 // easier to read.
200 var Errors = {
201   setDataWithBody: function(object) {
202     throw new Error("Attempt to set data attribute of a content object " +
203         "when the body attributes was already set.");
204   },
205   setBodyWithData: function(object) {
206     throw new Error("Attempt to set body attribute of a content object " +
207         "when the data attributes was already set.");
208   }
209 }
210 module.exports = Content;