overwatch-api-server
Version:
An Unoffical Overwatch HTTP API
191 lines (154 loc) • 6.14 kB
JavaScript
'use strict'
const constants = require('../constants')
const formatUtils = require('../formatUtils')
const recursorUtils = require('../recursorUtils')
const symbolPrimitive = require('../primitiveValues/symbol').tag
const AMBIGUOUS = constants.AMBIGUOUS
const DEEP_EQUAL = constants.DEEP_EQUAL
const UNEQUAL = constants.UNEQUAL
function describeComplex (key, value) {
return new ComplexProperty(key, value)
}
exports.describeComplex = describeComplex
function deserializeComplex (key, recursor) {
const value = recursor()
return new ComplexProperty(key, value)
}
exports.deserializeComplex = deserializeComplex
function describePrimitive (key, value) {
return new PrimitiveProperty(key, value)
}
exports.describePrimitive = describePrimitive
function deserializePrimitive (state) {
const key = state[0]
const value = state[1]
return new PrimitiveProperty(key, value)
}
exports.deserializePrimitive = deserializePrimitive
const complexTag = Symbol('ComplexProperty')
exports.complexTag = complexTag
const primitiveTag = Symbol('PrimitiveProperty')
exports.primitiveTag = primitiveTag
class Property {
constructor (key) {
this.key = key
}
compareKeys (expected) {
const result = this.key.compare(expected.key)
// Return AMBIGUOUS if symbol keys are unequal. It's likely that properties
// are compared in order of declaration, which is not the desired strategy.
// Returning AMBIGUOUS allows compare() and diff() to recognize this
// situation and sort the symbol properties before comparing them.
return result === UNEQUAL && this.key.tag === symbolPrimitive && expected.key.tag === symbolPrimitive
? AMBIGUOUS
: result
}
prepareDiff (expected, lhsRecursor, rhsRecursor, compareComplexShape, isCircular) {
// Circular values cannot be compared. They must be treated as being unequal when diffing.
if (isCircular(this.value) || isCircular(expected.value)) return {compareResult: UNEQUAL}
// Try to line up this or remaining properties with the expected properties.
const rhsFork = recursorUtils.fork(rhsRecursor)
const initialExpected = expected
do {
if (expected === null || expected.isProperty !== true) {
return {
actualIsExtraneous: true,
rhsRecursor: recursorUtils.unshift(rhsFork.recursor, initialExpected)
}
} else if (this.key.compare(expected.key) === DEEP_EQUAL) {
if (expected === initialExpected) {
return null
} else {
return {
expectedIsMissing: true,
lhsRecursor: recursorUtils.unshift(lhsRecursor, this),
rhsRecursor: rhsFork.recursor
}
}
}
expected = rhsFork.shared()
} while (true)
}
}
Object.defineProperty(Property.prototype, 'isProperty', { value: true })
class ComplexProperty extends Property {
constructor (key, value) {
super(key)
this.value = value
}
createRecursor () {
return recursorUtils.singleValue(this.value)
}
compare (expected) {
if (expected.isProperty !== true) return UNEQUAL
const keyResult = this.compareKeys(expected)
if (keyResult !== DEEP_EQUAL) return keyResult
return this.tag === expected.tag
? this.value.compare(expected.value)
: UNEQUAL
}
formatShallow (theme, indent) {
const increaseValueIndent = theme.property.increaseValueIndent === true
return new formatUtils.SingleValueFormatter(theme, value => {
if (typeof theme.property.customFormat === 'function') {
return theme.property.customFormat(theme, indent, this.key, value)
}
return value
.withFirstPrefixed(this.key.formatAsKey(theme) + theme.property.separator)
.withLastPostfixed(theme.property.after)
}, increaseValueIndent)
}
serialize () {
return this.key
}
}
Object.defineProperty(ComplexProperty.prototype, 'tag', { value: complexTag })
class PrimitiveProperty extends Property {
constructor (key, value) {
super(key)
this.value = value
}
compare (expected) {
if (expected.isProperty !== true) return UNEQUAL
const keyResult = this.compareKeys(expected)
if (keyResult !== DEEP_EQUAL) return keyResult
return this.tag !== expected.tag
? UNEQUAL
: this.value.compare(expected.value)
}
formatDeep (theme, indent) {
const increaseValueIndent = theme.property.increaseValueIndent === true
const valueIndent = increaseValueIndent ? indent.increase() : indent
// Since the key and value are formatted directly, modifiers are not
// applied. Apply modifiers to the property descriptor instead.
const formatted = this.value.formatDeep(theme, valueIndent)
if (typeof theme.property.customFormat === 'function') {
return theme.property.customFormat(theme, indent, this.key, formatted)
}
return formatted
.withFirstPrefixed(this.key.formatAsKey(theme) + theme.property.separator)
.withLastPostfixed(theme.property.after)
}
diffDeep (expected, theme, indent) {
// Verify a diff can be returned.
if (this.tag !== expected.tag || typeof this.value.diffDeep !== 'function') return null
// Only use this logic to diff values when the keys are the same.
if (this.key.compare(expected.key) !== DEEP_EQUAL) return null
const increaseValueIndent = theme.property.increaseValueIndent === true
const valueIndent = increaseValueIndent ? indent.increase() : indent
// Since the key and value are diffed directly, modifiers are not
// applied. Apply modifiers to the property descriptor instead.
const diff = this.value.diffDeep(expected.value, theme, valueIndent)
if (diff === null) return null
if (typeof theme.property.customFormat === 'function') {
return theme.property.customFormat(theme, indent, this.key, diff)
}
return diff
.withFirstPrefixed(this.key.formatAsKey(theme) + theme.property.separator)
.withLastPostfixed(theme.property.after)
}
serialize () {
return [this.key, this.value]
}
}
Object.defineProperty(PrimitiveProperty.prototype, 'tag', { value: primitiveTag })