diff --git a/Taskfile.yml b/Taskfile.yml index f30744a..2a7949e 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -31,6 +31,7 @@ tasks: --verbose \ --data \ -c ajv-keywords \ + -c ./src/sumOf.cjs \ -r schemas-json/classes.json \ -s schemas-json/character.json \ -d {{.CLI_ARGS}} diff --git a/package.json b/package.json index 85d65d1..5b02570 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "dependencies": { "ajv": "^8.11.0", "ajv-cli": "^5.0.0", - "ajv-keywords": "^5.1.0" + "ajv-keywords": "^5.1.0", + "json-pointer": "^0.6.2", + "lodash": "^4.17.21" } } diff --git a/schemas-json/character.json b/schemas-json/character.json index 704ef04..f5ddad2 100644 --- a/schemas-json/character.json +++ b/schemas-json/character.json @@ -19,6 +19,11 @@ "type" : "array" }, "max" : { + "sumOf" : { + "list" : { + "$data" : "1/log" + } + }, "type" : "number" } }, diff --git a/schemas-yaml/character.yml b/schemas-yaml/character.yml index 3abe8ed..92e268e 100644 --- a/schemas-yaml/character.yml +++ b/schemas-yaml/character.yml @@ -3,30 +3,30 @@ title: Hyperboria character sheet type: object additionalProperties: false required: - - name - - player - - statistics - - class - - level - - health + - name + - player + - statistics + - class + - level + - health properties: - name: &string - type: string - player: *string - class: { $ref: '/classes.json' } - statistics: - type: object - allRequired: true - properties: - strength: &stat - $ref: '#/$defs/statistic' - dexterity: *stat - constitution: *stat - intelligence: *stat - wisdom: *stat - charisma: *stat - level: { type: number, minimum: 1 } - health: { $ref: '#/$defs/health' } + name: &string + type: string + player: *string + class: { $ref: "/classes.json" } + statistics: + type: object + allRequired: true + properties: + strength: &stat + $ref: "#/$defs/statistic" + dexterity: *stat + constitution: *stat + intelligence: *stat + wisdom: *stat + charisma: *stat + level: { type: number, minimum: 1 } + health: { $ref: "#/$defs/health" } $defs: statistic: type: number @@ -34,14 +34,15 @@ $defs: maximum: 20 health: type: object - required: [ max ] + required: [max] properties: - max: { type: number } - current: { type: number } - log: - type: array - description: history of health rolls - items: { type: number } - minItems: { $data: /level } - maxItems: { $data: /level } - + max: + type: number + sumOf: { list: { $data: 1/log } } + current: { type: number } + log: + type: array + description: history of health rolls + items: { type: number } + minItems: { $data: /level } + maxItems: { $data: /level } diff --git a/src/sumOf.cjs b/src/sumOf.cjs new file mode 100644 index 0000000..2605de0 --- /dev/null +++ b/src/sumOf.cjs @@ -0,0 +1,47 @@ +const _ = require("lodash"); +const ptr = require("json-pointer"); + +function resolvePointer(data, rootPath, relativePath) { + if (relativePath[0] === "/") return ptr.get(data, relativePath); + + const m = relativePath.match(/^(\d+)(.*)/); + relativePath = m[2]; + for (let i = 0; i < parseInt(m[1]); i++) { + rootPath = rootPath.replace(/\/[^\/]+$/, ""); + } + + return ptr.get(data, rootPath + relativePath); +} + +module.exports = (ajv) => + ajv.addKeyword({ + keyword: "sumOf", + validate: function validate( + { list, map }, + total, + _parent, + { rootData, instancePath } + ) { + if (list.$data) { + list = resolvePointer(rootData, instancePath, list.$data); + } + + if (map) data = _.map(data, map); + + if (_.sum(list) === total) return true; + + validate.errors = [ + { + keyword: "sumOf", + message: "should add up to sum total", + params: { + list, + }, + }, + ]; + + return false; + }, + $data: true, + errors: true, + });