Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/net/streams/pbjsonstreamparser.js
1865 views
1
// Copyright 2016 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview A stream parser of StreamBody message in Protobuf-JSON format.
17
*
18
* 1. StreamBody proto message is defined as following:
19
*
20
* message StreamBody {
21
* repeated bytes messages = 1;
22
* google.rpc.Status status = 2;
23
* }
24
*
25
* 2. In Protobuf-JSON format, StreamBody is represented as a Protobuf-JSON
26
* array (different than JSON array by emitting null elements):
27
*
28
* - [ [ message1, message2, ..., messageN ] ] (no status)
29
* - [ , status ] (no message)
30
* - [ [ message1, message2, ..., messageN ] , status ]
31
*
32
* 3. All parsed messages and status will be delivered in a batch (array),
33
* with each constructed as {tag-id: content-string}.
34
*/
35
36
goog.module('goog.net.streams.PbJsonStreamParser');
37
38
var JsonStreamParser = goog.require('goog.net.streams.JsonStreamParser');
39
var StreamParser = goog.require('goog.net.streams.StreamParser');
40
var asserts = goog.require('goog.asserts');
41
var utils = goog.require('goog.net.streams.utils');
42
43
44
/**
45
* A stream parser of StreamBody message in Protobuf-JSON format.
46
*
47
* @constructor
48
* @struct
49
* @implements {StreamParser}
50
* @final
51
*/
52
var PbJsonStreamParser = function() {
53
/**
54
* Protobuf raw bytes stream parser
55
* @private {?JsonStreamParser}
56
*/
57
this.jsonStreamParser_ = null;
58
59
/**
60
* The current error message, if any.
61
* @private {?string}
62
*/
63
this.errorMessage_ = null;
64
65
/**
66
* The current position in the streamed data.
67
* @private {number}
68
*/
69
this.streamPos_ = 0;
70
71
/**
72
* The current parser state.
73
* @private {!State}
74
*/
75
this.state_ = State.INIT;
76
77
/**
78
* The currently buffered result (parsed JSON objects).
79
* @private {!Array<!Object>}
80
*/
81
this.result_ = [];
82
83
/**
84
* Whether the status has been parsed.
85
* @private {boolean}
86
*/
87
this.statusParsed_ = false;
88
};
89
90
91
/**
92
* The parser state.
93
* @enum {number}
94
*/
95
var State = {
96
INIT: 0, // expecting the beginning "["
97
ARRAY_OPEN: 1, // expecting the message array or the msg-status separator
98
MESSAGES: 2, // expecting the message array
99
MESSAGES_DONE: 3, // expecting the msg-status separator or the ending "]"
100
STATUS: 4, // expecting the status
101
ARRAY_END: 5, // expecting NO more non-whitespace input
102
INVALID: 6 // the stream has become invalid
103
};
104
105
106
/** @override */
107
PbJsonStreamParser.prototype.isInputValid = function() {
108
return this.errorMessage_ === null;
109
};
110
111
112
/** @override */
113
PbJsonStreamParser.prototype.getErrorMessage = function() {
114
return this.errorMessage_;
115
};
116
117
118
/** @override */
119
PbJsonStreamParser.prototype.parse = function(input) {
120
asserts.assertString(input);
121
122
var parser = this;
123
var pos = 0;
124
while (pos < input.length) {
125
if (!readMore()) {
126
return null;
127
}
128
129
switch (parser.state_) {
130
case State.INVALID: {
131
reportError('stream already broken');
132
break;
133
}
134
case State.INIT: {
135
if (input[pos] === '[') {
136
parser.state_ = State.ARRAY_OPEN;
137
pos++;
138
parser.streamPos_++;
139
} else {
140
reportError('unexpected input token');
141
}
142
break;
143
}
144
case State.ARRAY_OPEN: {
145
if (input[pos] === '[') {
146
parser.state_ = State.MESSAGES;
147
resetJsonStreamParser();
148
// Feed the '[' again in the next loop.
149
} else if (input[pos] === ',') {
150
parser.state_ = State.MESSAGES_DONE;
151
// Feed the ',' again in the next loop.
152
} else if (input[pos] === ']') {
153
parser.state_ = State.ARRAY_END;
154
pos++;
155
parser.streamPos_++;
156
} else {
157
reportError('unexpected input token');
158
}
159
break;
160
}
161
case State.MESSAGES: {
162
var messages = parser.jsonStreamParser_.parse(input.substring(pos));
163
addResultMessages(messages);
164
165
if (!parser.jsonStreamParser_.done()) {
166
parser.streamPos_ += input.length - pos;
167
pos = input.length; // end the loop
168
} else {
169
parser.state_ = State.MESSAGES_DONE;
170
var extra = parser.jsonStreamParser_.getExtraInput();
171
parser.streamPos_ += input.length - pos - extra.length;
172
input = extra;
173
pos = 0;
174
}
175
break;
176
}
177
case State.MESSAGES_DONE: {
178
if (input[pos] === ',') {
179
parser.state_ = State.STATUS;
180
resetJsonStreamParser();
181
// Feed a dummy "[" to match the ending "]".
182
parser.jsonStreamParser_.parse('[');
183
pos++;
184
parser.streamPos_++;
185
} else if (input[pos] === ']') {
186
parser.state_ = State.ARRAY_END;
187
pos++;
188
parser.streamPos_++;
189
}
190
break;
191
}
192
case State.STATUS: {
193
var status = parser.jsonStreamParser_.parse(input.substring(pos));
194
addResultStatus(status);
195
196
if (!parser.jsonStreamParser_.done()) {
197
parser.streamPos_ += input.length - pos;
198
pos = input.length; // end the loop
199
} else {
200
parser.state_ = State.ARRAY_END;
201
var extra = parser.jsonStreamParser_.getExtraInput();
202
parser.streamPos_ += input.length - pos - extra.length;
203
input = extra;
204
pos = 0;
205
}
206
break;
207
}
208
case State.ARRAY_END: {
209
reportError('extra input after stream end');
210
break;
211
}
212
}
213
}
214
215
if (parser.result_.length > 0) {
216
var results = parser.result_;
217
parser.result_ = [];
218
return results;
219
}
220
return null;
221
222
223
/**
224
* @param {string} errorMessage Additional error message
225
* @throws {!Error} Throws an error indicating where the stream is broken
226
*/
227
function reportError(errorMessage) {
228
parser.state_ = State.INVALID;
229
parser.errorMessage_ = 'The stream is broken @' + parser.streamPos_ + '/' +
230
pos + '. Error: ' + errorMessage + '. With input:\n';
231
throw new Error(parser.errorMessage_);
232
}
233
234
235
/**
236
* Advances to the first non-whitespace input character.
237
*
238
* @return {boolean} return false if no more non-whitespace input character
239
*/
240
function readMore() {
241
while (pos < input.length) {
242
if (!utils.isJsonWhitespace(input[pos])) {
243
return true;
244
}
245
pos++;
246
parser.streamPos_++;
247
}
248
return false;
249
}
250
251
function resetJsonStreamParser() {
252
parser.jsonStreamParser_ = new JsonStreamParser({
253
'allowCompactJsonArrayFormat': true,
254
'deliverMessageAsRawString': true
255
});
256
}
257
258
/** @param {?Array<string>} messages Parsed messages */
259
function addResultMessages(messages) {
260
if (messages) {
261
for (var i = 0; i < messages.length; i++) {
262
var tagged = {};
263
tagged[1] = messages[i];
264
parser.result_.push(tagged);
265
}
266
}
267
}
268
269
/** @param {?Array<string>} status Parsed status */
270
function addResultStatus(status) {
271
if (status) {
272
if (parser.statusParsed_ || status.length > 1) {
273
reportError('extra status: ' + status);
274
}
275
parser.statusParsed_ = true;
276
277
var tagged = {};
278
tagged[2] = status[0];
279
parser.result_.push(tagged);
280
}
281
}
282
};
283
284
285
exports = PbJsonStreamParser;
286
287