├── .gitignore ├── .travis.yml ├── IMPLEMENTATIONS.md ├── LANGUAGE.md ├── LICENSE ├── README.md ├── SCHEMA.md ├── SYNTAX.md ├── index.js ├── instructions ├── $call.json ├── $data.json ├── $delay.json ├── $exec.json ├── $func.json ├── $if.json ├── $quote.json ├── $ref.json └── index.js ├── macros ├── calc.json ├── call.json ├── exec.json └── index.js ├── package.json ├── schema ├── evaluate.json ├── evaluate.json.dot ├── expand_macros.json ├── expand_macros.json.dot ├── instruction.json ├── macro.json ├── schema.json ├── schema.json.dot └── schema_strict.json ├── scripts ├── generate.js └── travis-gh-pages └── spec ├── evaluate.spec.js ├── expand_macro.spec.js ├── instruction.spec.js ├── macro.spec.js ├── schema.spec.js └── scripts ├── $call.json ├── $data.json ├── $delay.json ├── $exec.json ├── $func.json ├── $if.json ├── $ref.json ├── parallel.json └── sequential.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | .DS_Store 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | after_script: 5 | - scripts/travis-gh-pages 6 | -------------------------------------------------------------------------------- /IMPLEMENTATIONS.md: -------------------------------------------------------------------------------- 1 | # JSONScript Implemetations 2 | 3 | [jsonscript-js](https://github.com/JSONScript/jsonscript-js) - JSONScript interpreter for nodejs 4 | 5 | [jsonscript-express](https://github.com/JSONScript/jsonscript-express) - express 4 middleware/route-handler for scripted processing of existing express app routes. 6 | 7 | [jsonscript-proxy](https://github.com/JSONScript/jsonscript-proxy) - proxy server for scripted processing of other existing services. 8 | -------------------------------------------------------------------------------- /LANGUAGE.md: -------------------------------------------------------------------------------- 1 | # JSONScript language 2 | 3 | ## The simplest script 4 | 5 | The simplest script in JSONScript is a single instruction that calls an external executor method with some arguments: 6 | 7 | ```json 8 | { 9 | "$exec": "router", 10 | "$method": "get", 11 | "$args": { "path": "/resource/1" } 12 | } 13 | ``` 14 | 15 | The executor is any object that is provided to the JSONScript interpreter by the environment it executes in. The executor is defined by its name that is either an identifier or UUID (UUIDs are used for temporary external objects that can be created in the process of script evaluation). 16 | 17 | In the example above it is an object with the name `"router"` (the value of the keyword `$exec`) that has different methods including `"get"` (the value of the keyword `$method`). 18 | 19 | When this simple script is evaluated its value will be the resolved value that the executor returns. See [Synchronous and asynchronous values](#synchronous-and-asynchronous-values). 20 | 21 | The full syntax above allows all properties to be evaluated - executor name and methods can be the result of some other scripts. For the majority of cases when these are constants the short syntax can be used: 22 | 23 | ```json 24 | { "$$router.get": { "path": "/resource/1" } } 25 | ``` 26 | 27 | This is achieved via [macros](#macros) support. 28 | 29 | JSONScript core includes other instructions that will be shown later and the interpreter may allow to define your own custom instructions. 30 | 31 | With JSONScript you can combine instructions in three ways: 32 | 33 | - sequential evaluation 34 | - parallel evaluation 35 | - evaluating instruction keyword values with the scripts 36 | 37 | 38 | ## Synchronous and asynchronous values 39 | 40 | Individual instructions and parts of the script can evaluate to synchronous and asynchronous value. 41 | 42 | A synchronous value is any data that is available and can be used immediately. 43 | 44 | An asynchronous value is a value that is currently not available and will be available (resolved/fullfilled/bound) in the future. Different programming languages implement asynchronous values as [promises, futures, etc.](https://en.wikipedia.org/wiki/Futures_and_promises) 45 | 46 | 47 | ## Sequential evaluation 48 | 49 | JSONScript can include several instructions that will be executed sequentially: 50 | 51 | ```json 52 | [ 53 | { "$$router.get": { "path": "/resource/1" } }, 54 | { "$$router.put": { "path": "/resource/1", "body": "test" } } 55 | ] 56 | ``` 57 | 58 | "Sequential" means that the next script begins evaluating only after the script in the previous item has evaluated (and if the result is an asynchronous value this value has resolved into synchronous). 59 | 60 | The example above will retrive the value of some resource and once it was retrieved it will update it. The sequential execution guarantees that it will be the value before the update. 61 | 62 | The result of the evaluation of the whole script above is a single asynchronous value (assuming that instructions return asynchronous values) that resolves into the array with results of both instructions. 63 | 64 | Sequential evaluation is not limited to executing individual instructions - any scripts can be combined using this JSONScript primitive. 65 | 66 | For example, this script does the same as the script above for two resources: 67 | 68 | ```json 69 | [ 70 | [ 71 | { "$$router.get": { "path": "/resource/1" } }, 72 | { "$$router.put": { "path": "/resource/1", "body": "test" } } 73 | ], 74 | [ 75 | { "$$router.get": { "path": "/resource/2" } }, 76 | { "$$router.put": { "path": "/resource/2", "body": "test" } } 77 | ] 78 | ] 79 | ``` 80 | 81 | 82 | The result of this script evaluation is the array of two arrays containing two items each, the items being the results of evaluation of individual instructions. 83 | 84 | 85 | ## Parallel evaluation 86 | 87 | JSONScript can include several instructions that will be executed in parallel: 88 | 89 | ```json 90 | { 91 | "res1": { "$$router.get": { "path": "/resource/1" } }, 92 | "res2": { "$$router.get": { "path": "/resource/2" } } 93 | } 94 | ``` 95 | 96 | The meaning of "parallel" depends on the environment in which the script executes and on the interpreter implementation. The implementation should not guarantee any order in which evaluation of individual scripts will start, and instructions don't wait until another instruction evaluates (and resolves) to begin executing. 97 | 98 | The example above retrieves the values fo two resources. The result of the evaluation of the whole script above is a single asynchronous value (assuming that instructions return asynchronous values) that resolves into the object with the results of both instructions available in properties `"res1"` and `"res2"`. 99 | 100 | The names of properties in the object can be any strings that do NOT start with "$" (properties that stat with "$" are resolved fot the instruction keywords). 101 | 102 | Parallel evaluation is not limited to executing individual instructions - any scripts can be combined using this JSONScript primitive. 103 | 104 | For example, the script below is similar to the example in the previous section that updates two resources but it does it in parallel: 105 | 106 | ```json 107 | { 108 | "res1": [ 109 | { "$$router.get": { "path": "/resource/1" } }, 110 | { "$$router.put": { "path": "/resource/1", "body": "test" } } 111 | ], 112 | "res2": [ 113 | { "$$router.get": { "path": "/resource/2" } }, 114 | { "$$router.put": { "path": "/resource/2", "body": "test" } } 115 | ] 116 | } 117 | ``` 118 | 119 | This example combines parallel and sequential evaluation. Each resource update only starts after the current value is retrieved, but the update of `"/resource/2"` does not need to wait until the `"/resource/1"` finished updating. 120 | 121 | The result of this script evaluation is the object with two properties `"res1"` and `"res2"` each containing two items with the results of individual instructions. 122 | 123 | You can see how by combining individual instruction calls, sequential and parallel evaluation you can build advanced scripts. 124 | 125 | Let's see what other instructions are defined in JSONScript core. 126 | 127 | 128 | ## Accessing data instance with `$data` 129 | 130 | During the evaluation the script can use the data instance passed to the interpeter in addition to the script: 131 | 132 | ```json 133 | [ 134 | { "$$router.get": { "path": { "$data": "/path" } } }, 135 | { "$$router.put": { "$data": "" } } 136 | ] 137 | ``` 138 | 139 | Data instance: 140 | 141 | ```json 142 | { 143 | "path": "/resource/1", 144 | "body": { "test": "test" } 145 | } 146 | ``` 147 | 148 | The instruction to get data has a single keyword `$data` that is a JSON-pointer to the part of the passed data instance. 149 | 150 | `$data` allows to separate the passed data from the script in this way avoiding repetition and making the scripts reusable. 151 | 152 | Not only some part of arguments can use scripts to evaluate it, any value in the script can be any other script as long as it is evaluated to the correct type of value. 153 | 154 | For example, the executor name can be the result of the call to another executor: 155 | 156 | ```json 157 | { 158 | "$exec": { "$exec": "chooseRouter" }, 159 | "$method": "get", 160 | "$args": { "path": { "$data": "/path" } } 161 | } 162 | ``` 163 | 164 | 165 | ## Accessing the parts of the current script with `$ref` 166 | 167 | The script can use results from any part of the script in another part of the script with `$ref` instruction. 168 | 169 | The previous example where executor name was the result of another script evaluation could be re-written like this: 170 | 171 | ```json 172 | { 173 | "router": { "$exec": "chooseRouter" }, 174 | "response": { 175 | "$exec": { "$ref": "2/router" }, 176 | "$method": "get", 177 | "$args": { "path": { "$data": "/path" } } 178 | } 179 | } 180 | ``` 181 | 182 | In this way the script will evaluate to the object that contains both the response and the name of the router that returned it. 183 | 184 | The value of `$ref` keyword should be an absolute or relative JSON-pointer. 185 | 186 | If an absolute JSON-pointer is used it means the pointer in the whole script object. 187 | 188 | The realtive JSON-pointer is the pointer relative to `$ref` instruction object, so `"0/"` means the instruction itself (it can't be evaluated though, see below), `"1/"` - the parent object etc. 189 | 190 | Although the example uses parallel processing, the second instruction will not start executing until the first one completes because references should always return evaluated values of the script, rather than the script itself. 191 | 192 | It is easy to create the script that will never evaluate: 193 | - two instructions using references to the results of each other. 194 | - the instruction in array (sequential processing) using the reference to the result of the next instruction. 195 | - the reference to itself or to the child of itself. 196 | 197 | JSONScript interpreters should both try to determine such situations as early as possible and to allow defining evaluation timeout(s). 198 | 199 | 200 | ## Conditional evaluation with `$if` 201 | 202 | `$if` instruction can be used to choose the strict that will be evaluated based on some condition: 203 | 204 | ```json 205 | { 206 | "$if": { "$$checkAvailability": "router1" }, 207 | "$then": { "$$router1.get": { "path": "/resource/1" } }, 208 | "$else": { "$$router2.get": { "path": "/resource/1" } } 209 | } 210 | ``` 211 | 212 | The result of the evaluation of the script in `$if` keyword should be a boolean value, otherwise the whole script will fail to evailuate (no type coercion is made). 213 | 214 | If the condition is `true` then the script in `$then` keyword will be evaluted and its result will be the result of `$if` instruction, otherwise the script in `$else` will be evaluated and `$if` evaluate to its result. 215 | 216 | Please note that the interpreter should NOT evaluate both scripts and choose the result - it should evaluate only one of the scripts. 217 | 218 | `$else` keyword is optional, if it is absent and the condition is `false`, `$if` will evaluate to `null`. 219 | 220 | Scalar values can be used in any place where the script is expected - they evaluate to themselves. We can refactor the script above in this way: 221 | 222 | ```json 223 | { 224 | "$exec": { 225 | "$if": { "$$checkAvailability": "router1" }, 226 | "$then": "router1", 227 | "$else": "router2" 228 | }, 229 | "$method": "get", 230 | "$args": { "path": "/resource/1" } 231 | } 232 | ``` 233 | 234 | or using reference: 235 | 236 | ```json 237 | { 238 | "router": { 239 | "$if": { "$$checkAvailability": "router1" }, 240 | "$then": "router1", 241 | "$else": "router2" 242 | }, 243 | "response": { 244 | "$exec": { "$ref": "2/router" }, 245 | "$method": "get", 246 | "$args": { "path": "/resource/1" } 247 | } 248 | } 249 | ``` 250 | 251 | In the examples above `$if` instruction evaluates to `"router1"` or to `"router2"`, depending on the condition. In the first case the script returns only `get` result, the result of the second script includes that name of executor that executed the method. 252 | 253 | 254 | ## Delayed evaluation with `$delay` 255 | 256 | `$delay` instruction can be used to delay the start of evaluation of any script. That can be useful, for example, if you need to ensure that one script starts evaluating after another script starts, but you don't need for it to wait for the completion (as in sequential processing): 257 | 258 | ```json 259 | { 260 | "res1": { "$$router.get": { "path": "/resource/1" } }, 261 | "res2": { 262 | "$delay": { "$$router.get": { "path": "/resource/2" } }, 263 | "$wait": 50 264 | } 265 | } 266 | ``` 267 | 268 | The evaluation result will be the same as without `$delay` istruction, but the second "$exec" instruction will start executing at least 50 milliseconds later than the first. 269 | 270 | This instruction can also be used to create asynchronous value from synchronous value. For example if some executor expects an asynchronous value as an argument and you want to pass a constant, you can use `$delay`: 271 | 272 | ```json 273 | { 274 | "$$logger.resolve": { 275 | "message": "Resolved", 276 | "asyncValue": { "$delay": "test", "$wait": 1000 } 277 | } 278 | } 279 | ``` 280 | 281 | In the example above a hypothetical logger logs message when asynchronous value is resolved. `$delay` instruction result is an asynchrnous value that resolves 1 second after its evaluation with the value `"test"`. 282 | 283 | `$wait` keyword is optional, the default is 0. It means that the interpreter should schedule the script evaluation as soon as possible but do not execute it immediately. 284 | 285 | 286 | ## Defining and calling functions with `$func` and `$call` 287 | 288 | Anonymous or named function can be defined in the script to be passed to executors (either predefined or supplied by user) or simply to be used multiple times. 289 | 290 | ```json 291 | [ 292 | { 293 | "$func": { "$$router.get": { "path": { "$data": "/path" } } }, 294 | "$name": "getRes", 295 | "$args": [ "path" ] 296 | }, 297 | { "$#getRes": [ "/resource/1" ] }, 298 | { 299 | "$call": { "$ref": "/0" }, 300 | "$args": { "path": "/resource/2" } 301 | }, 302 | { 303 | "$call": { "$ref": "1/0" }, 304 | "$args": "/resource/3" 305 | } 306 | ] 307 | ``` 308 | 309 | In the example above the same function `getRes` is used three times, being called by name and using $ref with absolute and relative JSON-pointers. Arguments can be passed to function as array, as an object (property names should match parameters declarations, otherwise an exception will be thrown) and as a scalar value if there is only one parameter. 310 | 311 | Functions can be used as parameters in the executors: 312 | 313 | ```json 314 | { 315 | "$$array.map": { 316 | "data": [ 317 | "/resource/1", 318 | "/resource/2", 319 | "/resource/3" 320 | ], 321 | "iterator": { 322 | "$func": { 323 | "$$router1.get": { "path": { "$data": "/path" } } 324 | }, 325 | "$args": ["path"] 326 | } 327 | } 328 | } 329 | ``` 330 | 331 | See [Array iteration](#array-iteration). 332 | 333 | If the function was previously defined it can be passed either using `"$ref"` with an absolute or relative JSON-pointer or `{ "$func": "myfunc" }. The latter always evaluates as the reference to the existing function rather than the function that always returns string "myfunc", to define the function that always returns the same string you can use "$quote". 334 | 335 | 336 | ## Using any value without evaluation with `$quote` 337 | 338 | To insert an object that contains properties that start with `"$"` that normally should only be used in instructions you can use `$quote` instruction: For example, this script: 339 | 340 | ```json 341 | { 342 | "$quote": { 343 | "$exec": "myExec" 344 | } 345 | } 346 | ``` 347 | 348 | evaluates as: `{ "$exec": "myExec" }` and the executor is not called. 349 | 350 | `$quote` can also be used to define the function that always returns the same string: 351 | 352 | ```json 353 | { "$func": { "$quote": "foo" } } 354 | ``` 355 | 356 | The anonymous function defined above always returns the string `"foo"`. Without `$quote` it would have been the reference to the function with the name `foo`. 357 | 358 | 359 | ## Calculations 360 | 361 | Predefined executor `calc` defines methods for arythmetic, comparison and logical operations. For all operations the arguments (`$args`) should be an array and operations are applied to the list: 362 | 363 | ```json 364 | { "$+": [ 1, 2, 3 ] } 365 | ``` 366 | 367 | or using the full syntax: 368 | 369 | ```json 370 | { 371 | "$exec": "calc", 372 | "$method": "add", 373 | "$args": [ 1, 2, 3 ] 374 | } 375 | ``` 376 | 377 | Full syntax can be useful if you need to determine the required operation using some script: 378 | 379 | ```json 380 | { 381 | "$exec": "calc", 382 | "$method": { "$data": "/method" }, 383 | "$args": [ 1, 2, 3 ] 384 | } 385 | ``` 386 | 387 | For arythmetic and comparison operations arguments must be numbers, there is no type coercion. 388 | 389 | For boolean operations arguments must be boolean values. 390 | 391 | Equality operations can work with any type. 392 | 393 | 394 | Defined operations: 395 | 396 | | method|short syntax|evaluation| 397 | |--------------|:---:|---| 398 | | add |"$+" |add all arguments| 399 | | subtract |"$-" |subtract arguments from the first argument| 400 | | multiply |"$*" |multiply all arguments| 401 | | divide |"$/" |divide the first argument by the rest| 402 | | equal |"$=="|true if all arguments are equal| 403 | | notEqual |"$!="|true if at least one argument is different| 404 | | greater |"$>" |true if arguments are descending| 405 | | greaterEqual |"$>="|true if arguments are not ascending| 406 | | lesser |"$<" |true if arguments are ascending| 407 | | lesserEqual |"$<="|true if arguments are not descending| 408 | | and |"$&&"|true if all arguments are true| 409 | | or |"$\|\|"|true if one or more arguments are true and the rest are false| 410 | | xor |"$^^"|true if exactly one argument is true and others are false| 411 | | not |"$!" |negates boolean value| 412 | 413 | 414 | ## String operations 415 | 416 | Predefined executor `str` defines methods for string operations: 417 | 418 | ```json 419 | { "$$str.concat": [ "a", "b", "c" ] } 420 | ``` 421 | 422 | Defined operations: 423 | 424 | | method |arguments|evaluation| 425 | |---------|---------|----------| 426 | | concat |[s1, s2, ...] |concatenate all arguments (must be strings)| 427 | | slice |[s, begin, end]|get substring of string `s` from index `begin` until index `end` (not included), omitted end means until the end of string, negative index - count from the end| 428 | | pos |[s1, s2]|position of string `s2` in string `s1`| 429 | | lower |s|convert string to lower case| 430 | | upper |s|convert string to upper case| 431 | 432 | 433 | ## Array iteration 434 | 435 | Predefined executor `array` implements methods for array iteration: 436 | 437 | ```json 438 | { 439 | "$$array.map": { 440 | "data": [ 441 | "/resource/1", 442 | "/resource/2", 443 | "/resource/3" 444 | ], 445 | "iterator": { 446 | "$func": { 447 | "$$router.get": { "path": { "$data": "/path" } } 448 | }, 449 | "$args": ["path"] 450 | } 451 | } 452 | } 453 | ``` 454 | 455 | The example above calls the method `get` of executor `router` for all paths. The result of evaluation of this script will be the array of responses. 456 | 457 | Same script using full syntax: 458 | 459 | ```json 460 | { 461 | "$exec": "array", 462 | "$method": "map", 463 | "$args": { 464 | "data": [ 465 | "/resource/1", 466 | "/resource/2", 467 | "/resource/3" 468 | ], 469 | "iterator": { 470 | "$func": { 471 | "$exec": "router", 472 | "$method": "get", 473 | "$args": { "path": { "$data": "/path" } } 474 | }, 475 | "$args": ["path"] 476 | } 477 | } 478 | } 479 | ``` 480 | 481 | Defined array methods: 482 | 483 | | method |evaluation result| 484 | |--------|---| 485 | | map |new array with function call results for each item| 486 | | filter |new array of original items for which function calls return `true`| 487 | | every |`true` if all function calls return `true`| 488 | | some |`true` if at least one function call returns `true`| 489 | 490 | 491 | This script filters only positive numbers from array: 492 | 493 | ``` 494 | { 495 | "$$array.filter": { 496 | "data": [ -2, -1, 0, 1, 2, 3 ], 497 | "iterator": { 498 | "$func": { 499 | "$>": [ { "$data": "/num" }, 0 ] 500 | }, 501 | "$args": ["num"] 502 | } 503 | } 504 | } 505 | ``` 506 | 507 | For all methods iterator function is called with 3 parameters: array item, item index and the array itself. 508 | 509 | 510 | ## Macros 511 | 512 | JSONScript defines several core macros to support short syntax for calculations and for calling executors methods and functions. The interpreter may allow to define your own custom macros. 513 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 JSONScript 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSONScript 2 | 3 | Language for scripted server-side processing of existing endpoints and services. 4 | 5 | [![Build Status](https://travis-ci.org/JSONScript/jsonscript.svg?branch=master)](https://travis-ci.org/JSONScript/jsonscript) 6 | [![npm version](https://badge.fury.io/js/jsonscript.svg)](https://www.npmjs.com/package/jsonscript) 7 | 8 | 9 | ## Script example 10 | 11 | ```json 12 | { 13 | "$$array.map": { 14 | "data": [ 15 | { "path": "/resource/1", "body": { "test": 1 } }, 16 | { "path": "/resource/2", "body": { "test": 2 } }, 17 | { "path": "/resource/3", "body": { "test": 3 } }, 18 | ], 19 | "iterator": { 20 | "$func": [ 21 | { "$$router.get": { "path": { "$data": "/path" } } }, 22 | { "$$router.put": { "$data": "/" } }, 23 | ], 24 | "$args": ["path", "body"] 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | Using YAML for the same script makes it more readable: 31 | 32 | ```yaml 33 | $$array.map: 34 | data: 35 | - { path: /resource/1, body: { test: 1 } } 36 | - { path: /resource/2, body: { test: 2 } } 37 | - { path: /resource/3, body: { test: 3 } } 38 | iterator: 39 | $func: 40 | - $$router.get: { path: { $data: /path } } 41 | - $$router.put: { $data: / } 42 | $args: [ path, body ] 43 | ``` 44 | 45 | When executed on the server, the script above iterates array of requests, retrieves resource for each path and then updates it with a new value. 46 | 47 | See [Language](https://github.com/JSONScript/jsonscript/blob/master/LANGUAGE.md). 48 | 49 | 50 | ## Problem 51 | 52 | Management of remote systems is usually done via APIs. 53 | 54 | It is often required to make multiple sequential or parallel calls, sometimes with some conditions between calls, to get the required result. It can be achieved in two ways: 55 | 56 | 1. Sending multiple requests to the remote system and implementing all the logic in the client system. The advantage of this approach is that the remote system remains unchanged and the client can easily change the logic and flow of requests. The disadvantage is the latency and the traffic - each request should travel via the network. 57 | 58 | 2. Implementing additional methods/endpoints/parameters in the remote system. The advantage of this approach is that the client has to make only one request. The disadvantage is that it requires changing the remote system (= coding + testing + documenting + deploying + monitoring + supporting...). In some cases it is simply impossible. When it is possible, it inevitably leads to the growing complexity of the remote system as more and more specialized methods/APIs are added to it. 59 | 60 | In some cases, developers implement "batch endpoints" that allow to process multiple requests sequentially or in parallel in a single HTTP request. It covers only use cases when results are independent and there are no conditions or some other logic between requests. 61 | 62 | 63 | ## Solution 64 | 65 | JSONScript allows you to send a script to the remote system that will be interpreted by the remote system. It will execute the script and return all results to the client. All this in a single HTTP (or any other transport) request. 66 | 67 | JSONScript allows to keep the API of remote system conscise and simple, only implementing basic methods. At the same time JSONScript allows the client to implement an advanced logic with conditions and looping, sequential and concurrent execution, defining and calling functions and handling exceptions. In this way quite advanced execution can be requested from the remote system in a single transport request. 68 | 69 | At the same time JSONScript allows keeping the remote system completely secure as only the functions and objects registered with the interpreter can be used from the JSONScript script and the interpreter can limit resources (time, memory, etc.) that the script can use. 70 | 71 | 72 | ## Script execution 73 | 74 | As the script executes, each instruction returns some data. By default this data replaces the script itself and all results will be available to the interpreter to pass back to the host system that requested execution. Host system usually sends results back to the client, but can do anything else with them, e.g. logging, storing to the database, etc.). 75 | 76 | 77 | ## Schema 78 | 79 | See [Schema](https://github.com/JSONScript/jsonscript/blob/master/SCHEMA.md) for JSON-schemas for the script and for instruction definitions. 80 | 81 | 82 | ## Implementations 83 | 84 | JSONScript interpreter for node-js: [jsonscript-js](https://github.com/epoberezkin/jsonscript-js) 85 | 86 | Express 4 middleware/route-handler: [jsonscript-express](https://github.com/JSONScript/jsonscript-express) (it supports scripted processing of existing express app routes) 87 | 88 | Proxy server: [jsonscript-proxy](https://github.com/JSONScript/jsonscript-proxy) (it supports scripted processing of other existing services). 89 | 90 | 91 | ## License 92 | 93 | [MIT](https://github.com/JSONScript/jsonscript/blob/master/LICENSE) 94 | -------------------------------------------------------------------------------- /SCHEMA.md: -------------------------------------------------------------------------------- 1 | # JSONScript Schema 2 | 3 | JSONScript uses JSON-Schema standard both for the validation schemas and for the schemas that define macro expansion and evaluation process. 4 | 5 | [JSONScript schema](http://www.jsonscript.org/schema/schema.json#) - the schema for JSONScript that does not validate scalar keywords in instructions (keyword values can be scripts and have to be validated when the script is evaluated). 6 | 7 | [JSONScript strict schema](http://www.jsonscript.org/schema/schema_strict.json#) - the schema for JSONScript that validates scalar keywords in instructions. 8 | 9 | [Macro expansion schema](http://www.jsonscript.org/schema/expand_macros.json#) - this schema defines macro expansion process. It can be used by implementations to expand macros in the scripts before their evaluation. It contains non-standard keyword `expandJsMacro`. 10 | 11 | [Evaluation schema](http://www.jsonscript.org/schema/evaluate.json#) - this schema defines evalution process. It can be used by implementations to evaluate scripts. It contains non-standard keywords. 12 | 13 | [Instruction definition schema](http://www.jsonscript.org/schema/instruction.json#) - the schema for instruction defnitions. The definitions of both standard and user-defined instructions should be valid according to this schema. 14 | 15 | [Macro definition schema](http://www.jsonscript.org/schema/macro.json#) - the schema for macro definition. The definitions of both standard and user-defined macros should be valid according to this schema. 16 | -------------------------------------------------------------------------------- /SYNTAX.md: -------------------------------------------------------------------------------- 1 | # JSONScript syntax 2 | 3 | In progress - not complete, see [Language](https://github.com/JSONScript/jsonscript/blob/master/LANGUAGE.md). 4 | 5 | 6 | ## Script 7 | 8 | If the script is the object where all property names do NOT start with "$" character then sub-scripts in all properties of the object are evaluated in parallel. The meaning of "parallel" execution depends on the platform, the only important thing is that the sub-scripts begin evaluating without waiting for other scripts to complete evaluating of for asynchronous values to become synchronous. 9 | 10 | If the script is the array then sub-scripts in all items of this array are evaluated sequentially. It means that the script in any item can begin evaluating only after the script in the previous item has completed evaluating into a synchronous value. 11 | 12 | If the script is an object where all properties start with "$" it can be a valid instruction. If only some of the properties start with "$" the script is invalid. 13 | 14 | If the script is a scalar value (string, number, boolean or null) it evalutes into the same value. 15 | 16 | 17 | ## Instructions 18 | 19 | 20 | ### Instruction definitions 21 | 22 | All core JSONScript instructions are defined using [instruction definition files](https://github.com/JSONScript/jsonscript/tree/master/instructions), that should be valid according to the [instruction schema](https://github.com/JSONScript/jsonscript/blob/master/schema/instruction.json). JSONScript interpereters can allow adding custom instructions using the same format. 23 | 24 | Instruction definition file includes: 25 | 26 | - name (main keyword) 27 | - allowed keywords 28 | - required keywords 29 | - keywords that should be evaluated before the instruction itself is evaluated 30 | - schemas for evaluated keyword values 31 | 32 | See Defining instructions (TODO) for more information on the file format. 33 | 34 | 35 | ### Instruction evaluation 36 | 37 | Instruction is evaluated in the following way: 38 | 39 | 1. Keywords that should be pre-evaluated are evaluated 40 | 2. If some of them are evaluated into asynchronous values, they should resolve to synchronous value. 41 | 3. Instruction itself is evaluated, it can evaluate to: 42 | - synchronous value 43 | - asynchronous value 44 | - script 45 | - asynchronus script 46 | 47 | If the instruction is evaluated into a script, this script should be evaluated. If this script is asynchronous, it should resolve before it is evaluated. 48 | 49 | 50 | ### Core instructions 51 | 52 | #### `$exec` 53 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | schema: require('./schema/schema.json'), 3 | schema_strict: require('./schema/schema_strict.json'), 4 | evaluate: require('./schema/evaluate.json') 5 | }; 6 | -------------------------------------------------------------------------------- /instructions/$call.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "$call", 3 | "description": "function call", 4 | "keywords": ["$call", "$args"], 5 | "required": ["$call"], 6 | "evaluate": { 7 | "validatorKeyword": "eval$call", 8 | "title": "function call", 9 | "description": "calls function" 10 | }, 11 | "schema": { 12 | "$call": { 13 | "anyOf": [ 14 | { 15 | "type": "string", 16 | "anyOf": [ 17 | { "pattern": "^[A-Za-z_$][A-Za-z_$0-9]+$" }, 18 | { "format": "uuid" } 19 | ] 20 | }, 21 | { 22 | "description": "custom keyword to validate that value is a function", 23 | "function": true 24 | } 25 | ] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /instructions/$data.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "$data", 3 | "description": "data from the data instance using JSON-pointer expression, $data value should evalute to a string before the expression is evaluated", 4 | "keywords": ["$data"], 5 | "required": ["$data"], 6 | "evaluate": { 7 | "validatorKeyword": "eval$data", 8 | "title": "$data reference to the part of the data instance", 9 | "description": "Keyword should replace $data instruction with the data value at the position defined by JSON-pointer" 10 | }, 11 | "schema": { 12 | "$data": { 13 | "type": "string", 14 | "format": "json-pointer" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /instructions/$delay.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "$delay", 3 | "description": "delayed script evaluation", 4 | "keywords": ["$delay", "$wait"], 5 | "required": ["$delay"], 6 | "evaluate": { 7 | "keywords": ["$wait"], 8 | "validatorKeyword": "eval$delay", 9 | "title": "delayed script evaluation", 10 | "description": "evaluates script in $delay after the delay of $wait milliseconds (default is 0)" 11 | }, 12 | "schema": { 13 | "$wait": { 14 | "type": "integer", 15 | "default": 0 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /instructions/$exec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "$exec", 3 | "description": "call to external object, $exec and $method values should evaluate to a string", 4 | "keywords": ["$exec", "$method", "$args"], 5 | "required": ["$exec"], 6 | "evaluate": { 7 | "validatorKeyword": "eval$exec", 8 | "title": "$exec call", 9 | "description": "call function or method in $exec instruction; keyword should replace the instruction with synchronous or asynchronous value" 10 | }, 11 | "schema": { 12 | "$exec": { 13 | "type": "string", 14 | "anyOf": [ 15 | { "pattern": "^[A-Za-z_$][A-Za-z_$0-9]+$" }, 16 | { "format": "uuid" } 17 | ] 18 | }, 19 | "$method": { 20 | "type": "string", 21 | "pattern": "^[A-Za-z_$][A-Za-z_$0-9]+$" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /instructions/$func.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "$func", 3 | "description": "function definition", 4 | "keywords": ["$func", "$name", "$args"], 5 | "required": ["$func"], 6 | "evaluate": { 7 | "keywords": ["$name", "$args"], 8 | "validatorKeyword": "eval$func", 9 | "title": "function definition", 10 | "description": "defines anonymous or named function" 11 | }, 12 | "schema": { 13 | "$name": { 14 | "type": "string", 15 | "anyOf": [ 16 | { "pattern": "^[A-Za-z_$][A-Za-z_$0-9]+$" }, 17 | { "format": "uuid" } 18 | ] 19 | }, 20 | "$args": { 21 | "type": "array", 22 | "minItems": 1, 23 | "items": { 24 | "anyOf": [ 25 | { 26 | "type": "string", 27 | "pattern": "^[A-Za-z_$][A-Za-z_$0-9]+$" 28 | }, 29 | { 30 | "type": "object", 31 | "minProperties": 1, 32 | "maxProperties": 1, 33 | "patternProperties": { 34 | "^[A-Za-z_$][A-Za-z_$0-9]+$": { 35 | "$ref": "http://json-schema.org/draft-04/schema#" 36 | } 37 | } 38 | } 39 | ] 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /instructions/$if.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "$if", 3 | "description": "conditional evaluation", 4 | "keywords": ["$if", "$then", "$else"], 5 | "required": ["$if", "$then"], 6 | "evaluate": { 7 | "keywords": ["$if"], 8 | "validatorKeyword": "eval$if", 9 | "title": "$if/$then/$else", 10 | "description": "evaluates $if, if it is true - instruction evaluates to the result of $then evaluation ($else is skipped), if it is false - evaluates to $else ($then is skipped), otherwise throws an exception. If $if is false and $else is not defined it evaluates to null" 11 | }, 12 | "schema": { 13 | "$if": { 14 | "type": "boolean" 15 | }, 16 | "$else": { 17 | "default": null 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /instructions/$quote.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "$quote", 3 | "description": "use value without evaluation", 4 | "keywords": ["$quote"], 5 | "required": ["$quote"], 6 | "evaluate": { 7 | "keywords": [], 8 | "validatorKeyword": "eval$quote", 9 | "title": "use value", 10 | "description": "use value without evaluation" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /instructions/$ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "$ref", 3 | "description": "data/script from the current script using absolute/relative JSON-pointer expression", 4 | "keywords": ["$ref"], 5 | "required": ["$ref"], 6 | "evaluate": { 7 | "validatorKeyword": "eval$ref", 8 | "title": "$ref to the part of the script", 9 | "description": "Keyword should replace $ref instruction with the result of the script evaluation at this position" 10 | }, 11 | "schema": { 12 | "$ref": { 13 | "type": "string", 14 | "anyOf": [ 15 | { "format": "json-pointer" }, 16 | { "format": "relative-json-pointer" } 17 | ] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /instructions/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = [ 4 | require('./$exec.json'), 5 | require('./$ref.json'), 6 | require('./$data.json'), 7 | require('./$if.json'), 8 | require('./$delay.json'), 9 | require('./$func.json'), 10 | require('./$call.json'), 11 | require('./$quote.json') 12 | ]; 13 | -------------------------------------------------------------------------------- /macros/calc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "calc", 3 | "description": "short calculations syntax", 4 | "rules": [ 5 | { 6 | "description": "calculation", 7 | "pattern": { 8 | "\\$([+\\-*/=!<>&\\|^]{1,2})": 1 9 | }, 10 | "script": { 11 | "$exec": "calc", 12 | "$method": { "$1": { 13 | "+": "add", 14 | "-": "subtract", 15 | "*": "multiply", 16 | "/": "divide", 17 | "==": "equal", 18 | "!=": "notEqual", 19 | ">": "greater", 20 | ">=": "greaterEqual", 21 | "<": "lesser", 22 | "<=": "lesserEqual", 23 | "&&": "and", 24 | "||": "or", 25 | "^^": "xor", 26 | "!": "not" 27 | } }, 28 | "$args": 1 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /macros/call.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "call", 3 | "description": "short syntax for function call", 4 | "rules": [ 5 | { 6 | "description": "call named function with arguments", 7 | "pattern": { 8 | "^\\$\\#(.+)$": 1 9 | }, 10 | "script": { 11 | "$call": "$1", 12 | "$args": 1 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /macros/exec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exec", 3 | "description": "short syntax for executor call", 4 | "rules": [ 5 | { 6 | "description": "executor call with method", 7 | "pattern": { 8 | "^\\$\\$([^\\.]+)\\.([^\\.]+)$": 1 9 | }, 10 | "script": { 11 | "$exec": "$1", 12 | "$method": "$2", 13 | "$args": 1 14 | } 15 | }, 16 | { 17 | "description": "executor call without method", 18 | "pattern": { 19 | "^\\$\\$([^\\.]+)$": 1 20 | }, 21 | "script": { 22 | "$exec": "$1", 23 | "$args": 1 24 | } 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /macros/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = [ 4 | require('./exec.json'), 5 | require('./call.json'), 6 | require('./calc.json') 7 | ]; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsonscript", 3 | "version": "0.6.0", 4 | "description": "Platform independent asynchronous and concurrent scripting language using JSON format", 5 | "main": "index.js", 6 | "scripts": { 7 | "generate": "node scripts/generate", 8 | "test-spec": "mocha --recursive --reporter=spec spec", 9 | "test": "npm run generate && npm run test-spec" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/JSONScript/jsonscript.git" 14 | }, 15 | "keywords": [ 16 | "JSON", 17 | "JSONScript" 18 | ], 19 | "author": "Evgeny Poberezkin", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/JSONScript/jsonscript/issues" 23 | }, 24 | "homepage": "https://github.com/JSONScript/jsonscript", 25 | "devDependencies": { 26 | "ajv": "^4.7.0", 27 | "dot": "^1.0.3", 28 | "gh-pages-generator": "^0.2.2", 29 | "json-schema-test": "^1.2.1", 30 | "mocha": "^2.2.5", 31 | "pre-commit": "^1.1.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /schema/evaluate.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://www.jsonscript.org/schema/evaluate.json#", 3 | "$schema": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#", 4 | "title": "JSONScript evaluation schema", 5 | "description": "Schema with custom keywords that evaluates JSON script. It assumes that the script is valid", 6 | "allOf": [ 7 | { 8 | "switch": [ 9 | { 10 | "if": { 11 | "type": "object" 12 | }, 13 | "then": { 14 | "switch": [ 15 | { 16 | "if": { 17 | "required": [ 18 | "$exec" 19 | ] 20 | }, 21 | "then": { 22 | "title": "instruction $exec", 23 | "allOf": [ 24 | { 25 | "title": "evaluate properties", 26 | "description": "evaluates all or some keywords using this (full) schema, properties-scripts are replaced with returned synchronous or asynchronous value", 27 | "additionalProperties": { 28 | "$ref": "#" 29 | } 30 | }, 31 | { 32 | "title": "object to [async] value", 33 | "description": "Merge object properties into a single [asynchronous] object value", 34 | "objectToAsync": true 35 | }, 36 | { 37 | "title": "execute instruction", 38 | "description": "executes supported script instructions", 39 | "validateAsync": { 40 | "allOf": [ 41 | { 42 | "description": "validate evaluated instruction keywords", 43 | "properties": { 44 | "$exec": { 45 | "type": "string", 46 | "anyOf": [ 47 | { 48 | "pattern": "^[A-Za-z_$][A-Za-z_$0-9]+$" 49 | }, 50 | { 51 | "format": "uuid" 52 | } 53 | ] 54 | }, 55 | "$method": { 56 | "type": "string", 57 | "pattern": "^[A-Za-z_$][A-Za-z_$0-9]+$" 58 | } 59 | } 60 | }, 61 | { 62 | "description": "execute instruction using custom keyword", 63 | "eval$exec": true 64 | } 65 | ] 66 | } 67 | } 68 | ] 69 | } 70 | }, 71 | { 72 | "if": { 73 | "required": [ 74 | "$ref" 75 | ] 76 | }, 77 | "then": { 78 | "title": "instruction $ref", 79 | "allOf": [ 80 | { 81 | "title": "evaluate properties", 82 | "description": "evaluates all or some keywords using this (full) schema, properties-scripts are replaced with returned synchronous or asynchronous value", 83 | "additionalProperties": { 84 | "$ref": "#" 85 | } 86 | }, 87 | { 88 | "title": "object to [async] value", 89 | "description": "Merge object properties into a single [asynchronous] object value", 90 | "objectToAsync": true 91 | }, 92 | { 93 | "title": "execute instruction", 94 | "description": "executes supported script instructions", 95 | "validateAsync": { 96 | "allOf": [ 97 | { 98 | "description": "validate evaluated instruction keywords", 99 | "properties": { 100 | "$ref": { 101 | "type": "string", 102 | "anyOf": [ 103 | { 104 | "format": "json-pointer" 105 | }, 106 | { 107 | "format": "relative-json-pointer" 108 | } 109 | ] 110 | } 111 | } 112 | }, 113 | { 114 | "description": "execute instruction using custom keyword", 115 | "eval$ref": true 116 | } 117 | ] 118 | } 119 | } 120 | ] 121 | } 122 | }, 123 | { 124 | "if": { 125 | "required": [ 126 | "$data" 127 | ] 128 | }, 129 | "then": { 130 | "title": "instruction $data", 131 | "allOf": [ 132 | { 133 | "title": "evaluate properties", 134 | "description": "evaluates all or some keywords using this (full) schema, properties-scripts are replaced with returned synchronous or asynchronous value", 135 | "additionalProperties": { 136 | "$ref": "#" 137 | } 138 | }, 139 | { 140 | "title": "object to [async] value", 141 | "description": "Merge object properties into a single [asynchronous] object value", 142 | "objectToAsync": true 143 | }, 144 | { 145 | "title": "execute instruction", 146 | "description": "executes supported script instructions", 147 | "validateAsync": { 148 | "allOf": [ 149 | { 150 | "description": "validate evaluated instruction keywords", 151 | "properties": { 152 | "$data": { 153 | "type": "string", 154 | "format": "json-pointer" 155 | } 156 | } 157 | }, 158 | { 159 | "description": "execute instruction using custom keyword", 160 | "eval$data": true 161 | } 162 | ] 163 | } 164 | } 165 | ] 166 | } 167 | }, 168 | { 169 | "if": { 170 | "required": [ 171 | "$if" 172 | ] 173 | }, 174 | "then": { 175 | "title": "instruction $if", 176 | "allOf": [ 177 | { 178 | "title": "evaluate properties", 179 | "description": "evaluates all or some keywords using this (full) schema, properties-scripts are replaced with returned synchronous or asynchronous value", 180 | "properties": { 181 | "$if": { 182 | "$ref": "#" 183 | } 184 | } 185 | }, 186 | { 187 | "title": "object to [async] value", 188 | "description": "Merge object properties into a single [asynchronous] object value", 189 | "objectToAsync": true 190 | }, 191 | { 192 | "title": "execute instruction", 193 | "description": "executes supported script instructions", 194 | "validateAsync": { 195 | "allOf": [ 196 | { 197 | "description": "validate evaluated instruction keywords", 198 | "properties": { 199 | "$if": { 200 | "type": "boolean" 201 | }, 202 | "$else": { 203 | "default": null 204 | } 205 | } 206 | }, 207 | { 208 | "description": "execute instruction using custom keyword", 209 | "eval$if": true 210 | } 211 | ] 212 | } 213 | } 214 | ] 215 | } 216 | }, 217 | { 218 | "if": { 219 | "required": [ 220 | "$delay" 221 | ] 222 | }, 223 | "then": { 224 | "title": "instruction $delay", 225 | "allOf": [ 226 | { 227 | "title": "evaluate properties", 228 | "description": "evaluates all or some keywords using this (full) schema, properties-scripts are replaced with returned synchronous or asynchronous value", 229 | "properties": { 230 | "$wait": { 231 | "$ref": "#" 232 | } 233 | } 234 | }, 235 | { 236 | "title": "object to [async] value", 237 | "description": "Merge object properties into a single [asynchronous] object value", 238 | "objectToAsync": true 239 | }, 240 | { 241 | "title": "execute instruction", 242 | "description": "executes supported script instructions", 243 | "validateAsync": { 244 | "allOf": [ 245 | { 246 | "description": "validate evaluated instruction keywords", 247 | "properties": { 248 | "$wait": { 249 | "type": "integer", 250 | "default": 0 251 | } 252 | } 253 | }, 254 | { 255 | "description": "execute instruction using custom keyword", 256 | "eval$delay": true 257 | } 258 | ] 259 | } 260 | } 261 | ] 262 | } 263 | }, 264 | { 265 | "if": { 266 | "required": [ 267 | "$func" 268 | ] 269 | }, 270 | "then": { 271 | "title": "instruction $func", 272 | "allOf": [ 273 | { 274 | "title": "evaluate properties", 275 | "description": "evaluates all or some keywords using this (full) schema, properties-scripts are replaced with returned synchronous or asynchronous value", 276 | "properties": { 277 | "$name": { 278 | "$ref": "#" 279 | }, 280 | "$args": { 281 | "$ref": "#" 282 | } 283 | } 284 | }, 285 | { 286 | "title": "object to [async] value", 287 | "description": "Merge object properties into a single [asynchronous] object value", 288 | "objectToAsync": true 289 | }, 290 | { 291 | "title": "execute instruction", 292 | "description": "executes supported script instructions", 293 | "validateAsync": { 294 | "allOf": [ 295 | { 296 | "description": "validate evaluated instruction keywords", 297 | "properties": { 298 | "$name": { 299 | "type": "string", 300 | "anyOf": [ 301 | { 302 | "pattern": "^[A-Za-z_$][A-Za-z_$0-9]+$" 303 | }, 304 | { 305 | "format": "uuid" 306 | } 307 | ] 308 | }, 309 | "$args": { 310 | "type": "array", 311 | "minItems": 1, 312 | "items": { 313 | "anyOf": [ 314 | { 315 | "type": "string", 316 | "pattern": "^[A-Za-z_$][A-Za-z_$0-9]+$" 317 | }, 318 | { 319 | "type": "object", 320 | "minProperties": 1, 321 | "maxProperties": 1, 322 | "patternProperties": { 323 | "^[A-Za-z_$][A-Za-z_$0-9]+$": { 324 | "$ref": "http://json-schema.org/draft-04/schema#" 325 | } 326 | } 327 | } 328 | ] 329 | } 330 | } 331 | } 332 | }, 333 | { 334 | "description": "execute instruction using custom keyword", 335 | "eval$func": true 336 | } 337 | ] 338 | } 339 | } 340 | ] 341 | } 342 | }, 343 | { 344 | "if": { 345 | "required": [ 346 | "$call" 347 | ] 348 | }, 349 | "then": { 350 | "title": "instruction $call", 351 | "allOf": [ 352 | { 353 | "title": "evaluate properties", 354 | "description": "evaluates all or some keywords using this (full) schema, properties-scripts are replaced with returned synchronous or asynchronous value", 355 | "additionalProperties": { 356 | "$ref": "#" 357 | } 358 | }, 359 | { 360 | "title": "object to [async] value", 361 | "description": "Merge object properties into a single [asynchronous] object value", 362 | "objectToAsync": true 363 | }, 364 | { 365 | "title": "execute instruction", 366 | "description": "executes supported script instructions", 367 | "validateAsync": { 368 | "allOf": [ 369 | { 370 | "description": "validate evaluated instruction keywords", 371 | "properties": { 372 | "$call": { 373 | "anyOf": [ 374 | { 375 | "type": "string", 376 | "anyOf": [ 377 | { 378 | "pattern": "^[A-Za-z_$][A-Za-z_$0-9]+$" 379 | }, 380 | { 381 | "format": "uuid" 382 | } 383 | ] 384 | }, 385 | { 386 | "description": "custom keyword to validate that value is a function", 387 | "function": true 388 | } 389 | ] 390 | } 391 | } 392 | }, 393 | { 394 | "description": "execute instruction using custom keyword", 395 | "eval$call": true 396 | } 397 | ] 398 | } 399 | } 400 | ] 401 | } 402 | }, 403 | { 404 | "if": { 405 | "required": [ 406 | "$quote" 407 | ] 408 | }, 409 | "then": { 410 | "title": "instruction $quote", 411 | "allOf": [ 412 | { 413 | "title": "execute instruction", 414 | "description": "executes supported script instructions", 415 | "validateAsync": { 416 | "description": "execute instruction using custom keyword", 417 | "eval$quote": true 418 | } 419 | } 420 | ] 421 | } 422 | }, 423 | { 424 | "then": { 425 | "title": "parallel execution", 426 | "allOf": [ 427 | { 428 | "title": "evaluate properties", 429 | "description": "evaluates all properties using the same schema, properties-scripts are replaced with returned synchronous or asynchronous value", 430 | "additionalProperties": { 431 | "$ref": "#" 432 | } 433 | }, 434 | { 435 | "title": "object to [async] value", 436 | "description": "Merge object properties into a single [asynchronous] object value", 437 | "objectToAsync": true 438 | } 439 | ] 440 | } 441 | } 442 | ] 443 | } 444 | }, 445 | { 446 | "if": { 447 | "type": "array" 448 | }, 449 | "then": { 450 | "title": "sequential execution", 451 | "description": "queues items so that the next items is executed only after the previous asynchronous value receives data", 452 | "itemsSerial": { 453 | "$ref": "#" 454 | } 455 | } 456 | }, 457 | { 458 | "then": { 459 | "title": "scalar values", 460 | "description": "convert scalar values to asynchronouse values", 461 | "valueToAsync": true 462 | } 463 | } 464 | ] 465 | }, 466 | { 467 | "description": "store pointer to evaluted object and resolve pending references", 468 | "resolvePendingRefs": true 469 | } 470 | ] 471 | } -------------------------------------------------------------------------------- /schema/evaluate.json.dot: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://www.jsonscript.org/schema/evaluate.json#", 3 | "$schema": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#", 4 | "title": "JSONScript evaluation schema", 5 | "description": "Schema with custom keywords that evaluates JSON script. It assumes that the script is valid", 6 | "allOf": [ 7 | { 8 | "switch": [ 9 | { 10 | "if": { "type": "object" }, 11 | "then": { 12 | "switch": [ 13 | {{~ it.instructions:inst }} 14 | { 15 | "if": { "required": [ "{{=inst.name}}" ] }, 16 | "then": {{# def.instruction }} 17 | }, 18 | {{~}} 19 | { 20 | "then": { 21 | "title": "parallel execution", 22 | "allOf": [ 23 | { 24 | "title": "evaluate properties", 25 | "description": "evaluates all properties using the same schema, properties-scripts are replaced with returned synchronous or asynchronous value", 26 | "additionalProperties": { "$ref": "#" } 27 | }, 28 | { 29 | "title": "object to [async] value", 30 | "description": "Merge object properties into a single [asynchronous] object value", 31 | "objectToAsync": true 32 | } 33 | ] 34 | } 35 | } 36 | ] 37 | } 38 | }, 39 | { 40 | "if": { "type": "array" }, 41 | "then": { 42 | "title": "sequential execution", 43 | "description": "queues items so that the next items is executed only after the previous asynchronous value receives data", 44 | "itemsSerial": { "$ref": "#" } 45 | } 46 | }, 47 | { 48 | "then": { 49 | "title": "scalar values", 50 | "description": "convert scalar values to asynchronouse values", 51 | "valueToAsync": true 52 | } 53 | } 54 | ] 55 | }, 56 | { 57 | "description": "store pointer to evaluted object and resolve pending references", 58 | "resolvePendingRefs": true 59 | } 60 | ] 61 | } 62 | 63 | 64 | {{## def.instruction: 65 | { 66 | "title": "instruction {{=inst.name}}", 67 | "allOf": [ 68 | {{ var evalKeywords = inst.evaluate.keywords; }} 69 | {{? !evalKeywords || evalKeywords.length }} 70 | { 71 | "title": "evaluate properties", 72 | "description": "evaluates all or some keywords using this (full) schema, properties-scripts are replaced with returned synchronous or asynchronous value", 73 | {{? evalKeywords }} 74 | "properties": { 75 | {{~ evalKeywords:keyword:i }} 76 | {{?i}},{{?}} 77 | "{{=keyword}}": { "$ref": "#" } 78 | {{~}} 79 | } 80 | {{??}} 81 | "additionalProperties": { "$ref": "#" } 82 | {{?}} 83 | }, 84 | { 85 | "title": "object to [async] value", 86 | "description": "Merge object properties into a single [asynchronous] object value", 87 | "objectToAsync": true 88 | }, 89 | {{?}} 90 | { 91 | "title": "execute instruction", 92 | "description": "executes supported script instructions", 93 | "validateAsync": { 94 | {{? inst.schema }} 95 | "allOf": [ 96 | { 97 | "description": "validate evaluated instruction keywords", 98 | "properties": {{= JSON.stringify(inst.schema) }} 99 | }, 100 | { 101 | "description": "execute instruction using custom keyword", 102 | "{{=inst.evaluate.validatorKeyword}}": true 103 | } 104 | ] 105 | {{??}} 106 | "description": "execute instruction using custom keyword", 107 | "{{=inst.evaluate.validatorKeyword}}": true 108 | {{?}} 109 | } 110 | } 111 | ] 112 | } 113 | #}} 114 | -------------------------------------------------------------------------------- /schema/expand_macros.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://www.jsonscript.org/schema/expand_macros.json#", 3 | "$schema": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#", 4 | "title": "JSONScript macro expansion schema", 5 | "description": "Schema with custom keywords that expands macros in JSON script.", 6 | "switch": [ 7 | { 8 | "if": { 9 | "type": "object" 10 | }, 11 | "then": { 12 | "allOf": [ 13 | { 14 | "anyOf": [ 15 | { 16 | "expandJsMacro": { 17 | "description": "executor call with method", 18 | "pattern": { 19 | "^\\$\\$([^\\.]+)\\.([^\\.]+)$": 1 20 | }, 21 | "script": { 22 | "$exec": "$1", 23 | "$method": "$2", 24 | "$args": 1 25 | } 26 | } 27 | }, 28 | { 29 | "expandJsMacro": { 30 | "description": "executor call without method", 31 | "pattern": { 32 | "^\\$\\$([^\\.]+)$": 1 33 | }, 34 | "script": { 35 | "$exec": "$1", 36 | "$args": 1 37 | } 38 | } 39 | }, 40 | { 41 | "expandJsMacro": { 42 | "description": "call named function with arguments", 43 | "pattern": { 44 | "^\\$\\#(.+)$": 1 45 | }, 46 | "script": { 47 | "$call": "$1", 48 | "$args": 1 49 | } 50 | } 51 | }, 52 | { 53 | "expandJsMacro": { 54 | "description": "calculation", 55 | "pattern": { 56 | "\\$([+\\-*/=!<>&\\|^]{1,2})": 1 57 | }, 58 | "script": { 59 | "$exec": "calc", 60 | "$method": { 61 | "$1": { 62 | "+": "add", 63 | "-": "subtract", 64 | "*": "multiply", 65 | "/": "divide", 66 | "==": "equal", 67 | "!=": "notEqual", 68 | ">": "greater", 69 | ">=": "greaterEqual", 70 | "<": "lesser", 71 | "<=": "lesserEqual", 72 | "&&": "and", 73 | "||": "or", 74 | "^^": "xor", 75 | "!": "not" 76 | } 77 | }, 78 | "$args": 1 79 | } 80 | } 81 | }, 82 | { 83 | "additionalProperties": { 84 | "$ref": "#" 85 | } 86 | } 87 | ] 88 | }, 89 | { 90 | "additionalProperties": { 91 | "$ref": "#" 92 | } 93 | } 94 | ] 95 | } 96 | }, 97 | { 98 | "if": { 99 | "type": "array" 100 | }, 101 | "then": { 102 | "items": { 103 | "$ref": "#" 104 | } 105 | } 106 | } 107 | ] 108 | } -------------------------------------------------------------------------------- /schema/expand_macros.json.dot: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://www.jsonscript.org/schema/expand_macros.json#", 3 | "$schema": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#", 4 | "title": "JSONScript macro expansion schema", 5 | "description": "Schema with custom keywords that expands macros in JSON script.", 6 | "switch": [ 7 | { 8 | "if": { "type": "object" }, 9 | "then": { 10 | "allOf": [ 11 | { 12 | "anyOf": [ 13 | {{~ it.macros:macro }} 14 | {{~ macro.rules:rule }} 15 | { "expandJsMacro": {{= JSON.stringify(rule) }} }, 16 | {{~}} 17 | {{~}} 18 | { "additionalProperties": { "$ref": "#" } } 19 | ] 20 | }, 21 | { "additionalProperties": { "$ref": "#" } } 22 | ] 23 | } 24 | }, 25 | { 26 | "if": { "type": "array" }, 27 | "then": { "items": { "$ref": "#" } } 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /schema/instruction.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://www.jsonscript.org/schema/instruction.json#", 3 | "$schema": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#", 4 | "description": "schema for instruction definition", 5 | "type": "object", 6 | "required": [ "name", "description", "keywords", "required", "evaluate" ], 7 | "additionalProperties": false, 8 | "properties": { 9 | "name": { 10 | "decription": "instruction name, should be the same as the main instruction keyword", 11 | "$ref": "#scriptKeyword" 12 | }, 13 | "description": { 14 | "type": "string", 15 | "minLength": 1 16 | }, 17 | "keywords": { 18 | "desription": "all allowed instruction keywords", 19 | "allOf": [ 20 | { "$ref": "#keywordsArray" }, 21 | { "contains": { "constant": { "$data": "2/name" } } } 22 | ] 23 | }, 24 | "required": { 25 | "desription": "required instruction keywords", 26 | "allOf": [ 27 | { "$ref": "#keywordsArray" }, 28 | { "contains": { "constant": { "$data": "2/name" } } } 29 | ] 30 | }, 31 | "evaluate": { 32 | "type": "object", 33 | "required": [ "validatorKeyword" ], 34 | "properties": { 35 | "keywords": { 36 | "description": "keywords that should be evaluated before validator keyword is called. If this property is absent, all keywords will be evaluated (in parallel)", 37 | "anyOf": [ 38 | { "$ref": "#keywordsArray" }, 39 | { "$ref": "#emptyArray" } 40 | ] 41 | }, 42 | "validatorKeyword": { 43 | "description": "the custom 'validation' keyword that should be defined in the validator that evaluates the script", 44 | "type": "string", 45 | "minLength": 1, 46 | "pattern": "^[a-z_$][a-z0-9_$]*$" 47 | }, 48 | "title": { 49 | "type": "string" 50 | }, 51 | "description": { 52 | "type": "string" 53 | } 54 | } 55 | }, 56 | "schema": { 57 | "description": "the schemas for evaluated values of instruction keywords", 58 | "type": "object", 59 | "patternProperties": { 60 | "^\\$[a-z]": { "$ref": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#" } 61 | }, 62 | "additionalProperties": false 63 | } 64 | }, 65 | "definitions": { 66 | "scriptKeyword": { 67 | "id": "#scriptKeyword", 68 | "description": "$ character + lowercase identifier", 69 | "type": "string", 70 | "pattern": "^\\$[a-z]+$" 71 | }, 72 | "keywordsArray": { 73 | "id": "#keywordsArray", 74 | "type": "array", 75 | "items": { "$ref": "#scriptKeyword" }, 76 | "minItems": 1, 77 | "uniqueItems": true 78 | }, 79 | "emptyArray": { 80 | "id": "#emptyArray", 81 | "type": "array", 82 | "maxItems": 0 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /schema/macro.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://www.jsonscript.org/schema/macro.json#", 3 | "$schema": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#", 4 | "description": "schema for macro definition", 5 | "type": "object", 6 | "required": [ "name", "description", "rules" ], 7 | "additionalProperties": false, 8 | "properties": { 9 | "name": { 10 | "decription": "macro name", 11 | "$ref": "#nonEmptyString" 12 | }, 13 | "description": { 14 | "$ref": "#nonEmptyString" 15 | }, 16 | "rules": { 17 | "description": "macro rules", 18 | "type": "array", 19 | "minItems": 1, 20 | "items": { 21 | "type": "object", 22 | "required": [ "description", "pattern", "script" ], 23 | "additionalProperties": false, 24 | "properties": { 25 | "description": { "$ref": "#nonEmptyString" }, 26 | "pattern": { 27 | "type": "object", 28 | "minProperties": 1, 29 | "maxProperties": 1, 30 | "additionalProperties": { 31 | "description": "property itself should be a valid regular expression", 32 | "type": "integer" 33 | } 34 | }, 35 | "script": { 36 | "type": "object", 37 | "additionalProperties": false, 38 | "patternProperties": { 39 | "^\\$[a-z]+$": { 40 | "anyOf": [ 41 | { 42 | "description": "this string referes to the partial match in macro pattern", 43 | "type": "string", 44 | "pattern": "^\\$[1-9]$" 45 | }, 46 | { 47 | "description": "object with a single property that refers to the match; the value of the property is a substitution map", 48 | "type": "object", 49 | "minProperties": 1, 50 | "maxProperties": 1, 51 | "additionalProperties": false, 52 | "patternProperties": { 53 | "^\\$[1-9]$": { 54 | "type": "object", 55 | "additionalProperties": { 56 | "type": "string" 57 | } 58 | } 59 | } 60 | }, 61 | { 62 | "description": "this number referes to the value in the macro", 63 | "type": "integer" 64 | }, 65 | { 66 | "description": "any valid JSONScript" 67 | } 68 | ] 69 | } 70 | } 71 | } 72 | } 73 | } 74 | } 75 | }, 76 | "definitions": { 77 | "nonEmptyString": { 78 | "id": "#nonEmptyString", 79 | "type": "string", 80 | "minLength": 1 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /schema/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://www.jsonscript.org/schema/schema.json#", 3 | "$schema": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#", 4 | "title": "JSONScript schema", 5 | "description": "JSONScript script with instructions (generated from template)", 6 | "anyOf": [ 7 | { 8 | "$ref": "#$exec" 9 | }, 10 | { 11 | "$ref": "#$ref" 12 | }, 13 | { 14 | "$ref": "#$data" 15 | }, 16 | { 17 | "$ref": "#$if" 18 | }, 19 | { 20 | "$ref": "#$delay" 21 | }, 22 | { 23 | "$ref": "#$func" 24 | }, 25 | { 26 | "$ref": "#$call" 27 | }, 28 | { 29 | "$ref": "#$quote" 30 | }, 31 | { 32 | "$ref": "#macro_exec_0" 33 | }, 34 | { 35 | "$ref": "#macro_exec_1" 36 | }, 37 | { 38 | "$ref": "#macro_call_0" 39 | }, 40 | { 41 | "$ref": "#macro_calc_0" 42 | }, 43 | { 44 | "$ref": "#parallel" 45 | }, 46 | { 47 | "$ref": "#sequential" 48 | }, 49 | { 50 | "$ref": "#scalar" 51 | } 52 | ], 53 | "definitions": { 54 | "_$exec": { 55 | "id": "#$exec", 56 | "title": "$exec", 57 | "description": "call to external object, $exec and $method values should evaluate to a string", 58 | "type": "object", 59 | "properties": { 60 | "$exec": { 61 | "$ref": "#" 62 | }, 63 | "$method": { 64 | "$ref": "#" 65 | }, 66 | "$args": { 67 | "$ref": "#" 68 | } 69 | }, 70 | "additionalProperties": false, 71 | "required": [ 72 | "$exec" 73 | ] 74 | }, 75 | "_$ref": { 76 | "id": "#$ref", 77 | "title": "$ref", 78 | "description": "data/script from the current script using absolute/relative JSON-pointer expression", 79 | "type": "object", 80 | "properties": { 81 | "$ref": { 82 | "$ref": "#" 83 | } 84 | }, 85 | "additionalProperties": false, 86 | "required": [ 87 | "$ref" 88 | ] 89 | }, 90 | "_$data": { 91 | "id": "#$data", 92 | "title": "$data", 93 | "description": "data from the data instance using JSON-pointer expression, $data value should evalute to a string before the expression is evaluated", 94 | "type": "object", 95 | "properties": { 96 | "$data": { 97 | "$ref": "#" 98 | } 99 | }, 100 | "additionalProperties": false, 101 | "required": [ 102 | "$data" 103 | ] 104 | }, 105 | "_$if": { 106 | "id": "#$if", 107 | "title": "$if", 108 | "description": "conditional evaluation", 109 | "type": "object", 110 | "properties": { 111 | "$if": { 112 | "$ref": "#" 113 | }, 114 | "$then": { 115 | "$ref": "#" 116 | }, 117 | "$else": { 118 | "$ref": "#" 119 | } 120 | }, 121 | "additionalProperties": false, 122 | "required": [ 123 | "$if", 124 | "$then" 125 | ] 126 | }, 127 | "_$delay": { 128 | "id": "#$delay", 129 | "title": "$delay", 130 | "description": "delayed script evaluation", 131 | "type": "object", 132 | "properties": { 133 | "$delay": { 134 | "$ref": "#" 135 | }, 136 | "$wait": { 137 | "$ref": "#" 138 | } 139 | }, 140 | "additionalProperties": false, 141 | "required": [ 142 | "$delay" 143 | ] 144 | }, 145 | "_$func": { 146 | "id": "#$func", 147 | "title": "$func", 148 | "description": "function definition", 149 | "type": "object", 150 | "properties": { 151 | "$func": { 152 | "$ref": "#" 153 | }, 154 | "$name": { 155 | "$ref": "#" 156 | }, 157 | "$args": { 158 | "$ref": "#" 159 | } 160 | }, 161 | "additionalProperties": false, 162 | "required": [ 163 | "$func" 164 | ] 165 | }, 166 | "_$call": { 167 | "id": "#$call", 168 | "title": "$call", 169 | "description": "function call", 170 | "type": "object", 171 | "properties": { 172 | "$call": { 173 | "$ref": "#" 174 | }, 175 | "$args": { 176 | "$ref": "#" 177 | } 178 | }, 179 | "additionalProperties": false, 180 | "required": [ 181 | "$call" 182 | ] 183 | }, 184 | "_$quote": { 185 | "id": "#$quote", 186 | "title": "$quote", 187 | "description": "use value without evaluation", 188 | "type": "object", 189 | "properties": { 190 | "$quote": { 191 | "$ref": "#" 192 | } 193 | }, 194 | "additionalProperties": false, 195 | "required": [ 196 | "$quote" 197 | ] 198 | }, 199 | "_macro_exec_0": { 200 | "id": "#macro_exec_0", 201 | "description": "short syntax for executor call", 202 | "type": "object", 203 | "patternProperties": { 204 | "^\\$\\$([^\\.]+)\\.([^\\.]+)$": { 205 | "$ref": "#" 206 | } 207 | }, 208 | "maxProperties": 1, 209 | "minProperties": 1, 210 | "additionalProperties": false, 211 | "patternRequired": [ 212 | "^\\$\\$([^\\.]+)\\.([^\\.]+)$" 213 | ] 214 | }, 215 | "_macro_exec_1": { 216 | "id": "#macro_exec_1", 217 | "description": "short syntax for executor call", 218 | "type": "object", 219 | "patternProperties": { 220 | "^\\$\\$([^\\.]+)$": { 221 | "$ref": "#" 222 | } 223 | }, 224 | "maxProperties": 1, 225 | "minProperties": 1, 226 | "additionalProperties": false, 227 | "patternRequired": [ 228 | "^\\$\\$([^\\.]+)$" 229 | ] 230 | }, 231 | "_macro_call_0": { 232 | "id": "#macro_call_0", 233 | "description": "short syntax for function call", 234 | "type": "object", 235 | "patternProperties": { 236 | "^\\$\\#(.+)$": { 237 | "$ref": "#" 238 | } 239 | }, 240 | "maxProperties": 1, 241 | "minProperties": 1, 242 | "additionalProperties": false, 243 | "patternRequired": [ 244 | "^\\$\\#(.+)$" 245 | ] 246 | }, 247 | "_macro_calc_0": { 248 | "id": "#macro_calc_0", 249 | "description": "short calculations syntax", 250 | "type": "object", 251 | "patternProperties": { 252 | "\\$([+\\-*/=!<>&\\|^]{1,2})": { 253 | "$ref": "#" 254 | } 255 | }, 256 | "maxProperties": 1, 257 | "minProperties": 1, 258 | "additionalProperties": false, 259 | "patternRequired": [ 260 | "\\$([+\\-*/=!<>&\\|^]{1,2})" 261 | ] 262 | }, 263 | "parallel": { 264 | "id": "#parallel", 265 | "description": "scripts in the object are executed in parallel, property names should not start with $", 266 | "type": "object", 267 | "patternProperties": { 268 | "^[^$]": { 269 | "$ref": "#" 270 | } 271 | }, 272 | "additionalProperties": false 273 | }, 274 | "sequential": { 275 | "id": "#sequential", 276 | "description": "scripts in array are executed sequentially", 277 | "type": "array", 278 | "items": { 279 | "$ref": "#" 280 | } 281 | }, 282 | "scalar": { 283 | "id": "#scalar", 284 | "description": "scalar values are also valid JSONScript", 285 | "type": [ 286 | "string", 287 | "number", 288 | "integer", 289 | "boolean", 290 | "null" 291 | ] 292 | } 293 | } 294 | } -------------------------------------------------------------------------------- /schema/schema.json.dot: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://www.jsonscript.org/schema/schema{{?it.strictSchema}}_strict{{?}}.json#", 3 | "$schema": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#", 4 | "title": "JSONScript schema", 5 | "description": "JSONScript script with instructions (generated from template)", 6 | "anyOf": [ 7 | {{~ it.instructions:inst }} 8 | { "$ref": "#{{=inst.name}}" }, 9 | {{~}} 10 | {{~ it.macros:macro }} 11 | {{~ macro.rules:rule:i }} 12 | { "$ref": "#macro_{{=macro.name}}_{{=i}}" }, 13 | {{~}} 14 | {{~}} 15 | { "$ref": "#parallel" }, 16 | { "$ref": "#sequential" }, 17 | { "$ref": "#scalar" } 18 | ], 19 | "definitions": { 20 | {{~ it.instructions:inst }} 21 | "_{{=inst.name}}": { 22 | "id": "#{{=inst.name}}", 23 | "title": "{{=inst.name}}", 24 | "description": "{{=inst.description}}", 25 | "type": "object", 26 | "properties": { 27 | {{~ inst.keywords:keyword:i }} 28 | {{?i}},{{?}} 29 | {{ 30 | var keywordSch = inst.schema && inst.schema[keyword] 31 | , validateScalarType = it.strictSchema && keywordSch && typeof keywordSch.type == 'string' && 32 | ['string', 'number', 'integer', 'boolean', 'null'].indexOf(keywordSch.type) >= 0; 33 | }} 34 | {{? validateScalarType }} 35 | "{{=keyword}}": { 36 | "anyOf": [ 37 | {{= JSON.stringify(inst.schema[keyword]) }}, 38 | { 39 | "allOf": [ 40 | { "type": [ "object", "array" ] }, 41 | { "$ref": "#" } 42 | ] 43 | } 44 | ] 45 | } 46 | {{??}} 47 | "{{=keyword}}": { "$ref": "#" } 48 | {{?}} 49 | {{~}} 50 | }, 51 | "additionalProperties": false, 52 | "required": {{= JSON.stringify(inst.required) }} 53 | }, 54 | {{~}} 55 | {{~ it.macros:macro }} 56 | {{~ macro.rules:rule:i }} 57 | {{ var patternKey = Object.keys(rule.pattern)[0].replace(/\\/g, '\\\\'); }} 58 | "_macro_{{=macro.name}}_{{=i}}": { 59 | "id": "#macro_{{=macro.name}}_{{=i}}", 60 | "description": "{{=macro.description}}", 61 | "type": "object", 62 | "patternProperties": { 63 | "{{= patternKey }}": { "$ref": "#" } 64 | }, 65 | "maxProperties": 1, 66 | "minProperties": 1, 67 | "additionalProperties": false, 68 | "patternRequired": [ "{{= patternKey }}" ] 69 | }, 70 | {{~}} 71 | {{~}} 72 | "parallel": { 73 | "id": "#parallel", 74 | "description": "scripts in the object are executed in parallel, property names should not start with $", 75 | "type": "object", 76 | "patternProperties": { 77 | "^[^$]": { "$ref": "#" } 78 | }, 79 | "additionalProperties": false 80 | }, 81 | "sequential": { 82 | "id": "#sequential", 83 | "description": "scripts in array are executed sequentially", 84 | "type": "array", 85 | "items": { "$ref": "#" } 86 | }, 87 | "scalar": { 88 | "id": "#scalar", 89 | "description": "scalar values are also valid JSONScript", 90 | "type": [ "string", "number", "integer", "boolean", "null" ] 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /schema/schema_strict.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://www.jsonscript.org/schema/schema_strict.json#", 3 | "$schema": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#", 4 | "title": "JSONScript schema", 5 | "description": "JSONScript script with instructions (generated from template)", 6 | "anyOf": [ 7 | { 8 | "$ref": "#$exec" 9 | }, 10 | { 11 | "$ref": "#$ref" 12 | }, 13 | { 14 | "$ref": "#$data" 15 | }, 16 | { 17 | "$ref": "#$if" 18 | }, 19 | { 20 | "$ref": "#$delay" 21 | }, 22 | { 23 | "$ref": "#$func" 24 | }, 25 | { 26 | "$ref": "#$call" 27 | }, 28 | { 29 | "$ref": "#$quote" 30 | }, 31 | { 32 | "$ref": "#macro_exec_0" 33 | }, 34 | { 35 | "$ref": "#macro_exec_1" 36 | }, 37 | { 38 | "$ref": "#macro_call_0" 39 | }, 40 | { 41 | "$ref": "#macro_calc_0" 42 | }, 43 | { 44 | "$ref": "#parallel" 45 | }, 46 | { 47 | "$ref": "#sequential" 48 | }, 49 | { 50 | "$ref": "#scalar" 51 | } 52 | ], 53 | "definitions": { 54 | "_$exec": { 55 | "id": "#$exec", 56 | "title": "$exec", 57 | "description": "call to external object, $exec and $method values should evaluate to a string", 58 | "type": "object", 59 | "properties": { 60 | "$exec": { 61 | "anyOf": [ 62 | { 63 | "type": "string", 64 | "anyOf": [ 65 | { 66 | "pattern": "^[A-Za-z_$][A-Za-z_$0-9]+$" 67 | }, 68 | { 69 | "format": "uuid" 70 | } 71 | ] 72 | }, 73 | { 74 | "allOf": [ 75 | { 76 | "type": [ 77 | "object", 78 | "array" 79 | ] 80 | }, 81 | { 82 | "$ref": "#" 83 | } 84 | ] 85 | } 86 | ] 87 | }, 88 | "$method": { 89 | "anyOf": [ 90 | { 91 | "type": "string", 92 | "pattern": "^[A-Za-z_$][A-Za-z_$0-9]+$" 93 | }, 94 | { 95 | "allOf": [ 96 | { 97 | "type": [ 98 | "object", 99 | "array" 100 | ] 101 | }, 102 | { 103 | "$ref": "#" 104 | } 105 | ] 106 | } 107 | ] 108 | }, 109 | "$args": { 110 | "$ref": "#" 111 | } 112 | }, 113 | "additionalProperties": false, 114 | "required": [ 115 | "$exec" 116 | ] 117 | }, 118 | "_$ref": { 119 | "id": "#$ref", 120 | "title": "$ref", 121 | "description": "data/script from the current script using absolute/relative JSON-pointer expression", 122 | "type": "object", 123 | "properties": { 124 | "$ref": { 125 | "anyOf": [ 126 | { 127 | "type": "string", 128 | "anyOf": [ 129 | { 130 | "format": "json-pointer" 131 | }, 132 | { 133 | "format": "relative-json-pointer" 134 | } 135 | ] 136 | }, 137 | { 138 | "allOf": [ 139 | { 140 | "type": [ 141 | "object", 142 | "array" 143 | ] 144 | }, 145 | { 146 | "$ref": "#" 147 | } 148 | ] 149 | } 150 | ] 151 | } 152 | }, 153 | "additionalProperties": false, 154 | "required": [ 155 | "$ref" 156 | ] 157 | }, 158 | "_$data": { 159 | "id": "#$data", 160 | "title": "$data", 161 | "description": "data from the data instance using JSON-pointer expression, $data value should evalute to a string before the expression is evaluated", 162 | "type": "object", 163 | "properties": { 164 | "$data": { 165 | "anyOf": [ 166 | { 167 | "type": "string", 168 | "format": "json-pointer" 169 | }, 170 | { 171 | "allOf": [ 172 | { 173 | "type": [ 174 | "object", 175 | "array" 176 | ] 177 | }, 178 | { 179 | "$ref": "#" 180 | } 181 | ] 182 | } 183 | ] 184 | } 185 | }, 186 | "additionalProperties": false, 187 | "required": [ 188 | "$data" 189 | ] 190 | }, 191 | "_$if": { 192 | "id": "#$if", 193 | "title": "$if", 194 | "description": "conditional evaluation", 195 | "type": "object", 196 | "properties": { 197 | "$if": { 198 | "anyOf": [ 199 | { 200 | "type": "boolean" 201 | }, 202 | { 203 | "allOf": [ 204 | { 205 | "type": [ 206 | "object", 207 | "array" 208 | ] 209 | }, 210 | { 211 | "$ref": "#" 212 | } 213 | ] 214 | } 215 | ] 216 | }, 217 | "$then": { 218 | "$ref": "#" 219 | }, 220 | "$else": { 221 | "$ref": "#" 222 | } 223 | }, 224 | "additionalProperties": false, 225 | "required": [ 226 | "$if", 227 | "$then" 228 | ] 229 | }, 230 | "_$delay": { 231 | "id": "#$delay", 232 | "title": "$delay", 233 | "description": "delayed script evaluation", 234 | "type": "object", 235 | "properties": { 236 | "$delay": { 237 | "$ref": "#" 238 | }, 239 | "$wait": { 240 | "anyOf": [ 241 | { 242 | "type": "integer", 243 | "default": 0 244 | }, 245 | { 246 | "allOf": [ 247 | { 248 | "type": [ 249 | "object", 250 | "array" 251 | ] 252 | }, 253 | { 254 | "$ref": "#" 255 | } 256 | ] 257 | } 258 | ] 259 | } 260 | }, 261 | "additionalProperties": false, 262 | "required": [ 263 | "$delay" 264 | ] 265 | }, 266 | "_$func": { 267 | "id": "#$func", 268 | "title": "$func", 269 | "description": "function definition", 270 | "type": "object", 271 | "properties": { 272 | "$func": { 273 | "$ref": "#" 274 | }, 275 | "$name": { 276 | "anyOf": [ 277 | { 278 | "type": "string", 279 | "anyOf": [ 280 | { 281 | "pattern": "^[A-Za-z_$][A-Za-z_$0-9]+$" 282 | }, 283 | { 284 | "format": "uuid" 285 | } 286 | ] 287 | }, 288 | { 289 | "allOf": [ 290 | { 291 | "type": [ 292 | "object", 293 | "array" 294 | ] 295 | }, 296 | { 297 | "$ref": "#" 298 | } 299 | ] 300 | } 301 | ] 302 | }, 303 | "$args": { 304 | "$ref": "#" 305 | } 306 | }, 307 | "additionalProperties": false, 308 | "required": [ 309 | "$func" 310 | ] 311 | }, 312 | "_$call": { 313 | "id": "#$call", 314 | "title": "$call", 315 | "description": "function call", 316 | "type": "object", 317 | "properties": { 318 | "$call": { 319 | "$ref": "#" 320 | }, 321 | "$args": { 322 | "$ref": "#" 323 | } 324 | }, 325 | "additionalProperties": false, 326 | "required": [ 327 | "$call" 328 | ] 329 | }, 330 | "_$quote": { 331 | "id": "#$quote", 332 | "title": "$quote", 333 | "description": "use value without evaluation", 334 | "type": "object", 335 | "properties": { 336 | "$quote": { 337 | "$ref": "#" 338 | } 339 | }, 340 | "additionalProperties": false, 341 | "required": [ 342 | "$quote" 343 | ] 344 | }, 345 | "_macro_exec_0": { 346 | "id": "#macro_exec_0", 347 | "description": "short syntax for executor call", 348 | "type": "object", 349 | "patternProperties": { 350 | "^\\$\\$([^\\.]+)\\.([^\\.]+)$": { 351 | "$ref": "#" 352 | } 353 | }, 354 | "maxProperties": 1, 355 | "minProperties": 1, 356 | "additionalProperties": false, 357 | "patternRequired": [ 358 | "^\\$\\$([^\\.]+)\\.([^\\.]+)$" 359 | ] 360 | }, 361 | "_macro_exec_1": { 362 | "id": "#macro_exec_1", 363 | "description": "short syntax for executor call", 364 | "type": "object", 365 | "patternProperties": { 366 | "^\\$\\$([^\\.]+)$": { 367 | "$ref": "#" 368 | } 369 | }, 370 | "maxProperties": 1, 371 | "minProperties": 1, 372 | "additionalProperties": false, 373 | "patternRequired": [ 374 | "^\\$\\$([^\\.]+)$" 375 | ] 376 | }, 377 | "_macro_call_0": { 378 | "id": "#macro_call_0", 379 | "description": "short syntax for function call", 380 | "type": "object", 381 | "patternProperties": { 382 | "^\\$\\#(.+)$": { 383 | "$ref": "#" 384 | } 385 | }, 386 | "maxProperties": 1, 387 | "minProperties": 1, 388 | "additionalProperties": false, 389 | "patternRequired": [ 390 | "^\\$\\#(.+)$" 391 | ] 392 | }, 393 | "_macro_calc_0": { 394 | "id": "#macro_calc_0", 395 | "description": "short calculations syntax", 396 | "type": "object", 397 | "patternProperties": { 398 | "\\$([+\\-*/=!<>&\\|^]{1,2})": { 399 | "$ref": "#" 400 | } 401 | }, 402 | "maxProperties": 1, 403 | "minProperties": 1, 404 | "additionalProperties": false, 405 | "patternRequired": [ 406 | "\\$([+\\-*/=!<>&\\|^]{1,2})" 407 | ] 408 | }, 409 | "parallel": { 410 | "id": "#parallel", 411 | "description": "scripts in the object are executed in parallel, property names should not start with $", 412 | "type": "object", 413 | "patternProperties": { 414 | "^[^$]": { 415 | "$ref": "#" 416 | } 417 | }, 418 | "additionalProperties": false 419 | }, 420 | "sequential": { 421 | "id": "#sequential", 422 | "description": "scripts in array are executed sequentially", 423 | "type": "array", 424 | "items": { 425 | "$ref": "#" 426 | } 427 | }, 428 | "scalar": { 429 | "id": "#scalar", 430 | "description": "scalar values are also valid JSONScript", 431 | "type": [ 432 | "string", 433 | "number", 434 | "integer", 435 | "boolean", 436 | "null" 437 | ] 438 | } 439 | } 440 | } -------------------------------------------------------------------------------- /scripts/generate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var doT = require('dot'); 5 | var instructions = require('../instructions'); 6 | var macros = require('../macros'); 7 | 8 | generateSchema('schema'); 9 | generateSchema('schema', true); 10 | generateSchema('expand_macros'); 11 | generateSchema('evaluate'); 12 | 13 | 14 | function generateSchema(schemaName, strictSchema) { 15 | var template = getSchemaTemplate(schemaName); 16 | var schemaStr = template({ 17 | instructions: instructions, 18 | macros: macros, 19 | strictSchema: strictSchema 20 | }); 21 | schemaStr = JSON.stringify(JSON.parse(schemaStr), null, ' '); 22 | var schemaFile = getFileName(schemaName); 23 | if (strictSchema) schemaFile = schemaFile.replace('.json', '_strict.json'); 24 | fs.writeFileSync(schemaFile, schemaStr); 25 | } 26 | 27 | 28 | function getSchemaTemplate(schemaName) { 29 | var fileName = getFileName(schemaName) + '.dot'; 30 | var templateStr = fs.readFileSync(fileName, 'utf8'); 31 | return doT.compile(templateStr); 32 | } 33 | 34 | 35 | function getFileName(schemaName) { 36 | return __dirname + '/../schema/' + schemaName + '.json'; 37 | } 38 | -------------------------------------------------------------------------------- /scripts/travis-gh-pages: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" ]]; then 6 | git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qE '\.md$|\.json$|^LICENSE$' && { 7 | rm -rf ../gh-pages 8 | git clone -b gh-pages --single-branch https://${GITHUB_TOKEN}@github.com/JSONScript/jsonscript.git ../gh-pages 9 | mkdir -p ../gh-pages/_source 10 | cp *.md ../gh-pages/_source 11 | cp schema/*.json ../gh-pages/schema 12 | cp LICENSE ../gh-pages/_source 13 | currentDir=$(pwd) 14 | cd ../gh-pages 15 | $currentDir/node_modules/.bin/gh-pages-generator 16 | git config user.email "$GIT_USER_EMAIL" 17 | git config user.name "$GIT_USER_NAME" 18 | git add . 19 | git commit -am "updated by travis build #$TRAVIS_BUILD_NUMBER" 20 | git push --quiet origin gh-pages > /dev/null 2>&1 21 | } 22 | fi 23 | -------------------------------------------------------------------------------- /spec/evaluate.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Ajv = require('ajv'); 4 | 5 | var ajv = Ajv({ allErrors: true, v5: true }); 6 | 7 | 8 | describe('evaluate schema', function() { 9 | it('should be valid and should compile', function() { 10 | ajv.compile(require('../schema/evaluate')); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /spec/expand_macro.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Ajv = require('ajv'); 4 | 5 | var ajv = Ajv({ allErrors: true, v5: true }); 6 | 7 | 8 | describe('expand_macros schema', function() { 9 | it('should be valid and should compile', function() { 10 | ajv.compile(require('../schema/expand_macros')); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /spec/instruction.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var instructions = require('../instructions'); 4 | var Ajv = require('ajv'); 5 | var ajv = Ajv({ v5: true }); 6 | var validate = ajv.compile(require('../schema/instruction.json')); 7 | var assert = require('assert'); 8 | 9 | 10 | describe('instruction definition validation', function() { 11 | instructions.forEach(function (instruction) { 12 | it(instruction.name + ' should be valid according to schema', function() { 13 | validate(instruction); 14 | assert.strictEqual(validate.errors, null); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /spec/macro.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var macros = require('../macros'); 4 | var Ajv = require('ajv'); 5 | var ajv = Ajv({ v5: true }); 6 | ajv.addSchema(require('../schema/schema.json')); 7 | var validate = ajv.compile(require('../schema/macro.json')); 8 | var assert = require('assert'); 9 | 10 | 11 | describe('macro definition validation', function() { 12 | macros.forEach(function (macro) { 13 | it(macro.name + ' should be valid according to schema', function() { 14 | validate(macro); 15 | assert.strictEqual(validate.errors, null); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /spec/schema.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var jsonSchemaTest = require('json-schema-test') 4 | , Ajv = require('ajv'); 5 | 6 | var ajv = Ajv({ allErrors: true, v5: true }); 7 | ajv.addSchema(require('../schema/schema')); 8 | ajv.addSchema(require('../schema/schema_strict')); 9 | 10 | 11 | jsonSchemaTest([ ajv ], { 12 | description: 'jsonscript schema and examples tests', 13 | suites: { 'examples': './scripts/{**/,}*.json' }, 14 | cwd: __dirname, 15 | hideFolder: 'scripts/', 16 | timeout: 10000 17 | }); 18 | -------------------------------------------------------------------------------- /spec/scripts/$call.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "$call - function call", 4 | "schemas": [ 5 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 6 | { "$ref": "http://www.jsonscript.org/schema/schema.json#$call" }, 7 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 8 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#$call" } 9 | ], 10 | "tests": [ 11 | { 12 | "description": "call named function, no arguments", 13 | "data": { 14 | "$call": "myfunc" 15 | }, 16 | "valid": true 17 | }, 18 | { 19 | "description": "call named function, args is array", 20 | "data": { 21 | "$call": "myfunc", 22 | "$args": [ 1, 2 ] 23 | }, 24 | "valid": true 25 | }, 26 | { 27 | "description": "call named function, args is object", 28 | "data": { 29 | "$call": "myfunc", 30 | "$args": { "x": 1, "y": 2 } 31 | }, 32 | "valid": true 33 | }, 34 | { 35 | "description": "call named function, args is scalar", 36 | "data": { 37 | "$call": "myfunc", 38 | "$args": "foo" 39 | }, 40 | "valid": true 41 | }, 42 | { 43 | "description": "call inline anonymous function", 44 | "data": { 45 | "$call": { 46 | "$func": { 47 | "$exec": "calc", 48 | "$method": "add", 49 | "$args": [ 50 | { "$data": "/x" }, 51 | { "$data": "/y" } 52 | ] 53 | }, 54 | "$args": [ "x", "y" ] 55 | }, 56 | "$args": { "x": 1, "y": 2 } 57 | }, 58 | "valid": true 59 | }, 60 | { 61 | "description": "call function by reference", 62 | "data": { 63 | "$call": { "$ref": "/defs/myfunc" }, 64 | "$args": { "x": 1, "y": 2 } 65 | }, 66 | "valid": true 67 | } 68 | ] 69 | }, 70 | { 71 | "description": "$call syntax errors", 72 | "schemas": [ 73 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 74 | { "$ref": "http://www.jsonscript.org/schema/schema.json#$call" }, 75 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 76 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#$call" } 77 | ], 78 | "tests": [ 79 | { 80 | "description": "$call is scalar but not a string (passes validation, fails at eval time)", 81 | "data": { "$call": 1 }, 82 | "valid": true 83 | }, 84 | { 85 | "description": "$call is invalid string (passes validation, fails at eval time)", 86 | "data": { "$call": "foo%bar" }, 87 | "valid": true 88 | }, 89 | { 90 | "description": "additional properties", 91 | "data": { 92 | "$call": "myfunc", 93 | "extras": {} 94 | }, 95 | "valid": false 96 | } 97 | ] 98 | } 99 | ] 100 | -------------------------------------------------------------------------------- /spec/scripts/$data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "$data - reference to the data object", 4 | "schemas": [ 5 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 6 | { "$ref": "http://www.jsonscript.org/schema/schema.json#$data" }, 7 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 8 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#$data" } 9 | ], 10 | "tests": [ 11 | { 12 | "description": "value should be a valid JSON-pointer", 13 | "data": { "$data": "/foo/bar" }, 14 | "valid": true 15 | }, 16 | { 17 | "description": "value can be any valid script", 18 | "data": { "$data": { "$exec": "func" } }, 19 | "valid": true 20 | }, 21 | { 22 | "description": "value can be another $data instruction", 23 | "data": { "$data": { "$data": "/foo/bar" } }, 24 | "valid": true 25 | }, 26 | { 27 | "description": "if value is invalid script then invalid", 28 | "data": { "$data": { "$exec": "func", "extra": {} } }, 29 | "valid": false 30 | } 31 | ] 32 | }, 33 | { 34 | "description": "$data syntax errors", 35 | "schemas": [ 36 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 37 | { "$ref": "http://www.jsonscript.org/schema/schema.json#$data" } 38 | ], 39 | "tests": [ 40 | { 41 | "description": "relative JSON-pointer is invalid", 42 | "data": { "$data": "1/foo/bar" }, 43 | "valid": true 44 | }, 45 | { 46 | "description": "invalid JSON-pointer is invalid", 47 | "data": { "$data": "foo" }, 48 | "valid": true 49 | }, 50 | { 51 | "description": "$data is scalar but not a string is invalid", 52 | "data": { "$data": 1 }, 53 | "valid": true 54 | } 55 | ] 56 | }, 57 | { 58 | "description": "$data syntax errors with strict schema", 59 | "schemas": [ 60 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 61 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#$data" } 62 | ], 63 | "tests": [ 64 | { 65 | "description": "relative JSON-pointer is invalid", 66 | "data": { "$data": "1/foo/bar" }, 67 | "valid": false 68 | }, 69 | { 70 | "description": "invalid JSON-pointer is invalid", 71 | "data": { "$data": "foo" }, 72 | "valid": false 73 | }, 74 | { 75 | "description": "$data is scalar but not a string is invalid", 76 | "data": { "$data": 1 }, 77 | "valid": false 78 | } 79 | ] 80 | } 81 | ] 82 | -------------------------------------------------------------------------------- /spec/scripts/$delay.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "$delay - delayed script execution", 4 | "schemas": [ 5 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 6 | { "$ref": "http://www.jsonscript.org/schema/schema.json#$delay" }, 7 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 8 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#$delay" } 9 | ], 10 | "tests": [ 11 | { 12 | "description": "$delay value is any valid script", 13 | "data": { "$delay": { "$exec": "foo" } }, 14 | "valid": true 15 | }, 16 | { 17 | "description": "$delay with invalid script is invalid", 18 | "data": { "$delay": { "$exec": "foo", "extra": {} } }, 19 | "valid": false 20 | }, 21 | { 22 | "description": "optional $wait should evaluate to integer (milliseconds)", 23 | "data": { "$delay": { "$exec": "foo" }, "$wait": 20 }, 24 | "valid": true 25 | }, 26 | { 27 | "description": "$wait can be any script (object/array)", 28 | "data": { "$delay": { "$exec": "foo" }, "$wait": { "$exec": "bar" } }, 29 | "valid": true 30 | } 31 | ] 32 | }, 33 | { 34 | "description": "$delay syntax errors", 35 | "schemas": [ 36 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 37 | { "$ref": "http://www.jsonscript.org/schema/schema.json#$delay" } 38 | ], 39 | "tests": [ 40 | { 41 | "description": "$wait cannot be scalar non-integer", 42 | "data": { "$delay": { "$exec": "foo" }, "$wait": "20" }, 43 | "valid": true 44 | } 45 | ] 46 | }, 47 | { 48 | "description": "$delay syntax errors with strict schema", 49 | "schemas": [ 50 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 51 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#$delay" } 52 | ], 53 | "tests": [ 54 | { 55 | "description": "$wait cannot be scalar non-integer", 56 | "data": { "$delay": { "$exec": "foo" }, "$wait": "20" }, 57 | "valid": false 58 | } 59 | ] 60 | } 61 | ] 62 | -------------------------------------------------------------------------------- /spec/scripts/$exec.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "$exec - call to external executor", 4 | "schemas": [ 5 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 6 | { "$ref": "http://www.jsonscript.org/schema/schema.json#$exec" }, 7 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 8 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#$exec" } 9 | ], 10 | "tests": [ 11 | { 12 | "description": "with method and args", 13 | "data": { 14 | "$exec": "myobject", 15 | "$method": "doit", 16 | "$args": [1, 2, 3] 17 | }, 18 | "valid": true 19 | }, 20 | { 21 | "description": "with args", 22 | "data": { 23 | "$exec": "myfunc", 24 | "$args": [1, 2, 3] 25 | }, 26 | "valid": true 27 | }, 28 | { 29 | "description": "without args", 30 | "data": { 31 | "$exec": "myfunc" 32 | }, 33 | "valid": true 34 | }, 35 | { 36 | "description": "additional propertes are invalid", 37 | "data": { 38 | "$exec": "myfunc", 39 | "extra": {} 40 | }, 41 | "valid": false 42 | }, 43 | { 44 | "description": "valid if $args is a valid script", 45 | "data": { 46 | "$exec": "myfunc", 47 | "$args": { 48 | "$exec": "myfunc2", 49 | "$args": [1, 2, 3] 50 | } 51 | }, 52 | "valid": true 53 | }, 54 | { 55 | "description": "valid if $args contains valid scripts", 56 | "data": { 57 | "$exec": "myfunc", 58 | "$args": [ 59 | { 60 | "$exec": "myfunc2", 61 | "$args": [1, 2, 3] 62 | }, 63 | { 64 | "$exec": "myfunc2", 65 | "$args": [4, 5, 6] 66 | } 67 | ] 68 | }, 69 | "valid": true 70 | }, 71 | { 72 | "description": "invalid if $args is a invalid script", 73 | "data": { 74 | "$exec": "myfunc", 75 | "$args": { 76 | "$exec": "myfunc2", 77 | "extra": {} 78 | } 79 | }, 80 | "valid": false 81 | }, 82 | { 83 | "description": "invalid if $args contains invalid scripts", 84 | "data": { 85 | "$exec": "myfunc", 86 | "$args": [ 87 | { 88 | "$exec": "myfunc2", 89 | "$args": [1, 2, 3] 90 | }, 91 | { 92 | "$exec": "myfunc2", 93 | "extra": {} 94 | } 95 | ] 96 | }, 97 | "valid": false 98 | } 99 | ] 100 | }, 101 | { 102 | "description": "$exec syntax errors", 103 | "schemas": [ 104 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 105 | { "$ref": "http://www.jsonscript.org/schema/schema.json#$exec" } 106 | ], 107 | "tests": [ 108 | { 109 | "description": "$exec is scalar but not a string", 110 | "data": { "$exec": 1 }, 111 | "valid": true 112 | }, 113 | { 114 | "description": "$exec is invalid identifier", 115 | "data": { "$exec": "foo%bar" }, 116 | "valid": true 117 | }, 118 | { 119 | "description": "$method is scalar but not a string", 120 | "data": { "$exec": "foo", "$method": 1 }, 121 | "valid": true 122 | }, 123 | { 124 | "description": "$method is invalid identifier", 125 | "data": { "$exec": "foo", "$method": "%bar" }, 126 | "valid": true 127 | } 128 | ] 129 | }, 130 | { 131 | "description": "$exec syntax errors with strict schema", 132 | "schemas": [ 133 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 134 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#$exec" } 135 | ], 136 | "tests": [ 137 | { 138 | "description": "$exec is scalar but not a string", 139 | "data": { "$exec": 1 }, 140 | "valid": false 141 | }, 142 | { 143 | "description": "$exec is invalid identifier", 144 | "data": { "$exec": "foo%bar" }, 145 | "valid": false 146 | }, 147 | { 148 | "description": "$method is scalar but not a string", 149 | "data": { "$exec": "foo", "$method": 1 }, 150 | "valid": false 151 | }, 152 | { 153 | "description": "$method is invalid identifier", 154 | "data": { "$exec": "foo", "$method": "%bar" }, 155 | "valid": false 156 | } 157 | ] 158 | } 159 | ] 160 | -------------------------------------------------------------------------------- /spec/scripts/$func.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "$func - function definition", 4 | "schemas": [ 5 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 6 | { "$ref": "http://www.jsonscript.org/schema/schema.json#$func" }, 7 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 8 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#$func" } 9 | ], 10 | "tests": [ 11 | { 12 | "description": "anonymous function, no arguments definition", 13 | "data": { 14 | "$func": { 15 | "$exec": "myobject", 16 | "$method": "doit", 17 | "$args": [ { "$data": "/0" }, { "$data": "/1" } ] 18 | } 19 | }, 20 | "valid": true 21 | }, 22 | { 23 | "description": "anonymous function with named arguments", 24 | "data": { 25 | "$func": { 26 | "$exec": "myobject", 27 | "$method": "doit", 28 | "$args": [ { "$data": "/x" }, { "$data": "/y" } ] 29 | }, 30 | "$args": ["x", "y"] 31 | }, 32 | "valid": true 33 | }, 34 | { 35 | "description": "named function with arguments with schemas", 36 | "data": { 37 | "$func": { 38 | "$exec": "myobject", 39 | "$method": "doit", 40 | "$args": [ { "$data": "/x" }, { "$data": "/y" } ] 41 | }, 42 | "$name": "doit", 43 | "$args": [ 44 | { "x": { "type": "number" } }, 45 | { "y": { "type": "number" } } 46 | ] 47 | }, 48 | "valid": true 49 | }, 50 | { 51 | "description": "additional propertes are invalid", 52 | "data": { 53 | "$func": { 54 | "$exec": "myobject", 55 | "$method": "doit" 56 | }, 57 | "extra": true 58 | }, 59 | "valid": false 60 | }, 61 | { 62 | "description": "valid if $args is a valid script", 63 | "data": { 64 | "$func": { 65 | "$exec": "myobject", 66 | "$method": "doit" 67 | }, 68 | "$args": { 69 | "$exec": "myfunc2", 70 | "$args": [1, 2, 3] 71 | } 72 | }, 73 | "valid": true 74 | }, 75 | { 76 | "description": "invalid if $args is a invalid script", 77 | "data": { 78 | "$func": { 79 | "$exec": "myobject", 80 | "$method": "doit" 81 | }, 82 | "$args": { 83 | "$exec": "myfunc2", 84 | "extra": {} 85 | } 86 | }, 87 | "valid": false 88 | } 89 | ] 90 | }, 91 | { 92 | "description": "$func syntax errors", 93 | "schemas": [ 94 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 95 | { "$ref": "http://www.jsonscript.org/schema/schema.json#$func" } 96 | ], 97 | "tests": [ 98 | { 99 | "description": "$name is scalar but not a string", 100 | "data": { 101 | "$func": { 102 | "$exec": "myobject", 103 | "$method": "doit" 104 | }, 105 | "$name": 1 106 | }, 107 | "valid": true 108 | }, 109 | { 110 | "description": "$name is invalid identifier", 111 | "data": { 112 | "$func": { 113 | "$exec": "myobject", 114 | "$method": "doit" 115 | }, 116 | "$name": "foo%bar" 117 | }, 118 | "valid": true 119 | } 120 | ] 121 | }, 122 | { 123 | "description": "$func syntax errors with strict schema", 124 | "schemas": [ 125 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 126 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#$func" } 127 | ], 128 | "tests": [ 129 | { 130 | "description": "$name is scalar but not a string", 131 | "data": { 132 | "$func": { 133 | "$exec": "myobject", 134 | "$method": "doit" 135 | }, 136 | "$name": 1 137 | }, 138 | "valid": false 139 | }, 140 | { 141 | "description": "$name is invalid identifier", 142 | "data": { 143 | "$func": { 144 | "$exec": "myobject", 145 | "$method": "doit" 146 | }, 147 | "$name": "foo%bar" 148 | }, 149 | "valid": false 150 | } 151 | ] 152 | } 153 | ] 154 | -------------------------------------------------------------------------------- /spec/scripts/$if.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "$if - conditional evaluation", 4 | "schemas": [ 5 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 6 | { "$ref": "http://www.jsonscript.org/schema/schema.json#$if" }, 7 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 8 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#$if" } 9 | ], 10 | "tests": [ 11 | { 12 | "description": "$if/$then/$else can be any scripts", 13 | "data": { 14 | "$if": { "$data": "/cond" }, 15 | "$then": { "$exec": "foo"}, 16 | "$else": { "$exec": "bar" } 17 | }, 18 | "valid": true 19 | }, 20 | { 21 | "description": "$else is optional (it is null by default)", 22 | "data": { 23 | "$if": { "$data": "/cond" }, 24 | "$then": { "$exec": "foo"} 25 | }, 26 | "valid": true 27 | }, 28 | { 29 | "description": "additional properties are invalid", 30 | "data": { 31 | "$if": { "$data": "/cond" }, 32 | "$then": { "$exec": "foo"}, 33 | "extra": {} 34 | }, 35 | "valid": false 36 | }, 37 | { 38 | "description": "$if as boolean is valid", 39 | "data": { 40 | "$if": true, 41 | "$then": { "$exec": "foo"} 42 | }, 43 | "valid": true 44 | } 45 | ] 46 | }, 47 | { 48 | "description": "$if syntax errors", 49 | "schemas": [ 50 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 51 | { "$ref": "http://www.jsonscript.org/schema/schema.json#$if" } 52 | ], 53 | "tests": [ 54 | { 55 | "description": "$if as string is invalid", 56 | "data": { 57 | "$if": "true", 58 | "$then": { "$exec": "foo"} 59 | }, 60 | "valid": true 61 | }, 62 | { 63 | "description": "$if as scalar non-boolean is invalid", 64 | "data": { 65 | "$if": 1, 66 | "$then": { "$exec": "foo"} 67 | }, 68 | "valid": true 69 | } 70 | ] 71 | }, 72 | { 73 | "description": "$if syntax errors with strict schema", 74 | "schemas": [ 75 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 76 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#$if" } 77 | ], 78 | "tests": [ 79 | { 80 | "description": "$if as string is invalid", 81 | "data": { 82 | "$if": "true", 83 | "$then": { "$exec": "foo"} 84 | }, 85 | "valid": false 86 | }, 87 | { 88 | "description": "$if as scalar non-boolean is invalid", 89 | "data": { 90 | "$if": 1, 91 | "$then": { "$exec": "foo"} 92 | }, 93 | "valid": false 94 | } 95 | ] 96 | } 97 | ] 98 | -------------------------------------------------------------------------------- /spec/scripts/$ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "$ref - reference to the script being evaluated", 4 | "schemas": [ 5 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 6 | { "$ref": "http://www.jsonscript.org/schema/schema.json#$ref" }, 7 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 8 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#$ref" } 9 | ], 10 | "tests": [ 11 | { 12 | "description": "value should be a valid JSON-pointer", 13 | "data": { "$ref": "/foo/bar" }, 14 | "valid": true 15 | }, 16 | { 17 | "description": "relative JSON-pointer is valid", 18 | "data": { "$ref": "1/foo/bar" }, 19 | "valid": true 20 | }, 21 | { 22 | "description": "value can be any valid script", 23 | "data": { "$ref": { "$exec": "func" } }, 24 | "valid": true 25 | }, 26 | { 27 | "description": "value can be another $ref instruction", 28 | "data": { "$ref": { "$ref": "/foo/bar" } }, 29 | "valid": true 30 | }, 31 | { 32 | "description": "if value is invalid script then invalid", 33 | "data": { "$ref": { "$exec": "func", "extra": {} } }, 34 | "valid": false 35 | } 36 | ] 37 | }, 38 | { 39 | "description": "$if syntax errors", 40 | "schemas": [ 41 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 42 | { "$ref": "http://www.jsonscript.org/schema/schema.json#$ref" } 43 | ], 44 | "tests": [ 45 | { 46 | "description": "invalid JSON-pointer is invalid", 47 | "data": { "$ref": "foo" }, 48 | "valid": true 49 | }, 50 | { 51 | "description": "$ref is scalar but not a string", 52 | "data": { "$ref": 1 }, 53 | "valid": true 54 | } 55 | ] 56 | }, 57 | { 58 | "description": "$if syntax errors with strict schema", 59 | "schemas": [ 60 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 61 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#$ref" } 62 | ], 63 | "tests": [ 64 | { 65 | "description": "invalid JSON-pointer is invalid", 66 | "data": { "$ref": "foo" }, 67 | "valid": false 68 | }, 69 | { 70 | "description": "$ref is scalar but not a string", 71 | "data": { "$ref": 1 }, 72 | "valid": false 73 | } 74 | ] 75 | } 76 | ] 77 | -------------------------------------------------------------------------------- /spec/scripts/parallel.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "parallel evaluation", 4 | "schemas": [ 5 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 6 | { "$ref": "http://www.jsonscript.org/schema/schema.json#parallel" }, 7 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 8 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#parallel" } 9 | ], 10 | "tests": [ 11 | { 12 | "description": "object with scripts is valid", 13 | "data": { 14 | "foo": { "$exec": "foo" }, 15 | "bar": { "$exec": "bar" }, 16 | "baz": 3 17 | }, 18 | "valid": true 19 | }, 20 | { 21 | "description": "object with invalid scripts is invalid", 22 | "data": { 23 | "foo": { "$exec": "foo", "$extra": {} }, 24 | "bar": { "$exec": "bar" } 25 | }, 26 | "valid": false 27 | }, 28 | { 29 | "description": "properties starting with $ is invalid", 30 | "data": { 31 | "$foo": { "$exec": "foo" }, 32 | "bar": { "$exec": "bar" } 33 | }, 34 | "valid": false 35 | } 36 | ] 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /spec/scripts/sequential.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "sequential evaluation", 4 | "schemas": [ 5 | { "$ref": "http://www.jsonscript.org/schema/schema.json#" }, 6 | { "$ref": "http://www.jsonscript.org/schema/schema.json#sequential" }, 7 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#" }, 8 | { "$ref": "http://www.jsonscript.org/schema/schema_strict.json#sequential" } 9 | ], 10 | "tests": [ 11 | { 12 | "description": "array with scripts is valid", 13 | "data": [ { "$exec": "foo" }, { "$exec": "bar" }, 3 ], 14 | "valid": true 15 | }, 16 | { 17 | "description": "array with invalid scripts is invalid", 18 | "data": [ { "$exec": "foo", "$extra": {} }, { "$exec": "bar" } ], 19 | "valid": false 20 | } 21 | ] 22 | } 23 | ] 24 | --------------------------------------------------------------------------------