api-tuner 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/bin/tuner.sh +91 -0
- package/lib/curl-format.txt +4 -0
- package/lib/download-eye.sh +6 -0
- package/lib/merge-curl-output.js +60 -0
- package/lib/replace-quotes.sh +1 -0
- package/lib/shapes.ttl +14 -0
- package/lib/summarise-results.js +44 -0
- package/logging/debug.n3 +4 -0
- package/logging/info.n3 +4 -0
- package/package.json +46 -0
- package/rules/assertions.n3 +58 -0
- package/rules/curl-body.n3 +58 -0
- package/rules/files.n3 +50 -0
- package/rules/logging.n3 +24 -0
- package/rules/requests.n3 +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Zazuko GmbH
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# 🎛️ API Tun3r 🎛️
|
|
2
|
+
|
|
3
|
+
**API** **T**ests **U**sing **n3** **R**ules
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- [SWI Prolog](https://www.swi-prolog.org/Download.html)
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
`npm i api-tuner`
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
> api-tuner --help
|
|
17
|
+
Usage: api-tuner [options] <path>...
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
--silent Less output
|
|
21
|
+
--debug Enable debug output
|
|
22
|
+
--raw Output raw results from eye
|
|
23
|
+
--base-iri <iri> Specify the base IRI for parsing the test case files
|
|
24
|
+
--version Show version information
|
|
25
|
+
--help Show this help message
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Example
|
|
29
|
+
|
|
30
|
+
Create a test case file `test.n3`:
|
|
31
|
+
|
|
32
|
+
```turtle
|
|
33
|
+
# test.n3
|
|
34
|
+
PREFIX : <http://example.com/>
|
|
35
|
+
PREFIX earl: <http://www.w3.org/ns/earl#>
|
|
36
|
+
PREFIX tuner: <https://api-tuner.described.at/>
|
|
37
|
+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
38
|
+
PREFIX log: <http://www.w3.org/2000/10/swap/log#>
|
|
39
|
+
PREFIX string: <http://www.w3.org/2000/10/swap/string#>
|
|
40
|
+
|
|
41
|
+
:getExampleDotCom
|
|
42
|
+
a earl:TestCase ;
|
|
43
|
+
rdfs:label "Simple GET test" ;
|
|
44
|
+
.
|
|
45
|
+
|
|
46
|
+
# Configure a request
|
|
47
|
+
:req
|
|
48
|
+
a tuner:Request ;
|
|
49
|
+
tuner:url <http://localhost:1080/example.com> ;
|
|
50
|
+
tuner:method "GET" ;
|
|
51
|
+
.
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
# Execute the request and capture its response
|
|
55
|
+
:req tuner:response ?res .
|
|
56
|
+
|
|
57
|
+
# Check the response status code and content type
|
|
58
|
+
?res tuner:http_code 200 ;
|
|
59
|
+
tuner:header ( "content-type" "text/html" ) ;
|
|
60
|
+
.
|
|
61
|
+
|
|
62
|
+
# Check the body contains the work "Example"
|
|
63
|
+
?res!tuner:body string:contains "Example Domain" .
|
|
64
|
+
} => {
|
|
65
|
+
# Use te EARL vocabulary to assert the test passed
|
|
66
|
+
:getExampleDotCom earl:outcome earl:passed .
|
|
67
|
+
} .
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Execute the test case:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
api-tuner test.n3
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## More examples
|
|
77
|
+
|
|
78
|
+
TBD
|
package/bin/tuner.sh
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
SCRIPT_PATH=$(dirname "$(readlink -f "$0")")
|
|
3
|
+
|
|
4
|
+
eye="swipl -x ${SCRIPT_PATH}/../eye/lib/eye.pvm --"
|
|
5
|
+
|
|
6
|
+
# function prints version
|
|
7
|
+
function version() {
|
|
8
|
+
# read from ./package.json
|
|
9
|
+
API_TUNER_VERSION=$(cat "${SCRIPT_PATH}"/../package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[[:space:]]')
|
|
10
|
+
echo "API-TUNER v${API_TUNER_VERSION}"
|
|
11
|
+
$eye --version
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function usage() {
|
|
15
|
+
echo "Usage: api-tuner [options] <path>..."
|
|
16
|
+
echo ""
|
|
17
|
+
echo "Options:"
|
|
18
|
+
echo " --silent Less output"
|
|
19
|
+
echo " --debug Enable debug output"
|
|
20
|
+
echo " --raw Output raw results from eye"
|
|
21
|
+
echo " --base-iri <iri> Specify the base IRI for parsing the test case files"
|
|
22
|
+
echo " --version Show version information"
|
|
23
|
+
echo " --help Show this help message"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
SILENT=false
|
|
27
|
+
BASE_IRI=""
|
|
28
|
+
DEBUG=false
|
|
29
|
+
SUMMARY="node ${SCRIPT_PATH}/../lib/summarise-results.js --summary"
|
|
30
|
+
PATHS=()
|
|
31
|
+
# USAGE: ./tuner.sh --debug --version ...paths
|
|
32
|
+
while [ $# -gt 0 ]; do
|
|
33
|
+
case "$1" in
|
|
34
|
+
--debug)
|
|
35
|
+
DEBUG=true
|
|
36
|
+
shift
|
|
37
|
+
;;
|
|
38
|
+
--silent)
|
|
39
|
+
SILENT=true
|
|
40
|
+
shift
|
|
41
|
+
;;
|
|
42
|
+
--raw)
|
|
43
|
+
SUMMARY="node ${SCRIPT_PATH}/../lib/summarise-results.js"
|
|
44
|
+
shift
|
|
45
|
+
;;
|
|
46
|
+
--base-iri)
|
|
47
|
+
BASE_IRI="$2"
|
|
48
|
+
shift
|
|
49
|
+
shift
|
|
50
|
+
;;
|
|
51
|
+
--version)
|
|
52
|
+
version
|
|
53
|
+
exit 0
|
|
54
|
+
;;
|
|
55
|
+
--help)
|
|
56
|
+
usage
|
|
57
|
+
exit 0
|
|
58
|
+
;;
|
|
59
|
+
*)
|
|
60
|
+
PATHS+=("$1")
|
|
61
|
+
shift
|
|
62
|
+
;;
|
|
63
|
+
esac
|
|
64
|
+
done
|
|
65
|
+
|
|
66
|
+
# if no paths
|
|
67
|
+
if [ ${#PATHS[@]} -eq 0 ]; then
|
|
68
|
+
usage
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
ARGS="--quiet --nope --pass"
|
|
73
|
+
|
|
74
|
+
if [ "$DEBUG" = true ]; then
|
|
75
|
+
ARGS="$ARGS ${SCRIPT_PATH}/../logging/debug.n3"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
if [ "$SILENT" != true ]; then
|
|
79
|
+
ARGS="$ARGS ${SCRIPT_PATH}/../logging/info.n3"
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
MERGED="$(mktemp)"
|
|
83
|
+
if [ -n "$BASE_IRI" ]; then
|
|
84
|
+
echo "base <$BASE_IRI>" > "$MERGED"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
for path in "${PATHS[@]}"; do
|
|
88
|
+
cat "$path" >> "$MERGED"
|
|
89
|
+
done
|
|
90
|
+
|
|
91
|
+
$eye $ARGS "${SCRIPT_PATH}"/../rules/*.n3 "${MERGED}" | $SUMMARY
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises'
|
|
2
|
+
import { createReadStream } from 'node:fs'
|
|
3
|
+
import jsonld from 'jsonld'
|
|
4
|
+
import rdf from '@zazuko/env-node'
|
|
5
|
+
import { write } from '@jeswr/pretty-turtle'
|
|
6
|
+
|
|
7
|
+
const ns = rdf.namespace('https://api-tuner.described.at/');
|
|
8
|
+
|
|
9
|
+
(async () => {
|
|
10
|
+
const bodyPath = process.argv[2]
|
|
11
|
+
|
|
12
|
+
const curlJsonPath = `${bodyPath}.curl.json`
|
|
13
|
+
const { response: responseJson, headers: headersJson } = JSON.parse((await fs.readFile(curlJsonPath)).toString())
|
|
14
|
+
/**
|
|
15
|
+
* @type {Record<string, string | number | null>}
|
|
16
|
+
*/
|
|
17
|
+
const curlJsonLd = Object.assign({
|
|
18
|
+
'@context': {
|
|
19
|
+
'@vocab': ns().value,
|
|
20
|
+
},
|
|
21
|
+
}, responseJson)
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @type {import('@rdfjs/types').Quad[]}
|
|
25
|
+
*/
|
|
26
|
+
const responseQuads = await jsonld.toRDF(curlJsonLd)
|
|
27
|
+
const response = rdf.clownface({
|
|
28
|
+
dataset: rdf.dataset(responseQuads),
|
|
29
|
+
}).has(ns.exitcode).addOut(rdf.ns.rdf.type, ns.Response)
|
|
30
|
+
|
|
31
|
+
let contentType
|
|
32
|
+
if (typeof responseJson.content_type === 'string') {
|
|
33
|
+
contentType = responseJson.content_type.substring(0, responseJson.content_type.indexOf(';')) || responseJson.content_type
|
|
34
|
+
}
|
|
35
|
+
let parser
|
|
36
|
+
if (contentType) {
|
|
37
|
+
parser = rdf.formats.parsers.get(contentType)
|
|
38
|
+
}
|
|
39
|
+
if (parser) {
|
|
40
|
+
const bodyGraph = rdf.blankNode()
|
|
41
|
+
const bodyStream = parser.import(createReadStream(bodyPath))
|
|
42
|
+
for await (const quad of bodyStream) {
|
|
43
|
+
response.dataset.add(rdf.quad(quad.subject, quad.predicate, quad.object, bodyGraph))
|
|
44
|
+
}
|
|
45
|
+
response.addOut(ns.body, bodyGraph)
|
|
46
|
+
} else {
|
|
47
|
+
const body = await fs.readFile(bodyPath, 'utf-8')
|
|
48
|
+
if (body) {
|
|
49
|
+
response.addOut(ns.body, body)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const headers = Object.entries(headersJson).flatMap(([header, values]) =>
|
|
54
|
+
values.map(value => response.blankNode().addOut(ns.name, header).addOut(ns.value, value)))
|
|
55
|
+
response.addOut(ns.headers, headers)
|
|
56
|
+
|
|
57
|
+
process.stdout.write(await write([...response.dataset], {
|
|
58
|
+
format: 'text/n3',
|
|
59
|
+
}))
|
|
60
|
+
})()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sed -e 's/\\"/"/g'
|
package/lib/shapes.ttl
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
PREFIX earl: <http://www.w3.org/ns/earl#>
|
|
2
|
+
PREFIX sh: <http://www.w3.org/ns/shacl#>
|
|
3
|
+
|
|
4
|
+
[
|
|
5
|
+
a sh:NodeShape ;
|
|
6
|
+
sh:targetClass earl:TestCase ;
|
|
7
|
+
sh:property
|
|
8
|
+
[
|
|
9
|
+
sh:path earl:outcome ;
|
|
10
|
+
sh:hasValue earl:passed ;
|
|
11
|
+
sh:maxCount 1 ;
|
|
12
|
+
sh:message "Test failed" ;
|
|
13
|
+
] ;
|
|
14
|
+
] .
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { PassThrough } from 'node:stream'
|
|
2
|
+
import SHACLValidator from 'rdf-validate-shacl'
|
|
3
|
+
import rdf from '@zazuko/env-node'
|
|
4
|
+
import yargs from 'yargs'
|
|
5
|
+
import { hideBin } from 'yargs/helpers'
|
|
6
|
+
|
|
7
|
+
const argv = yargs(hideBin(process.argv)).argv
|
|
8
|
+
|
|
9
|
+
const shapesTtl = new URL('./shapes.ttl', import.meta.url)
|
|
10
|
+
|
|
11
|
+
;(async () => {
|
|
12
|
+
const shapes = await rdf.dataset().import(rdf.fromFile(shapesTtl))
|
|
13
|
+
const validator = new SHACLValidator(shapes, {
|
|
14
|
+
factory: rdf,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const dataPassThrough = new PassThrough()
|
|
18
|
+
process.stdin.pipe(dataPassThrough)
|
|
19
|
+
|
|
20
|
+
if (!argv.summary) {
|
|
21
|
+
process.stdin.pipe(process.stdout)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const data = await rdf.dataset().import(rdf.formats.parsers.import('text/n3', dataPassThrough, {
|
|
25
|
+
format: 'n3',
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
const validationReport = validator.validate(data)
|
|
29
|
+
|
|
30
|
+
if (argv.summary) {
|
|
31
|
+
const testCases = rdf.clownface({ dataset: data }).has(rdf.ns.rdf.type, rdf.ns.earl.TestCase)
|
|
32
|
+
|
|
33
|
+
for (const testCase of testCases.toArray()) {
|
|
34
|
+
const result = validationReport.results.find(result => result.focusNode.equals(testCase.term))
|
|
35
|
+
if (result?.severity.equals(rdf.ns.sh.Violation)) {
|
|
36
|
+
process.stdout.write(`❌ FAIL <${testCase.value}>\n`)
|
|
37
|
+
} else {
|
|
38
|
+
process.stdout.write(`✅ PASS <${testCase.value}>\n`)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
process.exit(validationReport.conforms ? 0 : 1)
|
|
44
|
+
})()
|
package/logging/debug.n3
ADDED
package/logging/info.n3
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "api-tuner",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"api-tuner": "./bin/tuner.sh"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"download-eye": "EYE_VERSION=11.5.2 ./lib/download-eye.sh",
|
|
12
|
+
"postinstall": "([ -d eye ] || npm run download-eye) && eye/install.sh --prefix=eye",
|
|
13
|
+
"prepare": "husky",
|
|
14
|
+
"lint": "eslint . --quiet --ignore-path .gitignore",
|
|
15
|
+
"test": "./bin/tuner.sh tests/*.n3 tests/**/*.n3",
|
|
16
|
+
"release": "changeset publish"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"bin/tuner.sh",
|
|
20
|
+
"logging",
|
|
21
|
+
"lib",
|
|
22
|
+
"rules"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@changesets/cli": "^2.27.12",
|
|
26
|
+
"@jeswr/pretty-turtle": "^1.5.0",
|
|
27
|
+
"@zazuko/env-node": "^2.1.4",
|
|
28
|
+
"jsonld": "^8.3.3",
|
|
29
|
+
"rdf-validate-shacl": "^0.5.6",
|
|
30
|
+
"yargs": "^17.7.2"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@rdfjs/types": "^1",
|
|
34
|
+
"@tpluscode/eslint-config": "^0.5.0",
|
|
35
|
+
"@types/jsonld": "^1.5.15",
|
|
36
|
+
"@types/rdf-validate-shacl": "^0.4.9",
|
|
37
|
+
"@types/yargs": "^17.0.33",
|
|
38
|
+
"husky": "^9.1.7",
|
|
39
|
+
"lint-staged": "^15.4.3"
|
|
40
|
+
},
|
|
41
|
+
"lint-staged": {
|
|
42
|
+
"*.{js,ts}": [
|
|
43
|
+
"eslint --fix --quiet"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
PREFIX tuner: <https://api-tuner.described.at/>
|
|
2
|
+
prefix string: <http://www.w3.org/2000/10/swap/string#>
|
|
3
|
+
prefix log: <http://www.w3.org/2000/10/swap/log#>
|
|
4
|
+
|
|
5
|
+
{
|
|
6
|
+
?res tuner:http_code ?value .
|
|
7
|
+
} <= {
|
|
8
|
+
?res log:includes {
|
|
9
|
+
[] a tuner:Response ;
|
|
10
|
+
tuner:http_code ?actualValue .
|
|
11
|
+
} .
|
|
12
|
+
|
|
13
|
+
(
|
|
14
|
+
{ ?actualValue log:equalTo ?value }
|
|
15
|
+
true
|
|
16
|
+
{
|
|
17
|
+
("Expected status code " ?value " but got " ?actualValue)!string:concatenation^tuner:info .
|
|
18
|
+
true log:equalTo false .
|
|
19
|
+
}
|
|
20
|
+
) log:ifThenElseIn ?SCOPE .
|
|
21
|
+
} .
|
|
22
|
+
|
|
23
|
+
{
|
|
24
|
+
?res tuner:body ?body .
|
|
25
|
+
} <= {
|
|
26
|
+
?res log:includes {
|
|
27
|
+
[] a tuner:Response ; tuner:body ?body .
|
|
28
|
+
} .
|
|
29
|
+
} .
|
|
30
|
+
|
|
31
|
+
{
|
|
32
|
+
?res tuner:header ( ?name ?value ) .
|
|
33
|
+
} <= {
|
|
34
|
+
?res tuner:header ( ?name ?value log:equalTo ) .
|
|
35
|
+
}.
|
|
36
|
+
|
|
37
|
+
{
|
|
38
|
+
?res tuner:header ( ?name ?value ?builtIn ) .
|
|
39
|
+
} <= {
|
|
40
|
+
?name string:lowerCase ?nameLower .
|
|
41
|
+
|
|
42
|
+
?res log:includes {
|
|
43
|
+
[] a tuner:Response ;
|
|
44
|
+
tuner:headers [
|
|
45
|
+
tuner:name ?nameLower ;
|
|
46
|
+
tuner:value ?actualValue ;
|
|
47
|
+
] .
|
|
48
|
+
} .
|
|
49
|
+
|
|
50
|
+
(
|
|
51
|
+
{ ?actualValue ?builtIn ?value }
|
|
52
|
+
true
|
|
53
|
+
{
|
|
54
|
+
("Expected header '" ?name "' '" ?value "' to satisfy '" ?builtIn "' but got '" ?actualValue "'")!string:concatenation^tuner:info .
|
|
55
|
+
true log:equalTo false .
|
|
56
|
+
}
|
|
57
|
+
) log:ifThenElseIn ?SCOPE .
|
|
58
|
+
} .
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
PREFIX list: <http://www.w3.org/2000/10/swap/list#>
|
|
2
|
+
PREFIX tuner: <https://api-tuner.described.at/>
|
|
3
|
+
PREFIX string: <http://www.w3.org/2000/10/swap/string#>
|
|
4
|
+
PREFIX log: <http://www.w3.org/2000/10/swap/log#>
|
|
5
|
+
prefix file: <http://www.w3.org/2000/10/swap/file#>
|
|
6
|
+
prefix e: <http://eulersharp.sourceforge.net/2003/03swap/log-rules#>
|
|
7
|
+
|
|
8
|
+
{
|
|
9
|
+
?body </#body> ( ?curlArgs ?requestBodyFile ) .
|
|
10
|
+
} <= {
|
|
11
|
+
?body log:rawType log:Formula .
|
|
12
|
+
?body log:n3String ?serialized .
|
|
13
|
+
() file:temp ?requestBodyFile .
|
|
14
|
+
|
|
15
|
+
# Save body to file so that it can be used in the curl command reliably
|
|
16
|
+
(
|
|
17
|
+
"echo '" ?serialized "' | "
|
|
18
|
+
# need to replace quotes around string
|
|
19
|
+
( "lib/replace-quotes.sh"!file:libPath )!string:concatenation
|
|
20
|
+
" > " ?requestBodyFile
|
|
21
|
+
)!string:concatenation!e:exec .
|
|
22
|
+
|
|
23
|
+
( " -H 'Content-Type:text/turtle' --data-binary @" ?requestBodyFile ) string:concatenation ?curlArgs .
|
|
24
|
+
} .
|
|
25
|
+
|
|
26
|
+
{
|
|
27
|
+
?fileUrl </#body> ( ?curlArgs [] ) .
|
|
28
|
+
} <= {
|
|
29
|
+
( " --data-binary " ?fileUrl!file:curlFileReference ) string:concatenation ?curlArgs .
|
|
30
|
+
} .
|
|
31
|
+
|
|
32
|
+
{
|
|
33
|
+
?multipartBody </#body> ( ?curlArgs [] ) .
|
|
34
|
+
} <= {
|
|
35
|
+
?multipartBody!log:rawType list:in ( log:LabeledBlankNode log:UnlabeledBlankNode ) .
|
|
36
|
+
|
|
37
|
+
(
|
|
38
|
+
?formField
|
|
39
|
+
{
|
|
40
|
+
?multipartBody tuner:form ( ?name ?value ) .
|
|
41
|
+
|
|
42
|
+
(
|
|
43
|
+
{ ?value log:rawType log:Literal }
|
|
44
|
+
{
|
|
45
|
+
( " -F " ?name "=" ?value ) string:concatenation ?formField .
|
|
46
|
+
}
|
|
47
|
+
{
|
|
48
|
+
( " -F " ?name "=" ?value!file:curlFileReference ) string:concatenation ?formField .
|
|
49
|
+
}
|
|
50
|
+
) log:ifThenElseIn [] .
|
|
51
|
+
}
|
|
52
|
+
?formFields
|
|
53
|
+
) log:collectAllIn [] .
|
|
54
|
+
|
|
55
|
+
(
|
|
56
|
+
?formFields!string:concatenation
|
|
57
|
+
) string:concatenation ?curlArgs .
|
|
58
|
+
} .
|
package/rules/files.n3
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
prefix e: <http://eulersharp.sourceforge.net/2003/03swap/log-rules#>
|
|
2
|
+
prefix string: <http://www.w3.org/2000/10/swap/string#>
|
|
3
|
+
prefix log: <http://www.w3.org/2000/10/swap/log#>
|
|
4
|
+
prefix file: <http://www.w3.org/2000/10/swap/file#>
|
|
5
|
+
|
|
6
|
+
{
|
|
7
|
+
() file:temp ?path .
|
|
8
|
+
} <= {
|
|
9
|
+
( "" ) file:temp ?path .
|
|
10
|
+
} .
|
|
11
|
+
|
|
12
|
+
{
|
|
13
|
+
( ?suffix ) file:temp ?path .
|
|
14
|
+
} <= {
|
|
15
|
+
?uri log:uri ( "urn:rand:" ( 1000 )!e:random )!string:concatenation .
|
|
16
|
+
|
|
17
|
+
(
|
|
18
|
+
#"/tmp/"
|
|
19
|
+
?uri!log:uuid
|
|
20
|
+
?suffix
|
|
21
|
+
) string:concatenation ?path .
|
|
22
|
+
} .
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
?path file:rm ?iDoNotCare .
|
|
26
|
+
} <= {
|
|
27
|
+
( "rm " ?path )!string:concatenation!e:exec .
|
|
28
|
+
} .
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
?relative file:libPath ?absolute .
|
|
32
|
+
} <= {
|
|
33
|
+
(
|
|
34
|
+
(<>!log:uri "/[^/]+$" "/")!string:replace 8
|
|
35
|
+
) string:substring ?basePath .
|
|
36
|
+
|
|
37
|
+
(
|
|
38
|
+
?basePath "../" ?relative
|
|
39
|
+
) string:concatenation ?absolute .
|
|
40
|
+
} .
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
?fileUrl file:curlFileReference ?ref .
|
|
44
|
+
} <= {
|
|
45
|
+
?fileUrl log:uri ?fileUri .
|
|
46
|
+
?fileUri string:startsWith "file:" .
|
|
47
|
+
( ?fileUri 6 ) string:substring ?relative .
|
|
48
|
+
|
|
49
|
+
( "@$(pwd)/" ?relative ) string:concatenation ?ref .
|
|
50
|
+
} .
|
package/rules/logging.n3
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
PREFIX tuner: <https://api-tuner.described.at/>
|
|
2
|
+
prefix earl: <http://www.w3.org/ns/earl#>
|
|
3
|
+
prefix log: <http://www.w3.org/2000/10/swap/log#>
|
|
4
|
+
prefix string: <http://www.w3.org/2000/10/swap/string#>
|
|
5
|
+
|
|
6
|
+
{
|
|
7
|
+
?left tuner:trace ?right .
|
|
8
|
+
} <= {
|
|
9
|
+
(
|
|
10
|
+
{ [] tuner:logLevel 'debug' }
|
|
11
|
+
{ 'DEBUG' log:trace ?right . }
|
|
12
|
+
true
|
|
13
|
+
) log:ifThenElseIn ?SCOPE .
|
|
14
|
+
} .
|
|
15
|
+
|
|
16
|
+
{
|
|
17
|
+
?left tuner:info ?right .
|
|
18
|
+
} <= {
|
|
19
|
+
(
|
|
20
|
+
{ [] tuner:logLevel 'info' }
|
|
21
|
+
{ 'INFO' log:trace ?right . }
|
|
22
|
+
true
|
|
23
|
+
) log:ifThenElseIn ?SCOPE .
|
|
24
|
+
} .
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
PREFIX list: <http://www.w3.org/2000/10/swap/list#>
|
|
2
|
+
prefix e: <http://eulersharp.sourceforge.net/2003/03swap/log-rules#>
|
|
3
|
+
prefix log: <http://www.w3.org/2000/10/swap/log#>
|
|
4
|
+
prefix string: <http://www.w3.org/2000/10/swap/string#>
|
|
5
|
+
PREFIX tuner: <https://api-tuner.described.at/>
|
|
6
|
+
prefix file: <http://www.w3.org/2000/10/swap/file#>
|
|
7
|
+
prefix earl: <http://www.w3.org/ns/earl#>
|
|
8
|
+
|
|
9
|
+
{
|
|
10
|
+
(?fieldName ?fieldValue) tuner:headerArg ?curlArg .
|
|
11
|
+
} <= {
|
|
12
|
+
( " -H '" ?fieldName ":" ?fieldValue "'" ) string:concatenation ?curlArg .
|
|
13
|
+
} .
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
?req tuner:response ?res .
|
|
17
|
+
} <= {
|
|
18
|
+
{
|
|
19
|
+
?req a tuner:Request .
|
|
20
|
+
?req tuner:method ?method .
|
|
21
|
+
?req tuner:url ?endpointUri .
|
|
22
|
+
} log:callWithOptional {
|
|
23
|
+
?req tuner:body ?body .
|
|
24
|
+
} .
|
|
25
|
+
({ ?req tuner:done true } false true) log:ifThenElseIn ?SCOPE .
|
|
26
|
+
true log:becomes { ?req tuner:done true } .
|
|
27
|
+
|
|
28
|
+
(
|
|
29
|
+
{ ?req tuner:header [] . }
|
|
30
|
+
{
|
|
31
|
+
( ?header { ?req tuner:header ?header } ?headers ) log:collectAllIn [] .
|
|
32
|
+
( ?headers tuner:headerArg )!list:map string:concatenation ?headersArgs .
|
|
33
|
+
}
|
|
34
|
+
{ ?headersArgs log:equalTo "" }
|
|
35
|
+
) log:ifThenElseIn [] .
|
|
36
|
+
|
|
37
|
+
?endpointUri log:uri ?endpoint .
|
|
38
|
+
|
|
39
|
+
( ?method " " ?endpoint )!string:concatenation^tuner:trace .
|
|
40
|
+
|
|
41
|
+
() file:temp ?responseBodyFile .
|
|
42
|
+
( ?responseBodyFile ".curl.json" ) string:concatenation ?responseHeadersFile .
|
|
43
|
+
( ?responseBodyFile ".n3" ) string:concatenation ?responseFile .
|
|
44
|
+
|
|
45
|
+
( "Calling " ?method " " ?endpoint )!string:concatenation^tuner:info .
|
|
46
|
+
|
|
47
|
+
(
|
|
48
|
+
{ ?req tuner:body ?body }
|
|
49
|
+
{ ?body </#body> ( ?bodyArgs ?requestBodyFile ) }
|
|
50
|
+
{ ?bodyArgs log:equalTo "" }
|
|
51
|
+
) log:ifThenElseIn [] .
|
|
52
|
+
|
|
53
|
+
(
|
|
54
|
+
"curl -s -X " ?method " '" ?endpoint "'"
|
|
55
|
+
?headersArgs
|
|
56
|
+
?bodyArgs
|
|
57
|
+
" -w @" ( "lib/curl-format.txt"!file:libPath )!string:concatenation
|
|
58
|
+
" -o " ?responseBodyFile
|
|
59
|
+
" > " ?responseHeadersFile
|
|
60
|
+
) string:concatenation ?command .
|
|
61
|
+
|
|
62
|
+
?command^tuner:trace .
|
|
63
|
+
?command!e:exec .
|
|
64
|
+
|
|
65
|
+
(
|
|
66
|
+
"node " "lib/merge-curl-output.js"!file:libPath " "
|
|
67
|
+
?responseBodyFile
|
|
68
|
+
" > " ?responseFile
|
|
69
|
+
)!string:concatenation!e:exec .
|
|
70
|
+
|
|
71
|
+
("file://" ?responseFile)!string:concatenation^log:uri log:semantics ?res .
|
|
72
|
+
?res^tuner:trace .
|
|
73
|
+
|
|
74
|
+
( { ?requestBodyFile log:rawType log:Literal } { ?requestBodyFile!file:rm } true ) log:ifThenElseIn [] .
|
|
75
|
+
?responseHeadersFile!file:rm .
|
|
76
|
+
?responseBodyFile!file:rm .
|
|
77
|
+
?responseFile!file:rm .
|
|
78
|
+
} .
|