From a26533708e60e9c750d404a2e93e129194b5c79c Mon Sep 17 00:00:00 2001 From: arafato Date: Thu, 22 Feb 2018 14:33:52 +0100 Subject: [PATCH] implements #141 #129 --- lib/core/ErrorCodes.js | 3 +- lib/core/table/TableStorageManager.js | 13 +++++- lib/middleware/table/validation.js | 16 +++++-- lib/model/table/AzuriteTableRequest.js | 1 + .../{TableGenerator.js => EntityGenerator.js} | 10 ++--- lib/model/table/RequestPayloadParser.js | 4 +- lib/model/table/TableProxy.js | 8 ++-- lib/validation/table/ConflictingTable.js | 18 ++++++++ lib/validation/table/ValidationContext.js | 43 +++++++++++++++++++ 9 files changed, 99 insertions(+), 17 deletions(-) rename lib/model/table/{TableGenerator.js => EntityGenerator.js} (76%) create mode 100644 lib/validation/table/ConflictingTable.js create mode 100644 lib/validation/table/ValidationContext.js diff --git a/lib/core/ErrorCodes.js b/lib/core/ErrorCodes.js index 6b33be9..dd4ed68 100644 --- a/lib/core/ErrorCodes.js +++ b/lib/core/ErrorCodes.js @@ -67,5 +67,6 @@ module.exports = { PopReceiptMismatch: new ErrorCode('PopReceiptMismatch', 400, 'The specified pop receipt did not match the pop receipt for a dequeued message.'), // TABLE - AtomXmlNotSupported: new ErrorCode('Atom+XmlNotSupported', 501, 'Atom feed is currently not supported ny Azurite.') + AtomXmlNotSupported: new ErrorCode('Atom+XmlNotSupported', 501, 'Atom feed is currently not supported by Azurite.'), + TableAlreadyExists: new ErrorCode('TableAlreadyExists', 409, 'The table specified already exists.') } \ No newline at end of file diff --git a/lib/core/table/TableStorageManager.js b/lib/core/table/TableStorageManager.js index b472957..32f2510 100644 --- a/lib/core/table/TableStorageManager.js +++ b/lib/core/table/TableStorageManager.js @@ -6,6 +6,7 @@ const Loki = require('lokijs'), fsn = BbPromise.promisifyAll(require("fs")), AzuriteTableResponse = require('./../../model/table/AzuriteTableResponse'), TableProxy = require('./../../model/table/TableProxy'), + EntityGenerator = require('./../../model/table/EntityGenerator'), Tables = require('./../Constants').TableStorageTables, env = require('./../../core/env'); @@ -39,9 +40,19 @@ class TableStorageManager { createTable(request) { const coll = this.db.getCollection(Tables.Tables); - const proxy = new TableProxy(coll.insert(request.tableName)); + const tableEntity = EntityGenerator.generateTable(request.tableName); + const proxy = new TableProxy(coll.insert(tableEntity)); return BbPromise.resolve(new AzuriteTableResponse({ proxy: proxy })); } + + _getTable(name) { + const coll = this.db.getCollection(Tables.Tables); + const result = coll.chain() + .find({ name: name }) + .data(); + return (result.length === 0) ? undefined : new TableProxy(result[0]); + } + } module.exports = new TableStorageManager(); \ No newline at end of file diff --git a/lib/middleware/table/validation.js b/lib/middleware/table/validation.js index d40b7f9..669f148 100644 --- a/lib/middleware/table/validation.js +++ b/lib/middleware/table/validation.js @@ -4,8 +4,10 @@ const BbPromise = require('bluebird'), N = require('./../../core/HttpHeaderNames'), AError = require('./../../core/AzuriteError'), ErrorCodes = require('./../../core/ErrorCodes'), - Operations = require('./../../core/Constants').Operations.Table; - + Operations = require('./../../core/Constants').Operations.Table, + tsm = require('./../../core/table/TableStorageManager'), + ValidationContext = require('./../../validation/table/ValidationContext'), + ConflictingTableVal = require('./../../validation/table/ConflictingTable'); module.exports = (req, res, next) => { BbPromise.try(() => { @@ -13,7 +15,12 @@ module.exports = (req, res, next) => { if (req.headers[N.CONTENT_TYPE] === `application/atom+xml`) { throw new AError(ErrorCodes.AtomXmlNotSupported); } - const validationContext = new ValidationContext({}); + const request = req.azuriteRequest, + tableProxy = tsm._getTable(request.tableName), + validationContext = new ValidationContext({ + request: request, + table: tableProxy + }); validations[req.azuriteOperation](validationContext); next(); }).catch((e) => { @@ -29,5 +36,6 @@ validations[undefined] = () => { } validations[Operations.CREATE_TABLE] = (valContext) => { - // TODO: Implement validation + valContext + .run(ConflictingTableVal); } \ No newline at end of file diff --git a/lib/model/table/AzuriteTableRequest.js b/lib/model/table/AzuriteTableRequest.js index 3269246..a42acc3 100644 --- a/lib/model/table/AzuriteTableRequest.js +++ b/lib/model/table/AzuriteTableRequest.js @@ -24,6 +24,7 @@ class AzuriteTableRequest { } _initHttpProps(httpHeaders) { + this.httpProps[N.CONTENT_TYPE] = httpHeaders[N.CONTENT_TYPE] || `application/json;`; this.httpProps[N.ACCEPT] = httpHeaders[N.ACCEPT] || `application/json;odata=nometadata`; this.httpProps[N.PREFER] = httpHeaders[N.PREFER] || `return-content`; } diff --git a/lib/model/table/TableGenerator.js b/lib/model/table/EntityGenerator.js similarity index 76% rename from lib/model/table/TableGenerator.js rename to lib/model/table/EntityGenerator.js index 50cdc56..9c58228 100644 --- a/lib/model/table/TableGenerator.js +++ b/lib/model/table/EntityGenerator.js @@ -1,11 +1,11 @@ 'use strict'; /** - * Generates a Table Storage 'Table' Entity + * Generates a Table Storage 'Table' entity and a Table Storage 'Entity' entity. * * @class TableGenerator */ -class TableGenerator { +class EntityGenerator { constructor() { } @@ -16,9 +16,9 @@ class TableGenerator { * @returns * @memberof TableGenerator */ - generateStorageEntity(name) { + generateTable(name) { const entity = {}; - entity.TableName = name; + entity.name = name; const baseUrl = `http://127.0.0.1:10002/devstoreaccount1/`; entity.odata = {}; entity.odata.metadata = `${baseUrl}$metadata#Tables/@Element`; @@ -29,4 +29,4 @@ class TableGenerator { } } -module.exports = new TableGenerator(); \ No newline at end of file +module.exports = new EntityGenerator(); \ No newline at end of file diff --git a/lib/model/table/RequestPayloadParser.js b/lib/model/table/RequestPayloadParser.js index 30d1661..371fc3e 100644 --- a/lib/model/table/RequestPayloadParser.js +++ b/lib/model/table/RequestPayloadParser.js @@ -8,10 +8,10 @@ class RequestPayLoadParser { parse(contentType, body) { switch (contentType) { - case 'application/atom+xml': + case 'application/atom+xml;': throw new IAError(`accept value of 'atom+xml' is currently not supported by Azurite`); break; - case 'application/json': + case 'application/json;': const txt = body.toString('utf8'); return JSON.parse(txt); break; diff --git a/lib/model/table/TableProxy.js b/lib/model/table/TableProxy.js index f483a5f..c2c078f 100644 --- a/lib/model/table/TableProxy.js +++ b/lib/model/table/TableProxy.js @@ -6,7 +6,7 @@ const ODataMode = require('./../../core/Constants').ODataMode, class TableProxy { constructor(entity) { this._ = entity; - this.name = entity.TableName; + this.name = entity.name; } /** @@ -20,13 +20,13 @@ class TableProxy { switch (mode) { case ODataMode.NONE: return { - TableName: this._.TableName + TableName: this._.name } break; case ODataMode.MINIMAL: return { "odata.metadata": this._.odata.metadata, - TableName: this._.TableName + TableName: this._.name } break; case ODataMode.FULL: @@ -35,7 +35,7 @@ class TableProxy { "odata.type": this._.odata.type, "odata.id": this._.odata.id, "odata.editLink": this._.odata.editLink, - TableName: this._.TableName + TableName: this._.name } break; default: diff --git a/lib/validation/table/ConflictingTable.js b/lib/validation/table/ConflictingTable.js new file mode 100644 index 0000000..e666b67 --- /dev/null +++ b/lib/validation/table/ConflictingTable.js @@ -0,0 +1,18 @@ +'use strict'; + +const AError = require('./../../core/AzuriteError'), + ErrorCodes = require('./../../core/ErrorCodes'); + + +class ConflictingTable { + constructor() { + } + + validate({ table = undefined }) { + if (table !== undefined) { + throw new AError(ErrorCodes.TableAlreadyExists); + } + } +} + +module.exports = new ConflictingTable; \ No newline at end of file diff --git a/lib/validation/table/ValidationContext.js b/lib/validation/table/ValidationContext.js new file mode 100644 index 0000000..173b22d --- /dev/null +++ b/lib/validation/table/ValidationContext.js @@ -0,0 +1,43 @@ +'use strict'; + +/** + * The in-memory DB of Azurite serves as the exclusive source of truth for every validation. + * Since the validation is synchronous / single-threaded we can be certain about the exact state of the entire + * application before and after @see ValidationContext exits. + * + * In case a validation fails an according @see AzuriteException is thrown which is then processed + * by the validation middleware module middleware/table/validation.js + * + * @class ValidationContext + */ +class ValidationContext { + constructor({ request = undefined, table = undefined, entity = undefined }) { + this.request = request; + this.table = table; + this.entity = entity; + } + + /** + * Runs a validation module. + * + * @param {Object} valModule + * @param {Object} moduleOptions - allows a validation module to selectively add attributes or overwrite them + * @param {boolean} skip - if set to true validation module is not run. + * @returns this + * + * @memberOf ValidationContext + */ + run(valModule, moduleOptions, skip) { + if (skip) { + return this; + } + valModule.validate({ + request: moduleOptions ? moduleOptions.request || this.request : this.request, + table: moduleOptions ? moduleOptions.table || this.table : this.table, + entity: moduleOptions ? moduleOptions.entity || this.entity : this.entity, + moduleOptions: moduleOptions }); + return this; + } +} + +module.exports = ValidationContext; \ No newline at end of file