├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitlab-ci.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── cli.js ├── example ├── config-errors.yaml ├── config-multiple-queries.yaml ├── config-referencing-paths.yaml ├── config-referencing-query-context.yaml ├── config-sorting-duplicates.yaml ├── config-sparql.yaml ├── config-validation-errors.yaml ├── config.yaml ├── paths │ ├── movies_actor.yaml │ └── music_musician.yaml ├── pipeModules │ ├── filterT.js │ ├── filterT_bad.js │ └── filterT_withParameters.js ├── public │ ├── favicon.ico │ ├── javascript │ │ └── error500.js │ └── stylesheets │ │ ├── error404.css │ │ ├── error500.css │ │ └── list.css ├── views │ ├── artist-writer.pug │ ├── directors-sparql.pug │ ├── directors.pug │ ├── error404.html │ ├── error500.html │ ├── movies.handlebars │ ├── movies.pug │ ├── movies_directors.handlebars │ ├── songs-with-artist.handlebars │ ├── songs.handlebars │ └── songs_movies.handlebars └── walderQueryInfo │ ├── movies_actor_info.yaml │ └── music_musician_info.yaml ├── lib ├── converters │ ├── converter.js │ ├── html-converter.js │ └── rdf-converter.js ├── create-logger.js ├── handlers │ ├── content-negotiation-handler.js │ ├── graphql-ld-handler.js │ ├── handler.js │ ├── postprocess-handler.js │ ├── request-handler.js │ ├── response-handler.js │ └── sparql-handler.js ├── loaders │ ├── loader.js │ ├── pipe-module-loader.js │ └── template-loader.js ├── models │ ├── html-info.js │ ├── query-info.js │ └── route-info.js ├── parsers │ ├── config-file-parser.js │ ├── data-sources-parser.js │ ├── html-parser.js │ ├── parameter-parser.js │ ├── pipe-module-parser.js │ ├── query-parser.js │ ├── resource-parser.js │ └── route-parser.js ├── server.js ├── utils.js ├── validators │ ├── graphql-ld-validator.js │ ├── html-validator.js │ ├── main-validator.js │ └── sub-validator.js └── walder.js ├── logo ├── logo.png └── logo.svg ├── package.json ├── test ├── converters │ ├── html-converter.js │ └── rdf-converter.js ├── example │ └── test.js ├── graphql-ld.js ├── handlers │ ├── graphql-ld-handler.js │ └── sparql-handler.js ├── loaders │ ├── pipe-module-loader.js │ └── template-loader.js ├── parsers │ ├── config-file-parser.js │ ├── data-sources-parser.js │ ├── graphql-ld-parser.js │ ├── html-parser.js │ ├── parameter-parser.js │ ├── pipe-module-parser.js │ ├── resource-parser.js │ └── route-parser.js ├── resources │ ├── book.pug │ ├── conf-x-walder-errors-handlebars.yaml │ ├── conf-x-walder-errors-md.yaml │ ├── conf-x-walder-errors-pug.yaml │ ├── config-errors.yaml │ ├── config-frontmatter.yaml │ ├── config-htmlvalidator.yaml │ ├── config-image.yaml │ ├── config-lenient.yaml │ ├── config-missing-default-error-pages.yaml │ ├── config-njk.yaml │ ├── config-no-query.yaml │ ├── config-no-resources.yaml │ ├── config-partial-resources.yaml │ ├── config-resources-path.yaml │ ├── config-two-path-parameters.yaml │ ├── config.yaml │ ├── example-data.js │ ├── filter-t.js │ ├── layout-in-layout-test │ │ ├── layout-bottom.liquid │ │ ├── layout-top.liquid │ │ └── page.md │ ├── layout-query-results-test │ │ ├── layouts │ │ │ └── my-layout.pug │ │ └── views │ │ │ └── text.md │ ├── layouts-test │ │ ├── config.yaml │ │ ├── expected-output.html │ │ ├── layouts │ │ │ └── my-layout.pug │ │ └── views │ │ │ └── text.md │ ├── layouts │ │ ├── error-layout.pug │ │ ├── layout-fm.pug │ │ └── simple-layout.pug │ ├── movies.pug │ ├── multiple-config-files │ │ ├── config-with-invalid-refs.yaml │ │ ├── config-with-refs.yaml │ │ ├── config-without-refs.yaml │ │ └── paths │ │ │ ├── artist-artist.yaml │ │ │ ├── more-movies-actor.yaml │ │ │ ├── movies-actor.yaml │ │ │ ├── music-musician-no-duplicates.yaml │ │ │ └── music-musician-sorted.yaml │ ├── my-layout.pug │ ├── pipe-modules │ │ ├── combine.js │ │ ├── filter-t-async.js │ │ ├── filter-t-bad.js │ │ ├── filter-t-query-key.js │ │ ├── filter-t-with-parameters.js │ │ ├── filter-t.js │ │ ├── get-ids.js │ │ └── sparql │ │ │ ├── combine.js │ │ │ ├── filter-t-async.js │ │ │ ├── filter-t-bad.js │ │ │ ├── filter-t-query-key.js │ │ │ └── filter-t.js │ ├── public │ │ └── device.jpg │ ├── pug-include-test │ │ ├── config.yaml │ │ ├── expected-output.html │ │ └── views │ │ │ ├── header.pug │ │ │ └── text.pug │ ├── query-datasources │ │ ├── additional-datasources │ │ │ ├── graphql-ld │ │ │ │ ├── config.yaml │ │ │ │ ├── expected-output.json │ │ │ │ └── getIds.js │ │ │ └── sparql │ │ │ │ ├── config.yaml │ │ │ │ ├── expected-output.json │ │ │ │ └── getIds.js │ │ └── single-query │ │ │ ├── graphql-ld │ │ │ ├── config.yaml │ │ │ ├── expected-output.json │ │ │ └── getIds.js │ │ │ └── sparql │ │ │ ├── config.yaml │ │ │ ├── expected-output.json │ │ │ └── getIds.js │ ├── sparql │ │ ├── config-errors.yaml │ │ ├── config-frame.yaml │ │ ├── config-lenient.yaml │ │ └── config.yaml │ ├── test-layout.md │ ├── test.md │ ├── view-with-jsonld.pug │ └── views │ │ ├── artist-writer.pug │ │ ├── directors.pug │ │ ├── error404.html │ │ ├── error404alt.handlebars │ │ ├── error404alt.md │ │ ├── error404alt.pug │ │ ├── error500.html │ │ ├── error500alt.handlebars │ │ ├── error500alt.md │ │ ├── error500alt.pug │ │ ├── invalid-frontmatter.pug │ │ ├── layout_test.pug │ │ ├── lenient.pug │ │ ├── missing-layout.pug │ │ ├── movies-combine.pug │ │ ├── movies-directors.handlebars │ │ ├── movies-query-key.pug │ │ ├── movies.handlebars │ │ ├── movies.pug │ │ ├── songs-movies.handlebars │ │ ├── songs.handlebars │ │ ├── sparql │ │ ├── artist-writer.pug │ │ ├── error404.html │ │ ├── error500.html │ │ ├── lenient.pug │ │ ├── movies-combine.pug │ │ ├── movies-frame.pug │ │ ├── movies-query-key.pug │ │ ├── movies.handlebars │ │ ├── movies.pug │ │ ├── songs-movies.handlebars │ │ └── songs.handlebars │ │ ├── text-fm-with-layout.pug │ │ ├── text-fm.pug │ │ └── text.pug ├── sparql.js ├── test.js └── validators │ ├── graphQLLDValidator.js │ ├── htmlValidator.js │ └── mainValidator.js └── yarn.lock /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | runs-on: ${{ matrix.os }} 6 | strategy: 7 | matrix: 8 | os: [ubuntu-latest] 9 | node-version: 10 | - 14.x 11 | - 16.x 12 | steps: 13 | - name: Use Node.js ${{ matrix.node-version }} 14 | uses: actions/setup-node@v2 15 | with: 16 | node-version: ${{ matrix.node-version }} 17 | - name: Check out repository 18 | uses: actions/checkout@v2 19 | - uses: actions/cache@v2 20 | with: 21 | path: '**/node_modules' 22 | key: ${{ runner.os }}-test-modules-${{ hashFiles('**/yarn.lock') }} 23 | - name: Install dependencies 24 | run: yarn install 25 | - name: Run tests 26 | run: yarn run test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | node_modules 4 | notes.txt 5 | *.log -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | default: 2 | image: node:10.16.3 3 | 4 | stages: 5 | - build 6 | - test 7 | 8 | cache: 9 | paths: 10 | - node_modules/ 11 | 12 | install_dependencies: 13 | stage: build 14 | script: 15 | - yarn install 16 | artifacts: 17 | paths: 18 | - node_modules/ 19 | 20 | testing:lts: 21 | stage: test 22 | script: yarn test 23 | 24 | testing:latest: 25 | image: node:latest 26 | stage: test 27 | script: yarn test 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2020 Ghent University - imec 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 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const Walder = require('../lib/walder'); 4 | const program = require('commander'); 5 | const utils = require('../lib/utils'); 6 | const pjson = require('../package'); 7 | 8 | // CLI 9 | program 10 | .version('v' + pjson.version, '-v, --version') 11 | .option('-c, --config ', 'YAML configuration file input') 12 | .option('-p, --port ', 'server port number', 3000) 13 | .option('-l, --log ', 'enable logging and set logging level (one of [error, warn, info, verbose, debug])', 'info') 14 | .option('--no-cache', 'disable Comunica default caching') 15 | .option('--lenient', 'turn Comunica errors on invalid data into warnings') 16 | .parse(process.argv); 17 | 18 | if (!program.config) { 19 | utils.printError('Error:\n\t-c --config required. Use -h for more info.'); 20 | } 21 | 22 | main(); 23 | 24 | async function main() { 25 | try { 26 | const walder = new Walder(program.config, {port: program.port, logging: program.log, cache: program.cache, lenient: program.lenient}); 27 | await walder.activate(); 28 | } catch (err) { 29 | // errors with some given types are handled already and shouldn't be thrown with a stacktrace etc. 30 | if (['IO_INVALID_REF', 'VALIDATION_ERROR'].includes(err.type)) { 31 | process.exit(1); 32 | } else { 33 | throw err; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/config-errors.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: ./ 7 | views: views 8 | pipe-modules: pipeModules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /bad_query: 14 | get: 15 | summary: Returns a status 500 error page - bad query. 16 | x-walder-query: 17 | graphql-query: > 18 | { 19 | id @single 20 | ... { # This will cause an error 21 | starring(label: "Brad Pitt") @single 22 | } 23 | } 24 | json-ld-context: > 25 | { 26 | "@context": { 27 | "Film": "http://dbpedia.org/ontology/Film", 28 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 29 | "starring": "http://dbpedia.org/ontology/starring" 30 | } 31 | } 32 | responses: 33 | 200: 34 | description: list of movies 35 | x-walder-input-text/html: movies.handlebars 36 | /bad_pipeModule: 37 | get: 38 | summary: Returns a status 500 error page - bad pipe module. 39 | x-walder-query: 40 | graphql-query: > 41 | { 42 | id @single 43 | ... on Film{ 44 | starring(label: "Brad Pitt") @single 45 | } 46 | } 47 | json-ld-context: > 48 | { 49 | "@context": { 50 | "Film": "http://dbpedia.org/ontology/Film", 51 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 52 | "starring": "http://dbpedia.org/ontology/starring" 53 | } 54 | } 55 | x-walder-postprocessing: 56 | filterT: 57 | source: filterT_bad.js 58 | responses: 59 | 200: 60 | description: list of movies 61 | x-walder-input-text/html: movies.handlebars 62 | x-walder-errors: 63 | 404: 64 | description: page not found error 65 | x-walder-input-text/html: error404.html 66 | 500: 67 | description: internal server error 68 | x-walder-input-text/html: error500.html 69 | -------------------------------------------------------------------------------- /example/config-multiple-queries.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: ./ 7 | views: views 8 | pipe-modules: pipeModules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /artist/{artist}: 14 | get: 15 | # Working example: David Bowie 16 | summary: Returns a list of songs and movies for a given artist. 17 | parameters: 18 | - in: path 19 | name: artist 20 | required: true 21 | schema: 22 | type: string 23 | description: The target artist 24 | x-walder-query: 25 | graphql-query: 26 | songs: > 27 | { 28 | label @single 29 | writer(label_en: $artist) @single 30 | artist @single(scope: all) { 31 | label 32 | } 33 | } 34 | films: > 35 | { 36 | id @single 37 | ... on Film { 38 | starring(label_en: $artist) @single 39 | } 40 | } 41 | json-ld-context: > 42 | { 43 | "@context": { 44 | "label": "http://www.w3.org/2000/01/rdf-schema#label", 45 | "label_en": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 46 | "writer": "http://dbpedia.org/ontology/writer", 47 | "artist": "http://dbpedia.org/ontology/musicalArtist", 48 | "Film": "http://dbpedia.org/ontology/Film", 49 | "starring": "http://dbpedia.org/ontology/starring" 50 | } 51 | } 52 | responses: 53 | 200: 54 | description: list of songs and movies 55 | x-walder-input-text/html: songs_movies.handlebars 56 | x-walder-errors: 57 | 404: 58 | description: page not found error 59 | x-walder-input-text/html: error404.html 60 | 500: 61 | description: internal server error 62 | x-walder-input-text/html: error500.html 63 | -------------------------------------------------------------------------------- /example/config-referencing-paths.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: ./ 7 | views: views 8 | pipe-modules: pipeModules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /music/{musician}: 14 | $ref: './paths/music_musician.yaml' 15 | /movies/{actor}: 16 | $ref: './paths/movies_actor.yaml' 17 | x-walder-errors: 18 | 404: 19 | description: page not found error 20 | x-walder-input-text/html: error404.html 21 | 500: 22 | description: internal server error 23 | x-walder-input-text/html: error500.html 24 | -------------------------------------------------------------------------------- /example/config-referencing-query-context.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: ./ 7 | views: views 8 | pipe-modules: pipeModules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /music/{musician}: 14 | get: 15 | summary: Returns a list of songs of the given musician. 16 | parameters: 17 | - in: path 18 | name: musician 19 | required: true 20 | schema: 21 | type: string 22 | description: The target musician 23 | x-walder-query: 24 | $ref: './walderQueryInfo/music_musician_info.yaml' 25 | responses: 26 | 200: 27 | description: list of songs 28 | x-walder-input-text/html: songs.handlebars 29 | /movies/{actor}: 30 | get: 31 | summary: Returns a list of the all movies the given actor stars in 32 | parameters: 33 | - in: path 34 | name: actor 35 | required: true 36 | schema: 37 | type: string 38 | description: The target actor 39 | x-walder-query: 40 | $ref: './walderQueryInfo/movies_actor_info.yaml' 41 | responses: 42 | 200: 43 | description: list of movies 44 | x-walder-input-text/html: movies.pug 45 | x-walder-errors: 46 | 404: 47 | description: page not found error 48 | x-walder-input-text/html: error404.html 49 | 500: 50 | description: internal server error 51 | x-walder-input-text/html: error500.html 52 | -------------------------------------------------------------------------------- /example/config-sparql.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: ./ 7 | views: views 8 | pipe-modules: pipeModules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /: 14 | get: 15 | summary: Returns a list of directors of movies starring Brad Pitt 16 | x-walder-query: 17 | sparql-query: > 18 | CONSTRUCT { 19 | ?movie dbpedia-owl:starring [ rdfs:label "Brad Pitt"@en ]; 20 | rdfs:label ?title; 21 | dbpedia-owl:director [ 22 | a dbpedia-owl:Director; 23 | rdfs:label ?name ]. 24 | } 25 | WHERE { 26 | ?movie dbpedia-owl:starring [ rdfs:label "Brad Pitt"@en ]; 27 | rdfs:label ?title; 28 | dbpedia-owl:director [ rdfs:label ?name ]. 29 | FILTER LANGMATCHES(LANG(?title), "EN") 30 | FILTER LANGMATCHES(LANG(?name), "EN") 31 | } 32 | json-ld-frame: > 33 | { 34 | "@context": {"@vocab": "http://www.w3.org/2000/01/rdf-schema#"}, 35 | "@type": "http://dbpedia.org/ontology/Director" 36 | } 37 | responses: 38 | 200: 39 | description: list of directors of movies starring Brad Pitt 40 | x-walder-input-text/html: directors-sparql.pug 41 | x-walder-errors: 42 | 404: 43 | description: page not found error 44 | x-walder-input-text/html: error404.html 45 | 500: 46 | description: internal server error 47 | x-walder-input-text/html: error500.html 48 | -------------------------------------------------------------------------------- /example/config-validation-errors.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: ./ 7 | views: views 8 | pipe-modules: pipeModules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /movies/{actor}: 14 | get: 15 | summary: Returns a list of the all movies the given actor stars in - will return a config file validation error 16 | x-walder-query: 17 | graphql-query: > 18 | { 19 | id @single 20 | ... on Film { 21 | starring(label: $actor) @single 22 | } 23 | } 24 | json-ld-context: > 25 | { 26 | "@context": { 27 | "Film": "http://dbpedia.org/ontology/Film", 28 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 29 | "starring": "http://dbpedia.org/ontology/starring" 30 | } 31 | } 32 | responses: 33 | 200: 34 | description: list of movies 35 | x-walder-input-text/html: movies.pug 36 | -------------------------------------------------------------------------------- /example/config.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: ./ 7 | views: views 8 | pipe-modules: pipeModules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /music/{musician}: 14 | get: 15 | summary: Returns a list of songs of the given musician. 16 | parameters: 17 | - in: path 18 | name: musician 19 | required: true 20 | schema: 21 | type: string 22 | description: The target musician 23 | x-walder-query: 24 | graphql-query: > 25 | { 26 | label @single 27 | artist(label_en: $musician) 28 | } 29 | json-ld-context: > 30 | { 31 | "@context": { 32 | "label": "http://www.w3.org/2000/01/rdf-schema#label", 33 | "label_en": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 34 | "writer": "http://dbpedia.org/ontology/writer", 35 | "artist": "http://dbpedia.org/ontology/musicalArtist" 36 | } 37 | } 38 | responses: 39 | 200: 40 | description: list of songs 41 | x-walder-input-text/html: songs.handlebars 42 | /artist/{artist}: 43 | get: 44 | summary: Returns a list of a given artist's songs written by a specific person. 45 | parameters: 46 | - in: path 47 | name: artist 48 | required: true 49 | schema: 50 | type: string 51 | description: The target artist 52 | - in: query 53 | name: writer 54 | required: true 55 | schema: 56 | type: string 57 | x-walder-query: 58 | graphql-query: > 59 | { 60 | label @single 61 | writer(label: $writer) @single 62 | artist @single(scope: all) { 63 | label(_:$artist) 64 | } 65 | } 66 | json-ld-context: > 67 | { 68 | "@context": { 69 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 70 | "writer": "http://dbpedia.org/ontology/writer", 71 | "artist": "http://dbpedia.org/ontology/musicalArtist" 72 | } 73 | } 74 | responses: 75 | 200: 76 | description: list of songs 77 | x-walder-input-text/html: artist-writer.pug 78 | /bradpitt-directors: 79 | get: 80 | summary: Returns a list of directors of movies starring Brad Pitt 81 | x-walder-query: 82 | graphql-query: > 83 | { 84 | director @single(scope: all) { 85 | label 86 | } 87 | starring(label_en: "Brad Pitt") @single 88 | } 89 | json-ld-context: > 90 | { 91 | "@context": { 92 | "label": "http://www.w3.org/2000/01/rdf-schema#label", 93 | "label_en": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 94 | "director": "http://dbpedia.org/ontology/director", 95 | "starring": "http://dbpedia.org/ontology/starring" 96 | } 97 | } 98 | responses: 99 | 200: 100 | description: list of directors of movies starring Brad Pitt 101 | x-walder-input-text/html: directors.pug 102 | /music/{artist}/postprocessed: 103 | get: 104 | summary: Returns a list of the all songs of a given artist, only keeping those with titles containing 'star'. 105 | parameters: 106 | - in: path 107 | name: artist 108 | required: true 109 | schema: 110 | type: string 111 | description: The target artist 112 | x-walder-query: 113 | graphql-query: > 114 | { 115 | label @single 116 | artist { 117 | label(_:$artist) 118 | } 119 | } 120 | json-ld-context: > 121 | { 122 | "@context": { 123 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 124 | "artist": "http://dbpedia.org/ontology/musicalArtist" 125 | } 126 | } 127 | x-walder-postprocessing: 128 | filterT: 129 | source: filterT.js 130 | responses: 131 | 200: 132 | description: list of songs 133 | x-walder-input-text/html: artist-writer.pug 134 | /movies/{actor}/postprocessed_with_variables: 135 | get: 136 | summary: Returns a list of the all movies the given actor stars in, filtered on movie titles containing 'A' and 'T' 137 | parameters: 138 | - in: path 139 | name: actor 140 | required: true 141 | schema: 142 | type: string 143 | description: The target actor 144 | x-walder-query: 145 | graphql-query: > 146 | { 147 | id @single 148 | ... on Film { 149 | starring(label: $actor) @single 150 | } 151 | } 152 | json-ld-context: > 153 | { 154 | "@context": { 155 | "Film": "http://dbpedia.org/ontology/Film", 156 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 157 | "starring": "http://dbpedia.org/ontology/starring" 158 | } 159 | } 160 | x-walder-postprocessing: 161 | filterT_withParameters: 162 | source: filterT_withParameters.js 163 | parameters: 164 | - _data 165 | - true 166 | responses: 167 | 200: 168 | description: list of movies 169 | x-walder-input-text/html: movies.pug 170 | x-walder-errors: 171 | 404: 172 | description: page not found error 173 | x-walder-input-text/html: error404.html 174 | 500: 175 | description: internal server error 176 | x-walder-input-text/html: error500.html 177 | -------------------------------------------------------------------------------- /example/paths/movies_actor.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | summary: Returns a list of the all movies the given actor stars in 3 | parameters: 4 | - in: path 5 | name: actor 6 | required: true 7 | schema: 8 | type: string 9 | description: The target actor 10 | x-walder-query: 11 | $ref: '../walderQueryInfo/movies_actor_info.yaml' 12 | responses: 13 | 200: 14 | description: list of movies 15 | x-walder-input-text/html: movies.pug 16 | 17 | -------------------------------------------------------------------------------- /example/paths/music_musician.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | summary: Returns a list of songs of the given musician. 3 | parameters: 4 | - in: path 5 | name: musician 6 | required: true 7 | schema: 8 | type: string 9 | description: The target musician 10 | x-walder-query: 11 | $ref: '../walderQueryInfo/music_musician_info.yaml' 12 | responses: 13 | 200: 14 | description: list of songs 15 | x-walder-input-text/html: songs.handlebars 16 | -------------------------------------------------------------------------------- /example/pipeModules/filterT.js: -------------------------------------------------------------------------------- 1 | module.exports.filterT = (data) => { 2 | let filteredData = {data: []}; 3 | for (const o of data.data) { 4 | if (o.label.toLowerCase().includes('star')) { 5 | filteredData.data.push(o); 6 | } 7 | } 8 | return filteredData; 9 | }; 10 | -------------------------------------------------------------------------------- /example/pipeModules/filterT_bad.js: -------------------------------------------------------------------------------- 1 | module.exports.filterT = (data) => { 2 | throw new Error('Roger, we got a problem!'); 3 | }; -------------------------------------------------------------------------------- /example/pipeModules/filterT_withParameters.js: -------------------------------------------------------------------------------- 1 | module.exports.filterT_withParameters = (data, extraLetters) => { 2 | let filteredData = {data: []}; 3 | for (const o of data.data) { 4 | if (o.id.match(/T/)) { 5 | if (extraLetters) { 6 | if (o.id.match(/A/)){ 7 | filteredData.data.push(o); 8 | } 9 | } else { 10 | filteredData.data.push(o); 11 | } 12 | } 13 | } 14 | return filteredData; 15 | }; 16 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KNowledgeOnWebScale/walder/fb85bf4b9186dc94001e289ecb14ea3a4100c8f6/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/javascript/error500.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | setTimeout(function(){ 3 | $('body').removeClass('loading'); 4 | }, 1000); 5 | }); 6 | -------------------------------------------------------------------------------- /example/public/stylesheets/error500.css: -------------------------------------------------------------------------------- 1 | /**/ 2 | :root { 3 | --main-color: #eaeaea; 4 | --stroke-color: black; 5 | 6 | } 7 | /**/ 8 | body { 9 | background: var(--main-color); 10 | } 11 | h1 { 12 | margin: 100px auto 0 auto; 13 | color: var(--stroke-color); 14 | font-family: 'Encode Sans Semi Condensed', Verdana, sans-serif; 15 | font-size: 10rem; line-height: 10rem; 16 | font-weight: 200; 17 | text-align: center; 18 | } 19 | h2 { 20 | margin: 20px auto 30px auto; 21 | font-family: 'Encode Sans Semi Condensed', Verdana, sans-serif; 22 | font-size: 1.5rem; 23 | font-weight: 200; 24 | text-align: center; 25 | } 26 | h1, h2 { 27 | -webkit-transition: opacity 0.5s linear, margin-top 0.5s linear; /* Safari */ 28 | transition: opacity 0.5s linear, margin-top 0.5s linear; 29 | } 30 | .loading h1, .loading h2 { 31 | margin-top: 0px; 32 | opacity: 0; 33 | } 34 | .gears { 35 | position: relative; 36 | margin: 0 auto; 37 | width: auto; height: 0; 38 | } 39 | .gear { 40 | position: relative; 41 | z-index: 0; 42 | width: 120px; height: 120px; 43 | margin: 0 auto; 44 | border-radius: 50%; 45 | background: var(--stroke-color); 46 | } 47 | .gear:before{ 48 | position: absolute; left: 5px; top: 5px; right: 5px; bottom: 5px; 49 | z-index: 2; 50 | content: ""; 51 | border-radius: 50%; 52 | background: var(--main-color); 53 | } 54 | .gear:after { 55 | position: absolute; left: 25px; top: 25px; 56 | z-index: 3; 57 | content: ""; 58 | width: 70px; height: 70px; 59 | border-radius: 50%; 60 | border: 5px solid var(--stroke-color); 61 | box-sizing: border-box; 62 | background: var(--main-color); 63 | } 64 | .gear.one { 65 | left: -130px; 66 | } 67 | .gear.two { 68 | top: -75px; 69 | } 70 | .gear.three { 71 | top: -235px; 72 | left: 130px; 73 | } 74 | .gear .bar { 75 | position: absolute; left: -15px; top: 50%; 76 | z-index: 0; 77 | width: 150px; height: 30px; 78 | margin-top: -15px; 79 | border-radius: 5px; 80 | background: var(--stroke-color); 81 | } 82 | .gear .bar:before { 83 | position: absolute; left: 5px; top: 5px; right: 5px; bottom: 5px; 84 | z-index: 1; 85 | content: ""; 86 | border-radius: 2px; 87 | background: var(--main-color); 88 | } 89 | .gear .bar:nth-child(2) { 90 | transform: rotate(60deg); 91 | -webkit-transform: rotate(60deg); 92 | } 93 | .gear .bar:nth-child(3) { 94 | transform: rotate(120deg); 95 | -webkit-transform: rotate(120deg); 96 | } 97 | @-webkit-keyframes clockwise { 98 | 0% { -webkit-transform: rotate(0deg);} 99 | 100% { -webkit-transform: rotate(360deg);} 100 | } 101 | @-webkit-keyframes anticlockwise { 102 | 0% { -webkit-transform: rotate(360deg);} 103 | 100% { -webkit-transform: rotate(0deg);} 104 | } 105 | @-webkit-keyframes clockwiseError { 106 | 0% { -webkit-transform: rotate(0deg);} 107 | 20% { -webkit-transform: rotate(30deg);} 108 | 40% { -webkit-transform: rotate(25deg);} 109 | 60% { -webkit-transform: rotate(30deg);} 110 | 100% { -webkit-transform: rotate(0deg);} 111 | } 112 | @-webkit-keyframes anticlockwiseErrorStop { 113 | 0% { -webkit-transform: rotate(0deg);} 114 | 20% { -webkit-transform: rotate(-30deg);} 115 | 60% { -webkit-transform: rotate(-30deg);} 116 | 100% { -webkit-transform: rotate(0deg);} 117 | } 118 | @-webkit-keyframes anticlockwiseError { 119 | 0% { -webkit-transform: rotate(0deg);} 120 | 20% { -webkit-transform: rotate(-30deg);} 121 | 40% { -webkit-transform: rotate(-25deg);} 122 | 60% { -webkit-transform: rotate(-30deg);} 123 | 100% { -webkit-transform: rotate(0deg);} 124 | } 125 | .gear.one { 126 | -webkit-animation: anticlockwiseErrorStop 2s linear infinite; 127 | } 128 | .gear.two { 129 | -webkit-animation: anticlockwiseError 2s linear infinite; 130 | } 131 | .gear.three { 132 | -webkit-animation: clockwiseError 2s linear infinite; 133 | } 134 | .loading .gear.one, .loading .gear.three { 135 | -webkit-animation: clockwise 3s linear infinite; 136 | } 137 | .loading .gear.two { 138 | -webkit-animation: anticlockwise 3s linear infinite; 139 | } -------------------------------------------------------------------------------- /example/public/stylesheets/list.css: -------------------------------------------------------------------------------- 1 | div { 2 | width: 500px; 3 | margin-left: 30px; 4 | } 5 | 6 | h2 { 7 | font: 400 40px/1.5 Helvetica, Verdana, sans-serif; 8 | margin: 0; 9 | padding: 0; 10 | } 11 | 12 | ul { 13 | list-style-type: none; 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | li { 19 | font: 200 20px/1.5 Helvetica, Verdana, sans-serif; 20 | margin-top: 2px; 21 | border-bottom: 1px solid #ccc; 22 | } 23 | 24 | li:last-child { 25 | border: none; 26 | } 27 | 28 | li a { 29 | text-decoration: none; 30 | color: #000; 31 | display: block; 32 | width: 200px; 33 | -webkit-transition: font-size 0.3s ease, background-color 0.3s ease; 34 | -moz-transition: font-size 0.3s ease, background-color 0.3s ease; 35 | -o-transition: font-size 0.3s ease, background-color 0.3s ease; 36 | -ms-transition: font-size 0.3s ease, background-color 0.3s ease; 37 | transition: font-size 0.3s ease, background-color 0.3s ease; 38 | } 39 | 40 | li a:hover { 41 | font-size: 30px; 42 | background: #f6f6f6; 43 | } -------------------------------------------------------------------------------- /example/views/artist-writer.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | body 6 | h1 Songs 7 | div 8 | ul 9 | each val in data 10 | li #{val.label} 11 | -------------------------------------------------------------------------------- /example/views/directors-sparql.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | body 6 | h1 Directors of movies starring Brad Pitt 7 | 8 | div 9 | ul 10 | each val in data["@graph"] 11 | li= val.label["@value"] 12 | -------------------------------------------------------------------------------- /example/views/directors.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | body 6 | h1 Directors of movies starring Brad Pitt 7 | div 8 | ul 9 | each val in data 10 | li= val.director.label 11 | -------------------------------------------------------------------------------- /example/views/error404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page not found 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 |
24 |
25 | 28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /example/views/error500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Internal server error 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

500

16 |

Unexpected Error :(

17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 36 |
37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /example/views/movies.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Movies

9 |
10 |
    11 | {{#each data}} 12 |
  • {{this.id}}
  • 13 | {{/each}} 14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /example/views/movies.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | body 6 | h1 Movies 7 | div 8 | ul 9 | each val in data 10 | li: a(href=val.id)= val.id -------------------------------------------------------------------------------- /example/views/movies_directors.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Movies and directors

9 |
10 |
    11 | {{#each data}} 12 |
  • {{this.label}} - {{this.director.label}}
  • 13 | {{/each}} 14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /example/views/songs-with-artist.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Songs

9 |
10 |
    11 | {{#each data}} 12 |
  • "{{this.label}}" by {{this.artist.label}}
  • 13 | {{/each}} 14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /example/views/songs.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Songs

9 |
10 |
    11 | {{#each data}} 12 |
  • {{this.label}}
  • 13 | {{/each}} 14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /example/views/songs_movies.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Songs & Movies

9 |
10 |
    11 | {{#each films}} 12 | {{#if this.id}} 13 |
  • {{this.id}}
  • 14 | {{/if}} 15 | {{/each}} 16 |
17 |
18 |
19 |
    20 | {{#each songs}} 21 | {{#if this.label}} 22 |
  • {{this.label}}
  • 23 | {{/if}} 24 | {{/each}} 25 |
26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /example/walderQueryInfo/movies_actor_info.yaml: -------------------------------------------------------------------------------- 1 | graphql-query: > 2 | { 3 | id @single 4 | ... on Film { 5 | starring(label: $actor) @single 6 | } 7 | } 8 | json-ld-context: > 9 | { 10 | "@context": { 11 | "Film": "http://dbpedia.org/ontology/Film", 12 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 13 | "starring": "http://dbpedia.org/ontology/starring" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/walderQueryInfo/music_musician_info.yaml: -------------------------------------------------------------------------------- 1 | graphql-query: > 2 | { 3 | label @single 4 | writer(label_en: $musician) @single 5 | artist @single(scope: all) { 6 | label 7 | } 8 | } 9 | json-ld-context: > 10 | { 11 | "@context": { 12 | "label": "http://www.w3.org/2000/01/rdf-schema#label", 13 | "label_en": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 14 | "writer": "http://dbpedia.org/ontology/writer", 15 | "artist": "http://dbpedia.org/ontology/musicalArtist" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/converters/converter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Converter interface. 5 | * 6 | * Converters are used for data reformatting. 7 | * 8 | * @type {module.Converter} 9 | */ 10 | module.exports = class Converter { 11 | constructor() { 12 | } 13 | 14 | convert() { 15 | } 16 | }; -------------------------------------------------------------------------------- /lib/converters/html-converter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Converter = require('./converter'); 4 | const cons = require('consolidate'); 5 | const fs = require('fs-extra'); 6 | const MarkdownIt = require('markdown-it'); 7 | const mdFootNote = require('markdown-it-footnote'); 8 | const tmp = require('tmp'); 9 | const HTMLInfo = require('../models/html-info'); 10 | const pug = require('pug'); 11 | 12 | const markdownOptions = { 13 | html: true, 14 | typographer: true, 15 | }; 16 | 17 | /** 18 | * Builds the given HTML template and completes it with the given JSON data. 19 | * 20 | * @type {module.HtmlConverter} 21 | */ 22 | module.exports = class HtmlConverter extends Converter { 23 | 24 | constructor(options = {logger: null}) { 25 | super(); 26 | 27 | this.logger = options.logger; 28 | this.templateLoader = options.templateLoader; 29 | } 30 | 31 | /** 32 | * This method generates HTML. 33 | * @param htmlInfo - The information about HTML (path, engine) 34 | * @param data - The JSON data that is used by the engine to substitute variables. 35 | * @returns {Promise} 36 | */ 37 | convert(htmlInfo, data, jsonld) { 38 | return new Promise(async (resolve, reject) => { 39 | let content; 40 | 41 | try { 42 | if (this.logger) { 43 | this.logger.debug(`Reading file "${htmlInfo.file}" from disk.`); 44 | } 45 | 46 | content = this.templateLoader.getTemplateFromCache(htmlInfo); 47 | 48 | if (this.logger) { 49 | this.logger.debug(`Done reading file "${htmlInfo.file}" from disk.`); 50 | } 51 | } catch (err) { 52 | reject(err); 53 | } 54 | 55 | // FrontMatter metadata as additional attributes in original data 56 | data = {...data, ...content.attributes}; 57 | 58 | if (jsonld) { 59 | data._queryResultsAsJSONLD = JSON.stringify(jsonld); 60 | } 61 | 62 | try { 63 | if (content.attributes.layout) { 64 | try { 65 | const layoutInfo = HTMLInfo.getLayoutInfo(htmlInfo, content.attributes.layout); 66 | if (this.logger) { 67 | this.logger.debug(`Generating HTML using template "${htmlInfo.file}".`); 68 | } 69 | 70 | const layoutDataContent = await this._convertToHTML(htmlInfo.engine, content.body, htmlInfo.file, data); 71 | 72 | if (this.logger) { 73 | this.logger.debug(`Done generating HTML using template "${htmlInfo.file}".`); 74 | } 75 | // FrontMatter metadata as additional attributes in layout data 76 | const layoutData = {content: layoutDataContent, ...content.attributes, ...data}; 77 | 78 | if (this.logger) { 79 | this.logger.debug(`Generating HTML using layout "${layoutInfo.file}".`); 80 | } 81 | 82 | const html = await this.convert(layoutInfo, layoutData, jsonld); 83 | 84 | if (this.logger) { 85 | this.logger.debug(`Done generating HTML using layout "${layoutInfo.file}".`); 86 | } 87 | 88 | resolve(html); 89 | } catch (err) { 90 | reject(err); 91 | } 92 | } else { 93 | if (this.logger) { 94 | this.logger.debug(`Generating HTML using template "${htmlInfo.file}".`); 95 | } 96 | 97 | resolve(await this._convertToHTML(htmlInfo.engine, content.body, htmlInfo.file, data)); 98 | 99 | if (this.logger) { 100 | this.logger.debug(`Done generating HTML using template "${htmlInfo.file}".`); 101 | } 102 | } 103 | } catch (err) { 104 | const error = new Error(err.msg || err.message); 105 | error.type = 'HTML_CONVERSION_FAILED'; 106 | error.subType = err.code; 107 | 108 | reject(error); 109 | } 110 | }); 111 | } 112 | 113 | async _convertToHTML(engine, fileContent, filePath, data) { 114 | return new Promise((resolve, reject) => { 115 | if (engine === 'html') { 116 | resolve(fileContent); 117 | } else if (engine === 'md') { 118 | const md = new MarkdownIt(markdownOptions).use(mdFootNote); 119 | resolve(md.render(fileContent)); 120 | } else { 121 | if (engine === 'pug') { 122 | const pugFn = pug.compile(fileContent, { 123 | filename: filePath 124 | }); 125 | const html = pugFn(data); 126 | 127 | resolve(html); 128 | } else { 129 | tmp.file(async (err, tmpPath, fd, cleanupCallback) => { 130 | if (err) { 131 | reject(err); 132 | } 133 | 134 | await fs.writeFile(tmpPath, fileContent, 'utf8'); 135 | const html = await cons[engine](tmpPath, data); 136 | 137 | cleanupCallback(); 138 | resolve(html); 139 | }); 140 | } 141 | } 142 | }); 143 | } 144 | }; 145 | -------------------------------------------------------------------------------- /lib/converters/rdf-converter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Converter = require('./converter'); 4 | const assert = require('assert'); 5 | const N3 = require('n3'); 6 | const JsonLD = require('jsonld'); 7 | 8 | // Supported RDF types, values matching https://github.com/rdfjs/N3.js notation 9 | const RDF_TYPES = { 10 | JSON_LD: 'jsonld', 11 | TURTLE: 'Turtle', 12 | NT: 'N-Triples', 13 | NQ: 'N-Quads' 14 | }; 15 | 16 | /** 17 | * Reformat the given JSON data to a supported RDF serialization. 18 | */ 19 | class RdfConverter extends Converter { 20 | constructor() { 21 | super(); 22 | } 23 | 24 | /** 25 | * Conversion always starts with reformatting the JSON to JSON-LD, followed by possible reformatting to other RDF types. 26 | * 27 | * @param outputFormat - one of the RDF_TYPES mentioned above 28 | * @param data - the data to be reformatted 29 | * @param queryInfo - query info object 30 | * @param inputFormat - the format of @data 31 | */ 32 | async convert(outputFormat, data, queryInfo, inputFormat) { 33 | assert(Object.values(RDF_TYPES).includes(outputFormat), 'Unsupported output RDF format requested!'); 34 | 35 | if (inputFormat) { 36 | assert(Object.values(RDF_TYPES).includes(inputFormat), 'Unsupported input RDF format requested!'); 37 | } 38 | 39 | if (outputFormat === inputFormat) { 40 | return data; 41 | } 42 | 43 | let jsonld; 44 | // Convert to JSON-LD first when the original query was a GRAPHQL-LD query. 45 | if (queryInfo.type === 'graphql-ld') { 46 | jsonld = this._toJSONLD(data, queryInfo); 47 | } else { 48 | const nquadStr = await this._convertQuadsToNQuadsStr(data); 49 | 50 | if (outputFormat === RDF_TYPES.NQ) { 51 | return nquadStr; 52 | } 53 | 54 | jsonld = await JsonLD.fromRDF(nquadStr, {format: 'application/n-quads'}); 55 | } 56 | 57 | if (outputFormat === RDF_TYPES.JSON_LD) { 58 | return jsonld; 59 | } else { 60 | return this._convertJSONLDToOther(jsonld, outputFormat); 61 | } 62 | } 63 | 64 | /** 65 | * Converts the given JSON to JSON-LD. 66 | * 67 | * @param data - the JSON data to convert 68 | * @param graphQLLD - GraphQLLD info object (see walder/lib/parsers/graphQLLDParser.js) 69 | */ 70 | _toJSONLD(data, graphQLLD) { 71 | const jsonld = {}; 72 | 73 | // Add @context 74 | jsonld['@context'] = graphQLLD.context['@context']; 75 | 76 | // Concat arrays 77 | let concatData = [].concat(...Object.values(data)); 78 | 79 | // Convert RDF/JS Terms to JSON-LD 80 | concatData = this._convertRDFJSTermsToJSONLD(concatData); 81 | 82 | // Add @graph 83 | jsonld['@graph'] = concatData; 84 | 85 | return jsonld; 86 | } 87 | 88 | _convertRDFJSTermsToJSONLD(data) { 89 | if (data.termType) { 90 | if (data.termType === 'NamedNode') { 91 | return {'@id': data.value}; 92 | } else { 93 | return data.value; 94 | } 95 | } else if (Array.isArray(data)) { 96 | return data.map(this._convertRDFJSTermsToJSONLD, this); 97 | } else { 98 | const result = {}; 99 | for (const key in data) { 100 | if (key === 'id') { 101 | if (Array.isArray(data[key])) { 102 | result['@id'] = data[key][0].value; 103 | } else { 104 | result['@id'] = data[key].value; 105 | } 106 | } else { 107 | result[key] = this._convertRDFJSTermsToJSONLD(data[key]); 108 | } 109 | } 110 | return result; 111 | } 112 | } 113 | 114 | /** 115 | * Reformat JSON-LD to other RDF serializations. 116 | * 117 | * @param data, the JSON-LD data to be reformatted 118 | * @param format, one of the RDF_TYPES mentioned above 119 | */ 120 | _convertJSONLDToOther(data, format) { 121 | return new Promise((resolve, reject) => { 122 | const writer = new N3.Writer({format}); 123 | 124 | JsonLD.toRDF(data, {format: 'application/n-quads'}, (err, nquads) => { 125 | const parser = new N3.Parser({format: 'N-Quads'}); 126 | writer.addQuads(parser.parse(nquads)); 127 | 128 | writer.end((error, result) => { 129 | if (error) { 130 | reject(error); 131 | } else { 132 | resolve(result); 133 | } 134 | }) 135 | }); 136 | }); 137 | } 138 | 139 | /** 140 | * Converts an array of quads to an N-Quads string. 141 | * @param quads - Array of quads. 142 | * @returns {Promise} 143 | * @private 144 | */ 145 | _convertQuadsToNQuadsStr(quads) { 146 | return new Promise((resolve, reject) => { 147 | const writer = new N3.Writer({format: 'application/n-quads'}); 148 | writer.addQuads(quads); 149 | 150 | writer.end((error, result) => { 151 | if (error) { 152 | reject(error); 153 | } else { 154 | resolve(result); 155 | } 156 | }); 157 | }); 158 | } 159 | } 160 | 161 | module.exports = { 162 | RDF_TYPES, 163 | RdfConverter 164 | }; 165 | -------------------------------------------------------------------------------- /lib/create-logger.js: -------------------------------------------------------------------------------- 1 | const winston = require('winston'); 2 | 3 | /** 4 | * This method creates a winston logger. 5 | * @param level The output level of the logger. 6 | */ 7 | function createLogger(level) { 8 | const myFormat = winston.format.printf(({level, message, timestamp}) => { 9 | return `${level} ${timestamp} : ${message}`; 10 | }); 11 | 12 | const logger = winston.createLogger({ 13 | format: winston.format.combine( 14 | winston.format.errors({stack: true}), 15 | winston.format.splat(), 16 | winston.format.timestamp(), 17 | myFormat 18 | ), 19 | exitOnError: false, // do not exit on handled exceptions 20 | }); 21 | 22 | // Log to the `console` with the colorized simple format. 23 | logger.add(new winston.transports.Console({ 24 | level, 25 | format: winston.format.combine( 26 | winston.format.colorize(), 27 | winston.format.simple(), 28 | winston.format.timestamp(), 29 | myFormat 30 | ), 31 | timestamp: true 32 | })); 33 | 34 | if (!level) { 35 | // Turn off logging 36 | logger.transports[0].silent = true; 37 | } 38 | 39 | return logger; 40 | } 41 | 42 | module.exports = createLogger; 43 | -------------------------------------------------------------------------------- /lib/handlers/content-negotiation-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Handler = require('./handler'); 4 | const ResponseHandler = require('./response-handler'); 5 | 6 | const accepts = require('accepts'); 7 | const util = require('util'); 8 | 9 | // Get required data converters 10 | const HtmlConverter = require('../converters/html-converter'); 11 | const {Converter: SPARQLJSONConverter} = require("sparqljson-to-tree"); 12 | const RdfConverter = require('../converters/rdf-converter').RdfConverter; 13 | const RDF_TYPES = require('../converters/rdf-converter').RDF_TYPES; 14 | 15 | // Available content-types 16 | // the order of this list is significant; should be server preferred order 17 | const CN_TYPES = [ 18 | 'text/html', 19 | 'application/ld+json', 20 | 'text/turtle', 21 | 'application/n-triples', 22 | 'application/n-quads', 23 | 'application/json' 24 | ]; 25 | 26 | // noinspection JSUnreachableSwitchBranches 27 | /** 28 | * Handles content negotiation. 29 | * 30 | * @type {module.ContentNegotiationHandler} 31 | */ 32 | module.exports = class ContentNegotiationHandler extends Handler { 33 | constructor(templateLoader, options = {logger: null}) { 34 | super(); 35 | this.responseHandler = new ResponseHandler(); 36 | this.logger = options.logger; 37 | this.templateLoader = templateLoader; 38 | this.rdfConverter = new RdfConverter(); 39 | } 40 | 41 | /** 42 | * Checks which data format the user requested, and sends a result appropriately. 43 | * 44 | * @param data, response data 45 | * @param htmlInfo, {statusCode: HTMLInfo} object (see walder/lib/models/htmlInfo.js) 46 | * @param queryInfo, Query info object 47 | * @param req, express 'request' object 48 | * @param res, express 'response' object 49 | */ 50 | async handle(data, htmlInfo, queryInfo, req, res) { 51 | const accept = accepts(req); 52 | const callback = this.responseHandler.handle(res, 200, accept.type(CN_TYPES)); 53 | 54 | try { 55 | switch (accept.type(CN_TYPES)) { 56 | case 'text/html': 57 | const converter = new HtmlConverter({logger: this.logger, templateLoader: this.templateLoader}); 58 | let materializedData = data; 59 | let jsonld = data; // If the query was a SPARQL query, the data is already JSON-LD. 60 | 61 | if (materializedData && queryInfo.type === 'graphql-ld') { 62 | jsonld = await this.rdfConverter.convert(RDF_TYPES.JSON_LD, materializedData, queryInfo); 63 | materializedData = SPARQLJSONConverter.materializeTree(materializedData); 64 | } 65 | 66 | const html = await converter.convert(htmlInfo['200'], materializedData, jsonld); 67 | callback(html); 68 | break; 69 | case 'application/ld+json': 70 | if (queryInfo.type === 'sparql' && queryInfo.jsonldFrame) { 71 | data = queryInfo.resultQuads; 72 | } 73 | 74 | data = [].concat(...Object.values(data)); 75 | 76 | callback(await this.rdfConverter.convert(RDF_TYPES.JSON_LD, data, queryInfo)); 77 | break; 78 | case 'text/turtle': 79 | if (queryInfo.type === 'sparql' && queryInfo.jsonldFrame) { 80 | data = queryInfo.resultQuads; 81 | } 82 | 83 | data = [].concat(...Object.values(data)); 84 | callback(await this.rdfConverter.convert(RDF_TYPES.TURTLE, data, queryInfo)); 85 | break; 86 | case 'application/n-triples': 87 | if (queryInfo.type === 'sparql' && queryInfo.jsonldFrame) { 88 | data = queryInfo.resultQuads; 89 | } 90 | 91 | data = [].concat(...Object.values(data)); 92 | callback(await this.rdfConverter.convert(RDF_TYPES.NT, data, queryInfo)); 93 | break; 94 | case 'application/n-quads': 95 | if (queryInfo.type === 'sparql' && queryInfo.jsonldFrame) { 96 | data = queryInfo.resultQuads; 97 | } 98 | 99 | data = [].concat(...Object.values(data)); 100 | callback(await this.rdfConverter.convert(RDF_TYPES.NQ, data, queryInfo)); 101 | break; 102 | case 'application/json': // for dev reasons 103 | res 104 | .set('Content-Type', 'application/json') 105 | .send(data); 106 | break; 107 | default: 108 | // Requested media type is invalid --> send 415 and error message 109 | this.responseHandler.handle(res, 415, 'application/json')({ 110 | status: 415, 111 | message: util.format('Requested invalid media type(s): %s', req.headers.accept) 112 | }); 113 | break 114 | } 115 | } 116 | catch (error) { 117 | // If something goes wrong with the data reformatting, send html with error code 500 118 | this.logger.error('Content negotiation (reformatting) error: ' + error.message); 119 | const converter = new HtmlConverter({logger: this.logger, templateLoader: this.templateLoader}); 120 | const html = await converter.convert(htmlInfo['500'], {}); 121 | this.responseHandler.handle(res, 500, 'text/html')(html); 122 | } 123 | }; 124 | }; 125 | -------------------------------------------------------------------------------- /lib/handlers/postprocess-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Handler = require('./handler'); 4 | const Utils = require('../utils'); 5 | 6 | /** 7 | * Handles the postprocessing of query results. 8 | * 9 | * @type {module.PostprocessHandler} 10 | */ 11 | module.exports = class PostprocessHandler extends Handler { 12 | constructor() { 13 | super(); 14 | } 15 | 16 | /** 17 | * This function executes pipe modules on query results. 18 | * @param data - The query results on which the pipe modules are executed. 19 | * @param pipeFunctions - Array of pipe functions. 20 | * @returns {{}} The result of the pipe modules. 21 | */ 22 | async handle(data, pipeFunctions) { 23 | let keys = Object.keys(data); 24 | const queryResultsPipeFunctionsMap = {}; 25 | 26 | pipeFunctions.forEach(pipeFunction => { 27 | // If a pipe function is only used for (a combination of) specific query results, 28 | // then it's not applied to the single query results. 29 | // These (combinations of) specific query results are not processed by 30 | // pipe functions for different (combinations of) specific query results or 31 | // pipe functions that do not specify specific query results. 32 | if (pipeFunction.queryResults) { 33 | pipeFunction.queryResults.sort(); 34 | const qrKey = pipeFunction.queryResults.join(); 35 | 36 | if (!queryResultsPipeFunctionsMap[qrKey]) { 37 | queryResultsPipeFunctionsMap[qrKey] = []; 38 | } 39 | 40 | queryResultsPipeFunctionsMap[qrKey].push(pipeFunction); 41 | } else { 42 | keys.forEach(queryResult => { 43 | if (!queryResultsPipeFunctionsMap[queryResult]) { 44 | queryResultsPipeFunctionsMap[queryResult] = []; 45 | } 46 | 47 | queryResultsPipeFunctionsMap[queryResult].push(pipeFunction); 48 | }) 49 | } 50 | }); 51 | 52 | const updatedKeys = Object.keys(queryResultsPipeFunctionsMap); 53 | 54 | for (let i = 0; i < updatedKeys.length; i ++) { 55 | const key = updatedKeys[i]; 56 | 57 | if (keys.includes(key)) { 58 | data[key] = (await Utils.pipe(queryResultsPipeFunctionsMap[key], data[key], key)); 59 | } else { 60 | data = (await Utils.pipe(queryResultsPipeFunctionsMap[key], data)); 61 | } 62 | } 63 | 64 | return data; 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /lib/handlers/request-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Handler = require('./handler'); 4 | const ResponseHandler = require('./response-handler'); 5 | const ContentNegotiationHandler = require('./content-negotiation-handler'); 6 | const HTMLConverter = require('../converters/html-converter'); 7 | const PostprocessHandler = require('./postprocess-handler'); 8 | 9 | /** 10 | * Handles requests. 11 | * 12 | * @type {module.RequestHandler} 13 | */ 14 | module.exports = class RequestHandler extends Handler { 15 | 16 | constructor(logger, graphQLLDHandler, templateLoader, sparqlHandler) { 17 | super(); 18 | this.graphQLLDHandler = graphQLLDHandler; 19 | this.sparqlHandler = sparqlHandler; 20 | this.responseHandler = new ResponseHandler(); 21 | this.contentNegotiationHandler = new ContentNegotiationHandler(templateLoader, {logger}); 22 | this.postprocessHandler = new PostprocessHandler(); 23 | this.logger = logger; 24 | this.templateLoader = templateLoader; 25 | } 26 | 27 | /** 28 | * Returns a function that executes the query and postprocessing. Used as the express route callback function. 29 | * 30 | * @param queryInfo, Object containing all information required for GraphQL-LD or SPARQL query execution 31 | * @param pipeFunctions, [{ function: pipeFunction, parameters: parameters }] List of objects with a postprocessing function and its parameters. 32 | * @param htmlInfo, { statusCode: HTMLInfo } object (see walder/lib/models/htmlInfo.js) 33 | * 34 | * @returns {Function}, Express route callback 35 | */ 36 | handle(queryInfo, pipeFunctions, htmlInfo, status) { 37 | const converter = new HTMLConverter({logger: this.logger, templateLoader: this.templateLoader}); 38 | 39 | return async (req, res, next) => { 40 | if (status !== 404) { 41 | if (queryInfo && Object.keys(queryInfo).length > 0) { 42 | if (queryInfo.type === `graphql-ld`) { 43 | this._query({queryInfo, req, res, pipeFunctions, htmlInfo, converter, queryHandler: this.graphQLLDHandler, errorPrefix: 'GRAPHQL'}); 44 | } else { 45 | this._query({queryInfo, req, res, pipeFunctions, htmlInfo, converter, queryHandler: this.sparqlHandler, errorPrefix: 'SPARQL'}); 46 | } 47 | } else { 48 | // No GraphQL or SPARQL query is defined, so we just use the template without data. 49 | try { 50 | status = 200; 51 | const html = await converter.convert(htmlInfo[String(status)], {}); 52 | this.responseHandler.handle(res, status, 'text/html')(html); 53 | } catch (err) { 54 | this.logger.error(`HTML conversion failed for "${req.path}": `); 55 | 56 | if (err.message) { 57 | this.logger.error(`${err.message}`); 58 | } 59 | 60 | status = 500; 61 | const html = await converter.convert(htmlInfo[String(status)], {}); 62 | this.responseHandler.handle(res, status, 'text/html')(html); 63 | } 64 | } 65 | } else { 66 | const html = await converter.convert(htmlInfo[String(status)], {}); 67 | this.responseHandler.handle(res, status, 'text/html')(html); 68 | } 69 | } 70 | }; 71 | 72 | /** 73 | * Executes a query via a provided handler. 74 | * @param options - All options for the function. 75 | * @returns {Promise} 76 | * @private 77 | */ 78 | async _query(options) { 79 | const {queryInfo, req, res, pipeFunctions, htmlInfo, converter, queryHandler, errorPrefix} = options; 80 | 81 | try { 82 | const data = await queryHandler.handle(queryInfo, req.params, req.query); 83 | // Postprocessing: Apply pipe modules to query result 84 | try { 85 | const pipeResult = await this.postprocessHandler.handle(data, pipeFunctions); 86 | this.contentNegotiationHandler.handle(pipeResult, htmlInfo, queryInfo, req, res); 87 | } catch (e) { 88 | // If something goes wrong with applying the pipe modules, send status code 500 89 | console.error('Pipe module error: ' + e.message); 90 | const html = await converter.convert(htmlInfo['500'], {}); 91 | this.responseHandler.handle(res, 500, 'text/html')(html); 92 | } 93 | } catch (e) { 94 | this.logger.error(errorPrefix + ' error: ' + e.message); 95 | 96 | // Check what kind of error was thrown and respond appropriately 97 | let status = 500; 98 | 99 | if (e.type === errorPrefix + '-MISSINGREQUIREDPARAMETERS' || 100 | e.type === errorPrefix + '-INTEGER-BELOW-MINIMUM' || 101 | e.type === errorPrefix + '-INTEGER-ABOVE-MAXIMUM') { 102 | status = 400; 103 | } else if (e.message.includes('Variable')) { 104 | status = 404; 105 | } 106 | 107 | const handleResponse = this.responseHandler.handle(res, status, 'text/html'); 108 | 109 | if (!htmlInfo[String(status)]) { 110 | this.logger.warn(`No HTML template is defined for status code ${status}. Sending an empty string instead.`); 111 | handleResponse(''); 112 | } else { 113 | const html = await converter.convert(htmlInfo[String(status)], {}); 114 | handleResponse(html); 115 | } 116 | } 117 | } 118 | }; 119 | -------------------------------------------------------------------------------- /lib/handlers/response-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Handler = require('./handler'); 4 | 5 | /** 6 | * Handles responses. 7 | * 8 | * @type {module.ResponseHandler} 9 | */ 10 | module.exports = class ResponseHandler extends Handler { 11 | constructor() { 12 | super(); 13 | } 14 | 15 | /** 16 | * Creates the callback function which sends out the result with the appropriate content type header. 17 | * 18 | * @param res, Express response object 19 | * @param status, Response status code 20 | * @param contentType, requested content type 21 | * @returns {Function} 22 | */ 23 | handle(res, status, contentType) { 24 | return (data) => { 25 | res 26 | .set('Content-Type', contentType) 27 | .status(status) 28 | .send(data) 29 | .end(); 30 | } 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /lib/handlers/sparql-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Handler = require('./handler'); 4 | 5 | const {LoggerPretty} = require("@comunica/logger-pretty"); 6 | const jsonld = require('jsonld'); 7 | const {RdfConverter, RDF_TYPES} = require("../converters/rdf-converter"); 8 | const {QueryEngine} = require("@comunica/query-sparql"); 9 | const parseDataSources = require('../parsers/data-sources-parser'); 10 | 11 | /** 12 | * Handles SPARQL querying. 13 | * 14 | * @type {module.SPARQLHandler} 15 | */ 16 | module.exports = class SPARQLHandler extends Handler { 17 | 18 | constructor(logger, pipeModulesPath, graphQLLDHandler) { 19 | super(logger, () => {return graphQLLDHandler}, () => {return this}, pipeModulesPath); 20 | 21 | this.logger = logger; 22 | this.pipeModulesPath = pipeModulesPath; 23 | this.graphQLLDHandler = graphQLLDHandler; 24 | } 25 | 26 | /** 27 | * Instantiates the variables in the given SPARQL query using the given path variables and query parameters, 28 | * then executes the given SPARQL query using Comunica. 29 | * 30 | * @param queryInfo - Object containing the Comunica configuration, cache setting, and the SPARQL query. 31 | * @param pathParams - Object containing path parameters to value mapping. 32 | * @param queryParams - Object containing query parameter to value mapping. 33 | * @returns {Promise<>} - SPARQL query results. 34 | */ 35 | async handle(queryInfo, pathParams = {}, queryParams = {}) { 36 | const newQueries = this.fillInParameters(queryInfo, pathParams, queryParams,'?', 'SPARQL'); 37 | 38 | const engineConfig = {}; 39 | engineConfig.queryEngine = await this.getEngineFromCache(queryInfo, parseDataSources); 40 | 41 | if (!queryInfo.cache) { 42 | await this.invalidateHttpCache(engineConfig.queryEngine); 43 | } 44 | 45 | if (this.logger) { 46 | this.logger.debug(JSON.stringify(newQueries)); 47 | } 48 | 49 | const results = {}; 50 | 51 | for (const key of Object.keys(newQueries)) { 52 | try { 53 | const config = JSON.parse(JSON.stringify(queryInfo.comunicaConfig)); // We make a deep copy because Comunica changes the config object. 54 | config.log = new LoggerPretty({ level: this.logger?.level }); 55 | 56 | const quadStream = await engineConfig.queryEngine.queryQuads(newQueries[key].query, 57 | config); 58 | if (this.logger) { 59 | this.logger.debug('SPARQL: result done.'); 60 | this.logger.debug('SPARQL: converting quad stream to array.'); 61 | } 62 | results[key] = (await quadStream.toArray()); 63 | 64 | if (queryInfo.jsonldFrame) { 65 | if (this.logger) { 66 | this.logger.debug('SPARQL: JSON-LD frame defined.'); 67 | } 68 | if (!queryInfo.resultQuads) { 69 | queryInfo.resultQuads = {}; 70 | } 71 | queryInfo.resultQuads[key] = results[key]; 72 | const converter = new RdfConverter(); 73 | results[key] = await converter.convert(RDF_TYPES.JSON_LD, results[key], queryInfo); 74 | if (this.logger) { 75 | this.logger.debug('SPARQL: result to JSON-LD done.'); 76 | } 77 | const framed = await jsonld.frame(results[key], JSON.parse(queryInfo.jsonldFrame)); 78 | if (this.logger) { 79 | this.logger.debug('SPARQL: framing JSON-LD done.'); 80 | } 81 | results[key] = framed; 82 | } else { 83 | if (this.logger) { 84 | this.logger.debug('SPARQL: no JSON-LD frame defined.'); 85 | } 86 | } 87 | } catch (error) { 88 | if (this.logger) { 89 | this.logger.debug(error); 90 | } 91 | 92 | const e = new Error(`Error during execution of SPARQL query "${newQueries[key].query}".`); 93 | e.comunicaMessage = error.message; 94 | throw e; 95 | } 96 | 97 | if (this.logger) { 98 | this.logger.verbose(JSON.stringify(results[key])); 99 | } 100 | } 101 | 102 | return results; 103 | } 104 | 105 | encodeIntegerAsParameterValue(val) { 106 | return val; 107 | } 108 | 109 | getNewEngine(queryInfo) { 110 | return new QueryEngine(); 111 | } 112 | }; 113 | -------------------------------------------------------------------------------- /lib/loaders/loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Loader interface. 5 | * 6 | * loaders are used to load data from external sources. 7 | * 8 | * @type {module.Loader} 9 | */ 10 | module.exports = class Loader { 11 | constructor() { 12 | } 13 | 14 | load() { 15 | } 16 | }; -------------------------------------------------------------------------------- /lib/loaders/pipe-module-loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Loader = require('./loader'); 4 | 5 | /** 6 | * Loads pipe modules. 7 | * Writes the retrieved pipe modules to 'walder.js/pipeModules/pipeModules.js' 8 | * 9 | * @type {module.PipeModuleLoader} 10 | */ 11 | module.exports = class PipeModuleLoader extends Loader { 12 | constructor() { 13 | super(); 14 | } 15 | 16 | /** 17 | * Loads local and remote pipe modules. 18 | * 19 | * @param pipeModules, list of pipe module objects {name, source, parameters} 20 | * @returns [{function: pipeFunction, parameters: parameters}], list of objects with a pipe function and its parameters 21 | */ 22 | static load(pipeModules) { 23 | let pipeFunctions = []; 24 | 25 | pipeModules.forEach(pipeModule => { 26 | const fn = require(pipeModule.source)[pipeModule.name]; 27 | 28 | if (!fn) { 29 | const message = `Pipe module "${pipeModule.name}" was not found in ${pipeModule.source}`; 30 | throw new Error(message); 31 | } 32 | 33 | pipeFunctions.push({ 34 | function: fn, 35 | parameters: pipeModule.parameters, 36 | queryResults: pipeModule.queryResults 37 | }); 38 | }); 39 | 40 | return pipeFunctions; 41 | } 42 | }; 43 | 44 | -------------------------------------------------------------------------------- /lib/loaders/template-loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Loader = require('./loader'); 4 | const fs = require("fs"); 5 | const frontMatter = require('front-matter'); 6 | const HTMLInfo = require("../models/html-info"); 7 | 8 | /** 9 | * Caches template files. 10 | * 11 | * @type {module.TemplateLoader} 12 | */ 13 | module.exports = class TemplateLoader extends Loader { 14 | constructor() { 15 | super(); 16 | this.cache = {}; 17 | } 18 | 19 | /** 20 | * Loads template file into cache. 21 | * 22 | * @param htmlInfo - The information about the HTML 23 | */ 24 | load(htmlInfo) { 25 | let content; 26 | 27 | // Try to read the file. 28 | try { 29 | content = fs.readFileSync(htmlInfo.file, 'utf8'); 30 | } catch (err) { 31 | const error = new Error(`Reading the file "${htmlInfo.file}" failed.`); 32 | error.type = 'IO_READING_FAILED'; 33 | // Pass the filename along so the validator can show the correct file if this error was caused by a layout. 34 | error.fileName = htmlInfo.file; 35 | throw error; 36 | } 37 | 38 | // Try to retrieve frontmatter from the file content. 39 | let matter; 40 | try { 41 | matter = frontMatter(content); // For some reason the async version take a super long time. 42 | this.cache[htmlInfo.file] = matter; 43 | } catch (err) { 44 | const error = new Error(`Reading frontmatter from the file "${htmlInfo.file}" failed.`); 45 | error.type = 'FRONTMATTER_FAILED'; 46 | // Pass the filename along so the validator can show the correct file if this error was caused by a layout. 47 | error.fileName = htmlInfo.file; 48 | throw error; 49 | } 50 | 51 | // If this template has a layout, also cache that layout. 52 | if (matter.attributes.layout) { 53 | const layoutInfo = HTMLInfo.getLayoutInfo(htmlInfo, matter.attributes.layout); 54 | this.load(layoutInfo); 55 | } 56 | } 57 | 58 | /** 59 | * Loads a template file from the cache and returns the content. 60 | * 61 | * @param htmlInfo - The information about the HTML 62 | * @returns FrontMatter - FrontMatter of the template 63 | */ 64 | getTemplateFromCache(htmlInfo) { 65 | const ret = this.cache[htmlInfo.file]; 66 | if (!ret) { 67 | const error = new Error(`Reading the file "${htmlInfo.file}" from the cache failed.`); 68 | error.type = 'IO_READING_FAILED'; 69 | throw error; 70 | } 71 | return ret; 72 | } 73 | } 74 | 75 | 76 | -------------------------------------------------------------------------------- /lib/models/html-info.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const {getEngineFromFilePath} = require('../utils'); 4 | /** 5 | * Contains the information required to render a HTML template. 6 | * 7 | * @type {module.HTMLInfo} 8 | */ 9 | module.exports = class HTMLInfo { 10 | constructor(engine, file, description, layoutsDir) { 11 | this.engine = engine; 12 | this.file = file; 13 | this.description = description; 14 | this.layoutsDir = layoutsDir; 15 | } 16 | 17 | /** 18 | * Creates a new htmlInfo object for a given layout file that extends a given template. 19 | * 20 | * @param htmlInfo - The HTMLInfo object for the template 21 | * @param layout - The layout file 22 | */ 23 | static getLayoutInfo(htmlInfo, layout) { 24 | const layoutPath = path.join(htmlInfo.layoutsDir, layout); 25 | const layoutEngine = getEngineFromFilePath(layoutPath); 26 | return new HTMLInfo(layoutEngine, layoutPath, htmlInfo.description, htmlInfo.layoutsDir) 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /lib/models/query-info.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Contains all information required to execute a GraphQL-LD or SPARQL query. 5 | * 6 | * @type {module.QueryInfo} 7 | */ 8 | module.exports = class QueryInfo { 9 | constructor(queries, context, comunicaConfig, cache, parameters = [], type = 'graphql-ld', jsonldFrame) { 10 | this.queries = queries; 11 | this.context = context; 12 | this.comunicaConfig = comunicaConfig; 13 | this.cache = cache; 14 | this.parameters = parameters; 15 | this.type = type; 16 | this.jsonldFrame = jsonldFrame; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /lib/models/route-info.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Represents express route information. 5 | * 6 | * @type {module.RouteInfo} 7 | */ 8 | module.exports = class RouteInfo { 9 | constructor(method, path) { 10 | this.method = method; 11 | this.path = path; 12 | } 13 | }; -------------------------------------------------------------------------------- /lib/parsers/config-file-parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const YAML = require('yaml'); 4 | const fs = require('fs'); 5 | const JSREF = require('json-refs'); 6 | 7 | module.exports = (configFilePath) => { 8 | return new Promise((resolve, reject) => { 9 | const file = fs.readFileSync(configFilePath, 'utf8'); 10 | let yamlObj = YAML.parse(file); 11 | 12 | // Replace $ref in the configFilePath with the corresponding .yaml files 13 | let options = { 14 | filter: ['relative', 'remote'], 15 | loaderOptions: { 16 | processContent: function (res, callback) { 17 | // Parse the corresponding .yaml file to objects 18 | callback(YAML.parse(res.text)); 19 | } 20 | }, 21 | location: configFilePath 22 | }; 23 | 24 | // Example that only resolves relative and remote references 25 | JSREF.resolveRefs(yamlObj, options) 26 | .then(function (res) { 27 | const keys = Object.keys(res.refs); 28 | let i = 0; 29 | 30 | while (i < keys.length && !res.refs[keys[i]].missing) { 31 | i++; 32 | } 33 | 34 | if (i < keys.length) { 35 | const error = new Error(`The referenced file at "${res.refs[keys[i]].fqURI}" does not exist.`); 36 | error.type = 'IO_INVALID_REF'; 37 | 38 | reject(error); 39 | } else { 40 | resolve(res.resolved); 41 | } 42 | }, function (err) { 43 | reject(err); 44 | }); 45 | }); 46 | 47 | 48 | // resolve all the references 49 | //return (await JSREF.resolveRefs(yamlObj, options)).resolved; 50 | } 51 | -------------------------------------------------------------------------------- /lib/parsers/data-sources-parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const parseQuery = require('./query-parser'); 4 | const parsePipeModule = require('./pipe-module-parser'); 5 | const PipeModuleLoader = require('../loaders/pipe-module-loader'); 6 | const PostprocessHandler = require('../handlers/postprocess-handler'); 7 | 8 | const defaultOptions = { 9 | cache: true 10 | }; 11 | 12 | /** 13 | * This function parses an array of data sources. 14 | * @param options - The optional options of this function. 15 | * @param options.dataSources - An array of data sources. 16 | * @param options.graphQLLDHandler - The GraphQLLDHandler to execute GraphQL-LD queries that return data sources. 17 | * @param options.sparqlHandler - The SPARQLHandler to execute SPARQL queries that return data sources. 18 | * @param options.pipeModulesPath - The path where pipe modules can be found. 19 | * @param options.cache - Use Comunica cache if this parameter is true. 20 | * @returns {Promise<[]>} 21 | */ 22 | module.exports = async (options = {}) => { 23 | options = {...defaultOptions, ...options}; 24 | const {dataSources, graphQLLDHandler, pipeModulesPath, cache, sparqlHandler} = options; 25 | let parsedDataSources = []; 26 | 27 | if (dataSources) { 28 | for (const el of dataSources) { 29 | if (typeof el === 'string' || el instanceof String) { 30 | parsedDataSources.push(el); 31 | } else { 32 | const queryInfo = parseQuery(el, {cache, defaultDataSources: parsedDataSources}); 33 | let results; 34 | 35 | if (queryInfo.type === 'graphql-ld') { 36 | results = await graphQLLDHandler.handle(queryInfo); 37 | } else { 38 | results = await sparqlHandler.handle(queryInfo); 39 | } 40 | 41 | const pipeModules = parsePipeModule(el['postprocessing'], pipeModulesPath); 42 | const pipeFunctions = PipeModuleLoader.load(pipeModules); 43 | const postprocessHandler = new PostprocessHandler(); 44 | const pipeResult = await postprocessHandler.handle(results, pipeFunctions); 45 | 46 | if (queryInfo.type === 'graphql-ld') { 47 | parsedDataSources = parsedDataSources.concat(pipeResult.data.map(a => a.value)); 48 | } else { 49 | parsedDataSources = parsedDataSources.concat(pipeResult.data); 50 | } 51 | } 52 | } 53 | } 54 | 55 | return parsedDataSources; 56 | } 57 | -------------------------------------------------------------------------------- /lib/parsers/html-parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const HTMLInfo = require('../models/html-info'); 4 | const {getEngineFromFilePath} = require('../utils'); 5 | const path = require('path'); 6 | 7 | /** 8 | * Parses the HTML template information and returns it as a {statusCode: HTMLInfo} object. 9 | */ 10 | module.exports = (data, basePath, layoutsDir) => { 11 | const result = {}; 12 | 13 | // Extract the file 14 | for (const statusCode in data) { 15 | let file = data[statusCode]['x-walder-input-text/html']; 16 | if (!path.isAbsolute(file)) { 17 | file = path.resolve(basePath, file); 18 | } 19 | 20 | // Extract the engine 21 | const engine = getEngineFromFilePath(file); 22 | 23 | result[statusCode] = new HTMLInfo(engine, file, data[statusCode].description, layoutsDir); 24 | } 25 | 26 | return result; 27 | }; 28 | -------------------------------------------------------------------------------- /lib/parsers/parameter-parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Parses the (path and query) parameters information corresponding to a route. 5 | * @param parameters 6 | * @returns {{}} - A parameterName to { required, type, description } mapping object. 7 | */ 8 | module.exports = (parameters) => { 9 | if (parameters) { 10 | const result = {}; 11 | 12 | for (const parameter of parameters) { 13 | result[parameter.name] = { 14 | required: parameter.required || false, 15 | type: parameter.schema?.type, 16 | minimum: parameter.schema?.minimum, 17 | maximum: parameter.schema?.maximum, 18 | description: parameter.description, 19 | in: parameter.in 20 | } 21 | } 22 | 23 | return result; 24 | } else { 25 | return {}; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /lib/parsers/pipe-module-parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Path = require('path'); 4 | 5 | /** 6 | * Parses pipe module data of config file. 7 | * @param data 8 | * @param basePath 9 | * @returns {[]} 10 | */ 11 | module.exports = (data, basePath) => { 12 | let pipeModules = []; 13 | 14 | for (const pipeModule in data) { 15 | let source = data[pipeModule].source; 16 | 17 | if (!Path.isAbsolute(source)) { 18 | source = Path.resolve(basePath, source); 19 | } 20 | 21 | let parameters = []; 22 | 23 | if (data[pipeModule].parameters) { 24 | parameters = data[pipeModule].parameters; 25 | } 26 | 27 | pipeModules.push({ 28 | name: pipeModule, 29 | source, 30 | parameters, 31 | queryResults: data[pipeModule].queryResults 32 | }); 33 | } 34 | return pipeModules; 35 | }; 36 | -------------------------------------------------------------------------------- /lib/parsers/query-parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const QueryInfo = require('../models/query-info'); 4 | 5 | const defaultOptions = { 6 | defaultDataSources: [], 7 | cache: true, 8 | lenient: false, 9 | parameters: [] 10 | } 11 | 12 | /** 13 | * This function parses an x-walder-query object of a config file and returns the corresponding QueryInfo object. 14 | * @param data - An x-walder-query object. 15 | * @param options - An object with options for the function. 16 | * @param options.defaultDataSources - An array of default data sources. 17 | * @param options.cache - Use cache of Comunica when true. 18 | * @param options.lenient - Turn Comunica errors on invalid data into warnings 19 | * @param options.parameters - An array of parameters that are associated with the same request as the query. 20 | * @returns A QueryInfo object. 21 | */ 22 | module.exports = (data, options = {}) => { 23 | const {defaultDataSources, cache, lenient, parameters} = {...defaultOptions, ...options}; 24 | 25 | if (data) { 26 | // Extract the query 27 | let type; 28 | let queries; 29 | let context; 30 | let jsonldFrame; 31 | 32 | if (data['graphql-query']) { 33 | type = `graphql-ld`; 34 | queries = data['graphql-query']; 35 | } else { 36 | type = 'sparql'; 37 | queries = data['sparql-query']; 38 | jsonldFrame = data['json-ld-frame'] || data['jsonld-frame'] 39 | } 40 | 41 | if (type === `graphql-ld`) { 42 | // Extract the context 43 | context = JSON.parse(data['json-ld-context']); 44 | 45 | // Support the smaller version of a context 46 | if (!context['@context']) { 47 | context = {'@context': context}; 48 | } 49 | } 50 | 51 | // Extract the global options (sort/duplicate/...) 52 | const options = data['options']; 53 | 54 | if (typeof queries === 'string') 55 | queries = {'data': {query: queries}}; 56 | Object.keys(queries).forEach(key => { 57 | 58 | // if there is no query key in the object, link the query to a query key 59 | if (!queries[key].query) { 60 | queries[key] = {query: queries[key]}; 61 | } 62 | // if there are global options, add them to every query 63 | if (options) { 64 | queries[key].options = options; 65 | } 66 | }); 67 | Object.keys(queries).forEach(key => queries[key].query = queries[key].query.replace(/\n/g, '').replace(/[ ]+/g, ' ')); 68 | // Extract the data sources 69 | let dataSources = defaultDataSources; 70 | 71 | // Check for path specific data sources 72 | if (data.datasources) { 73 | if (data.datasources.additional) { 74 | dataSources = [...new Set([...dataSources, ...data.datasources.sources])]; // Union of default and additional dataSources 75 | } else { 76 | dataSources = data.datasources.sources; 77 | } 78 | } 79 | // Create the comunica config object 80 | const comunicaConfig = { 81 | sources: dataSources, 82 | lenient: lenient 83 | }; 84 | 85 | return new QueryInfo(queries, context, comunicaConfig, cache, parameters, type, jsonldFrame); 86 | } else { 87 | return undefined; 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /lib/parsers/resource-parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | 6 | const DEFAULT_RESOURCES = { 7 | views: 'views', 8 | 'pipe-modules': 'pipe-modules', 9 | public: 'public', 10 | layouts: 'layouts' 11 | }; 12 | 13 | /** 14 | * This function parses the resource paths and returns them as absolute paths. 15 | * @param {Object} data - The object with the resource paths from the config file. 16 | * @param cwd - The absolute path used to create absolute paths from for relative paths. 17 | * @param logger - A Winston instance. 18 | * @returns {{}|{public: string, layouts: string, views: string, pipe-modules: string}} 19 | */ 20 | module.exports = (data, cwd, logger = null) => { 21 | if (!path.isAbsolute(cwd)) { 22 | throw new Error('The parameter "cwd" is not an absolute path.'); 23 | } 24 | 25 | const resources = {}; 26 | 27 | if (data) { 28 | if (data.path) { 29 | if (logger) { 30 | logger.warn(`"x-walder-resources.path" is deprecated. Use "x-walder-resources.root" instead.`); 31 | } 32 | 33 | data.root = data.path; 34 | } 35 | 36 | if (data.root) { 37 | if (path.isAbsolute(data.root)) { 38 | resources.root = data.root; 39 | } else { 40 | resources.root = path.resolve(cwd, data.root); 41 | } 42 | } else { 43 | resources.root = cwd; 44 | } 45 | 46 | resources.views = data.views ? data.views : DEFAULT_RESOURCES.views; 47 | 48 | if (!path.isAbsolute(resources.views)) { 49 | resources.views = path.resolve(resources.root, resources.views); 50 | } 51 | 52 | resources['pipe-modules'] = data['pipe-modules'] ? data['pipe-modules'] : DEFAULT_RESOURCES['pipe-modules']; 53 | if (!path.isAbsolute(resources['pipe-modules'])) { 54 | resources['pipe-modules'] = path.resolve(resources.root, resources['pipe-modules']); 55 | } 56 | 57 | resources.public = data.public ? data.public : DEFAULT_RESOURCES.public; 58 | if (!path.isAbsolute(resources.public)) { 59 | resources.public = path.resolve(resources.root, resources.public); 60 | } 61 | // check if public directory already exists, create if not 62 | if (!fs.existsSync(resources.public)) { 63 | fs.mkdirSync(resources.public, {recursive: true}); 64 | } 65 | 66 | resources.layouts = data.layouts ? data.layouts : DEFAULT_RESOURCES.layouts; 67 | if (!path.isAbsolute(resources.layouts)) { 68 | resources.layouts = path.resolve(resources.root, resources.layouts); 69 | } 70 | 71 | return resources; 72 | } else { 73 | const parsedDefaultResources = {...{root: cwd}, ...DEFAULT_RESOURCES}; 74 | 75 | for (const p in parsedDefaultResources) { 76 | parsedDefaultResources[p] = path.resolve(cwd, parsedDefaultResources[p]); 77 | } 78 | 79 | return parsedDefaultResources; 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /lib/parsers/route-parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const RouteInfo = require('../models/route-info'); 4 | 5 | /** 6 | * Parses the routing information and returns it as a RouteInfo object. 7 | * @param path 8 | * @param method 9 | */ 10 | module.exports = (path, method) => { 11 | // Check for route parameters 12 | if (path.includes('{')) { 13 | path = path.replace(/{/g, ':').replace(/}/g, ''); 14 | } 15 | 16 | // Remove escape characters 17 | if (path.includes('\\')) { 18 | path = path.replace('\\', ''); 19 | } 20 | 21 | return new RouteInfo(method, path); 22 | }; 23 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | const debug = require('debug')('walder:server'); 7 | const http = require('http'); 8 | 9 | module.exports.initialise = (app, port, logger) => { 10 | logger.info('Starting server...'); 11 | 12 | /** 13 | * Get port from environment and store in Express. 14 | */ 15 | port = normalizePort(port); 16 | app.set('port', port); 17 | 18 | 19 | /** 20 | * Create HTTP server. 21 | */ 22 | 23 | const server = http.createServer(app); 24 | 25 | /** 26 | * Listen on provided port, on all network interfaces. 27 | */ 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | logger.info(`Started listening on port: ${port}.\n`); 32 | 33 | /** 34 | * Normalize a port into a number, string, or false. 35 | */ 36 | function normalizePort(val) { 37 | const port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | function onError(error) { 56 | if (error.syscall !== 'listen') { 57 | throw error; 58 | } 59 | 60 | const bind = typeof port === 'string' 61 | ? 'Pipe ' + port 62 | : 'Port ' + port; 63 | 64 | // handle specific listen errors with friendly messages 65 | switch (error.code) { 66 | case 'EACCES': 67 | logger.error(bind + ' requires elevated privileges'); 68 | process.exit(1); 69 | break; 70 | case 'EADDRINUSE': 71 | logger.error(bind + ' is already in use'); 72 | process.exit(1); 73 | break; 74 | default: 75 | throw error; 76 | } 77 | } 78 | 79 | /** 80 | * Event listener for HTTP server "listening" event. 81 | */ 82 | function onListening() { 83 | const addr = server.address(); 84 | const bind = typeof addr === 'string' 85 | ? 'pipe ' + addr 86 | : 'port ' + addr.port; 87 | debug('Listening on ' + bind); 88 | } 89 | 90 | return server; 91 | }; 92 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const objectPath = require('object-path'); 5 | 6 | // set of extensions that should be replaced 7 | const extensionReplacements = { 8 | 'njk': 'nunjuck' 9 | } 10 | 11 | /** 12 | * Applies given pipe modules (functions) to the given data. 13 | * 14 | * @param fns - List of pipe functions 15 | * @param data - The data that is processed by the pipe functions 16 | * @param queryKey - The key of the query that resulted in the given data 17 | * @returns {function(*=): (*)} 18 | */ 19 | const pipe = async (fns, data, queryKey) => { 20 | for (let i = 0; i < fns.length; i ++) { 21 | const f = fns[i]; 22 | const parameters = [...f.parameters]; 23 | 24 | if (parameters.length > 0 && parameters[0] !== '_data'){ 25 | const object_path = parameters[0].slice(1); 26 | parameters[0] = objectPath.get(data, object_path); 27 | objectPath.set(data, object_path, f.function(...parameters)); 28 | } 29 | 30 | parameters[0] = data; 31 | 32 | if (queryKey) { 33 | parameters.push(queryKey); 34 | } 35 | 36 | data = await f.function(...parameters); 37 | } 38 | 39 | return data; 40 | }; 41 | 42 | /** 43 | * Print an error without stack trace and stop execution. 44 | * @param msg 45 | */ 46 | const printError = (msg) => { 47 | console.error('\n' + msg + '\n'); 48 | process.exit(1); 49 | }; 50 | 51 | function getEngineFromFilePath(filePath) { 52 | let extension = path.extname(filePath).slice(1); 53 | //replace some extensions 54 | if(extension in extensionReplacements){ 55 | return extensionReplacements[extension]; 56 | } 57 | return extension; 58 | } 59 | 60 | module.exports = { 61 | pipe, 62 | printError, 63 | getEngineFromFilePath 64 | }; 65 | -------------------------------------------------------------------------------- /lib/validators/graphql-ld-validator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const SubValidator = require('./sub-validator'); 4 | const util = require('util'); 5 | 6 | const ERROR_TEMPLATE = 7 | `Config file validation error for route '%s' - '%s': 8 | * Query variable(s): %s not described in the parameters section.`; 9 | 10 | /** 11 | * Validates the GraphQL-LD sections of the config file. 12 | * 13 | * @type {module.GraphQLLDInfoValidator} 14 | */ 15 | module.exports = class GraphQLLDInfoValidator extends SubValidator { 16 | constructor(logger) { 17 | super(); 18 | 19 | this.logger = logger; 20 | } 21 | 22 | /** 23 | * Validates GraphQL-LD info. 24 | * 25 | * @param input input to be validated. 26 | * @param input.routeInfo info about the route being validated, see walder/lib/models/route-info.js 27 | * @param input.parameters object containing all described path and query parameters for a route 28 | * @param input.graphQLLDInfo parsed GraphQLLD information, see walder/lib/models/graphql-ld-info.js 29 | * 30 | * @returns Promise, whose resolution is: {string} validation error description (undefined if all's well) 31 | */ 32 | async validate(input = {}) { 33 | if (input.routeInfo && input.graphQLLDInfo && input.parameters) { 34 | return this.validateVariables(input.routeInfo, input.graphQLLDInfo, input.parameters); 35 | } 36 | } 37 | 38 | /** 39 | * Checks if all variables used in the GraphQL-LD query are described in the parameters section. 40 | * 41 | * @param routeInfo info about the route being validated, see walder/lib/models/route-info.js 42 | * @param graphQLLDInfo parsed GraphQLLD information, see walder/lib/models/graphql-ld-info.js 43 | * @param parameters object containing all described path and query parameters for a route 44 | * 45 | * @returns Promise, whose resolution is: {string} validation error description (undefined if all's well) 46 | */ 47 | async validateVariables(routeInfo, graphQLLDInfo, parameters) { 48 | let array = []; 49 | for(const object of Object.values(graphQLLDInfo.queries)){ 50 | array.push(object.query); 51 | } 52 | const queryVariables = array.join('\n').match(/\$[a-zA-Z0-9]+/g); 53 | if (queryVariables) { 54 | const unknownVariables = queryVariables.filter((variable) => { 55 | let varName = variable.slice(1); 56 | 57 | // Pagination variable 'offset' is mapped to 'limit' 58 | if (varName === 'offset') { 59 | varName = 'limit'; 60 | } 61 | 62 | return !(varName in parameters); 63 | }); 64 | 65 | if (unknownVariables.length > 0) { 66 | return util.format(ERROR_TEMPLATE, routeInfo.path, routeInfo.method, unknownVariables.join(', ')); 67 | } 68 | } 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /lib/validators/html-validator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert').strict; 4 | const fs = require('fs-extra'); 5 | const SubValidator = require('./sub-validator'); 6 | const util = require('util'); 7 | 8 | const ERROR_TEMPLATE = 9 | `Config file validation error for route '%s' - '%s': 10 | * File(s) for HTML rendering not found: %s. 11 | * Parsing frontmatter failed for file(s): %s.`; 12 | 13 | /** 14 | * Validates the HTML sections of the config file. 15 | * 16 | * @type {module.HtmlInfoValidator} 17 | */ 18 | module.exports = class HtmlInfoValidator extends SubValidator { 19 | constructor(options) { 20 | super(); 21 | this.templateLoader = options.templateLoader; 22 | this.logger = options.logger; 23 | } 24 | 25 | /** 26 | * Validates HTML info. 27 | * 28 | * @param input input to be validated. 29 | * @param input.routeInfo info about the route being validated, see walder/lib/models/route-info.js 30 | * @param input.htmlInfoDictionary object containing html info (see walder/lib/models/html-info.js) per response code, see walder/lib/parsers/html-parser.js 31 | * 32 | * @returns Promise, whose resolution is: {string} validation error description (undefined if all's well) 33 | */ 34 | async validate(input = {}) { 35 | if (input.routeInfo && input.htmlInfoDictionary) { 36 | return await this.validateFiles(input.routeInfo, input.htmlInfoDictionary); 37 | } 38 | } 39 | 40 | /** 41 | * Checks if all files are available on the file system and load them into the cache. 42 | * 43 | * @param routeInfo info about the route being validated, see walder/lib/models/route-info.js 44 | * @param htmlInfoDictionary object containing html info (see walder/lib/models/html-info.js) per response code, see walder/lib/parsers/html-parser.js 45 | * 46 | * @returns Promise, whose resolution is: {string} validation error description (undefined if all's well) 47 | */ 48 | async validateFiles(routeInfo, htmlInfoDictionary) { 49 | const filesNotFound = []; 50 | const frontMatterFailed = []; 51 | 52 | for (const responseCode in htmlInfoDictionary) { 53 | const htmlInfo = htmlInfoDictionary[responseCode]; 54 | try { 55 | assert.ok(await fs.pathExists(htmlInfo.file)); 56 | //load into the cache 57 | this.templateLoader.load(htmlInfo); 58 | } catch (err) { 59 | if (err.type === 'FRONTMATTER_FAILED') { 60 | frontMatterFailed.push(err.fileName); 61 | } else { 62 | filesNotFound.push(err.fileName ? err.fileName : htmlInfo.file); 63 | } 64 | } 65 | } 66 | 67 | if(filesNotFound.length + frontMatterFailed.length > 0) { 68 | return util.format(ERROR_TEMPLATE, routeInfo.path, routeInfo.method, filesNotFound.join(' '), frontMatterFailed.join(' ')); 69 | } 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /lib/validators/main-validator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const GraphQLLDInfoValidator = require('./graphql-ld-validator') 4 | const HtmlInfoValidator = require('./html-validator.js'); 5 | 6 | /** 7 | * Validates the input configuration file by using other section specific sub validators. 8 | * Sub validators are added by adding them to the member variable subValidators. 9 | * 10 | * Keeps all found errors in the 'errors' property. 11 | * 12 | * @type {module.MainValidator} 13 | */ 14 | module.exports = class MainValidator { 15 | constructor(options) { 16 | this.logger = options.logger; 17 | this.errors = {}; 18 | this.templateLoader = options.templateLoader; 19 | 20 | // Contains all used sub validators 21 | this.subValidators = { 22 | GraphQLLD: new GraphQLLDInfoValidator(options.logger), 23 | HTML: new HtmlInfoValidator({templateLoader: this.templateLoader, logger: options.logger}) 24 | }; 25 | 26 | Object.keys(this.subValidators).forEach((type) => { 27 | this.errors[type] = []; 28 | }); 29 | 30 | // Latest errors 31 | this.prevErrors = {}; 32 | } 33 | 34 | /** 35 | * Validate the given input, by calling all known sub validators. 36 | * 37 | * @param input input to be validated. 38 | * Each sub validator tests the properties of this object, if they're defined. 39 | * Below a list of all possible properties. Extend as sub validaters are added (to subValidators). 40 | * @param input.routeInfo info about the route being validated, see walder/lib/models/route-info.js 41 | * @param input.parameters object containing all described path and query parameters for a route 42 | * @param input.graphQLLDInfo parsed GraphQLLD information, see walder/lib/models/graphql-ld-info.js 43 | * @param input.htmlInfoDictionary object containing html info (see walder/lib/models/html-info.js) per response code, see walder/lib/parsers/html-parser.js 44 | * 45 | * @returns Promise, whose resolution is: boolean; true if and only if validation error(s) occurred 46 | */ 47 | async validateAll(input = {}) { 48 | this.prevErrors = {}; 49 | 50 | let hasError = false; 51 | 52 | for (const type of Object.keys(this.errors)) { 53 | const errors = await this.subValidators[type].validate(input); 54 | if (errors) { 55 | hasError = true; 56 | this.prevErrors[type] = errors; 57 | this.errors[type].push(errors); 58 | if (this.logger) { 59 | this.logger.error('%s: %s', type, errors); 60 | } 61 | } 62 | } 63 | 64 | return hasError; 65 | } 66 | 67 | /** 68 | * If errors were found, throw them. 69 | */ 70 | finish() { 71 | let hasErrors = false; 72 | 73 | const output = ['Config file validation errors:\n']; 74 | 75 | Object.keys(this.errors).forEach((type) => { 76 | if (this.errors[type].length > 0) { 77 | output.push(` #${type}:`); 78 | 79 | for (const error of this.errors[type]) { 80 | hasErrors = true; 81 | 82 | output.push(` - ${error}`); 83 | } 84 | } 85 | }); 86 | 87 | if (hasErrors) { 88 | const err = new Error(output.join('\n')); 89 | err.type = 'VALIDATION_ERROR'; 90 | throw err; 91 | } 92 | } 93 | }; 94 | -------------------------------------------------------------------------------- /lib/validators/sub-validator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * SubValidator interface. 5 | * 6 | * validators are used to validate the configuration file input 7 | * 8 | * @type {module.SubValidator} 9 | */ 10 | module.exports = class SubValidator { 11 | constructor() { 12 | } 13 | 14 | /** 15 | * Validate the given input. 16 | * 17 | * @param input input to be validated. 18 | * Each sub validator tests the properties of this object, if they're defined. 19 | * See corresponding sub validator for specific properties. 20 | * 21 | * @returns Promise, whose resolution is: {string} validation error description (undefined if all's well) 22 | */ 23 | async validate() { 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KNowledgeOnWebScale/walder/fb85bf4b9186dc94001e289ecb14ea3a4100c8f6/logo/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "walder", 3 | "version": "4.1.4", 4 | "main": "./lib/walder", 5 | "scripts": { 6 | "start": "node ./bin/cli.js", 7 | "test": "mocha --recursive \"./test/\" --timeout 60000 " 8 | }, 9 | "bin": { 10 | "walder": "./bin/cli.js" 11 | }, 12 | "dependencies": { 13 | "@comunica/core": "^2.6.8", 14 | "@comunica/query-sparql": "2.5.2", 15 | "@comunica/utils-datasource": "^1.21.1", 16 | "accepts": "^1.3.7", 17 | "axios": "^0.19.0", 18 | "commander": "^5.0.0", 19 | "consolidate": "^0.15.1", 20 | "debug": "~2.6.9", 21 | "express": "~4.17.1", 22 | "front-matter": "^3.1.0", 23 | "fs-extra": "^9.1.0", 24 | "graphql": "^16.4.0", 25 | "graphql-ld": "^1.4.0", 26 | "graphql-ld-comunica": "^1.2.1", 27 | "handlebars": "^4.7.7", 28 | "jade-to-handlebars": "^2.3.0", 29 | "json-refs": "^3.0.15", 30 | "jsonld": "^1.6.2", 31 | "jsonld-context-parser": "^2.1.5", 32 | "jsonpath": "^1.0.2", 33 | "markdown-it": "^10.0.0", 34 | "markdown-it-footnote": "^3.0.3", 35 | "morgan": "~1.10.0", 36 | "n3": "^1.2.0", 37 | "nunjucks": "^3.2.3", 38 | "object-path": "^0.11.4", 39 | "pug": "^2.0.4", 40 | "sparqljson-to-tree": "^2.1.0", 41 | "tmp": "^0.1.0", 42 | "winston": "^3.2.1", 43 | "yaml": "^1.6.0" 44 | }, 45 | "devDependencies": { 46 | "@comunica/logger-pretty": "^2.6.8", 47 | "chai": "^4.3.4", 48 | "cheerio": "^1.0.0-rc.10", 49 | "is-html": "^2.0.0", 50 | "mocha": "^9.0.3", 51 | "supertest": "^6.1.4", 52 | "tinyliquid": "^0.2.34" 53 | }, 54 | "license": "MIT", 55 | "repository": { 56 | "type": "git", 57 | "url": "https://github.com/KNowledgeOnWebScale/walder.git" 58 | }, 59 | "packageManager": "yarn@1.22.11" 60 | } 61 | -------------------------------------------------------------------------------- /test/converters/html-converter.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | const isHTML = require('is-html'); 3 | const cheerio = require("cheerio"); 4 | const HtmlConverter = require('../../lib/converters/html-converter'); 5 | const TemplateLoader = require("../../lib/loaders/template-loader"); 6 | 7 | const EX_1_HTML_INFO = require('../resources/example-data').EX_1_HTML_CONVERTER_HTML_INFO; 8 | const EX_1_DATA = require('../resources/example-data').EX_1_HTML_CONVERTER_DATA; 9 | const EX_2_HTML_INFO = require('../resources/example-data').EX_2_HTML_CONVERTER_HTML_INFO; 10 | const EX_3_HTML_INFO = require('../resources/example-data').EX_3_HTML_CONVERTER_HTML_INFO; 11 | const EX_3_DATA = require('../resources/example-data').EX_3_HTML_CONVERTER_DATA; 12 | const EX_3_OUTPUT = require('../resources/example-data').EX_3_HTML_CONVERTER_OUTPUT; 13 | const EX_5_HTML_INFO = require('../resources/example-data').EX_5_HTML_CONVERTER_HTML_INFO; 14 | const EX_5_OUTPUT = require('../resources/example-data').EX_5_HTML_CONVERTER_OUTPUT; 15 | const EX_6_HTML_INFO = require('../resources/example-data').EX_6_HTML_CONVERTER_HTML_INFO; 16 | const EX_6_DATA = require('../resources/example-data').EX_6_HTML_CONVERTER_DATA; 17 | const EX_6_JSONLD = require('../resources/example-data').EX_6_HTML_CONVERTER_JSONLD; 18 | const EX_7_HTML_INFO = require('../resources/example-data').EX_7_HTML_CONVERTER_HTML_INFO; 19 | const EX_7_DATA = require('../resources/example-data').EX_7_HTML_CONVERTER_DATA; 20 | 21 | describe('HtmlConverter', function () { 22 | 23 | describe('# Functionality', function () { 24 | it('should be able to convert the given JSON to HTML using the given template and engine', async () => { 25 | const templateLoader = new TemplateLoader(); 26 | const converter = new HtmlConverter({templateLoader}); 27 | templateLoader.load(EX_1_HTML_INFO); 28 | const html = await converter.convert(EX_1_HTML_INFO, EX_1_DATA); 29 | isHTML(html).should.be.true; 30 | }); 31 | 32 | it('should be able to convert the given Markdown to HTML', async () => { 33 | const templateLoader = new TemplateLoader(); 34 | const converter = new HtmlConverter({templateLoader}); 35 | templateLoader.load(EX_2_HTML_INFO); 36 | const html = await converter.convert(EX_2_HTML_INFO, null); 37 | isHTML(html).should.be.true; 38 | }); 39 | 40 | it('should be able to convert the given Pug with front matter to HTML', async () => { 41 | const templateLoader = new TemplateLoader(); 42 | const converter = new HtmlConverter({templateLoader}); 43 | templateLoader.load(EX_3_HTML_INFO); 44 | const html = await converter.convert(EX_3_HTML_INFO, EX_3_DATA); 45 | isHTML(html).should.be.true; 46 | html.should.deep.equal(EX_3_OUTPUT); 47 | }); 48 | 49 | it('should be able to convert the given Markdown that extends a liquid layout that in turn also extends a liquid layout to html', async () => { 50 | const templateLoader = new TemplateLoader(); 51 | const converter = new HtmlConverter({templateLoader}); 52 | templateLoader.load(EX_5_HTML_INFO); 53 | const html = await converter.convert(EX_5_HTML_INFO); 54 | isHTML(html).should.be.true; 55 | html.should.deep.equal(EX_5_OUTPUT); 56 | }); 57 | 58 | it('should be able to include query results as JSON-LD in HTML', async () => { 59 | const templateLoader = new TemplateLoader(); 60 | const converter = new HtmlConverter({templateLoader}); 61 | templateLoader.load(EX_6_HTML_INFO); 62 | const html = await converter.convert(EX_6_HTML_INFO, EX_6_DATA, EX_6_JSONLD); 63 | isHTML(html).should.be.true; 64 | const $ = cheerio.load(html); 65 | const scripts = $("script"); 66 | scripts.length.should.equal(1); 67 | const actualJSONLD = JSON.parse(scripts[0].children[0].data); 68 | actualJSONLD.should.deep.equal(EX_6_JSONLD); 69 | }); 70 | 71 | it('should be able to use query results in layout', async () => { 72 | const templateLoader = new TemplateLoader(); 73 | const converter = new HtmlConverter({templateLoader}); 74 | templateLoader.load(EX_7_HTML_INFO); 75 | const html = await converter.convert(EX_7_HTML_INFO, EX_7_DATA, null); 76 | isHTML(html).should.be.true; 77 | html.should.include('
My Title
'); 78 | }); 79 | }) 80 | }); 81 | -------------------------------------------------------------------------------- /test/converters/rdf-converter.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | const RDF_TYPES = require('../../lib/converters/rdf-converter').RDF_TYPES; 3 | const RdfConverter = require('../../lib/converters/rdf-converter').RdfConverter; 4 | const ExampleData = require('../resources/example-data'); 5 | const jsonld = require('jsonld'); 6 | const N3 = require('n3'); 7 | 8 | describe('RdfConverter', function () { 9 | 10 | describe('# Functionality', function () { 11 | it('should be able to convert the given JSON to JSON-LD', async () => { 12 | const data = await new RdfConverter().convert( 13 | RDF_TYPES.JSON_LD, 14 | ExampleData.EX_1_RDF_CONVERTER_DATA, 15 | ExampleData.EX_1_RDF_CONVERTER_GRAPHQLLD); 16 | 17 | // If 'JsonLD' can turn it into N-Quads without errors, then it must be valid JSON-LD 18 | await jsonld.toRDF(data, {format: 'application/n-quads'}, (err, nquads) => { 19 | if (err) { 20 | throw new Error(err); 21 | } 22 | }); 23 | }); 24 | 25 | it('should be able to convert the given JSON to Turtle', async () => { 26 | const data = await new RdfConverter().convert( 27 | RDF_TYPES.TURTLE, 28 | ExampleData.EX_1_RDF_CONVERTER_DATA, 29 | ExampleData.EX_1_RDF_CONVERTER_GRAPHQLLD); 30 | 31 | const parser = new N3.Parser({format: 'Turtle'}); 32 | // If 'N3' can parse it, then it must be valid turtle 33 | parser.parse(data).length.should.be.above(0); 34 | }); 35 | 36 | it('should be able to convert the given JSON to N-Triples', async () => { 37 | const data = await new RdfConverter().convert( 38 | RDF_TYPES.NT, 39 | ExampleData.EX_1_RDF_CONVERTER_DATA, 40 | ExampleData.EX_1_RDF_CONVERTER_GRAPHQLLD); 41 | 42 | const parser = new N3.Parser({format: 'N-Triples'}); 43 | // If 'N3' can parse it, then it must be valid N-triples 44 | parser.parse(data).length.should.be.above(0); 45 | }); 46 | 47 | it('should be able to convert the given JSON to N-Quads', async () => { 48 | const data = await new RdfConverter().convert( 49 | RDF_TYPES.NQ, 50 | ExampleData.EX_1_RDF_CONVERTER_DATA, 51 | ExampleData.EX_1_RDF_CONVERTER_GRAPHQLLD); 52 | 53 | const parser = new N3.Parser({format: 'N-Quads'}); 54 | // If 'N3' can parse it, then it must be valid turtle 55 | parser.parse(data).length.should.be.above(0); 56 | }); 57 | 58 | it('should be able to convert empty JSON to N-Quads', async () => { 59 | const data = await new RdfConverter().convert( 60 | RDF_TYPES.NQ, 61 | ExampleData.EX_2_RDF_CONVERTER_DATA, 62 | ExampleData.EX_2_RDF_CONVERTER_GRAPHQLLD); 63 | 64 | const parser = new N3.Parser({format: 'N-Quads'}); 65 | // If 'N3' can parse it, then it must be valid turtle 66 | parser.parse(data).length.should.be.equal(0); 67 | }); 68 | }) 69 | }); 70 | -------------------------------------------------------------------------------- /test/example/test.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | const request = require('supertest'); 3 | const path = require('path'); 4 | const Walder = require('../../lib/walder'); 5 | 6 | const CONFIG_FILE = '../../example/config.yaml'; 7 | 8 | describe('Example', function () { 9 | 10 | describe('# Favicon', function () { 11 | before('Activating Walder', function () { 12 | const configFile = path.resolve(__dirname, CONFIG_FILE); 13 | const port = 9000; 14 | 15 | this.walder = new Walder(configFile, {port, logging:'error'}); 16 | this.walder.activate(); 17 | }); 18 | 19 | after('Deactivating Walder', function () { 20 | this.walder.deactivate(); 21 | }); 22 | 23 | it('should serve /favicon.ico', function (done) { 24 | request(this.walder.app) 25 | .get('/favicon.ico') 26 | .expect(200) 27 | .end(done); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/loaders/pipe-module-loader.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | const Path = require('path'); 3 | const PipeModuleLoader = require('../../lib/loaders/pipe-module-loader'); 4 | 5 | describe('PipeModuleLoader', function () { 6 | it('should return a list of {pipe functions, parameters}, given a list of pipe module objects {name, source, parameters}', function () { 7 | const pipeModules = [ 8 | { 9 | "name": "filterT", 10 | "source": Path.resolve(__dirname, '../resources/filter-t.js'), 11 | "parameters": [] 12 | }]; 13 | 14 | const pipeFunctions = PipeModuleLoader.load(pipeModules); 15 | 16 | pipeFunctions.should.be.a('Array'); 17 | 18 | pipeFunctions.forEach(pipeFunction => { 19 | pipeFunction.function.should.be.a('function'); 20 | pipeFunction.parameters.should.be.a('Array'); 21 | }) 22 | }) 23 | }); 24 | -------------------------------------------------------------------------------- /test/loaders/template-loader.js: -------------------------------------------------------------------------------- 1 | const TemplateLoader = require("../../lib/loaders/template-loader"); 2 | const fs = require("fs"); 3 | const HTMLInfo = require("../../lib/models/html-info"); 4 | const path = require('path'); 5 | require('chai').should(); 6 | 7 | describe('TemplateLoader', function () { 8 | it('should return a string with the content of a template if it was loaded earlier without reading from the file', function () { 9 | const htmlInfo = new HTMLInfo("pug", "test/resources/views/text.pug"); 10 | const templateLoader = new TemplateLoader(); 11 | templateLoader.load(htmlInfo); 12 | templateLoader.getTemplateFromCache(htmlInfo).body.should.contain("Hello World!"); 13 | }); 14 | 15 | it('should return a string with the content of a layout, when a template extending that layout has been cached earlier', function () { 16 | const htmlInfo = new HTMLInfo("pug", "test/resources/views/layout_test.pug", "test", "test/resources/layouts"); 17 | const templateLoader = new TemplateLoader(); 18 | templateLoader.load(htmlInfo); 19 | templateLoader.getTemplateFromCache(new HTMLInfo("pug", path.join(htmlInfo.layoutsDir, "simple-layout.pug"))).body.should.contain("Hello World!"); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/parsers/config-file-parser.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | const expect = require('chai').expect; 3 | 4 | const parseConfigFile = require('../../lib/parsers/config-file-parser'); 5 | const path = require('path'); 6 | 7 | describe('ConfigFileParser', function () { 8 | 9 | it('should be able to resolve the references in a config file and give an equal config file', async function() { 10 | const regularFile = path.resolve(__dirname, '../resources/multiple-config-files/config-without-refs.yaml'); 11 | const referenceFile = path.resolve(__dirname, '../resources/multiple-config-files/config-with-refs.yaml'); 12 | 13 | const regularYamlData = await parseConfigFile(regularFile); 14 | const referenceYamlData = await parseConfigFile(referenceFile); 15 | JSON.stringify(regularYamlData).should.eql(JSON.stringify(referenceYamlData)); 16 | }); 17 | 18 | it('should give an error because paths of refs are invalid', async function() { 19 | const file = path.resolve(__dirname, '../resources/multiple-config-files/config-with-invalid-refs.yaml'); 20 | let actualError = null; 21 | 22 | try { 23 | await parseConfigFile(file); 24 | } catch (err) { 25 | actualError = err; 26 | } finally { 27 | expect(actualError).to.not.be.null; 28 | } 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/parsers/data-sources-parser.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | 3 | const parseDataSources = require('../../lib/parsers/data-sources-parser'); 4 | const GraphQLLDHandler = require('../../lib/handlers/graphql-ld-handler'); 5 | const SPARQLHandler = require('../../lib/handlers/sparql-handler'); 6 | const createLogger = require('../../lib/create-logger'); 7 | const path = require('path'); 8 | const YAML = require('yaml'); 9 | const fs = require('fs'); 10 | 11 | describe('DataSourcesParser', function () { 12 | 13 | describe('# GRAPHQL-LD query', function () { 14 | it('should be able to execute query to get list of data sources', async function () { 15 | const file = fs.readFileSync(path.resolve(__dirname, '../resources/query-datasources/single-query/graphql-ld/config.yaml'), 'utf8'); 16 | const pipeModulesPath = path.resolve(__dirname, '../resources/query-datasources/single-query/graphql-ld'); 17 | this.yamlData = YAML.parse(file); 18 | let sparqlHandler; 19 | const graphQLLDHandler = new GraphQLLDHandler(createLogger(), pipeModulesPath, () => {return sparqlHandler}); 20 | sparqlHandler = new SPARQLHandler(createLogger(), pipeModulesPath, graphQLLDHandler); 21 | 22 | const actual = await parseDataSources({dataSources: this.yamlData['x-walder-datasources'], graphQLLDHandler, sparqlHandler, pipeModulesPath}); 23 | const expected = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../resources/query-datasources/single-query/graphql-ld/expected-output.json'))); 24 | actual.should.eql(expected); 25 | }); 26 | 27 | it('should be able to use additional data sources', async function () { 28 | const file = fs.readFileSync(path.resolve(__dirname, '../resources/query-datasources/additional-datasources/graphql-ld/config.yaml'), 'utf8'); 29 | const pipeModulesPath = path.resolve(__dirname, '../resources/query-datasources/additional-datasources/graphql-ld'); 30 | this.yamlData = YAML.parse(file); 31 | let sparqlHandler; 32 | const graphQLLDHandler = new GraphQLLDHandler(createLogger(), pipeModulesPath, () => {return sparqlHandler}); 33 | sparqlHandler = new SPARQLHandler(createLogger(), pipeModulesPath, graphQLLDHandler); 34 | 35 | const actual = await parseDataSources({dataSources:this.yamlData['x-walder-datasources'], graphQLLDHandler, sparqlHandler, pipeModulesPath}); 36 | const expected = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../resources/query-datasources/additional-datasources/graphql-ld/expected-output.json'))); 37 | actual.should.eql(expected); 38 | }); 39 | }); 40 | 41 | describe('# SPARQL query', function () { 42 | it('should be able to execute query to get list of data sources', async function () { 43 | const file = fs.readFileSync(path.resolve(__dirname, '../resources/query-datasources/single-query/sparql/config.yaml'), 'utf8'); 44 | const pipeModulesPath = path.resolve(__dirname, '../resources/query-datasources/single-query/sparql/'); 45 | this.yamlData = YAML.parse(file); 46 | let sparqlHandler; 47 | const graphQLLDHandler = new GraphQLLDHandler(createLogger(), pipeModulesPath, () => {return sparqlHandler}); 48 | sparqlHandler = new SPARQLHandler(createLogger(), pipeModulesPath, graphQLLDHandler); 49 | 50 | const actual = await parseDataSources({dataSources: this.yamlData['x-walder-datasources'], graphQLLDHandler, sparqlHandler, pipeModulesPath}); 51 | const expected = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../resources/query-datasources/single-query/sparql/expected-output.json'))); 52 | actual.should.eql(expected); 53 | }); 54 | 55 | it('should be able to use additional data sources', async function () { 56 | const file = fs.readFileSync(path.resolve(__dirname, '../resources/query-datasources/additional-datasources/sparql/config.yaml'), 'utf8'); 57 | const pipeModulesPath = path.resolve(__dirname, '../resources/query-datasources/additional-datasources/sparql'); 58 | this.yamlData = YAML.parse(file); 59 | let sparqlHandler; 60 | const graphQLLDHandler = new GraphQLLDHandler(createLogger(), pipeModulesPath, () => {return sparqlHandler}); 61 | sparqlHandler = new SPARQLHandler(createLogger(), pipeModulesPath, graphQLLDHandler); 62 | 63 | const actual = await parseDataSources({dataSources:this.yamlData['x-walder-datasources'], graphQLLDHandler, sparqlHandler, pipeModulesPath}); 64 | const expected = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../resources/query-datasources/additional-datasources/sparql/expected-output.json'))); 65 | actual.should.eql(expected); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/parsers/html-parser.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | const parseHTML = require('../../lib/parsers/html-parser'); 3 | const YAML = require('yaml'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const CONFIG_FILE = '../resources/config.yaml'; 7 | const NJK_CONFIG_FILE = '../resources/config-njk.yaml'; 8 | 9 | describe('HtmlParser', function () { 10 | { 11 | before(function () { 12 | const file = fs.readFileSync(path.resolve(__dirname, CONFIG_FILE), 'utf8'); 13 | const yamlData = YAML.parse(file); 14 | this.output = parseHTML(yamlData.paths['/movies/{actor}']['get'].responses, '', ''); 15 | }); 16 | 17 | describe('# Functionality', function () { 18 | it('should be able to parse, extract and format html information correctly from a YAML config file', function () { 19 | this.output.should.eql( 20 | { 21 | '200': { 22 | "engine": "pug", 23 | "file": path.resolve('', 'movies.pug'), 24 | "description": "list of movies", 25 | "layoutsDir": "" 26 | } 27 | } 28 | ) 29 | }); 30 | 31 | it('should be able to recognize .njk as a nunjuck file', function () { 32 | const njkFile = fs.readFileSync(path.resolve(__dirname, NJK_CONFIG_FILE), 'utf8'); 33 | const njkYamlData = YAML.parse(njkFile); 34 | parseHTML(njkYamlData.paths['/njk']['get'].responses, '', '')['200'].engine.should.eql('nunjuck'); 35 | }); 36 | 37 | }); 38 | 39 | describe('# Output format', function () { 40 | it('output object should have {statusCode: {engine, file}} properties', function () { 41 | this.output.should.have.property('200'); 42 | this.output['200'].should.have.property('engine'); 43 | this.output['200'].should.have.property('file'); 44 | }) 45 | }); 46 | 47 | } 48 | }); 49 | -------------------------------------------------------------------------------- /test/parsers/parameter-parser.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | 3 | const parseParameter = require('../../lib/parsers/parameter-parser'); 4 | const YAML = require('yaml'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const CONFIG_FILE = '../resources/config.yaml'; 8 | 9 | describe('ParameterParser', function () { 10 | { 11 | before(function () { 12 | const file = fs.readFileSync(path.resolve(__dirname, CONFIG_FILE), 'utf8'); 13 | const yamlData = YAML.parse(file); 14 | 15 | this.output = parseParameter(yamlData.paths['/movies/{actor}']['get'].parameters); 16 | }); 17 | 18 | describe('# Functionality', function () { 19 | it('should be able to parse, extract and format parameter information correctly from a YAML config file', function () { 20 | this.output.should.eql( 21 | { 22 | actor: { 23 | in: 'path', 24 | required: true, 25 | type: 'string', 26 | maximum: undefined, 27 | minimum: undefined, 28 | description: 'The target actor' 29 | } 30 | } 31 | ) 32 | }); 33 | }); 34 | 35 | describe('# Output format', function () { 36 | it('output object should have { parameter: { required, type, description }} properties', function () { 37 | this.output.should.have.property('actor'); 38 | this.output['actor'].should.have.property('required'); 39 | this.output['actor'].should.have.property('type'); 40 | this.output['actor'].should.have.property('description'); 41 | }) 42 | }); 43 | 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /test/parsers/pipe-module-parser.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | const parsePipeModule = require('../../lib/parsers/pipe-module-parser'); 3 | const Path = require('path'); 4 | const CONFIG_FILE = '../resources/config.yaml'; 5 | const YAML = require('yaml'); 6 | const fs = require('fs'); 7 | 8 | describe('PipeModuleParser', function () { 9 | 10 | before(function () { 11 | const file = fs.readFileSync(Path.resolve(__dirname, CONFIG_FILE), 'utf8'); 12 | this.yamlData = YAML.parse(file); 13 | 14 | this.output = parsePipeModule(this.yamlData.paths['/movies/{actor}']['get']['x-walder-postprocessing'], 15 | Path.resolve(this.yamlData['x-walder-resources'].root, this.yamlData['x-walder-resources']['pipe-modules'])); 16 | this.output_with_param = parsePipeModule(this.yamlData.paths['/movies/{actor}/postprocessed']['get']['x-walder-postprocessing'], 17 | Path.resolve(this.yamlData['x-walder-resources'].root, this.yamlData['x-walder-resources']['pipe-modules'])); 18 | }); 19 | 20 | describe('# Functionality', function () { 21 | it('should be able to parse and extract pipe modules correctly from a YAML config file', function () { 22 | this.output.should.eql( 23 | [ 24 | { 25 | "name": "filterT", 26 | "source": Path.resolve(this.yamlData['x-walder-resources'].root, this.yamlData['x-walder-resources']['pipe-modules'], 'filter-t.js'), 27 | "parameters": [], 28 | queryResults: undefined 29 | }] 30 | ) 31 | }); 32 | it('should be able to parse and extract pipe modules correctly from a YAML config file', function () { 33 | this.output_with_param.should.eql( 34 | [ 35 | { 36 | "name": "filterT_withParameters", 37 | "source": Path.resolve(this.yamlData['x-walder-resources'].root, this.yamlData['x-walder-resources']['pipe-modules'], 'filter-t-with-parameters.js'), 38 | "parameters": ["_data", true], 39 | queryResults: undefined 40 | }] 41 | ) 42 | }); 43 | }); 44 | 45 | describe('# Output format', function () { 46 | it('output should be a list of objects with {name, source, parameters} properties', function () { 47 | this.output.forEach((o) => { 48 | o.should.have.property('name'); 49 | o.should.have.property('source'); 50 | o.should.have.property('parameters'); 51 | }) 52 | }) 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/parsers/resource-parser.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | 3 | const YAML = require('yaml'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const parseResources = require('../../lib/parsers/resource-parser'); 7 | 8 | const CONFIG_FILE = 'test/resources/config.yaml'; 9 | const CONFIG_FILE_NO_RESOURCES = 'test/resources/config-no-resources.yaml'; 10 | const CONFIG_FILE_PARTIAL_RESOURCES = 'test/resources/config-partial-resources.yaml'; 11 | const CONFIG_FILE_PATH = 'test/resources/config-resources-path.yaml'; 12 | 13 | describe('ResourceParser', function () { 14 | 15 | afterEach(function () { 16 | // Remove 'public' directory created by the ResourceParser 17 | if(fs.existsSync('public')) { 18 | fs.rmdirSync('public'); 19 | } 20 | }); 21 | 22 | describe('# Functionality', function () { 23 | it('should be able to parse, extract and format resources information correctly from a YAML config file', function () { 24 | const file = fs.readFileSync(path.resolve(CONFIG_FILE), 'utf8'); 25 | const yamlData = YAML.parse(file); 26 | const cwd = path.resolve(__dirname, '../resources'); 27 | 28 | const output = parseResources(yamlData['x-walder-resources'], cwd); 29 | 30 | output.should.eql( 31 | { 32 | "root": path.resolve('test/resources'), 33 | "views": path.resolve('test/resources', 'views'), 34 | "pipe-modules": path.resolve('test/resources', 'pipe-modules'), 35 | "public": path.resolve('test/resources', 'public'), 36 | "layouts": path.resolve('test/resources', 'layouts'), 37 | } 38 | ) 39 | }); 40 | 41 | it('should be able to handle config files without a resources section and use default values instead', function () { 42 | const file = fs.readFileSync(path.resolve(CONFIG_FILE_NO_RESOURCES), 'utf8'); 43 | const yamlData = YAML.parse(file); 44 | const cwd = path.resolve(__dirname, '../resources'); 45 | 46 | const output = parseResources(yamlData['x-walder-resources'], cwd); 47 | output.should.eql({ 48 | root: cwd, 49 | views: path.resolve(cwd, 'views'), 50 | 'pipe-modules': path.resolve(cwd, 'pipe-modules'), 51 | public: path.resolve(cwd, 'public'), 52 | layouts: path.resolve(cwd, 'layouts') 53 | }) 54 | }); 55 | 56 | it('should be able to handle empty resource fields and use default values instead', function () { 57 | const file = fs.readFileSync(path.resolve(CONFIG_FILE_PARTIAL_RESOURCES), 'utf8'); 58 | const yamlData = YAML.parse(file); 59 | const cwd = path.resolve(__dirname, '../resources'); 60 | 61 | const output = parseResources(yamlData['x-walder-resources'], cwd); 62 | output.should.eql({ 63 | root: cwd, 64 | views: path.resolve(cwd, 'views'), 65 | 'pipe-modules': path.resolve(cwd, 'pipe-modules'), 66 | public: path.resolve(cwd, 'public'), 67 | layouts: path.resolve(cwd, 'layouts') 68 | }) 69 | }); 70 | 71 | it('should create a public directory with the given path if it does not exist yet', function () { 72 | const file = fs.readFileSync(path.resolve(CONFIG_FILE_NO_RESOURCES), 'utf8'); 73 | const yamlData = YAML.parse(file); 74 | const cwd = path.resolve(__dirname, '../resources'); 75 | 76 | const output = parseResources(yamlData, cwd); 77 | 78 | fs.existsSync(output.public).should.be.true; 79 | }); 80 | 81 | it('should be able to convert "path" to "root"', function () { 82 | const file = fs.readFileSync(path.resolve(CONFIG_FILE_PATH), 'utf8'); 83 | const yamlData = YAML.parse(file); 84 | const cwd = path.resolve(__dirname, '../resources'); 85 | 86 | const output = parseResources(yamlData['x-walder-resources'], cwd); 87 | 88 | output.should.eql( 89 | { 90 | "root": path.resolve('test/resources'), 91 | "views": path.resolve('test/resources', 'views'), 92 | "pipe-modules": path.resolve('test/resources', 'pipe-modules'), 93 | "public": path.resolve('test/resources', 'public'), 94 | "layouts": path.resolve('test/resources', 'layouts'), 95 | } 96 | ) 97 | }); 98 | }); 99 | 100 | describe('# Output format', function () { 101 | let output; 102 | 103 | before(() => { 104 | const file = fs.readFileSync(path.resolve(CONFIG_FILE), 'utf8'); 105 | const yamlData = YAML.parse(file); 106 | const cwd = path.resolve(__dirname, '../resources'); 107 | 108 | output = parseResources(yamlData['x-walder-resources'], cwd); 109 | }); 110 | 111 | it('output object should have {path, views, pipe-modules, public} properties', function () { 112 | output.should.have.property('root'); 113 | output.should.have.property('views'); 114 | output.should.have.property('pipe-modules'); 115 | output.should.have.property('public'); 116 | }); 117 | 118 | it('output object\'s values should always be absolute paths', function () { 119 | path.isAbsolute(output.root).should.be.true; 120 | path.isAbsolute(output.views).should.be.true; 121 | path.isAbsolute(output['pipe-modules']).should.be.true; 122 | path.isAbsolute(output.public).should.be.true; 123 | }); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /test/parsers/route-parser.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const parseRoute = require('../../lib/parsers/route-parser'); 6 | 7 | const CONFIG_FILE = '../resources/config.yaml'; 8 | 9 | describe('RouteParser', function () { 10 | { 11 | before(function () { 12 | const file = fs.readFileSync(path.resolve(__dirname, CONFIG_FILE), 'utf8'); 13 | 14 | this.output = parseRoute('/movies/{actor}', 'get'); 15 | }); 16 | 17 | describe('# Functionality', function () { 18 | it('should be able to parse, extract and format route information correctly from a YAML config file', function () { 19 | this.output.should.eql( 20 | { 21 | "path": "/movies/:actor", 22 | "method": "get" 23 | } 24 | ) 25 | }); 26 | }); 27 | 28 | describe('# Output format', function () { 29 | it('output object should have {path, method} properties', function () { 30 | this.output.should.have.property('path'); 31 | this.output.should.have.property('method'); 32 | }) 33 | }); 34 | 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /test/resources/book.pug: -------------------------------------------------------------------------------- 1 | --- 2 | title: My book 3 | --- 4 | h1 #{title} 5 | div #{description} 6 | -------------------------------------------------------------------------------- /test/resources/conf-x-walder-errors-handlebars.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: ./ 7 | views: views 8 | pipe-modules: pipeModules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /bad_query: 14 | get: 15 | summary: Returns a status 500 error page - bad query. 16 | x-walder-query: 17 | graphql-query: > 18 | { 19 | id @single 20 | ... { # This will cause an error 21 | starring(label: "Brad Pitt") @single 22 | } 23 | } 24 | json-ld-context: > 25 | { 26 | "@context": { 27 | "Film": "http://dbpedia.org/ontology/Film", 28 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 29 | "starring": "http://dbpedia.org/ontology/starring" 30 | } 31 | } 32 | responses: 33 | 200: 34 | description: list of movies 35 | x-walder-input-text/html: movies.handlebars 36 | x-walder-errors: 37 | 404: 38 | description: page not found error 39 | x-walder-input-text/html: error404alt.handlebars 40 | 500: 41 | description: internal server error 42 | x-walder-input-text/html: error500alt.handlebars 43 | 44 | -------------------------------------------------------------------------------- /test/resources/conf-x-walder-errors-md.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: ./ 7 | views: views 8 | pipe-modules: pipeModules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /bad_query: 14 | get: 15 | summary: Returns a status 500 error page - bad query. 16 | x-walder-query: 17 | graphql-query: > 18 | { 19 | id @single 20 | ... { # This will cause an error 21 | starring(label: "Brad Pitt") @single 22 | } 23 | } 24 | json-ld-context: > 25 | { 26 | "@context": { 27 | "Film": "http://dbpedia.org/ontology/Film", 28 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 29 | "starring": "http://dbpedia.org/ontology/starring" 30 | } 31 | } 32 | responses: 33 | 200: 34 | description: list of movies 35 | x-walder-input-text/html: movies.handlebars 36 | x-walder-errors: 37 | 404: 38 | description: page not found error 39 | x-walder-input-text/html: error404alt.md 40 | 500: 41 | description: internal server error 42 | x-walder-input-text/html: error500alt.md 43 | 44 | -------------------------------------------------------------------------------- /test/resources/conf-x-walder-errors-pug.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: ./ 7 | views: views 8 | pipe-modules: pipeModules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /bad_query: 14 | get: 15 | summary: Returns a status 500 error page - bad query. 16 | x-walder-query: 17 | graphql-query: > 18 | { 19 | id @single 20 | ... { # This will cause an error 21 | starring(label: "Brad Pitt") @single 22 | } 23 | } 24 | json-ld-context: > 25 | { 26 | "@context": { 27 | "Film": "http://dbpedia.org/ontology/Film", 28 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 29 | "starring": "http://dbpedia.org/ontology/starring" 30 | } 31 | } 32 | responses: 33 | 200: 34 | description: list of movies 35 | x-walder-input-text/html: movies.handlebars 36 | x-walder-errors: 37 | 404: 38 | description: page not found error 39 | x-walder-input-text/html: error404alt.pug 40 | 500: 41 | description: internal server error 42 | x-walder-input-text/html: error500alt.pug 43 | 44 | -------------------------------------------------------------------------------- /test/resources/config-errors.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | pipe-modules: pipe-modules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /movies/brad_pitt: 14 | get: 15 | summary: Returns a paginated list of all movies Brad Pitt stars in 16 | parameters: 17 | - in: query 18 | name: page 19 | required: true 20 | schema: 21 | type: integer 22 | minimum: 0 23 | description: The page that must be shown. 24 | - in: query 25 | name: limit 26 | required: true 27 | schema: 28 | type: integer 29 | minimum: 1 30 | description: Maximum number of elements to be shown on the current page. 31 | x-walder-query: 32 | graphql-query: > 33 | { 34 | id @single 35 | ... on Film{ 36 | starring(label: "Brad Pitt" first: $limit offset: $offset) @single 37 | } 38 | } 39 | json-ld-context: > 40 | { 41 | "@context": { 42 | "Film": "http://dbpedia.org/ontology/Film", 43 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 44 | "starring": "http://dbpedia.org/ontology/starring" 45 | } 46 | } 47 | responses: 48 | 200: 49 | description: list of movies 50 | x-walder-input-text/html: movies.handlebars 51 | /movies/{actor}: 52 | get: 53 | summary: Returns a list of the all movies the given actor stars in 54 | parameters: 55 | - in: path 56 | name: actor 57 | required: true 58 | schema: 59 | type: string 60 | description: The target actor 61 | x-walder-query: 62 | graphql-query: > 63 | { 64 | id @single 65 | ... on Film { 66 | starring(label: $actor) @single 67 | } 68 | } 69 | json-ld-context: > 70 | { 71 | "@context": { 72 | "Film": "http://dbpedia.org/ontology/Film", 73 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 74 | "starring": "http://dbpedia.org/ontology/starring" 75 | } 76 | } 77 | x-walder-postprocessing: 78 | filterT: 79 | source: filter-t-bad.js 80 | responses: 81 | 200: 82 | description: list of movies 83 | x-walder-input-text/html: movies.pug 84 | /badmovies/{actor}: 85 | get: 86 | summary: Returns a list of the all movies the given actor stars in 87 | parameters: 88 | - in: path 89 | name: actor 90 | required: true 91 | schema: 92 | type: string 93 | description: The target actor 94 | x-walder-query: 95 | graphql-query: > 96 | { 97 | id @single 98 | ... { # <---- THIS WILL THROW AN ERROR 99 | starring(label: $actor) @single 100 | } 101 | } 102 | json-ld-context: > 103 | { 104 | "@context": { 105 | "Film": "http://dbpedia.org/ontology/Film", 106 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 107 | "starring": "http://dbpedia.org/ontology/starring" 108 | } 109 | } 110 | x-walder-postprocessing: 111 | filterT: 112 | source: filter-t-bad.js 113 | responses: 114 | 200: 115 | description: list of movies 116 | x-walder-input-text/html: movies.pug 117 | x-walder-errors: 118 | 404: 119 | description: page not found error 120 | x-walder-input-text/html: error404.html 121 | 500: 122 | description: internal server error 123 | x-walder-input-text/html: error500.html 124 | -------------------------------------------------------------------------------- /test/resources/config-frontmatter.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | pipe-modules: pipe-modules 9 | public: public 10 | paths: 11 | /text-fm: 12 | get: 13 | summary: FrontMatter test in view template 14 | responses: 15 | 200: 16 | description: Page with FrontMatter attribute rendered from view template 17 | x-walder-input-text/html: text-fm.pug 18 | /text-fm-with-layout: 19 | get: 20 | summary: FrontMatter test in layout template 21 | responses: 22 | 200: 23 | description: Page with FrontMatter attribute rendered from layout template 24 | x-walder-input-text/html: text-fm-with-layout.pug 25 | -------------------------------------------------------------------------------- /test/resources/config-htmlvalidator.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | pipe-modules: pipe-modules 9 | public: public 10 | paths: 11 | /simple: 12 | get: 13 | summary: A simple path 14 | responses: 15 | 200: 16 | description: A simple response 17 | x-walder-input-text/html: text.pug 18 | /missing-html: 19 | get: 20 | summary: Show some text 21 | responses: 22 | 200: 23 | description: Some text (from a template) 24 | x-walder-input-text/html: missing-template.pug 25 | 300: 26 | description: Some text (from html) 27 | x-walder-input-text/html: missing-html.html 28 | /missing-layout: 29 | get: 30 | summary: Show some text 31 | responses: 32 | 200: 33 | description: Some text (from a template) 34 | x-walder-input-text/html: missing-layout.pug 35 | /invalid-frontmatter: 36 | get: 37 | summary: Show some text 38 | responses: 39 | 200: 40 | description: Some text (from a template) 41 | x-walder-input-text/html: invalid-frontmatter.pug 42 | x-walder-errors: 43 | 404: 44 | description: page not found error 45 | x-walder-input-text/html: error404.html 46 | 500: 47 | description: internal server error 48 | x-walder-input-text/html: error500.html 49 | -------------------------------------------------------------------------------- /test/resources/config-image.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: ./views 8 | public: ./public 9 | paths: 10 | x-walder-errors: 11 | 404: 12 | description: page not found error 13 | x-walder-input-text/html: error404.html 14 | 500: 15 | description: internal server error 16 | x-walder-input-text/html: error500.html 17 | -------------------------------------------------------------------------------- /test/resources/config-lenient.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: # OpenAPI metadata 3 | title: "Test lenient" 4 | version: 0.0.1 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | 9 | paths: 10 | /bad-jsonld: 11 | get: 12 | summary: Returns contact information. 13 | x-walder-query: 14 | graphql-query: > 15 | { 16 | id(_:KNOWS) 17 | location @single { 18 | name @single 19 | address @single 20 | } 21 | } 22 | json-ld-context: > 23 | { 24 | "@context": { 25 | "schema": "http://schema.org/", 26 | "location": "schema:location", 27 | "address": "schema:address", 28 | "name": "schema:name", 29 | "KNOWS": "https://data.knows.idlab.ugent.be/person/office/#" 30 | } 31 | } 32 | datasources: 33 | sources: 34 | - https://data.knows.idlab.ugent.be/person/office/# 35 | - https://data.vlaanderen.be/id/adres/20470097 36 | responses: 37 | 200: 38 | x-walder-input-text/html: lenient.pug 39 | x-walder-errors: 40 | 404: 41 | description: page not found error 42 | x-walder-input-text/html: error404.html 43 | 500: 44 | description: internal server error 45 | x-walder-input-text/html: error500.html 46 | -------------------------------------------------------------------------------- /test/resources/config-missing-default-error-pages.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | pipe-modules: pipe-modules 9 | public: public 10 | paths: 11 | /simple: 12 | get: 13 | summary: A simple path 14 | responses: 15 | 200: 16 | description: A simple response 17 | x-walder-input-text/html: text.pug 18 | x-walder-errors: 19 | 404: 20 | description: page not found error 21 | x-walder-input-text/html: error404notExisting.html 22 | 500: 23 | description: internal server error 24 | x-walder-input-text/html: error500notExisting.html 25 | -------------------------------------------------------------------------------- /test/resources/config-njk.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | pipe-modules: pipe-modules 9 | public: public 10 | paths: 11 | /njk: 12 | get: 13 | summary: Show some text 14 | responses: 15 | 200: 16 | description: Some text (from a template) 17 | x-walder-input-text/html: test.njk -------------------------------------------------------------------------------- /test/resources/config-no-query.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | paths: 9 | /: 10 | get: 11 | summary: Show text 12 | responses: 13 | 200: 14 | description: a text 15 | x-walder-input-text/html: text.pug 16 | x-walder-errors: 17 | 404: 18 | description: page not found error 19 | x-walder-input-text/html: error404.html 20 | 500: 21 | description: internal server error 22 | x-walder-input-text/html: error500.html 23 | -------------------------------------------------------------------------------- /test/resources/config-no-resources.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-datasources: 6 | - http://dbpedia.org/sparql 7 | paths: 8 | /movies/{actor}: 9 | get: 10 | summary: Returns a list of the all movies the given actor stars in 11 | parameters: 12 | - in: path 13 | name: actor 14 | required: true 15 | schema: 16 | type: string 17 | description: The target actor 18 | x-walder-query: 19 | graphql-query: > 20 | { 21 | id @single 22 | ... on Film { 23 | starring(label: $actor) @single 24 | } 25 | } 26 | json-ld-context: > 27 | { 28 | "@context": { 29 | "Film": "http://dbpedia.org/ontology/Film", 30 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 31 | "starring": "http://dbpedia.org/ontology/starring" 32 | } 33 | } 34 | x-walder-postprocessing: 35 | filterT: 36 | source: filter-t.js 37 | responses: 38 | 200: 39 | description: a list 40 | x-walder-input-text/html: list.pug 41 | x-walder-errors: 42 | 404: 43 | description: page not found error 44 | x-walder-input-text/html: error404.html 45 | 500: 46 | description: internal server error 47 | x-walder-input-text/html: error500.html 48 | -------------------------------------------------------------------------------- /test/resources/config-partial-resources.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | pipe-modules: pipe-modules 8 | public: public 9 | x-walder-datasources: 10 | - http://dbpedia.org/sparql 11 | paths: 12 | /movies/{actor}: 13 | get: 14 | summary: Returns a list of the all movies the given actor stars in 15 | parameters: 16 | - in: path 17 | name: actor 18 | required: true 19 | schema: 20 | type: string 21 | description: The target actor 22 | x-walder-query: 23 | graphql-query: > 24 | { 25 | id @single 26 | ... on Film { 27 | starring(label: $actor) @single 28 | } 29 | } 30 | json-ld-context: > 31 | { 32 | "@context": { 33 | "Film": "http://dbpedia.org/ontology/Film", 34 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 35 | "starring": "http://dbpedia.org/ontology/starring" 36 | } 37 | } 38 | x-walder-postprocessing: 39 | filterT: 40 | source: filter-t.js 41 | responses: 42 | 200: 43 | description: a list 44 | x-walder-input-text/html: list.pug 45 | x-walder-errors: 46 | 404: 47 | description: page not found error 48 | x-walder-input-text/html: error404.html 49 | 500: 50 | description: internal server error 51 | x-walder-input-text/html: error500.html 52 | -------------------------------------------------------------------------------- /test/resources/config-resources-path.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | pipe-modules: pipe-modules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /movies2/{actor}: 14 | get: 15 | summary: Returns a list of the all movies the given actor stars in 16 | parameters: 17 | - in: path 18 | name: actor 19 | required: true 20 | schema: 21 | type: string 22 | description: The target actor 23 | x-walder-query: 24 | graphql-query: > 25 | { 26 | id @single 27 | ... on Film { 28 | starring(label: $actor) @single 29 | } 30 | } 31 | json-ld-context: > 32 | { 33 | "Film": "http://dbpedia.org/ontology/Film", 34 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 35 | "starring": "http://dbpedia.org/ontology/starring" 36 | } 37 | responses: 38 | 200: 39 | description: list of movies 40 | x-walder-input-text/html: movies.pug 41 | -------------------------------------------------------------------------------- /test/resources/config-two-path-parameters.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | pipe-modules: pipe-modules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /season/{team}/{year}: 14 | get: 15 | parameters: 16 | - in: path 17 | name: team 18 | required: true 19 | schema: 20 | type: string 21 | description: The target team 22 | - in: path 23 | name: year 24 | required: true 25 | schema: 26 | type: string 27 | description: The target year 28 | x-walder-query: 29 | graphql-query: > 30 | { 31 | id @single 32 | type(_:Season) 33 | team(_: $team) 34 | year(_: $year) 35 | abstract @single 36 | } 37 | json-ld-context: > 38 | { 39 | "Season": "http://dbpedia.org/ontology/NCAATeamSeason", 40 | "type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", 41 | "team": {"@id": "http://dbpedia.org/property/team", "@language": "en"}, 42 | "year": {"@id": "http://dbpedia.org/ontology/year", "@type": "http://www.w3.org/2001/XMLSchema#gYear"}, 43 | "abstract": "http://dbpedia.org/ontology/abstract" 44 | } 45 | 46 | /season-2/{year}: 47 | get: 48 | parameters: 49 | - in: path 50 | name: year 51 | required: true 52 | schema: 53 | type: integer 54 | minimum: 1869 55 | maximum: 1870 56 | description: The target year 57 | x-walder-query: 58 | graphql-query: > 59 | { 60 | id @single 61 | type(_:Season) 62 | year(_: $year) 63 | abstract @single 64 | } 65 | json-ld-context: > 66 | { 67 | "Season": "http://dbpedia.org/ontology/NCAATeamSeason", 68 | "type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", 69 | "team": {"@id": "http://dbpedia.org/property/team", "@language": "en"}, 70 | "year": {"@id": "http://dbpedia.org/ontology/year", "@type": "http://www.w3.org/2001/XMLSchema#gYear"}, 71 | "abstract": "http://dbpedia.org/ontology/abstract" 72 | } 73 | 74 | x-walder-errors: 75 | 404: 76 | description: page not found error 77 | x-walder-input-text/html: error404.html 78 | 500: 79 | description: internal server error 80 | x-walder-input-text/html: error500.html 81 | -------------------------------------------------------------------------------- /test/resources/filter-t.js: -------------------------------------------------------------------------------- 1 | module.exports.filterT = (data) => { 2 | let filteredData = []; 3 | for (const o of data) { 4 | if (o.id.value.match(/T/)) { 5 | filteredData.push(o); 6 | } 7 | } 8 | return filteredData; 9 | }; 10 | 11 | module.exports.filterTSparql = (data) => { 12 | const filteredData = []; 13 | for (const quad of data) { 14 | if (quad.subject.value.match(/T/)) { 15 | filteredData.push(quad); 16 | } 17 | } 18 | return filteredData; 19 | }; 20 | -------------------------------------------------------------------------------- /test/resources/layout-in-layout-test/layout-bottom.liquid: -------------------------------------------------------------------------------- 1 | {{ content }} -------------------------------------------------------------------------------- /test/resources/layout-in-layout-test/layout-top.liquid: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layout-bottom.liquid 3 | --- 4 | {{content}} -------------------------------------------------------------------------------- /test/resources/layout-in-layout-test/page.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layout-top.liquid 3 | --- 4 | test -------------------------------------------------------------------------------- /test/resources/layout-query-results-test/layouts/my-layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title Layout 5 | body 6 | div #{article.title} 7 | div !{content} 8 | -------------------------------------------------------------------------------- /test/resources/layout-query-results-test/views/text.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: my-layout.pug 3 | --- 4 | 5 | # Introduction 6 | 7 | This is an introduction to something cool. 8 | -------------------------------------------------------------------------------- /test/resources/layouts-test/config.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | pipe-modules: pipe-modules 9 | public: public 10 | paths: 11 | /: 12 | get: 13 | summary: Returns simple page based on Markdown with a layout in Pug 14 | responses: 15 | 200: 16 | description: Simple page 17 | x-walder-input-text/html: text.md 18 | -------------------------------------------------------------------------------- /test/resources/layouts-test/expected-output.html: -------------------------------------------------------------------------------- 1 | Layout

Introduction

2 |

This is an introduction to something cool.

3 | 4 | -------------------------------------------------------------------------------- /test/resources/layouts-test/layouts/my-layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title Layout 5 | body !{content} 6 | -------------------------------------------------------------------------------- /test/resources/layouts-test/views/text.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: my-layout.pug 3 | --- 4 | 5 | # Introduction 6 | 7 | This is an introduction to something cool. 8 | -------------------------------------------------------------------------------- /test/resources/layouts/error-layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title Error layout 5 | body !{content} 6 | -------------------------------------------------------------------------------- /test/resources/layouts/layout-fm.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | if a2 5 | title #{a2} 6 | body !{content} 7 | -------------------------------------------------------------------------------- /test/resources/layouts/simple-layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | body !{content} 4 | | Hello World! 5 | -------------------------------------------------------------------------------- /test/resources/movies.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | body 6 | h1 Movies 7 | div 8 | ul 9 | each val in data 10 | li: a(href=val.id)= val.id -------------------------------------------------------------------------------- /test/resources/multiple-config-files/config-with-invalid-refs.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | pipe-modules: pipe-modules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /movies/{actor}: 14 | $ref: './invalid/paths/movies-actor.yaml' 15 | /more_movies/{actor}: 16 | $ref: './invalid/paths/more-movies-actor.yaml' 17 | 18 | x-walder-errors: 19 | 404: 20 | description: page not found error 21 | x-walder-input-text/html: error404.html 22 | 500: 23 | description: internal server error 24 | x-walder-input-text/html: error500.html 25 | -------------------------------------------------------------------------------- /test/resources/multiple-config-files/config-with-refs.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | pipe-modules: pipe-modules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /movies/{actor}: 14 | $ref: './paths/movies-actor.yaml' 15 | /more_movies/{actor}: 16 | $ref: './paths/more-movies-actor.yaml' 17 | /artist/{artist}: 18 | $ref: './paths/artist-artist.yaml' 19 | /music/{musician}/sorted: 20 | $ref: './paths/music-musician-sorted.yaml' 21 | /music/{musician}/no_duplicates: 22 | $ref: './paths/music-musician-no-duplicates.yaml' 23 | x-walder-errors: 24 | 404: 25 | description: page not found error 26 | x-walder-input-text/html: error404.html 27 | 500: 28 | description: internal server error 29 | x-walder-input-text/html: error500.html 30 | -------------------------------------------------------------------------------- /test/resources/multiple-config-files/paths/artist-artist.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | summary: Returns a list of songs and movies for a given artist. 3 | parameters: 4 | - in: path 5 | name: artist 6 | required: true 7 | schema: 8 | type: string 9 | description: The target artist 10 | x-walder-query: 11 | graphql-query: 12 | songs: > 13 | { 14 | label @single 15 | writer(label_en: $artist) @single 16 | artist @single(scope: all) { 17 | label 18 | } 19 | } 20 | films: > 21 | { 22 | id @single 23 | ... on Film { 24 | starring(label_en: $artist) @single 25 | } 26 | } 27 | json-ld-context: > 28 | { 29 | "@context": { 30 | "label": "http://www.w3.org/2000/01/rdf-schema#label", 31 | "label_en": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 32 | "writer": "http://dbpedia.org/ontology/writer", 33 | "artist": "http://dbpedia.org/ontology/musicalArtist", 34 | "Film": "http://dbpedia.org/ontology/Film", 35 | "starring": "http://dbpedia.org/ontology/starring" 36 | } 37 | } 38 | responses: 39 | 200: 40 | description: list of songs and movies 41 | x-walder-input-text/html: songs-movies.handlebars 42 | -------------------------------------------------------------------------------- /test/resources/multiple-config-files/paths/more-movies-actor.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | summary: Returns a list of the all movies the given actor stars in 3 | parameters: 4 | - in: path 5 | name: actor 6 | required: true 7 | schema: 8 | type: string 9 | description: The target actor 10 | x-walder-query: 11 | graphql-query: > 12 | { 13 | id @single 14 | ... on Film { 15 | starring(label: $actor) @single 16 | } 17 | } 18 | json-ld-context: > 19 | { 20 | "@context": { 21 | "Film": "http://dbpedia.org/ontology/Film", 22 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 23 | "starring": "http://dbpedia.org/ontology/starring" 24 | } 25 | } 26 | datasources: 27 | additional: true 28 | sources: 29 | - http://data.linkeddatafragments.org/harvard 30 | x-walder-postprocessing: 31 | filterT: 32 | source: filter-t.js 33 | responses: 34 | 200: 35 | description: list of movies 36 | x-walder-input-text/html: movies.pug 37 | -------------------------------------------------------------------------------- /test/resources/multiple-config-files/paths/movies-actor.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | summary: Returns a list of the all movies the given actor stars in 3 | parameters: 4 | - in: path 5 | name: actor 6 | required: true 7 | schema: 8 | type: string 9 | description: The target actor 10 | x-walder-query: 11 | graphql-query: > 12 | { 13 | id @single 14 | ... on Film { 15 | starring(label: $actor) @single 16 | } 17 | } 18 | json-ld-context: > 19 | { 20 | "@context": { 21 | "Film": "http://dbpedia.org/ontology/Film", 22 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 23 | "starring": "http://dbpedia.org/ontology/starring" 24 | } 25 | } 26 | x-walder-postprocessing: 27 | filterT: 28 | source: filter-t.js 29 | responses: 30 | 200: 31 | description: list of movies 32 | x-walder-input-text/html: movies.pug 33 | -------------------------------------------------------------------------------- /test/resources/multiple-config-files/paths/music-musician-no-duplicates.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | summary: Returns a list of songs of the given musician, with no duplicate song labels. 3 | parameters: 4 | - in: path 5 | name: musician 6 | required: true 7 | schema: 8 | type: string 9 | description: The target musician 10 | x-walder-query: 11 | graphql-query: > 12 | { 13 | label @single 14 | writer(label_en: $musician) @single 15 | artist @single(scope: all) { 16 | label 17 | } 18 | } 19 | json-ld-context: > 20 | { 21 | "@context": { 22 | "label": "http://www.w3.org/2000/01/rdf-schema#label", 23 | "label_en": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 24 | "writer": "http://dbpedia.org/ontology/writer", 25 | "artist": "http://dbpedia.org/ontology/musicalArtist" 26 | } 27 | } 28 | options: 29 | remove-duplicates: 30 | object: $[*] 31 | value: label 32 | responses: 33 | 200: 34 | description: list of songs 35 | x-walder-input-text/html: songs.handlebars 36 | -------------------------------------------------------------------------------- /test/resources/multiple-config-files/paths/music-musician-sorted.yaml: -------------------------------------------------------------------------------- 1 | get: 2 | summary: Returns a list of songs of the given musician, sorted by the label of the song. 3 | parameters: 4 | - in: path 5 | name: musician 6 | required: true 7 | schema: 8 | type: string 9 | description: The target musician 10 | x-walder-query: 11 | graphql-query: > 12 | { 13 | label @single 14 | writer(label_en: $musician) @single 15 | artist @single(scope: all) { 16 | label 17 | } 18 | } 19 | json-ld-context: > 20 | { 21 | "@context": { 22 | "label": "http://www.w3.org/2000/01/rdf-schema#label", 23 | "label_en": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 24 | "writer": "http://dbpedia.org/ontology/writer", 25 | "artist": "http://dbpedia.org/ontology/musicalArtist" 26 | } 27 | } 28 | options: 29 | sort: 30 | object: $[*] 31 | selectors: 32 | - value: label 33 | order: desc 34 | responses: 35 | 200: 36 | description: list of songs 37 | x-walder-input-text/html: songs.handlebars 38 | -------------------------------------------------------------------------------- /test/resources/my-layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title Layout 5 | body !{content} 6 | -------------------------------------------------------------------------------- /test/resources/pipe-modules/combine.js: -------------------------------------------------------------------------------- 1 | module.exports.combine = (data) => { 2 | if (data.movies1 && data.movies2) { 3 | data.movies3 = [{id: 'http://example.com/my-movie'}] 4 | 5 | return data; 6 | } else { 7 | throw new Error('Not all query results are provided to this pipe module.'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /test/resources/pipe-modules/filter-t-async.js: -------------------------------------------------------------------------------- 1 | module.exports.filterT = (data) => { 2 | return new Promise(resolve => { 3 | let filteredData = []; 4 | 5 | for (const o of data) { 6 | if (o.id.value.match(/T/)) { 7 | filteredData.push(o); 8 | } 9 | } 10 | 11 | resolve(filteredData); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /test/resources/pipe-modules/filter-t-bad.js: -------------------------------------------------------------------------------- 1 | module.exports.filterT = (data) => { 2 | throw new Error('Roger, we got a problem!'); 3 | }; -------------------------------------------------------------------------------- /test/resources/pipe-modules/filter-t-query-key.js: -------------------------------------------------------------------------------- 1 | module.exports.filterT = (data, queryKey) => { 2 | if (queryKey === 'movies') { 3 | let filteredData = []; 4 | 5 | for (const o of data) { 6 | if (o.id.value.match(/T/)) { 7 | filteredData.push(o); 8 | } 9 | } 10 | 11 | return filteredData; 12 | } else { 13 | throw new Error('Only movies are supported this by this pipe module.'); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /test/resources/pipe-modules/filter-t-with-parameters.js: -------------------------------------------------------------------------------- 1 | module.exports.filterT_withParameters = (data, extraLetters) => { 2 | let filteredData = []; 3 | for (const o of data) { 4 | if (o.id.value.match(/T/)) { 5 | if (extraLetters) { 6 | if (o.id.value.match(/A/)){ 7 | filteredData.push(o); 8 | } 9 | } else { 10 | filteredData.push(o); 11 | } 12 | } 13 | } 14 | return filteredData; 15 | }; 16 | -------------------------------------------------------------------------------- /test/resources/pipe-modules/filter-t.js: -------------------------------------------------------------------------------- 1 | module.exports.filterT = (data) => { 2 | let filteredData = []; 3 | for (const o of data) { 4 | if (o.id.value.match(/T/)) { 5 | filteredData.push(o); 6 | } 7 | } 8 | return filteredData; 9 | }; 10 | -------------------------------------------------------------------------------- /test/resources/pipe-modules/get-ids.js: -------------------------------------------------------------------------------- 1 | module.exports.getIds = (data) => { 2 | return data.map(a => a.id); 3 | }; 4 | -------------------------------------------------------------------------------- /test/resources/pipe-modules/sparql/combine.js: -------------------------------------------------------------------------------- 1 | module.exports.combine = (data) => { 2 | if (data.movies1 && data.movies2) { 3 | data.movies3 = [{id: 'http://example.com/my-movie'}] 4 | 5 | return data; 6 | } else { 7 | throw new Error('Not all query results are provided to this pipe module.'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /test/resources/pipe-modules/sparql/filter-t-async.js: -------------------------------------------------------------------------------- 1 | module.exports.filterT = (data) => { 2 | return new Promise(resolve => { 3 | let filteredData = []; 4 | 5 | for (const o of data) { 6 | if (o.subject.id.match(/T/)) { 7 | filteredData.push(o); 8 | } 9 | } 10 | 11 | resolve(filteredData); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /test/resources/pipe-modules/sparql/filter-t-bad.js: -------------------------------------------------------------------------------- 1 | module.exports.filterT = (data) => { 2 | throw new Error('Roger, we got a problem!'); 3 | }; -------------------------------------------------------------------------------- /test/resources/pipe-modules/sparql/filter-t-query-key.js: -------------------------------------------------------------------------------- 1 | module.exports.filterT = (data, queryKey) => { 2 | if (queryKey === 'movies') { 3 | let filteredData = []; 4 | 5 | for (const o of data) { 6 | if (o.subject.id.match(/T/)) { 7 | filteredData.push(o); 8 | } 9 | } 10 | 11 | return filteredData; 12 | } else { 13 | throw new Error('Only movies are supported this by this pipe module.'); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /test/resources/pipe-modules/sparql/filter-t.js: -------------------------------------------------------------------------------- 1 | module.exports.filterT = (data) => { 2 | const filteredData = []; 3 | for (const quad of data) { 4 | if (quad.subject.id.match(/T/)) { 5 | filteredData.push(quad); 6 | } 7 | } 8 | return filteredData; 9 | }; 10 | -------------------------------------------------------------------------------- /test/resources/public/device.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KNowledgeOnWebScale/walder/fb85bf4b9186dc94001e289ecb14ea3a4100c8f6/test/resources/public/device.jpg -------------------------------------------------------------------------------- /test/resources/pug-include-test/config.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | pipe-modules: pipe-modules 9 | public: public 10 | paths: 11 | /: 12 | get: 13 | summary: Returns page based on Pug with include 14 | responses: 15 | 200: 16 | description: Simple page 17 | x-walder-input-text/html: text.pug 18 | -------------------------------------------------------------------------------- /test/resources/pug-include-test/expected-output.html: -------------------------------------------------------------------------------- 1 |

Cool title

Nice paragraph

2 | -------------------------------------------------------------------------------- /test/resources/pug-include-test/views/header.pug: -------------------------------------------------------------------------------- 1 | h1 Cool title 2 | -------------------------------------------------------------------------------- /test/resources/pug-include-test/views/text.pug: -------------------------------------------------------------------------------- 1 | include header.pug 2 | 3 | p Nice paragraph 4 | -------------------------------------------------------------------------------- /test/resources/query-datasources/additional-datasources/graphql-ld/config.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | pipe-modules: pipe-modules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | - graphql-query: > 13 | { 14 | id @single 15 | starring(label: "Brad Pitt") @single 16 | } 17 | json-ld-context: > 18 | { 19 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 20 | "starring": "http://dbpedia.org/ontology/starring" 21 | } 22 | datasources: 23 | additional: false 24 | sources: 25 | - http://fragments.dbpedia.org/2014/en 26 | postprocessing: 27 | getIds: 28 | source: getIds.js 29 | parameters: 30 | - _data 31 | paths: 32 | /test: 33 | get: 34 | summary: Returns a list of the all movies the given actor stars in 35 | parameters: 36 | - in: path 37 | name: actor 38 | required: true 39 | schema: 40 | type: string 41 | description: The target actor 42 | x-walder-query: 43 | graphql-query: > 44 | { 45 | id @single 46 | ... on Film { 47 | starring(label: $actor) @single 48 | } 49 | } 50 | json-ld-context: > 51 | { 52 | "Film": "http://dbpedia.org/ontology/Film", 53 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 54 | "starring": "http://dbpedia.org/ontology/starring" 55 | } 56 | responses: 57 | 200: 58 | description: list of movies 59 | x-walder-input-text/html: movies.pug 60 | x-walder-errors: 61 | 404: 62 | description: page not found error 63 | x-walder-input-text/html: error404.html 64 | 500: 65 | description: internal server error 66 | x-walder-input-text/html: error500.html 67 | -------------------------------------------------------------------------------- /test/resources/query-datasources/additional-datasources/graphql-ld/expected-output.json: -------------------------------------------------------------------------------- 1 | [ 2 | "http://fragments.dbpedia.org/2016-04/en", 3 | "http://dbpedia.org/resource/A_River_Runs_Through_It_(film)", 4 | "http://dbpedia.org/resource/Across_the_Tracks", 5 | "http://dbpedia.org/resource/Babel_(film)", 6 | "http://dbpedia.org/resource/Burn_After_Reading", 7 | "http://dbpedia.org/resource/Cool_World", 8 | "http://dbpedia.org/resource/Cutting_Class", 9 | "http://dbpedia.org/resource/Fight_Club", 10 | "http://dbpedia.org/resource/Fury_(2014_film)", 11 | "http://dbpedia.org/resource/Happy_Feet_Two", 12 | "http://dbpedia.org/resource/Interview_with_the_Vampire:_The_Vampire_Chronicles", 13 | "http://dbpedia.org/resource/Johnny_Suede", 14 | "http://dbpedia.org/resource/Kalifornia", 15 | "http://dbpedia.org/resource/Legends_of_the_Fall", 16 | "http://dbpedia.org/resource/Meet_Joe_Black", 17 | "http://dbpedia.org/resource/Megamind", 18 | "http://dbpedia.org/resource/Moneyball_(film)", 19 | "http://dbpedia.org/resource/Mr._&_Mrs._Smith_(2005_film)", 20 | "http://dbpedia.org/resource/Ocean's_Eleven", 21 | "http://dbpedia.org/resource/Ocean's_Thirteen", 22 | "http://dbpedia.org/resource/Ocean's_Twelve", 23 | "http://dbpedia.org/resource/Seven_(film)", 24 | "http://dbpedia.org/resource/Seven_Years_in_Tibet_(1997_film)", 25 | "http://dbpedia.org/resource/Sinbad:_Legend_of_the_Seven_Seas", 26 | "http://dbpedia.org/resource/Sleepers_(film)", 27 | "http://dbpedia.org/resource/Snatch_(film)", 28 | "http://dbpedia.org/resource/Spy_Game", 29 | "http://dbpedia.org/resource/The_Assassination_of_Jesse_James_by_the_Coward_Robert_Ford", 30 | "http://dbpedia.org/resource/The_Curious_Case_of_Benjamin_Button_(film)", 31 | "http://dbpedia.org/resource/The_Dark_Side_of_the_Sun_(film)", 32 | "http://dbpedia.org/resource/The_Devil's_Own", 33 | "http://dbpedia.org/resource/The_Favor", 34 | "http://dbpedia.org/resource/The_Mexican", 35 | "http://dbpedia.org/resource/The_Tiger_(2014_film)", 36 | "http://dbpedia.org/resource/The_Tree_of_Life_(film)", 37 | "http://dbpedia.org/resource/Too_Young_to_Die%3F", 38 | "http://dbpedia.org/resource/Troy_(film)", 39 | "http://dbpedia.org/resource/True_Romance" 40 | ] 41 | -------------------------------------------------------------------------------- /test/resources/query-datasources/additional-datasources/graphql-ld/getIds.js: -------------------------------------------------------------------------------- 1 | module.exports.getIds = (data) => { 2 | return data.map(a => a.id); 3 | }; 4 | -------------------------------------------------------------------------------- /test/resources/query-datasources/additional-datasources/sparql/config.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | pipe-modules: pipe-modules 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | - sparql-query: > 13 | PREFIX rdfs: 14 | PREFIX dbo: 15 | CONSTRUCT { 16 | ?movie a dbo:Film. 17 | } WHERE { 18 | ?movie dbo:starring [ 19 | rdfs:label "Brad Pitt"@en 20 | ]. 21 | } 22 | datasources: 23 | additional: false 24 | sources: 25 | - http://fragments.dbpedia.org/2014/en 26 | postprocessing: 27 | getIds: 28 | source: getIds.js 29 | parameters: 30 | - _data 31 | paths: 32 | /test: 33 | get: 34 | summary: Returns a list of the all movies the given actor stars in 35 | parameters: 36 | - in: path 37 | name: actor 38 | required: true 39 | schema: 40 | type: string 41 | description: The target actor 42 | x-walder-query: 43 | graphql-query: > 44 | { 45 | id @single 46 | ... on Film { 47 | starring(label: $actor) @single 48 | } 49 | } 50 | json-ld-context: > 51 | { 52 | "Film": "http://dbpedia.org/ontology/Film", 53 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 54 | "starring": "http://dbpedia.org/ontology/starring" 55 | } 56 | responses: 57 | 200: 58 | description: list of movies 59 | x-walder-input-text/html: movies.pug 60 | x-walder-errors: 61 | 404: 62 | description: page not found error 63 | x-walder-input-text/html: error404.html 64 | 500: 65 | description: internal server error 66 | x-walder-input-text/html: error500.html 67 | -------------------------------------------------------------------------------- /test/resources/query-datasources/additional-datasources/sparql/expected-output.json: -------------------------------------------------------------------------------- 1 | [ 2 | "http://fragments.dbpedia.org/2016-04/en", 3 | "http://dbpedia.org/resource/A_River_Runs_Through_It_(film)", 4 | "http://dbpedia.org/resource/Across_the_Tracks", 5 | "http://dbpedia.org/resource/Babel_(film)", 6 | "http://dbpedia.org/resource/Burn_After_Reading", 7 | "http://dbpedia.org/resource/Cool_World", 8 | "http://dbpedia.org/resource/Cutting_Class", 9 | "http://dbpedia.org/resource/Fight_Club", 10 | "http://dbpedia.org/resource/Fury_(2014_film)", 11 | "http://dbpedia.org/resource/Happy_Feet_Two", 12 | "http://dbpedia.org/resource/Interview_with_the_Vampire:_The_Vampire_Chronicles", 13 | "http://dbpedia.org/resource/Johnny_Suede", 14 | "http://dbpedia.org/resource/Kalifornia", 15 | "http://dbpedia.org/resource/Legends_of_the_Fall", 16 | "http://dbpedia.org/resource/Meet_Joe_Black", 17 | "http://dbpedia.org/resource/Megamind", 18 | "http://dbpedia.org/resource/Moneyball_(film)", 19 | "http://dbpedia.org/resource/Mr._&_Mrs._Smith_(2005_film)", 20 | "http://dbpedia.org/resource/Ocean's_Eleven", 21 | "http://dbpedia.org/resource/Ocean's_Thirteen", 22 | "http://dbpedia.org/resource/Ocean's_Twelve", 23 | "http://dbpedia.org/resource/Seven_(film)", 24 | "http://dbpedia.org/resource/Seven_Years_in_Tibet_(1997_film)", 25 | "http://dbpedia.org/resource/Sinbad:_Legend_of_the_Seven_Seas", 26 | "http://dbpedia.org/resource/Sleepers_(film)", 27 | "http://dbpedia.org/resource/Snatch_(film)", 28 | "http://dbpedia.org/resource/Spy_Game", 29 | "http://dbpedia.org/resource/The_Assassination_of_Jesse_James_by_the_Coward_Robert_Ford", 30 | "http://dbpedia.org/resource/The_Curious_Case_of_Benjamin_Button_(film)", 31 | "http://dbpedia.org/resource/The_Dark_Side_of_the_Sun_(film)", 32 | "http://dbpedia.org/resource/The_Devil's_Own", 33 | "http://dbpedia.org/resource/The_Favor", 34 | "http://dbpedia.org/resource/The_Mexican", 35 | "http://dbpedia.org/resource/The_Tiger_(2014_film)", 36 | "http://dbpedia.org/resource/The_Tree_of_Life_(film)", 37 | "http://dbpedia.org/resource/Too_Young_to_Die%3F", 38 | "http://dbpedia.org/resource/Troy_(film)", 39 | "http://dbpedia.org/resource/True_Romance" 40 | ] 41 | -------------------------------------------------------------------------------- /test/resources/query-datasources/additional-datasources/sparql/getIds.js: -------------------------------------------------------------------------------- 1 | module.exports.getIds = (data) => { 2 | return data.map(a => a.subject.id); 3 | }; 4 | -------------------------------------------------------------------------------- /test/resources/query-datasources/single-query/graphql-ld/config.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | pipe-modules: . 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | - graphql-query: > 13 | { 14 | id @single 15 | starring(label: "Brad Pitt") @single 16 | } 17 | json-ld-context: > 18 | { 19 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 20 | "starring": "http://dbpedia.org/ontology/starring" 21 | } 22 | postprocessing: 23 | getIds: 24 | source: getIds.js 25 | parameters: 26 | - _data 27 | paths: 28 | /test: 29 | get: 30 | summary: Returns a list of the all movies the given actor stars in 31 | parameters: 32 | - in: path 33 | name: actor 34 | required: true 35 | schema: 36 | type: string 37 | description: The target actor 38 | x-walder-query: 39 | graphql-query: > 40 | { 41 | id @single 42 | ... on Film { 43 | starring(label: $actor) @single 44 | } 45 | } 46 | json-ld-context: > 47 | { 48 | "Film": "http://dbpedia.org/ontology/Film", 49 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 50 | "starring": "http://dbpedia.org/ontology/starring" 51 | } 52 | responses: 53 | 200: 54 | description: list of movies 55 | x-walder-input-text/html: movies.pug 56 | x-walder-errors: 57 | 404: 58 | description: page not found error 59 | x-walder-input-text/html: error404.html 60 | 500: 61 | description: internal server error 62 | x-walder-input-text/html: error500.html 63 | -------------------------------------------------------------------------------- /test/resources/query-datasources/single-query/graphql-ld/expected-output.json: -------------------------------------------------------------------------------- 1 | [ 2 | "http://fragments.dbpedia.org/2016-04/en", 3 | "http://dbpedia.org/resource/12_Monkeys", 4 | "http://dbpedia.org/resource/A_River_Runs_Through_It_(film)", 5 | "http://dbpedia.org/resource/Across_the_Tracks", 6 | "http://dbpedia.org/resource/Babel_(film)", 7 | "http://dbpedia.org/resource/Burn_After_Reading", 8 | "http://dbpedia.org/resource/Contact_(1992_film)", 9 | "http://dbpedia.org/resource/Cool_World", 10 | "http://dbpedia.org/resource/Cutting_Class", 11 | "http://dbpedia.org/resource/Happy_Feet_Two", 12 | "http://dbpedia.org/resource/Interview_with_the_Vampire_(film)", 13 | "http://dbpedia.org/resource/Johnny_Suede", 14 | "http://dbpedia.org/resource/Kalifornia", 15 | "http://dbpedia.org/resource/Legends_of_the_Fall", 16 | "http://dbpedia.org/resource/Meet_Joe_Black", 17 | "http://dbpedia.org/resource/Megamind", 18 | "http://dbpedia.org/resource/Moneyball_(film)", 19 | "http://dbpedia.org/resource/Mr._&_Mrs._Smith_(2005_film)", 20 | "http://dbpedia.org/resource/Oceans_Eleven", 21 | "http://dbpedia.org/resource/Oceans_Thirteen", 22 | "http://dbpedia.org/resource/Oceans_Trilogy", 23 | "http://dbpedia.org/resource/Oceans_Twelve", 24 | "http://dbpedia.org/resource/Seven_(1995_film)", 25 | "http://dbpedia.org/resource/Seven_Years_in_Tibet_(1997_film)", 26 | "http://dbpedia.org/resource/Sinbad:_Legend_of_the_Seven_Seas", 27 | "http://dbpedia.org/resource/Sleepers_(film)", 28 | "http://dbpedia.org/resource/Spy_Game", 29 | "http://dbpedia.org/resource/The_Assassination_of_Jesse_James_by_the_Coward_Robert_Ford", 30 | "http://dbpedia.org/resource/The_Audition_(2015_film)", 31 | "http://dbpedia.org/resource/The_Curious_Case_of_Benjamin_Button_(film)", 32 | "http://dbpedia.org/resource/The_Dark_Side_of_the_Sun_(film)", 33 | "http://dbpedia.org/resource/The_Devils_Own", 34 | "http://dbpedia.org/resource/The_Favor", 35 | "http://dbpedia.org/resource/The_Mexican", 36 | "http://dbpedia.org/resource/The_Tiger_(upcoming_film)", 37 | "http://dbpedia.org/resource/Thelma_&_Louise", 38 | "http://dbpedia.org/resource/Too_Young_to_Die%3F", 39 | "http://dbpedia.org/resource/Troy_(film)", 40 | "http://dbpedia.org/resource/True_Romance", 41 | "http://dbpedia.org/resource/Two-Fisted_Tales_(film)", 42 | "http://dbpedia.org/resource/War_Machine_(film)" 43 | ] 44 | -------------------------------------------------------------------------------- /test/resources/query-datasources/single-query/graphql-ld/getIds.js: -------------------------------------------------------------------------------- 1 | module.exports.getIds = (data) => { 2 | return data.map(a => a.id); 3 | }; 4 | -------------------------------------------------------------------------------- /test/resources/query-datasources/single-query/sparql/config.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: views 8 | pipe-modules: . 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | - sparql-query: > 13 | PREFIX rdfs: 14 | PREFIX dbo: 15 | CONSTRUCT { 16 | ?movie a dbo:Film. 17 | } WHERE { 18 | ?movie dbo:starring [ 19 | rdfs:label "Brad Pitt"@en 20 | ]. 21 | } 22 | postprocessing: 23 | getIds: 24 | source: getIds.js 25 | parameters: 26 | - _data 27 | paths: 28 | /test: 29 | get: 30 | summary: Returns a list of the all movies the given actor stars in 31 | parameters: 32 | - in: path 33 | name: actor 34 | required: true 35 | schema: 36 | type: string 37 | description: The target actor 38 | x-walder-query: 39 | graphql-query: > 40 | { 41 | id @single 42 | ... on Film { 43 | starring(label: $actor) @single 44 | } 45 | } 46 | json-ld-context: > 47 | { 48 | "Film": "http://dbpedia.org/ontology/Film", 49 | "label": { "@id": "http://www.w3.org/2000/01/rdf-schema#label", "@language": "en" }, 50 | "starring": "http://dbpedia.org/ontology/starring" 51 | } 52 | responses: 53 | 200: 54 | description: list of movies 55 | x-walder-input-text/html: movies.pug 56 | x-walder-errors: 57 | 404: 58 | description: page not found error 59 | x-walder-input-text/html: error404.html 60 | 500: 61 | description: internal server error 62 | x-walder-input-text/html: error500.html 63 | -------------------------------------------------------------------------------- /test/resources/query-datasources/single-query/sparql/expected-output.json: -------------------------------------------------------------------------------- 1 | [ 2 | "http://fragments.dbpedia.org/2016-04/en", 3 | "http://dbpedia.org/resource/12_Monkeys", 4 | "http://dbpedia.org/resource/A_River_Runs_Through_It_(film)", 5 | "http://dbpedia.org/resource/Across_the_Tracks", 6 | "http://dbpedia.org/resource/Babel_(film)", 7 | "http://dbpedia.org/resource/Burn_After_Reading", 8 | "http://dbpedia.org/resource/Contact_(1992_film)", 9 | "http://dbpedia.org/resource/Cool_World", 10 | "http://dbpedia.org/resource/Cutting_Class", 11 | "http://dbpedia.org/resource/Happy_Feet_Two", 12 | "http://dbpedia.org/resource/Interview_with_the_Vampire_(film)", 13 | "http://dbpedia.org/resource/Johnny_Suede", 14 | "http://dbpedia.org/resource/Kalifornia", 15 | "http://dbpedia.org/resource/Legends_of_the_Fall", 16 | "http://dbpedia.org/resource/Meet_Joe_Black", 17 | "http://dbpedia.org/resource/Megamind", 18 | "http://dbpedia.org/resource/Moneyball_(film)", 19 | "http://dbpedia.org/resource/Mr._&_Mrs._Smith_(2005_film)", 20 | "http://dbpedia.org/resource/Oceans_Eleven", 21 | "http://dbpedia.org/resource/Oceans_Thirteen", 22 | "http://dbpedia.org/resource/Oceans_Trilogy", 23 | "http://dbpedia.org/resource/Oceans_Twelve", 24 | "http://dbpedia.org/resource/Seven_(1995_film)", 25 | "http://dbpedia.org/resource/Seven_Years_in_Tibet_(1997_film)", 26 | "http://dbpedia.org/resource/Sinbad:_Legend_of_the_Seven_Seas", 27 | "http://dbpedia.org/resource/Sleepers_(film)", 28 | "http://dbpedia.org/resource/Spy_Game", 29 | "http://dbpedia.org/resource/The_Assassination_of_Jesse_James_by_the_Coward_Robert_Ford", 30 | "http://dbpedia.org/resource/The_Audition_(2015_film)", 31 | "http://dbpedia.org/resource/The_Curious_Case_of_Benjamin_Button_(film)", 32 | "http://dbpedia.org/resource/The_Dark_Side_of_the_Sun_(film)", 33 | "http://dbpedia.org/resource/The_Devils_Own", 34 | "http://dbpedia.org/resource/The_Favor", 35 | "http://dbpedia.org/resource/The_Mexican", 36 | "http://dbpedia.org/resource/The_Tiger_(upcoming_film)", 37 | "http://dbpedia.org/resource/Thelma_&_Louise", 38 | "http://dbpedia.org/resource/Too_Young_to_Die%3F", 39 | "http://dbpedia.org/resource/Troy_(film)", 40 | "http://dbpedia.org/resource/True_Romance", 41 | "http://dbpedia.org/resource/Two-Fisted_Tales_(film)", 42 | "http://dbpedia.org/resource/War_Machine_(film)" 43 | ] 44 | -------------------------------------------------------------------------------- /test/resources/query-datasources/single-query/sparql/getIds.js: -------------------------------------------------------------------------------- 1 | module.exports.getIds = (data) => { 2 | return data.map(a => a.subject.id); 3 | }; 4 | -------------------------------------------------------------------------------- /test/resources/sparql/config-errors.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: ../views/sparql 8 | pipe-modules: ../pipe-modules/sparql 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /movies/brad_pitt: 14 | get: 15 | summary: Returns a paginated list of all movies Brad Pitt stars in 16 | parameters: 17 | - in: query 18 | name: limit 19 | required: true 20 | schema: 21 | type: integer 22 | minimum: 1 23 | description: Maximum number of elements to be shown on the current page. 24 | x-walder-query: 25 | sparql-query: > 26 | PREFIX dbo: 27 | PREFIX rdfs: 28 | CONSTRUCT { 29 | ?film a dbo:Film. 30 | } WHERE { 31 | ?film a dbo:Film; 32 | dbo:starring [ 33 | rdfs:label ?actor 34 | ] 35 | } 36 | LIMIT ?limit 37 | responses: 38 | 200: 39 | description: list of movies 40 | x-walder-input-text/html: movies.handlebars 41 | /badmovies/{actor}: 42 | get: 43 | summary: Returns a list of the all movies the given actor stars in 44 | parameters: 45 | - in: path 46 | name: actor 47 | required: true 48 | schema: 49 | type: string 50 | description: The target actor 51 | x-walder-query: 52 | sparql-query: > 53 | CONSTRUCT { 54 | x-walder-postprocessing: 55 | filterT: 56 | source: filter-t-bad.js 57 | responses: 58 | 200: 59 | description: list of movies 60 | x-walder-input-text/html: movies.pug 61 | x-walder-errors: 62 | 404: 63 | description: page not found error 64 | x-walder-input-text/html: error404.html 65 | 500: 66 | description: internal server error 67 | x-walder-input-text/html: error500.html 68 | -------------------------------------------------------------------------------- /test/resources/sparql/config-frame.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: 'Example site with SPARQL queries and framing' 4 | version: 0.1.0 5 | x-walder-resources: 6 | root: . 7 | views: ../views/sparql 8 | pipe-modules: ../pipe-modules/sparql 9 | public: public 10 | x-walder-datasources: 11 | - http://fragments.dbpedia.org/2016-04/en 12 | paths: 13 | /movies/{actor}: 14 | get: 15 | summary: Returns a list of the all movies the given actor stars in 16 | parameters: 17 | - in: path 18 | name: actor 19 | required: true 20 | schema: 21 | type: string 22 | description: The target actor 23 | x-walder-query: 24 | sparql-query: > 25 | PREFIX dbo: 26 | PREFIX rdfs: 27 | CONSTRUCT { 28 | ?film a dbo:Film. 29 | } WHERE { 30 | ?film a dbo:Film; 31 | dbo:starring [ 32 | rdfs:label ?actor@en 33 | ] 34 | } 35 | json-ld-frame: > 36 | { 37 | "@context": {"@vocab": "http://dbpedia.org/ontology/"}, 38 | "@type": "Film" 39 | } 40 | responses: 41 | 200: 42 | description: list of movies 43 | x-walder-input-text/html: movies-frame.pug 44 | 45 | x-walder-errors: 46 | 404: 47 | description: page not found error 48 | x-walder-input-text/html: error404.html 49 | 500: 50 | description: internal server error 51 | x-walder-input-text/html: error500.html 52 | -------------------------------------------------------------------------------- /test/resources/sparql/config-lenient.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: # OpenAPI metadata 3 | title: "Test lenient" 4 | version: 0.0.1 5 | x-walder-resources: 6 | root: . 7 | views: ../views/sparql 8 | 9 | paths: 10 | /bad-jsonld: 11 | get: 12 | summary: Returns contact information. 13 | x-walder-query: 14 | sparql-query: > 15 | PREFIX schema: 16 | 17 | CONSTRUCT { 18 | ?location schema:name ?name. 19 | } WHERE { 20 | schema:location ?location. 21 | ?location schema:name ?name; 22 | schema:address ?address. 23 | } 24 | datasources: 25 | sources: 26 | - https://data.knows.idlab.ugent.be/person/office/# 27 | - https://data.vlaanderen.be/id/adres/20470097 28 | responses: 29 | 200: 30 | x-walder-input-text/html: lenient.pug 31 | x-walder-errors: 32 | 404: 33 | description: page not found error 34 | x-walder-input-text/html: error404.html 35 | 500: 36 | description: internal server error 37 | x-walder-input-text/html: error500.html 38 | -------------------------------------------------------------------------------- /test/resources/test-layout.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: my-layout.pug 3 | --- 4 | 5 | # Introduction 6 | 7 | This is an introduction to something cool. 8 | -------------------------------------------------------------------------------- /test/resources/test.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This is an introduction to something cool. 4 | -------------------------------------------------------------------------------- /test/resources/view-with-jsonld.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title Layout 5 | body 6 | script(type="application/ld+json") !{_queryResultsAsJSONLD} 7 | 8 | h1 Movies 9 | div 10 | ul 11 | each val in data 12 | li: a(href=val.id)= val.id 13 | -------------------------------------------------------------------------------- /test/resources/views/artist-writer.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | body 6 | h1 Songs 7 | div 8 | ul 9 | each val in data 10 | li #{val.label} 11 | -------------------------------------------------------------------------------- /test/resources/views/directors.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | body 6 | h1 Directors of movies starring Brad Pitt 7 | div 8 | ul 9 | each val in data 10 | li= val.director.label 11 | -------------------------------------------------------------------------------- /test/resources/views/error404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page not found 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 |
24 |
25 | 28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /test/resources/views/error404alt.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Encountered error 404

9 |

This page was generated using error404alt.handlebars.

10 | 11 | 12 | -------------------------------------------------------------------------------- /test/resources/views/error404alt.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: error-layout.pug 3 | --- 4 | 5 | # Encountered error 404 6 | 7 | This page was generated using error404alt.md. 8 | -------------------------------------------------------------------------------- /test/resources/views/error404alt.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | title Error 404 6 | body 7 | h1 Encountered error 404 8 | p This page was generated using error404alt.pug. 9 | -------------------------------------------------------------------------------- /test/resources/views/error500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Internal server error 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

500

16 |

Unexpected Error :(

17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 36 |
37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/resources/views/error500alt.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Encountered error 500

9 |

This page was generated using error500alt.handlebars.

10 | 11 | 12 | -------------------------------------------------------------------------------- /test/resources/views/error500alt.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: error-layout.pug 3 | --- 4 | 5 | # Encountered error 500 6 | 7 | This page was generated using error500alt.md. 8 | -------------------------------------------------------------------------------- /test/resources/views/error500alt.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | title Error 500 6 | body 7 | h1 Encountered error 500 8 | p This page was generated using error500alt.pug. -------------------------------------------------------------------------------- /test/resources/views/invalid-frontmatter.pug: -------------------------------------------------------------------------------- 1 | --- 2 | [title 3 | --- -------------------------------------------------------------------------------- /test/resources/views/layout_test.pug: -------------------------------------------------------------------------------- 1 | --- 2 | layout: simple-layout.pug 3 | --- 4 | Hello earth! -------------------------------------------------------------------------------- /test/resources/views/lenient.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | body 4 | h1 lenient 5 | div 6 | h2 location 7 | a(href=data[0].location.address) #{data[0].location.name} 8 | -------------------------------------------------------------------------------- /test/resources/views/missing-layout.pug: -------------------------------------------------------------------------------- 1 | --- 2 | layout: missing.pug 3 | --- -------------------------------------------------------------------------------- /test/resources/views/movies-combine.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | body 6 | h1 Movies 7 | div 8 | ul 9 | each val in movies3 10 | li: a(href=val.id)= val.id -------------------------------------------------------------------------------- /test/resources/views/movies-directors.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Movies and directors

9 |
10 |
    11 | {{#each data}} 12 |
  • {{this.label}} - {{this.director.label}}
  • 13 | {{/each}} 14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /test/resources/views/movies-query-key.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | body 6 | h1 Movies 7 | div 8 | ul 9 | each val in movies 10 | li: a(href=val.id)= val.id -------------------------------------------------------------------------------- /test/resources/views/movies.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Movies

9 |
10 |
    11 | {{#each data}} 12 |
  • {{this.id}}
  • 13 | {{/each}} 14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /test/resources/views/movies.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | body 6 | h1 Movies 7 | div 8 | ul 9 | each val in data 10 | li: a(href=val.id)= val.id -------------------------------------------------------------------------------- /test/resources/views/songs-movies.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Songs & Movies

9 |
10 |
    11 | {{#each films}} 12 | {{#if this.id}} 13 |
  • {{this.id}}
  • 14 | {{/if}} 15 | {{/each}} 16 |
17 |
18 |
19 |
    20 | {{#each songs}} 21 | {{#if this.label}} 22 |
  • {{this.label}}
  • 23 | {{/if}} 24 | {{/each}} 25 |
26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /test/resources/views/songs.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Songs

9 |
10 |
    11 | {{#each data}} 12 |
  • {{this.label}}
  • 13 | {{/each}} 14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /test/resources/views/sparql/artist-writer.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | body 6 | h1 Songs 7 | div 8 | ul 9 | each val in data 10 | li #{val.subject.id} 11 | -------------------------------------------------------------------------------- /test/resources/views/sparql/error404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page not found 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 |
24 |
25 | 28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /test/resources/views/sparql/error500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Internal server error 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

500

16 |

Unexpected Error :(

17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 36 |
37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/resources/views/sparql/lenient.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | body 4 | h1 lenient 5 | div 6 | h2 location 7 | a(href=data[0].subject.id) #{data[0].object.value} 8 | -------------------------------------------------------------------------------- /test/resources/views/sparql/movies-combine.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | body 6 | h1 Movies 7 | div 8 | ul 9 | each val in movies3 10 | li: a(href=val.id)= val.id -------------------------------------------------------------------------------- /test/resources/views/sparql/movies-frame.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | body 6 | h1 Movies 7 | div 8 | ul 9 | each val in data['@graph'] 10 | li #{val['@id']} 11 | -------------------------------------------------------------------------------- /test/resources/views/sparql/movies-query-key.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | body 6 | h1 Movies 7 | div 8 | ul 9 | each val in movies 10 | li: a(href=val.id)= val.id -------------------------------------------------------------------------------- /test/resources/views/sparql/movies.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Movies

9 |
10 |
    11 | {{#each data}} 12 |
  • {{this.id}}
  • 13 | {{/each}} 14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /test/resources/views/sparql/movies.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel='stylesheet', href='/stylesheets/list.css') 5 | body 6 | h1 Movies 7 | div 8 | ul 9 | each quad in data 10 | li: a(href=val)= quad.subject.id 11 | -------------------------------------------------------------------------------- /test/resources/views/sparql/songs-movies.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Songs & Movies

9 |

Movies

10 |
11 |
    12 | {{#each films}} 13 |
  • {{this.subject.id}}
  • 14 | {{/each}} 15 |
16 |
17 |

Songs

18 |
19 |
    20 | {{#each songs}} 21 |
  • {{this.subject.id}}
  • 22 | {{/each}} 23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /test/resources/views/sparql/songs.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Songs

9 |
10 |
    11 | {{#each data}} 12 |
  • {{this.label}}
  • 13 | {{/each}} 14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /test/resources/views/text-fm-with-layout.pug: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layout-fm.pug 3 | a2: Value for FrontMatter attribute a2! 4 | --- 5 | 6 | main Lorem ipsum 7 | -------------------------------------------------------------------------------- /test/resources/views/text-fm.pug: -------------------------------------------------------------------------------- 1 | --- 2 | a1: Value for FrontMatter attribute a1! 3 | --- 4 | 5 | doctype html 6 | html(lang="en") 7 | body 8 | main a1: #{a1} 9 | -------------------------------------------------------------------------------- /test/resources/views/text.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | body 4 | p Hello World! 5 | -------------------------------------------------------------------------------- /test/validators/graphQLLDValidator.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | const expect = require('chai').expect; 3 | 4 | const GraphQLLDValidator = require('../../lib/validators/graphql-ld-validator'); 5 | const RouteInfo = require('../../lib/models/route-info'); 6 | const parseGraphQLLD = require('../../lib/parsers/query-parser'); 7 | const parseParameter = require('../../lib/parsers/parameter-parser'); 8 | 9 | const YAML = require('yaml'); 10 | const fs = require('fs'); 11 | const Path = require('path'); 12 | 13 | const CONFIG_FILE = '../resources/config.yaml'; 14 | 15 | describe('GraphQLLDValidator', function () { 16 | { 17 | before(function () { 18 | const file = fs.readFileSync(Path.resolve(__dirname, CONFIG_FILE), 'utf8'); 19 | const yamlData = YAML.parse(file); 20 | 21 | const path = '/movies/{actor}'; 22 | const method = 'get'; 23 | 24 | this.routeInfo = new RouteInfo(path, method); 25 | this.graphQLLDInfo = parseGraphQLLD(yamlData.paths[path][method]['x-walder-query'], {}); 26 | this.parameters = parseParameter(yamlData.paths[path][method].parameters); 27 | this.graphQLLDValidator = new GraphQLLDValidator(); 28 | }); 29 | 30 | describe('# Variables', function () { 31 | it(`Should return 'undefined' when all GraphQL-LD variables are correctly described`, async function () { 32 | expect(await this.graphQLLDValidator.validate({routeInfo: this.routeInfo, parameters: this.parameters, graphQLLDInfo: this.graphQLLDInfo})).to.be.undefined; 33 | }); 34 | 35 | it('Should return an error string when there are undescribed variables', async function () { 36 | const output = await this.graphQLLDValidator.validate({routeInfo: this.routeInfo, parameters: {}, graphQLLDInfo: this.graphQLLDInfo}); 37 | output.should.be.a.string; 38 | output.should.include('error'); 39 | }) 40 | }) 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /test/validators/htmlValidator.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | const expect = require('chai').expect; 3 | 4 | const HTMLValidator = require('../../lib/validators/html-validator'); 5 | const RouteInfo = require('../../lib/models/route-info'); 6 | const parseResources = require('../../lib/parsers/resource-parser'); 7 | const parseHTML = require('../../lib/parsers/html-parser'); 8 | 9 | const YAML = require('yaml'); 10 | const fs = require('fs'); 11 | const Path = require('path'); 12 | const TemplateLoader = require("../../lib/loaders/template-loader"); 13 | 14 | const CONFIG_FILE = '../resources/config-htmlvalidator.yaml'; 15 | 16 | describe('HTMLValidator', function () { 17 | { 18 | before(function () { 19 | const fileAbsPath = Path.resolve(__dirname, CONFIG_FILE); 20 | const file = fs.readFileSync(fileAbsPath, 'utf8'); 21 | this.yamlData = YAML.parse(file); 22 | this.resources = parseResources(this.yamlData['x-walder-resources'], Path.dirname(fileAbsPath)); 23 | this.htmlValidator = new HTMLValidator({templateLoader: new TemplateLoader()}); 24 | }); 25 | 26 | describe('# Files', function () { 27 | it(`Should return 'undefined' when all HTML and template files are available`, async function () { 28 | const path = '/simple'; 29 | const method = 'get'; 30 | const routeInfo = new RouteInfo(path, method); 31 | const htmlInfoDictionary = parseHTML(this.yamlData.paths[path][method].responses, this.resources.views, this.resources.layouts); 32 | const output = await this.htmlValidator.validate({routeInfo, htmlInfoDictionary}); 33 | expect(output).to.be.undefined; 34 | }); 35 | 36 | it('Should return an error string when there are unavailable HTML or template files', async function () { 37 | const path = '/missing-html'; 38 | const method = 'get'; 39 | const routeInfo = new RouteInfo(path, method); 40 | const htmlInfoDictionary = parseHTML(this.yamlData.paths[path][method].responses, this.resources.views, this.resources.layouts); 41 | const output = await this.htmlValidator.validate({routeInfo, htmlInfoDictionary}); 42 | 43 | output.should.be.a.string; 44 | output.should.include('missing-template.pug'); 45 | output.should.include('missing-html.html'); 46 | }); 47 | 48 | /** 49 | * If template A extends layout B and B has invalid frontmatter or does not exist. The error must point to B and not to A. 50 | */ 51 | it('Should return an error string when there is an unavailable layout pointing to the layout file', async function () { 52 | const path = '/missing-layout'; 53 | const method = 'get'; 54 | const routeInfo = new RouteInfo(path, method); 55 | const htmlInfoDictionary = parseHTML(this.yamlData.paths[path][method].responses, this.resources.views, this.resources.layouts); 56 | const output = await this.htmlValidator.validate({routeInfo, htmlInfoDictionary}); 57 | 58 | output.should.be.a.string; 59 | output.should.include('missing.pug'); 60 | }); 61 | 62 | it('Should return an error string when there is invalid frontmatter', async function () { 63 | const path = '/invalid-frontmatter'; 64 | const method = 'get'; 65 | const routeInfo = new RouteInfo(path, method); 66 | const htmlInfoDictionary = parseHTML(this.yamlData.paths[path][method].responses, this.resources.views, this.resources.layouts); 67 | const output = await this.htmlValidator.validate({routeInfo, htmlInfoDictionary}); 68 | 69 | output.should.be.a.string; 70 | output.should.include('invalid-frontmatter.pug'); 71 | }); 72 | }) 73 | } 74 | }); 75 | -------------------------------------------------------------------------------- /test/validators/mainValidator.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | const expect = require('chai').expect; 3 | const assert = require('chai').assert; 4 | 5 | const MainValidator = require('../../lib/validators/main-validator'); 6 | const RouteInfo = require('../../lib/models/route-info'); 7 | const parseGraphQLLD = require('../../lib/parsers/query-parser'); 8 | const parseParameter = require('../../lib/parsers/parameter-parser'); 9 | 10 | const YAML = require('yaml'); 11 | const fs = require('fs'); 12 | const Path = require('path'); 13 | const TemplateLoader = require("../../lib/loaders/template-loader"); 14 | const createLogger = require("../../lib/create-logger"); 15 | 16 | const CONFIG_FILE = '../resources/config.yaml'; 17 | 18 | describe('MainValidator', function () { 19 | { 20 | describe('# Functionality', function () { 21 | async function validateConfig (valid) { 22 | const file = fs.readFileSync(Path.resolve(__dirname, CONFIG_FILE), 'utf8'); 23 | const yamlData = YAML.parse(file); 24 | 25 | const path = '/movies/{actor}'; 26 | const method = 'get'; 27 | 28 | const mainValidator = new MainValidator({templateLoader: new TemplateLoader(), logger: createLogger()}); 29 | 30 | const routeInfo = new RouteInfo(path, method); 31 | const graphQLLDInfo = parseGraphQLLD(yamlData.paths[path][method]['x-walder-query'], {}); 32 | const parameters = valid ? parseParameter(yamlData.paths[path][method].parameters) : {}; 33 | 34 | await mainValidator.validateAll({routeInfo, parameters, graphQLLDInfo}); 35 | mainValidator.finish(); 36 | } 37 | 38 | it('Should not throw an error when the given config file does not contain errors', async function() { 39 | try { 40 | await validateConfig(true); 41 | } catch (e) { 42 | assert.fail("Has thrown"); 43 | } 44 | }); 45 | 46 | it('Should throw an error when the given config file contains errors', async function () { 47 | try { 48 | await validateConfig(false); 49 | assert.fail("Hasn't thrown"); 50 | } catch (e) { 51 | // OK 52 | } 53 | }); 54 | }) 55 | } 56 | }); 57 | --------------------------------------------------------------------------------