UNPKG

@sap/odata-v4

Version:

OData V4.0 server library

231 lines (193 loc) 8.07 kB
'use strict'; const Reader = require('./Reader'); const DeserializationError = require('../errors/DeserializationError'); /** * The dataReader can read up to a stop pattern, a given count of bytes and untill no more payload is available * @enum {{STOP_PATTERN: string, BYTE_COUNT: string, TILL_END: string}} */ const STOP_CONDITION = { STOP_PATTERN: 'STOP_PATTERN', // Read until stop pattern, after this the caches read position points to begin of stop pattern BYTE_COUNT: 'BYTE_COUNT', // Read a given count of bytes, after this the read position points to the first byte after the read bytes TILL_END: 'TILL_END' // Read until there is no more input data, see "last" parameter on method readCache (avoids search and byte counting) }; /** * States * @type {{READ_NEXT_CHUNK: number, FINISHED: number}} */ const STATES = { READ_NEXT_CHUNK: 0, // data reader consumes chunks from cache FINISHED: 1 // data reader finished (e.g. pattern, length or end of input reached) }; /** * Events * @type {string} * @enum {{DATA: string, END: string}} */ const EVENTS = { DATA: 'data', // data reader consumed data from cache END: 'end' // data reader reached state FINISHED }; /** * The data reader reads bytes from the cache until the stop condition is reached. If the cache is empty * and the data reader needs more data, the readCache function returns to the caller signaling that more data is * required. * * There are three available conditions: * STOP_PATTERN: Reader consumed bytes from cache until the stop pattern is reached * BYTE_COUNT: Reader consumes a given amount of bytes from the cache * TILL_END: Reader consumes until the caller signals that the end of data is reached * * The Following events are emitted through an injected emitter (the prefix is also injected) * <prefix>.data: For every consumed chunk * <prefix>.end: When the reader reaches state finished * * @extends Reader * */ class DataReader extends Reader { /** * Factory for automatic creation depending on content-type * * @param {ContentTypeInfo} contentTypeInfo - Content type which caused using this reader * (here not used, but still in interface due to generic caller) * @param {Object} headers - Header information (currently only the content-length header is evaluated) * @returns {DataReader} */ static createInstance(contentTypeInfo, headers) { return new DataReader(headers); } /** * Constructor * * @param {Object} headers - Header information (currently only the content-length header is evaluated) */ constructor(headers) { super(); /** * @enum {{STOP_PATTERN: string, BYTE_COUNT: string, TILL_END: string}} * @private */ this._stopCondition = STOP_CONDITION.TILL_END; // default /** * Store content-length for STOP_CONDITION.BYTE_COUNT * @type {number} * @private */ this._needToRead = null; // Check for content-length header, please not that these headers are not the nodejs generated headers if (headers) { const contentLength = headers['content-length']; if (contentLength) { this._contentLength = Number.parseInt(contentLength.getValue(), 10); this._needToRead = this._contentLength; this._stopCondition = STOP_CONDITION.BYTE_COUNT; } } this._state = STATES.READ_NEXT_CHUNK; } /** * Sets the stop pattern and sets the mode to STOP_PATTERN * * @param {string} stopPattern */ setStopPattern(stopPattern) { this._stopPattern = stopPattern; this._stopCondition = STOP_CONDITION.STOP_PATTERN; // default } /** * Consume data from cache. If the cache is empty and more data is required, this is signaled and readCache is called again. The state is preserved. * * @param {Reader} reader - Current reader * @param {Cache} cache - Cache containing data for processing * @param {boolean} last - Last call, no more date available * @returns {boolean} * this: this reader needs more data and caller should call this method again with more data in cache * false: this reader is finished caller should pop this reader from stack * null: new sub reader is on stack, call this method again after the sub reader is finished */ readCache(reader, cache, last) { let needMoreData = false; while (!needMoreData && this._state !== STATES.FINISHED) { switch (this._stopCondition) { case STOP_CONDITION.STOP_PATTERN: needMoreData = this._readToStopPattern(cache, last); break; case STOP_CONDITION.BYTE_COUNT: needMoreData = this._readAmount(cache, last); break; default: // STOP_CONDITION.TILL_END: needMoreData = this._readUntilEndOfInput(cache, last); break; } } if (this._state === STATES.FINISHED) { this.emit(EVENTS.END); return false; } if (last && needMoreData) { throw new DeserializationError('More data required to process multipart'); } return needMoreData; } /** * Read data until all input data is consumed * * @param {Cache} cache - Cache containing data for processing * @param {boolean} last - Last call, no more data available * @returns {boolean} - true if more data is required, false if no more data is required */ _readUntilEndOfInput(cache, last) { const consumedBytes = cache.getLength() - cache.getReadPos(); this.emitAndConsume(cache, consumedBytes, EVENTS.DATA); if (last) { this._state = STATES.FINISHED; return false; } return true; } /** * Read data until the stop pattern is reached. The read position is set to the beginning of the stop pattern. * The caller which starts the body reader is responsible for reading the stop pattern. * * @param {Cache} cache - Cache containing data for processing * @returns {boolean} - true if more data is required, false if no more data is required */ _readToStopPattern(cache) { if (cache.getLength() - cache.getReadPos() < this._stopPattern.length) { return true; } const pos = cache.indexOf(this._stopPattern, cache.getSearchPosition()); if (pos === -1) { // stop pattern not found const consumed = cache.getLength() - cache.getReadPos() - this._stopPattern.length; this.emitAndConsume(cache, consumed, EVENTS.DATA); return true; } // stop pattern found const consumed = pos - cache.getReadPos(); this.emitAndConsume(cache, consumed, EVENTS.DATA); this._state = STATES.FINISHED; return false; } /** * Read data until a given amount of bytes are read. * * @param {Cache} cache - Cache containing data for processing * @returns {boolean} - true if more data is required, false if no more data is required */ _readAmount(cache) { const available = cache.getLength() - cache.getReadPos(); if (available < this._needToRead) { this.emitAndConsume(cache, available, EVENTS.DATA); this._needToRead -= available; return true; } // there are more byte in buffer than needed this.emitAndConsume(cache, this._needToRead, EVENTS.DATA); this._needToRead = 0; this._state = STATES.FINISHED; return false; } } DataReader.EVENTS = EVENTS; module.exports = DataReader;