import { parseQueryParams } from '../utils/url_helper'
import isArray from 'lodash/isArray'
import toPairs from 'lodash/toPairs'
import camelCase from 'lodash/camelCase'
import isString from 'lodash/isString'
import get from 'lodash/get'
import { toCamelCaseDeep } from '../utils/case_converter'
/**
* JSON API response wrapper.
*
* JSON API spec: http: //jsonapi.org/
*
* @class
*/
export class JsonApiResponse {
/**
* Wrap a raw axios.js response object.
*
* @constructor
* @param {object} rawResponse Raw axios.js response object.
* @param {ShelfNetwork} sdk ShelfNetwork instance.
*/
constructor (rawResponse, sdk, server) {
this._rawResponse = rawResponse
this._sdk = sdk
this._server = server
this._parseResponse(rawResponse)
}
/**
* Get response data.
*/
get data () {
return this._data
}
/**
* Get response HTTP status.
*/
get httpStatus () {
return this._rawResponse.status
}
/**
* Get response headers.
*/
get headers () {
return this._rawResponse.headers
}
/**
* Get response metadata.
*/
get meta () {
return this._meta
}
/**
* Override JSON serialization.
*
* @return {object} Data to be serialized.
*/
toJSON () {
return this.data
}
_parseResponse (response) {
// NOTE: Horizon follows the JSON API spec
if (response.status === 204) {
return
}
let body = response.data
if (!body.data) {
this._data = null
} else if (isArray(body.data)) {
this._data = body.data.map((item) => {
let parsed = this._parseResourceObjectData(item)
this._resolveRelationships(item, parsed)
this._resolveLinks(item, parsed)
return parsed
})
} else {
this._data = this._parseResourceObjectData(body.data)
this._resolveRelationships(body.data, this)
}
this._resolveLinks(body, this)
this._data = toCamelCaseDeep(this._data)
this._meta = toCamelCaseDeep(body.meta)
}
_parseResourceObjectData (resourceObject) {
let data = {
id: resourceObject.id,
resourceType: resourceObject.type
}
return Object.assign(data, resourceObject.attributes)
}
_resolveLinks (source, target) {
if (!source.links) {
return
}
toPairs(source.links).forEach((pair) => {
let key = pair[0]
let value = pair[1]
let methodName = camelCase('fetch_' + key)
let link = isString(value) ? value : value.href
target[methodName] = this._makeLinkCaller(link)
})
delete source.links
}
_makeLinkCaller (link) {
return () => {
let query = parseQueryParams(link)
let callBuilder = this._server._makeCallBuilder()
.appendUrlSegment(link)
if (this._sdk.token || this._sdk.wallet) {
callBuilder = callBuilder.withCredentials()
}
return callBuilder.get(query)
}
}
_resolveRelationships (source, target) {
if (!source.relationships) {
return
}
let relationships = {}
Object.keys(source.relationships).forEach((key) => {
let value = source.relationships[key]
let parsed
if (isArray(value.data)) {
parsed = value.data.map(item => {
let parsedItem = this._parseRelationshipData(item)
this._resolveLinks(item, parsedItem)
this._resolveRelationships(parsedItem, parsedItem)
return parsedItem
})
} else {
parsed = this._parseRelationshipData(value.data)
this._resolveLinks(value, parsed)
this._resolveRelationships(parsed, parsed)
}
relationships[camelCase(key)] = parsed
})
delete source.relationships
target.relationships = relationships
}
_parseRelationshipData (relationship) {
let data = {
id: relationship.id,
resourceType: relationship.type
}
let included = this._getIncluded(relationship.type, relationship.id)
if (included) {
data = Object.assign(data, toCamelCaseDeep(included.attributes))
if (included.relationships) {
data.relationships = included.relationships
}
}
return data
}
_getIncluded (type, id) {
let included = get(this._rawResponse, 'data.included')
if (!included) {
return null
}
return included.find(x => x.type === type && x.id === id)
}
}