| 'use strict'; |
| |
| var errors = require('./../errors'); |
| var uuid = require('./../uuid'); |
| |
| function toObject(array) { |
| return array.reduce(function (obj, item) { |
| obj[item] = true; |
| return obj; |
| }, {}); |
| } |
| // List of top level reserved words for doc |
| var reservedWords = toObject([ |
| '_id', |
| '_rev', |
| '_attachments', |
| '_deleted', |
| '_revisions', |
| '_revs_info', |
| '_conflicts', |
| '_deleted_conflicts', |
| '_local_seq', |
| '_rev_tree', |
| //replication documents |
| '_replication_id', |
| '_replication_state', |
| '_replication_state_time', |
| '_replication_state_reason', |
| '_replication_stats', |
| // Specific to Couchbase Sync Gateway |
| '_removed' |
| ]); |
| |
| // List of reserved words that should end up the document |
| var dataWords = toObject([ |
| '_attachments', |
| //replication documents |
| '_replication_id', |
| '_replication_state', |
| '_replication_state_time', |
| '_replication_state_reason', |
| '_replication_stats' |
| ]); |
| |
| // Determine id an ID is valid |
| // - invalid IDs begin with an underescore that does not begin '_design' or |
| // '_local' |
| // - any other string value is a valid id |
| // Returns the specific error object for each case |
| exports.invalidIdError = function (id) { |
| var err; |
| if (!id) { |
| err = errors.error(errors.MISSING_ID); |
| } else if (typeof id !== 'string') { |
| err = errors.error(errors.INVALID_ID); |
| } else if (/^_/.test(id) && !(/^_(design|local)/).test(id)) { |
| err = errors.error(errors.RESERVED_ID); |
| } |
| if (err) { |
| throw err; |
| } |
| }; |
| |
| function parseRevisionInfo(rev) { |
| if (!/^\d+\-./.test(rev)) { |
| return errors.error(errors.INVALID_REV); |
| } |
| var idx = rev.indexOf('-'); |
| var left = rev.substring(0, idx); |
| var right = rev.substring(idx + 1); |
| return { |
| prefix: parseInt(left, 10), |
| id: right |
| }; |
| } |
| |
| function makeRevTreeFromRevisions(revisions, opts) { |
| var pos = revisions.start - revisions.ids.length + 1; |
| |
| var revisionIds = revisions.ids; |
| var ids = [revisionIds[0], opts, []]; |
| |
| for (var i = 1, len = revisionIds.length; i < len; i++) { |
| ids = [revisionIds[i], {status: 'missing'}, [ids]]; |
| } |
| |
| return [{ |
| pos: pos, |
| ids: ids |
| }]; |
| } |
| |
| // Preprocess documents, parse their revisions, assign an id and a |
| // revision for new writes that are missing them, etc |
| exports.parseDoc = function (doc, newEdits) { |
| |
| var nRevNum; |
| var newRevId; |
| var revInfo; |
| var opts = {status: 'available'}; |
| if (doc._deleted) { |
| opts.deleted = true; |
| } |
| |
| if (newEdits) { |
| if (!doc._id) { |
| doc._id = uuid(); |
| } |
| newRevId = uuid(32, 16).toLowerCase(); |
| if (doc._rev) { |
| revInfo = parseRevisionInfo(doc._rev); |
| if (revInfo.error) { |
| return revInfo; |
| } |
| doc._rev_tree = [{ |
| pos: revInfo.prefix, |
| ids: [revInfo.id, {status: 'missing'}, [[newRevId, opts, []]]] |
| }]; |
| nRevNum = revInfo.prefix + 1; |
| } else { |
| doc._rev_tree = [{ |
| pos: 1, |
| ids : [newRevId, opts, []] |
| }]; |
| nRevNum = 1; |
| } |
| } else { |
| if (doc._revisions) { |
| doc._rev_tree = makeRevTreeFromRevisions(doc._revisions, opts); |
| nRevNum = doc._revisions.start; |
| newRevId = doc._revisions.ids[0]; |
| } |
| if (!doc._rev_tree) { |
| revInfo = parseRevisionInfo(doc._rev); |
| if (revInfo.error) { |
| return revInfo; |
| } |
| nRevNum = revInfo.prefix; |
| newRevId = revInfo.id; |
| doc._rev_tree = [{ |
| pos: nRevNum, |
| ids: [newRevId, opts, []] |
| }]; |
| } |
| } |
| |
| exports.invalidIdError(doc._id); |
| |
| doc._rev = nRevNum + '-' + newRevId; |
| |
| var result = {metadata : {}, data : {}}; |
| for (var key in doc) { |
| /* istanbul ignore else */ |
| if (doc.hasOwnProperty(key)) { |
| var specialKey = key[0] === '_'; |
| if (specialKey && !reservedWords[key]) { |
| var error = errors.error(errors.DOC_VALIDATION, key); |
| error.message = errors.DOC_VALIDATION.message + ': ' + key; |
| throw error; |
| } else if (specialKey && !dataWords[key]) { |
| result.metadata[key.slice(1)] = doc[key]; |
| } else { |
| result.data[key] = doc[key]; |
| } |
| } |
| } |
| return result; |
| }; |