Path: blob/trunk/third_party/closure/goog/net/streams/pbjsonstreamparser.js
1865 views
// Copyright 2016 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview A stream parser of StreamBody message in Protobuf-JSON format.16*17* 1. StreamBody proto message is defined as following:18*19* message StreamBody {20* repeated bytes messages = 1;21* google.rpc.Status status = 2;22* }23*24* 2. In Protobuf-JSON format, StreamBody is represented as a Protobuf-JSON25* array (different than JSON array by emitting null elements):26*27* - [ [ message1, message2, ..., messageN ] ] (no status)28* - [ , status ] (no message)29* - [ [ message1, message2, ..., messageN ] , status ]30*31* 3. All parsed messages and status will be delivered in a batch (array),32* with each constructed as {tag-id: content-string}.33*/3435goog.module('goog.net.streams.PbJsonStreamParser');3637var JsonStreamParser = goog.require('goog.net.streams.JsonStreamParser');38var StreamParser = goog.require('goog.net.streams.StreamParser');39var asserts = goog.require('goog.asserts');40var utils = goog.require('goog.net.streams.utils');414243/**44* A stream parser of StreamBody message in Protobuf-JSON format.45*46* @constructor47* @struct48* @implements {StreamParser}49* @final50*/51var PbJsonStreamParser = function() {52/**53* Protobuf raw bytes stream parser54* @private {?JsonStreamParser}55*/56this.jsonStreamParser_ = null;5758/**59* The current error message, if any.60* @private {?string}61*/62this.errorMessage_ = null;6364/**65* The current position in the streamed data.66* @private {number}67*/68this.streamPos_ = 0;6970/**71* The current parser state.72* @private {!State}73*/74this.state_ = State.INIT;7576/**77* The currently buffered result (parsed JSON objects).78* @private {!Array<!Object>}79*/80this.result_ = [];8182/**83* Whether the status has been parsed.84* @private {boolean}85*/86this.statusParsed_ = false;87};888990/**91* The parser state.92* @enum {number}93*/94var State = {95INIT: 0, // expecting the beginning "["96ARRAY_OPEN: 1, // expecting the message array or the msg-status separator97MESSAGES: 2, // expecting the message array98MESSAGES_DONE: 3, // expecting the msg-status separator or the ending "]"99STATUS: 4, // expecting the status100ARRAY_END: 5, // expecting NO more non-whitespace input101INVALID: 6 // the stream has become invalid102};103104105/** @override */106PbJsonStreamParser.prototype.isInputValid = function() {107return this.errorMessage_ === null;108};109110111/** @override */112PbJsonStreamParser.prototype.getErrorMessage = function() {113return this.errorMessage_;114};115116117/** @override */118PbJsonStreamParser.prototype.parse = function(input) {119asserts.assertString(input);120121var parser = this;122var pos = 0;123while (pos < input.length) {124if (!readMore()) {125return null;126}127128switch (parser.state_) {129case State.INVALID: {130reportError('stream already broken');131break;132}133case State.INIT: {134if (input[pos] === '[') {135parser.state_ = State.ARRAY_OPEN;136pos++;137parser.streamPos_++;138} else {139reportError('unexpected input token');140}141break;142}143case State.ARRAY_OPEN: {144if (input[pos] === '[') {145parser.state_ = State.MESSAGES;146resetJsonStreamParser();147// Feed the '[' again in the next loop.148} else if (input[pos] === ',') {149parser.state_ = State.MESSAGES_DONE;150// Feed the ',' again in the next loop.151} else if (input[pos] === ']') {152parser.state_ = State.ARRAY_END;153pos++;154parser.streamPos_++;155} else {156reportError('unexpected input token');157}158break;159}160case State.MESSAGES: {161var messages = parser.jsonStreamParser_.parse(input.substring(pos));162addResultMessages(messages);163164if (!parser.jsonStreamParser_.done()) {165parser.streamPos_ += input.length - pos;166pos = input.length; // end the loop167} else {168parser.state_ = State.MESSAGES_DONE;169var extra = parser.jsonStreamParser_.getExtraInput();170parser.streamPos_ += input.length - pos - extra.length;171input = extra;172pos = 0;173}174break;175}176case State.MESSAGES_DONE: {177if (input[pos] === ',') {178parser.state_ = State.STATUS;179resetJsonStreamParser();180// Feed a dummy "[" to match the ending "]".181parser.jsonStreamParser_.parse('[');182pos++;183parser.streamPos_++;184} else if (input[pos] === ']') {185parser.state_ = State.ARRAY_END;186pos++;187parser.streamPos_++;188}189break;190}191case State.STATUS: {192var status = parser.jsonStreamParser_.parse(input.substring(pos));193addResultStatus(status);194195if (!parser.jsonStreamParser_.done()) {196parser.streamPos_ += input.length - pos;197pos = input.length; // end the loop198} else {199parser.state_ = State.ARRAY_END;200var extra = parser.jsonStreamParser_.getExtraInput();201parser.streamPos_ += input.length - pos - extra.length;202input = extra;203pos = 0;204}205break;206}207case State.ARRAY_END: {208reportError('extra input after stream end');209break;210}211}212}213214if (parser.result_.length > 0) {215var results = parser.result_;216parser.result_ = [];217return results;218}219return null;220221222/**223* @param {string} errorMessage Additional error message224* @throws {!Error} Throws an error indicating where the stream is broken225*/226function reportError(errorMessage) {227parser.state_ = State.INVALID;228parser.errorMessage_ = 'The stream is broken @' + parser.streamPos_ + '/' +229pos + '. Error: ' + errorMessage + '. With input:\n';230throw new Error(parser.errorMessage_);231}232233234/**235* Advances to the first non-whitespace input character.236*237* @return {boolean} return false if no more non-whitespace input character238*/239function readMore() {240while (pos < input.length) {241if (!utils.isJsonWhitespace(input[pos])) {242return true;243}244pos++;245parser.streamPos_++;246}247return false;248}249250function resetJsonStreamParser() {251parser.jsonStreamParser_ = new JsonStreamParser({252'allowCompactJsonArrayFormat': true,253'deliverMessageAsRawString': true254});255}256257/** @param {?Array<string>} messages Parsed messages */258function addResultMessages(messages) {259if (messages) {260for (var i = 0; i < messages.length; i++) {261var tagged = {};262tagged[1] = messages[i];263parser.result_.push(tagged);264}265}266}267268/** @param {?Array<string>} status Parsed status */269function addResultStatus(status) {270if (status) {271if (parser.statusParsed_ || status.length > 1) {272reportError('extra status: ' + status);273}274parser.statusParsed_ = true;275276var tagged = {};277tagged[2] = status[0];278parser.result_.push(tagged);279}280}281};282283284exports = PbJsonStreamParser;285286287