mirror of https://github.com/hashicorp/consul
173 lines
6.3 KiB
JavaScript
173 lines
6.3 KiB
JavaScript
import Serializer from './http';
|
|
|
|
import { set } from '@ember/object';
|
|
import {
|
|
HEADERS_SYMBOL as HTTP_HEADERS_SYMBOL,
|
|
HEADERS_INDEX as HTTP_HEADERS_INDEX,
|
|
HEADERS_DATACENTER as HTTP_HEADERS_DATACENTER,
|
|
HEADERS_NAMESPACE as HTTP_HEADERS_NAMESPACE,
|
|
} from 'consul-ui/utils/http/consul';
|
|
import { CACHE_CONTROL as HTTP_HEADERS_CACHE_CONTROL } from 'consul-ui/utils/http/headers';
|
|
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
|
import { NSPACE_KEY } from 'consul-ui/models/nspace';
|
|
import createFingerprinter from 'consul-ui/utils/create-fingerprinter';
|
|
|
|
const DEFAULT_NSPACE = 'default';
|
|
|
|
const map = function(obj, cb) {
|
|
if (!Array.isArray(obj)) {
|
|
return [obj].map(cb)[0];
|
|
}
|
|
return obj.map(cb);
|
|
};
|
|
|
|
const attachHeaders = function(headers, body, query = {}) {
|
|
// lowercase everything incase we get browser inconsistencies
|
|
const lower = {};
|
|
Object.keys(headers).forEach(function(key) {
|
|
lower[key.toLowerCase()] = headers[key];
|
|
});
|
|
// Add a 'pretend' Datacenter/Nspace header, they are not headers
|
|
// the come from the request but we add them here so we can use them later
|
|
// for store reconciliation
|
|
if (typeof query.dc !== 'undefined') {
|
|
lower[HTTP_HEADERS_DATACENTER.toLowerCase()] = query.dc;
|
|
}
|
|
lower[HTTP_HEADERS_NAMESPACE.toLowerCase()] =
|
|
typeof query.ns !== 'undefined' ? query.ns : DEFAULT_NSPACE;
|
|
//
|
|
body[HTTP_HEADERS_SYMBOL] = lower;
|
|
return body;
|
|
};
|
|
|
|
export default Serializer.extend({
|
|
attachHeaders: attachHeaders,
|
|
fingerprint: createFingerprinter(DATACENTER_KEY, NSPACE_KEY),
|
|
respondForQuery: function(respond, query) {
|
|
return respond((headers, body) =>
|
|
attachHeaders(
|
|
headers,
|
|
map(body, this.fingerprint(this.primaryKey, this.slugKey, query.dc)),
|
|
query
|
|
)
|
|
);
|
|
},
|
|
respondForQueryRecord: function(respond, query) {
|
|
return respond((headers, body) =>
|
|
attachHeaders(headers, this.fingerprint(this.primaryKey, this.slugKey, query.dc)(body), query)
|
|
);
|
|
},
|
|
respondForCreateRecord: function(respond, serialized, data) {
|
|
const slugKey = this.slugKey;
|
|
const primaryKey = this.primaryKey;
|
|
return respond((headers, body) => {
|
|
// If creates are true use the info we already have
|
|
if (body === true) {
|
|
body = data;
|
|
}
|
|
// Creates need a primaryKey adding
|
|
return this.fingerprint(primaryKey, slugKey, data[DATACENTER_KEY])(body);
|
|
});
|
|
},
|
|
respondForUpdateRecord: function(respond, serialized, data) {
|
|
const slugKey = this.slugKey;
|
|
const primaryKey = this.primaryKey;
|
|
return respond((headers, body) => {
|
|
// If updates are true use the info we already have
|
|
// TODO: We may aswell avoid re-fingerprinting here if we are just
|
|
// going to reuse data then its already fingerprinted and as the response
|
|
// is true we don't have anything changed so the old fingerprint stays the same
|
|
// as long as nothing in the fingerprint has been edited (the namespace?)
|
|
if (body === true) {
|
|
body = data;
|
|
}
|
|
return this.fingerprint(primaryKey, slugKey, data[DATACENTER_KEY])(body);
|
|
});
|
|
},
|
|
respondForDeleteRecord: function(respond, serialized, data) {
|
|
const slugKey = this.slugKey;
|
|
const primaryKey = this.primaryKey;
|
|
return respond((headers, body) => {
|
|
// Deletes only need the primaryKey/uid returning
|
|
// and they need the slug key AND potential namespace in order to
|
|
// create the correct uid/fingerprint
|
|
return {
|
|
[primaryKey]: this.fingerprint(
|
|
primaryKey,
|
|
slugKey,
|
|
data[DATACENTER_KEY]
|
|
)({
|
|
[slugKey]: data[slugKey],
|
|
[NSPACE_KEY]: data[NSPACE_KEY],
|
|
})[primaryKey],
|
|
};
|
|
});
|
|
},
|
|
// this could get confusing if you tried to override
|
|
// say `normalizeQueryResponse`
|
|
// TODO: consider creating a method for each one of the `normalize...Response` family
|
|
normalizeResponse: function(store, modelClass, payload, id, requestType) {
|
|
// Pick the meta/headers back off the payload and cleanup
|
|
// before we go through serializing
|
|
const headers = payload[HTTP_HEADERS_SYMBOL] || {};
|
|
delete payload[HTTP_HEADERS_SYMBOL];
|
|
const normalizedPayload = this.normalizePayload(payload, id, requestType);
|
|
// put the meta onto the response, here this is ok
|
|
// as JSON-API allows this and our specific data is now in
|
|
// response[primaryModelClass.modelName]
|
|
// so we aren't in danger of overwriting anything
|
|
// (which was the reason for the Symbol-like property earlier)
|
|
// use a method modelled on ember-data methods so we have the opportunity to
|
|
// do this on a per-model level
|
|
const meta = this.normalizeMeta(store, modelClass, headers, normalizedPayload, id, requestType);
|
|
if (requestType !== 'query') {
|
|
normalizedPayload.meta = meta;
|
|
}
|
|
const res = this._super(
|
|
store,
|
|
modelClass,
|
|
{
|
|
meta: meta,
|
|
[modelClass.modelName]: normalizedPayload,
|
|
},
|
|
id,
|
|
requestType
|
|
);
|
|
// If the result of the super normalizeResponse is undefined
|
|
// its because the JSONSerializer (which REST inherits from)
|
|
// doesn't recognise the requestType, in this case its likely to be an 'action'
|
|
// request rather than a specific 'load me some data' one.
|
|
// Therefore its ok to bypass the store here for the moment
|
|
// we currently use this for self, but it also would affect any custom
|
|
// methods that use a serializer in our custom service/store
|
|
if (typeof res === 'undefined') {
|
|
return payload;
|
|
}
|
|
return res;
|
|
},
|
|
timestamp: function() {
|
|
return new Date().getTime();
|
|
},
|
|
normalizeMeta: function(store, modelClass, headers, payload, id, requestType) {
|
|
const meta = {
|
|
cacheControl: headers[HTTP_HEADERS_CACHE_CONTROL.toLowerCase()],
|
|
cursor: headers[HTTP_HEADERS_INDEX.toLowerCase()],
|
|
dc: headers[HTTP_HEADERS_DATACENTER.toLowerCase()],
|
|
nspace: headers[HTTP_HEADERS_NAMESPACE.toLowerCase()],
|
|
};
|
|
if (typeof headers['x-range'] !== 'undefined') {
|
|
meta.range = headers['x-range'];
|
|
}
|
|
if (requestType === 'query') {
|
|
meta.date = this.timestamp();
|
|
payload.forEach(function(item) {
|
|
set(item, 'SyncTime', meta.date);
|
|
});
|
|
}
|
|
return meta;
|
|
},
|
|
normalizePayload: function(payload, id, requestType) {
|
|
return payload;
|
|
},
|
|
});
|