├── .codacy.yaml ├── .dockerignore ├── .github └── workflows │ ├── ci.yml │ ├── clean.yml │ ├── publish_docker.yml │ ├── publish_gh_pages.yml │ └── publish_oss_sonatype.yml ├── .gitignore ├── .jvmopts ├── .scalafmt.conf ├── .zenodo.json ├── Dockerfile ├── LICENSE ├── README.md ├── build.sbt ├── docs ├── api-deployment │ ├── deployment_docker.md │ ├── deployment_manual.md │ └── deployment_overview.md ├── api-testing-auditing │ ├── testing-auditing_integration.md │ ├── testing-auditing_logs.md │ └── testing-auditing_munit.md ├── api-usage │ ├── streaming.md │ └── usage_cli.md ├── home.md └── webpage │ └── webpage_info.md ├── modules └── server │ └── src │ ├── main │ ├── resources │ │ └── logback.xml │ └── scala │ │ └── es │ │ └── weso │ │ └── rdfshape │ │ └── server │ │ ├── Server.scala │ │ ├── api │ │ ├── ServiceRouteOperation.scala │ │ ├── definitions │ │ │ ├── ApiDefaults.scala │ │ │ ├── ApiDefinitions.scala │ │ │ └── UmlDefinitions.scala │ │ ├── format │ │ │ ├── Format.scala │ │ │ └── dataFormats │ │ │ │ ├── DataFormat.scala │ │ │ │ ├── GraphicFormat.scala │ │ │ │ ├── HtmlFormat.scala │ │ │ │ ├── RdfFormat.scala │ │ │ │ ├── ShapeMapFormat.scala │ │ │ │ └── schemaFormats │ │ │ │ ├── SchemaFormat.scala │ │ │ │ ├── ShExFormat.scala │ │ │ │ └── ShaclFormat.scala │ │ ├── routes │ │ │ ├── ApiService.scala │ │ │ ├── api │ │ │ │ └── service │ │ │ │ │ └── BaseService.scala │ │ │ ├── data │ │ │ │ ├── logic │ │ │ │ │ ├── DataSource.scala │ │ │ │ │ ├── aux │ │ │ │ │ │ └── InferenceCodecs.scala │ │ │ │ │ ├── operations │ │ │ │ │ │ ├── DataConvert.scala │ │ │ │ │ │ ├── DataExtract.scala │ │ │ │ │ │ ├── DataInfo.scala │ │ │ │ │ │ ├── DataOperation.scala │ │ │ │ │ │ └── DataQuery.scala │ │ │ │ │ └── types │ │ │ │ │ │ ├── Data.scala │ │ │ │ │ │ ├── DataSingle.scala │ │ │ │ │ │ └── merged │ │ │ │ │ │ ├── DataCompound.scala │ │ │ │ │ │ └── MergedModels.scala │ │ │ │ └── service │ │ │ │ │ ├── DataService.scala │ │ │ │ │ └── operations │ │ │ │ │ ├── DataConvertInput.scala │ │ │ │ │ ├── DataExtractInput.scala │ │ │ │ │ ├── DataInfoInput.scala │ │ │ │ │ └── DataQueryInput.scala │ │ │ ├── endpoint │ │ │ │ ├── logic │ │ │ │ │ ├── Endpoint.scala │ │ │ │ │ ├── Outgoing.scala │ │ │ │ │ └── query │ │ │ │ │ │ ├── SparqlQuery.scala │ │ │ │ │ │ └── SparqlQuerySource.scala │ │ │ │ └── service │ │ │ │ │ ├── EndpointService.scala │ │ │ │ │ └── operations │ │ │ │ │ └── EndpointOutgoingInput.scala │ │ │ ├── fetch │ │ │ │ └── service │ │ │ │ │ └── FetchService.scala │ │ │ ├── permalink │ │ │ │ ├── logic │ │ │ │ │ └── Permalink.scala │ │ │ │ └── service │ │ │ │ │ └── PermalinkService.scala │ │ │ ├── schema │ │ │ │ ├── logic │ │ │ │ │ ├── SchemaSource.scala │ │ │ │ │ ├── aux │ │ │ │ │ │ └── SchemaAdapter.scala │ │ │ │ │ ├── operations │ │ │ │ │ │ ├── SchemaConvert.scala │ │ │ │ │ │ ├── SchemaInfo.scala │ │ │ │ │ │ ├── SchemaOperation.scala │ │ │ │ │ │ ├── SchemaValidate.scala │ │ │ │ │ │ └── stream │ │ │ │ │ │ │ ├── StreamValidation.scala │ │ │ │ │ │ │ ├── configuration │ │ │ │ │ │ │ ├── StreamValidationConfiguration.scala │ │ │ │ │ │ │ ├── StreamValidationExtractorConfiguration.scala │ │ │ │ │ │ │ ├── StreamValidationStreamConfiguration.scala │ │ │ │ │ │ │ └── StreamValidationValidatorConfiguration.scala │ │ │ │ │ │ │ └── transformations │ │ │ │ │ │ │ ├── CometTransformations.scala │ │ │ │ │ │ │ ├── ValidationResultTransformations.scala │ │ │ │ │ │ │ └── WebSocketTransformations.scala │ │ │ │ │ ├── trigger │ │ │ │ │ │ ├── TriggerMode.scala │ │ │ │ │ │ ├── TriggerModeType.scala │ │ │ │ │ │ ├── TriggerShapeMap.scala │ │ │ │ │ │ └── TriggerTargetDeclarations.scala │ │ │ │ │ └── types │ │ │ │ │ │ ├── Schema.scala │ │ │ │ │ │ └── SchemaSimple.scala │ │ │ │ └── service │ │ │ │ │ ├── SchemaService.scala │ │ │ │ │ └── operations │ │ │ │ │ ├── SchemaConvertInput.scala │ │ │ │ │ ├── SchemaInfoInput.scala │ │ │ │ │ ├── SchemaValidateInput.scala │ │ │ │ │ └── SchemaValidateStreamInput.scala │ │ │ ├── shapemap │ │ │ │ ├── logic │ │ │ │ │ ├── ShapeMap.scala │ │ │ │ │ ├── ShapeMapSource.scala │ │ │ │ │ └── operations │ │ │ │ │ │ ├── ShapeMapInfo.scala │ │ │ │ │ │ └── ShapeMapOperation.scala │ │ │ │ └── service │ │ │ │ │ ├── ShapeMapService.scala │ │ │ │ │ └── operations │ │ │ │ │ └── ShapeMapInfoInput.scala │ │ │ └── wikibase │ │ │ │ ├── logic │ │ │ │ ├── model │ │ │ │ │ ├── Wikibase.scala │ │ │ │ │ ├── Wikidata.scala │ │ │ │ │ └── objects │ │ │ │ │ │ ├── wikibase │ │ │ │ │ │ ├── WikibaseEntity.scala │ │ │ │ │ │ ├── WikibaseObject.scala │ │ │ │ │ │ ├── WikibaseProperty.scala │ │ │ │ │ │ └── WikibaseSchema.scala │ │ │ │ │ │ └── wikidata │ │ │ │ │ │ ├── WikidataEntity.scala │ │ │ │ │ │ ├── WikidataObject.scala │ │ │ │ │ │ ├── WikidataProperty.scala │ │ │ │ │ │ └── WikidataSchema.scala │ │ │ │ └── operations │ │ │ │ │ ├── WikibaseOperation.scala │ │ │ │ │ ├── WikibaseOperationDetails.scala │ │ │ │ │ ├── WikibaseOperationFormats.scala │ │ │ │ │ ├── WikibaseOperationResult.scala │ │ │ │ │ ├── get │ │ │ │ │ ├── WikibaseGetLabels.scala │ │ │ │ │ ├── WikibaseGetOperation.scala │ │ │ │ │ └── WikibasePropTypes.scala │ │ │ │ │ ├── languages │ │ │ │ │ └── WikibaseLanguages.scala │ │ │ │ │ ├── query │ │ │ │ │ └── WikibaseQueryOperation.scala │ │ │ │ │ ├── schema │ │ │ │ │ ├── WikibaseSchemaContent.scala │ │ │ │ │ ├── WikibaseSchemaExtract.scala │ │ │ │ │ ├── WikibaseSchemaValidate.scala │ │ │ │ │ └── WikibaseSheXerExtract.scala │ │ │ │ │ └── search │ │ │ │ │ ├── WikibaseSearchEntity.scala │ │ │ │ │ ├── WikibaseSearchLexeme.scala │ │ │ │ │ ├── WikibaseSearchOperation.scala │ │ │ │ │ ├── WikibaseSearchProperty.scala │ │ │ │ │ ├── WikibaseSearchSchema.scala │ │ │ │ │ └── WikibaseSearchTypes.scala │ │ │ │ └── service │ │ │ │ ├── WikibaseService.scala │ │ │ │ └── operations │ │ │ │ ├── WikibaseOperationInput.scala │ │ │ │ └── WikibaseValidateInput.scala │ │ ├── swagger │ │ │ └── package.scala │ │ └── utils │ │ │ ├── Http4sUtils.scala │ │ │ ├── OptEitherF.scala │ │ │ └── parameters │ │ │ ├── IncomingRequestParameters.scala │ │ │ └── PartsMap.scala │ │ ├── html2rdf │ │ ├── HtmlToRdf.scala │ │ └── RdfSourceTypes.scala │ │ ├── implicits │ │ ├── codecs │ │ │ └── package.scala │ │ ├── query_parsers │ │ │ └── package.scala │ │ └── string_parsers │ │ │ └── package.scala │ │ └── utils │ │ ├── error │ │ ├── ExitCodes.scala │ │ ├── SysUtils.scala │ │ └── exceptions │ │ │ ├── JsonConversionException.scala │ │ │ ├── SSLContextCreationException.scala │ │ │ ├── UnexpectedWebSocketFrameException.scala │ │ │ └── WikibaseServiceException.scala │ │ ├── json │ │ └── JsonUtils.scala │ │ ├── networking │ │ └── NetworkingUtils.scala │ │ ├── numeric │ │ └── NumericUtils.scala │ │ ├── other │ │ ├── MyEnum.scala │ │ └── package.scala │ │ └── secure │ │ └── SSLHelper.scala │ └── test │ └── scala │ └── es │ └── weso │ └── rdfshape │ └── server │ └── api │ ├── ResultMatcher.scala │ ├── TestHttp4sTest.scala │ └── compoundData │ └── CompoundDataTest.pending ├── project ├── build.properties └── plugins.sbt ├── src └── main │ └── scala │ └── es │ └── weso │ └── rdfshape │ ├── Main.scala │ ├── cli │ ├── ArgumentsData.scala │ └── CliManager.scala │ └── logging │ ├── LoggingLevels.scala │ ├── LoggingManager.scala │ └── filters │ └── VerbosityFilter.scala ├── version.sbt └── website ├── .gitignore ├── babel.config.js ├── docusaurus.config.js ├── package.json ├── project └── build.properties ├── sidebars.js ├── src ├── components │ ├── HomepageFeatures.js │ └── HomepageFeatures.module.css ├── css │ └── custom.css └── pages │ ├── docs.js │ ├── index.js │ └── index.module.css ├── static ├── .nojekyll ├── favicon.ico └── img │ ├── logo-weso-footer.png │ ├── logo-weso.png │ ├── preview.png │ ├── rocket.svg │ ├── scala-icon.svg │ ├── scala.svg │ ├── tutorial │ ├── docsVersionDropdown.png │ └── localeDropdown.png │ └── webdocs.svg └── yarn.lock /.codacy.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | rubocop: 4 | exclude_paths: 5 | - config/engines.yml 6 | duplication: 7 | exclude_paths: 8 | - config/engines.yml 9 | metric: 10 | exclude_paths: 11 | - config/engines.yml 12 | coverage: 13 | exclude_paths: 14 | - config/engines.yml 15 | exclude_paths: 16 | - 'modules/server/src/main/resources/static/**' -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .dockerignore 3 | 4 | target 5 | logs 6 | .bloop 7 | .bsp 8 | .scalafmt.conf 9 | notes 10 | 11 | .git 12 | .gitignore 13 | .github 14 | .codacy.yaml 15 | 16 | .metals 17 | .vscode 18 | .idea 19 | 20 | .directory 21 | .DS_Store 22 | 23 | .codacy.yaml 24 | .zenodo.json 25 | .scalafmt.conf 26 | *.md 27 | LICENSE 28 | Procfile 29 | 30 | website 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Continuous Integration 9 | 10 | on: 11 | pull_request: 12 | branches: ['*'] 13 | push: 14 | branches: ['*'] 15 | 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | jobs: 20 | build: 21 | name: Build and Test 22 | strategy: 23 | matrix: 24 | os: [ubuntu-latest] 25 | scala: [2.13.8] 26 | java: [adopt@1.11] 27 | runs-on: ${{ matrix.os }} 28 | steps: 29 | - name: Checkout current branch (full) 30 | uses: actions/checkout@v2 31 | with: 32 | fetch-depth: 0 33 | 34 | - name: Setup Java and Scala 35 | uses: olafurpg/setup-scala@v10 36 | with: 37 | java-version: ${{ matrix.java }} 38 | 39 | - name: Cache sbt 40 | uses: actions/cache@v2 41 | with: 42 | path: | 43 | ~/.sbt 44 | ~/.ivy2/cache 45 | ~/.coursier/cache/v1 46 | ~/.cache/coursier/v1 47 | ~/AppData/Local/Coursier/Cache/v1 48 | ~/Library/Caches/Coursier/v1 49 | key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} 50 | 51 | - name: Check that workflows are up to date 52 | run: sbt ++${{ matrix.scala }} githubWorkflowCheck 53 | 54 | - name: Build project 55 | run: sbt ++${{ matrix.scala }} test 56 | 57 | - name: Compress target directories 58 | run: tar cf targets.tar target modules/server/target rdfshape-docs/target project/target 59 | 60 | - name: Upload target directories 61 | uses: actions/upload-artifact@v2 62 | with: 63 | name: target-${{ matrix.os }}-${{ matrix.scala }}-${{ matrix.java }} 64 | path: targets.tar 65 | 66 | publish: 67 | name: Publish Artifacts 68 | needs: [build] 69 | if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main') 70 | strategy: 71 | matrix: 72 | os: [ubuntu-latest] 73 | scala: [2.13.8] 74 | java: [adopt@1.11] 75 | runs-on: ${{ matrix.os }} 76 | steps: 77 | - name: Checkout current branch (full) 78 | uses: actions/checkout@v2 79 | with: 80 | fetch-depth: 0 81 | 82 | - name: Setup Java and Scala 83 | uses: olafurpg/setup-scala@v10 84 | with: 85 | java-version: ${{ matrix.java }} 86 | 87 | - name: Cache sbt 88 | uses: actions/cache@v2 89 | with: 90 | path: | 91 | ~/.sbt 92 | ~/.ivy2/cache 93 | ~/.coursier/cache/v1 94 | ~/.cache/coursier/v1 95 | ~/AppData/Local/Coursier/Cache/v1 96 | ~/Library/Caches/Coursier/v1 97 | key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} 98 | 99 | - name: Download target directories (2.13.8) 100 | uses: actions/download-artifact@v2 101 | with: 102 | name: target-${{ matrix.os }}-2.13.8-${{ matrix.java }} 103 | 104 | - name: Inflate target directories (2.13.8) 105 | run: | 106 | tar xf targets.tar 107 | rm targets.tar 108 | 109 | - name: Publish project 110 | run: sbt ++${{ matrix.scala }} +publish -------------------------------------------------------------------------------- /.github/workflows/clean.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Clean 9 | 10 | on: push 11 | 12 | jobs: 13 | delete-artifacts: 14 | name: Delete Artifacts 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - name: Delete artifacts 20 | run: | 21 | # Customize those three lines with your repository and credentials: 22 | REPO=${GITHUB_API_URL}/repos/${{ github.repository }} 23 | 24 | # A shortcut to call GitHub API. 25 | ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; } 26 | 27 | # A temporary file which receives HTTP response headers. 28 | TMPFILE=/tmp/tmp.$$ 29 | 30 | # An associative array, key: artifact name, value: number of artifacts of that name. 31 | declare -A ARTCOUNT 32 | 33 | # Process all artifacts on this repository, loop on returned "pages". 34 | URL=$REPO/actions/artifacts 35 | while [[ -n "$URL" ]]; do 36 | 37 | # Get current page, get response headers in a temporary file. 38 | JSON=$(ghapi --dump-header $TMPFILE "$URL") 39 | 40 | # Get URL of next page. Will be empty if we are at the last page. 41 | URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*.*//') 42 | rm -f $TMPFILE 43 | 44 | # Number of artifacts on this page: 45 | COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') )) 46 | 47 | # Loop on all artifacts on this page. 48 | for ((i=0; $i < $COUNT; i++)); do 49 | 50 | # Get name of artifact and count instances of this name. 51 | name=$(jq <<<$JSON -r ".artifacts[$i].name?") 52 | ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1)) 53 | 54 | id=$(jq <<<$JSON -r ".artifacts[$i].id?") 55 | size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") )) 56 | printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size 57 | ghapi -X DELETE $REPO/actions/artifacts/$id 58 | done 59 | done -------------------------------------------------------------------------------- /.github/workflows/publish_docker.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker image 2 | on: 3 | push: 4 | branches: [ master, main ] 5 | tags: [ "*" ] 6 | 7 | jobs: 8 | push_to_registry: 9 | name: Push Docker image to GitHub Container Registry 10 | if: ${{ github.repository_owner == 'weso' }} # Do not run in forks 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Set up Docker Buildx 14 | uses: docker/setup-buildx-action@v1 15 | 16 | - name: Login to GitHub Container Registry 17 | uses: docker/login-action@v1 18 | with: 19 | registry: ghcr.io 20 | username: ${{ secrets.GITHUB_TOKEN }} 21 | password: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | - name: Build and push to GitHub Container Registry 24 | id: docker_build 25 | uses: docker/build-push-action@v2 26 | with: 27 | push: true 28 | tags: ghcr.io/weso/rdfshape-api:${{ github.sha }} 29 | 30 | - name: Image digest 31 | run: echo ${{ steps.docker_build.outputs.digest }} 32 | -------------------------------------------------------------------------------- /.github/workflows/publish_gh_pages.yml: -------------------------------------------------------------------------------- 1 | name: Publish website (Mdoc + Docusaurus) 2 | on: 3 | push: 4 | branches: [ main, master ] 5 | tags: [ "*" ] 6 | jobs: 7 | publish: 8 | name: Build a docusaurus website with guides and scaladoc 9 | if: ${{ github.repository_owner == 'weso' }} # Do not run in forks 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repo 13 | uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | - name: Set up Scala 17 | uses: olafurpg/setup-scala@v11 18 | with: 19 | java-version: openjdk@1.14 20 | 21 | - name: Set up GPG 22 | uses: olafurpg/setup-gpg@v3 23 | 24 | - name: Setup graphviz/dot for scaladoc diagrams 25 | uses: ts-graphviz/setup-graphviz@v1 26 | 27 | - name: Build and publish docusaurus site, generating the scaladoc 28 | run: sbt '++2.13.8 docs/docusaurusPublishGhpages' 29 | env: 30 | GIT_DEPLOY_KEY: ${{ secrets.RDFSHAPE_API_DEPLOY_KEY }} 31 | -------------------------------------------------------------------------------- /.github/workflows/publish_oss_sonatype.yml: -------------------------------------------------------------------------------- 1 | name: Publish OSS Sonatype 2 | on: 3 | push: 4 | branches: [ master, main ] 5 | tags: [ "*" ] 6 | 7 | jobs: 8 | publish: 9 | if: ${{ github.repository_owner == 'weso' }} # Do not run in forks 10 | strategy: 11 | matrix: 12 | os: [ ubuntu-latest ] 13 | scala: [ 2.13.6 ] 14 | java: [ adopt@1.11 ] 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - uses: actions/checkout@v2.3.4 18 | with: 19 | fetch-depth: 0 20 | - uses: olafurpg/setup-scala@v10 21 | with: 22 | java-version: ${{ matrix.java }} 23 | - run: sbt ci-release 24 | env: 25 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 26 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 27 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 28 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Emacs 2 | *~ 3 | \#*\# 4 | /.emacs.desktop 5 | /.emacs.desktop.lock 6 | *.elc 7 | auto-save-list 8 | tramp 9 | .\#* 10 | 11 | .bloop 12 | .metals 13 | metals.sbt 14 | .bsp 15 | 16 | # Java 17 | *.class 18 | *.log 19 | 20 | # sbt specific 21 | dist/* 22 | target/ 23 | 24 | lib_managed/ 25 | src_managed/ 26 | project/boot/ 27 | project/plugins/project/ 28 | 29 | # Scala-IDE specific 30 | .scala_dependencies 31 | 32 | .settings 33 | .settings/* 34 | .project 35 | .classpath 36 | .cache 37 | /bin 38 | settings.json 39 | .vscode 40 | .ensime_cache 41 | .ensime 42 | .idea 43 | 44 | node_modules/ 45 | .grunt 46 | 47 | # Logs 48 | logs 49 | npm-debug.log* 50 | yarn-debug.log* 51 | yarn-error.log* 52 | 53 | # Runtime data 54 | pids 55 | *.pid 56 | *.seed 57 | *.pid.lock 58 | .npm 59 | *.tgz 60 | 61 | # Generated documentation 62 | scaladoc 63 | 64 | # Other 65 | .directory 66 | 67 | -------------------------------------------------------------------------------- /.jvmopts: -------------------------------------------------------------------------------- 1 | -Xms512M 2 | -Xmx4096M 3 | -Xss2M 4 | -XX:MaxMetaspaceSize=1024M -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "2.7.5" 2 | style = defaultWithAlign 3 | maxColumn = 80 4 | comments.wrap = standalone 5 | spaces.afterKeywordBeforeParen = false 6 | -------------------------------------------------------------------------------- /.zenodo.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "RDFShape implementation", 3 | "license": "other-open", 4 | "title": "RDFShape: Online demo implementation of ShEx and SHACL", 5 | "version": "v0.0.70", 6 | "upload_type": "software", 7 | "publication_date": "2018-04-04", 8 | "creators": [ 9 | { 10 | "affiliation": "University of Oviedo", 11 | "name": "Jose Emilio Labra Gayo" 12 | } 13 | ], 14 | "access_right": "open", 15 | "related_identifiers": [ 16 | { 17 | "scheme": "url", 18 | "identifier": "https://github.com/labra/shaclex/tree/v0.0.69", 19 | "relation": "isSupplementTo" 20 | }, 21 | { 22 | "scheme": "doi", 23 | "identifier": "10.5281/zenodo.1212658", 24 | "relation": "isPartOf" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ## Build environment. 2 | # Java 11, Scala 2.12.13, SBT 1.5.0 included. 3 | FROM hseeberger/scala-sbt:11.0.14.1-oraclelinux8_1.6.2_2.13.8 as build 4 | # Build application inside /app 5 | WORKDIR /app 6 | 7 | # Install git 8 | RUN microdnf update -y && microdnf install git -y --nodocs --refresh 9 | 10 | # Copy all application files 11 | COPY . ./ 12 | # Build to /app/target/universal/rdfshape.zip 13 | RUN ["sbt", "Universal / packageBin"] 14 | 15 | ## Prod environment. 16 | FROM adoptopenjdk/openjdk12:jre-12.0.2_10-ubuntu as prod 17 | LABEL org.opencontainers.image.source="https://github.com/weso/rdfshape-api" 18 | WORKDIR /app 19 | 20 | # Copy zip with universal executable 21 | COPY --from=build /app/target/universal/rdfshape.zip . 22 | 23 | # Download required programs dependencies. Unzip binaries. 24 | RUN apt -qq -y update && apt -qq -y upgrade && \ 25 | apt -qq -y install unzip graphviz && \ 26 | unzip -q rdfshape.zip && \ 27 | rm rdfshape.zip 28 | 29 | # Add rdfshape to path 30 | ENV PATH="/app/rdfshape/bin:${PATH}" 31 | 32 | # Run 33 | # Port for the app to run 34 | ENV PORT=8080 35 | EXPOSE $PORT 36 | # Timeout for streaming validations (seconds) 37 | ENV STREAM_TIMEOUT=40 38 | # Non-priviledged user to run the app 39 | RUN addgroup --system rdfshape && adduser --system --shell /bin/false --ingroup rdfshape rdfshape 40 | RUN chown -R rdfshape:rdfshape /app 41 | USER rdfshape 42 | 43 | # JVM settings to allow connection to DB 44 | ENV SSL_FIX="-Djdk.tls.client.protocols=TLSv1.2" 45 | 46 | # Define commands to launch RDFShape 47 | ENV HTTPS_CLI_ARG="--https" 48 | ENV RDFSHAPE_CMD_HTTP="rdfshape $SSL_FIX --port $PORT --stream-timeout $STREAM_TIMEOUT -s" 49 | ENV RDFSHAPE_CMD_HTTPS="$RDFSHAPE_CMD_HTTP $HTTPS_CLI_ARG" 50 | 51 | CMD bash -c "if [[ ! -z '$USE_HTTPS' ]]; then $RDFSHAPE_CMD_HTTPS; else $RDFSHAPE_CMD_HTTP; fi" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jose Emilio Labra Gayo 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 | -------------------------------------------------------------------------------- /docs/api-deployment/deployment_docker.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: deployment_docker 3 | title: Running with Docker 4 | --- 5 | 6 | # Running with Docker 7 | 8 | ## Requirements 9 | 10 | * Docker must be installed in the machine that will build the images and/or run 11 | the containers. 12 | 13 | ## Building the image 14 | 15 | * Pull from our [container registry](@API_CONTAINER_REGISTRY@) or... 16 | * Use the provided Dockerfile to build @APP_NAME@ images: 17 | 1. Run `docker build -t {YOUR_IMAGE_NAME} .` from the project folder. 18 | > No build arguments are required. 19 | 20 | ## Running containers 21 | 22 | * When running a container, you may provide the following environment variables 23 | via `--env`: 24 | - `PORT` [optional]: 25 | - Port where the API is exposed inside the container. Default is 8080. 26 | - `USE_HTTPS` [optional]: 27 | - Any non-empty value to try to serve via HTTPS, leave undefined for 28 | HTTP. 29 | - `STREAM_TIMEOUT` [optional]: 30 | - Integer value specifying how many seconds a streaming validation 31 | should wait for incoming data before being terminated. Defaults to 40. 32 | 33 | ## Supported tags 34 | 35 | - _:stable_: Stable build updated manually. 36 | - <_:hashed_tags_>: Automated builds by our CI pipeline. With the latest 37 | features uploaded to our repository but lacking 38 | internal testing. 39 | 40 | ## Serving with HTTPS 41 | 42 | Follow the indications above. -------------------------------------------------------------------------------- /docs/api-deployment/deployment_manual.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: deployment_manual 3 | title: Running with SBT 4 | --- 5 | 6 | # Running with SBT 7 | 8 | ## Requirements 9 | 10 | * @APP_NAME@ requires [SBT](https://www.scala-sbt.org/) to be built 11 | 12 | ## Interactive mode with SBT 13 | 14 | The way to familiarize yourself with the software is to run the `sbt` command, which will open the _sbt shell_, 15 | and execute commands from there. 16 | 17 | 1. Clone this repository 18 | 2. Go to directory where @APP_NAME@ source code is located and execute `sbt`. After some time downloading dependencies and 19 | compiling the source code, the _sbt shell_ will launch if everything went right. 20 | 3. From this point, you may execute several commands from the sbt shell: 21 | - `run` for the API to launch and be accessible at [localhost:8080](http://localhost:8080). 22 | - `run --help` to see the help menu with further usage 23 | information. 24 | 25 | ## Binary mode 26 | 27 | The fastest way to run @APP_NAME@ is to compile the code and generate an executable file: 28 | 29 | 1. Clone this repo and run `sbt`, as seen above. 30 | 2. From the sbt shell, run `Universal/packageBin`. 31 | 3. A zip file with an executable and all the program dependencies will be created 32 | inside `(ProjectFolder)/target/universal`. 33 | 34 | ## Serving with HTTPS 35 | 36 | You can serve @APP_NAME@ with HTTPS in 2 ways: 37 | 38 | 1. **[Recommended]** Web server setup: 39 | - Run a web server (i.e., Nginx) in your machine or in a separate container and configure it as a reverse proxy that 40 | forwards incoming requests to the API. Configure your web server to use HTTPS to communicate with clients. 41 | - Launch the application **normally** (no `--https` is required, the web server will handle it). 42 | 2. Manual setup: 43 | - Set the following environment variables in your machine/container, so it can search and use your certificates in 44 | a [Java keystore](https://docs.oracle.com/javase/8/docs/api/java/security/KeyStore.html): 45 | - `KEYSTORE_PATH`: location of the keystore storing the certificate. 46 | - `KEYSTORE_PASSWORD`: password protecting the keystore (leave empty if there is none). 47 | - `KEYMANAGER_PASSWORD`: password protecting the certificate (leave empty is there is none). 48 | - Launch the application with the `--https` argument. 49 | -------------------------------------------------------------------------------- /docs/api-deployment/deployment_overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: deployment_overview 3 | title: Deploying @APP_NAME@ 4 | --- 5 | 6 | # Deploying @APP_NAME@ 7 | 8 | @APP_NAME@ can be either launched with [SBT](https://www.scala-sbt.org/) or run as a [Docker](https://www.docker.com/) 9 | container. We encourage the usage of Docker images if there is no need to modify the product to suit your needs. 10 | 11 | To learn more, proceed to the following pages under this category. -------------------------------------------------------------------------------- /docs/api-testing-auditing/testing-auditing_integration.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: testing-auditing_integration 3 | title: Integration tests and other 4 | --- 5 | 6 | # Integration tests and other 7 | 8 | You may use any specific software to test the inner workings of @APP_NAME@ and see if they fit your needs. We strongly 9 | suggest using one of these: 10 | 11 | - [_curl_](https://curl.se/) (CLI) 12 | - [_Postman_](https://www.postman.com/) (GUI) -------------------------------------------------------------------------------- /docs/api-testing-auditing/testing-auditing_logs.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: testing-auditing_logs 3 | title: Logging system 4 | --- 5 | 6 | # Logging system 7 | 8 | ## Infrastructure 9 | 10 | This project uses a logging framework provided by two mature libraries: 11 | 12 | 1. [Logback](http://logback.qos.ch/): Framework's back-end, provides customizable logging levels and log appenders for 13 | console, files, etc. 14 | 15 | 2. [scala-logging](https://github.com/lightbend/scala-logging): Framework's front-end, reduces the verbosity of logging 16 | messages from the code thanks to several macros and utilities. 17 | 18 | ## Functionality 19 | 20 | ### Console log messages 21 | 22 | @APP_NAME@ is configured to use a [Console Appender](http://logback.qos.ch/manual/appenders.html#ConsoleAppender) to log 23 | messages to the console, refer to [CLI section](/rdfshape-api/docs/api-usage/usage_cli) to configure what is logged to 24 | the console via CLI arguments. 25 | 26 | ### File log messages 27 | 28 | @APP_NAME@ is configured to use 29 | a [Rolling File Appender](http://logback.qos.ch/manual/appenders.html#RollingFileAppender) to store all log messages of 30 | level **DEBUG** and above inside _.log_ files, whether this messages are verbosely shown on console or not. 31 | 32 | The logs written to the files: 33 | 34 | - Are located inside a `logs` folder, in the application's execution path. Therefore, make sure you run the app with a 35 | user with write access and from a location that is writable. 36 | 37 | - Follow a [size-based rolling policy](http://logback.qos.ch/manual/appenders.html#SizeBasedTriggeringPolicy), which 38 | implies that logs are rotated and compressed when they reach a certain file size defined in logback's configuration 39 | file. 40 | 41 | ### Adding custom functionality 42 | 43 | The project is already configured to work as explained above. For further configuration, check 44 | the [logback.xml](https://github.com/weso/rdfshape-api/blob/master/modules/server/src/main/resources/logback.xml) 45 | configuration file or the documentation of each respective library. -------------------------------------------------------------------------------- /docs/api-testing-auditing/testing-auditing_munit.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: testing-auditing_munit 3 | title: Unit tests with Munit 4 | --- 5 | 6 | # Unit tests with Munit 7 | 8 | @APP_NAME@ comes with some built-in unit tests written with [munit](https://scalameta.org/munit/). These are used 9 | internally for basic testing. 10 | 11 | You can run the tests by running `sbt test` or simply `test` from the SBT shell. -------------------------------------------------------------------------------- /docs/api-usage/usage_cli.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: usage_cli 3 | title: Command Line Interface 4 | --- 5 | 6 | # Command Line Interface 7 | 8 | ## Command Reference 9 | 10 | @APP_NAME@'s CLI currently supports the following launch-arguments: 11 | 12 | - `--https` Attempt to serve the API via HTTPS (default is false), searching for 13 | certificates as specified in the current environment. 14 | - `-p, --port ` Port in which the API will listen for requests. Values 15 | must be in range 1-65535 (default is 8080). 16 | - `-s, --silent` Enable silent mode in order not to log any output to console ( 17 | default is false). 18 | - `-t, --stream-timeout ` Seconds that the server will wait before 19 | closing a streaming validation for which no data is received. Values must be 20 | in range 1-1800 (default is 40). 21 | - `-v, --verbose` Show additional logging information (use cumulative times for 22 | additional info, like: `-vvv`). 23 | - `--version` Print the version of the program. 24 | - `--help` Print the help menu. 25 | 26 | ## Verbosity levels 27 | 28 | When using the `-v, --verbose` CLI argument, the following logging messages are 29 | shown on console at each time: 30 | 31 | - `No verbose argument` **ERROR** level messages 32 | - `-v` **WARN** level messages and upwards 33 | - `-vv` **INFO** level messages and upwards (includes client connections and 34 | requests) 35 | - `-vvv` **DEBUG** level messages and upwards 36 | 37 | ## JVM Custom Arguments 38 | 39 | In case @APP_NAME@ is having trouble to generate permalinks due to an SSL issue, 40 | try adding the following argument: 41 | 42 | - `-Djdk.tls.client.protocols=TLSv1.2` 43 | 44 | ## Examples 45 | 46 | 1. Launching @APP_NAME@ in port 8081: 47 | 48 | - `rdfshape -p 8081` 49 | 50 | 2. Launching @APP_NAME@ in port 80, try to use the HTTPS configuration from the 51 | environment: 52 | 53 | - `rdfshape -p 80 --https` 54 | 55 | 3. Launching @APP_NAME@ in port 8080, with the maximum verbosity level: 56 | 57 | - `rdfshape -vvv` 58 | -------------------------------------------------------------------------------- /docs/home.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: home 3 | title: RDFShape API Web Docs 4 | --- 5 | 6 | # @APP_NAME@ Web Docs 7 | 8 | ## @VERSION@ 9 | 10 | The following docs contain guides on the usage and deployment of @APP_NAME@. Therefore, this may be useful: 11 | 12 | - From a back-end perspective: you want to consume the API in a machine-readable format. 13 | - If you are willing to build your own API client to use RDFShape for your particular use case. 14 | 15 | If you are willing to consume the API graphically or use it as a playground with a human-readable format, refer to our 16 | own [@CLIENT_NAME@](@CLIENT_REPO@). 17 | 18 | --- 19 | 20 | WESO members may access additional documentation [here](@WESOLOCAL_URL@). -------------------------------------------------------------------------------- /docs/webpage/webpage_info.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: webpage_info 3 | title: About this webpage 4 | --- 5 | 6 | # About this webpage 7 | 8 | The website for @APP_NAME@ is currently hosted in @WEBPAGE_URL@. It is a [React](https://reactjs.org/) webpage 9 | automatically generated by [Docusaurus](https://docusaurus.io/). 10 | 11 | Pushes to the main branch trigger an automatic re-build and re-publish of the page with the latest changes (if any). 12 | This is done with the 13 | following [GitHub action](https://github.com/weso/rdfshape-api/blob/master/.github/workflows/publish_gh_pages.yml). 14 | 15 | ## Website edition 16 | 17 | The website contents are located: 18 | 19 | 1. Inside the _[website](https://github.com/weso/rdfshape-api/tree/master/website) folder:_ Docusaurus 20 | configuration files, React pages, header/footer/sidebar contents, etc. 21 | 2. Inside the _[docs](https://github.com/weso/rdfshape-api/tree/master/docs) folder:_ Markdown files, first 22 | processed by [mdoc](https://scalameta.org/mdoc/) and eventually by Docusaurus to create 23 | the [Web Docs](https://www.weso.es/rdfshape-api/docs/). 24 | 25 | ## Website creation guidelines 26 | 27 | ### Web pages 28 | 29 | In order to create new pages, create a JS file inside _website/src/pages_ for Docusaurus to be aware of its existence 30 | and assign them a URL based on their location inside the _pages_ folder. 31 | 32 | ### Web docs 33 | 34 | Create new pages using markdown syntax inside the _docs_ folder. These pages will be processed by _mdoc_ and assigned a 35 | URL inside `/docs` by _docusaurus_ when running the task `docs/docusaurusCreateSite` from SBT. 36 | 37 | ## Issues while creating the webpage 38 | 39 | In order to use _mdoc_ in combination with _docusaurus_, this [guide](https://scalameta.org/mdoc/docs/docusaurus.html) 40 | was followed. However, minor issues occurred: 41 | 42 | 1. The _package.json_ had to be modified to include the script `publish-gh-pages`. 43 | 2. The [Docusaurus config file](https://github.com/weso/rdfshape-api/blob/master/website/docusaurus.config.js) had to be 44 | modified to indicate what the location of the markdown files with the web docs is. 45 | 3. The [sidebar configuration file](https://github.com/weso/rdfshape-api/blob/master/website/sidebars.js) was modified 46 | to customize the sidebar that is used as navigation when browsing the web docs. -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/ServiceRouteOperation.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api 2 | 3 | import io.circe.Decoder 4 | 5 | /** This trait defines an operation performed by a route in a service 6 | * and the way to decode the incoming data into the required object 7 | * 8 | * All errors occurred during object instantiation will be mapped to 9 | * DecodingErrors 10 | * 11 | * @tparam C Type into which the client data will be decoded for usage in this route 12 | */ 13 | trait ServiceRouteOperation[C] { 14 | 15 | /** Decoder in charge of converting the data sent by the client to a 16 | * usable domain structure 17 | */ 18 | implicit val decoder: Decoder[C] 19 | } 20 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/definitions/ApiDefaults.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.definitions 2 | 3 | import es.weso.rdf.nodes.IRI 4 | import es.weso.rdf.{InferenceEngine, NONE} 5 | import es.weso.rdfshape.server.api.format.dataFormats.schemaFormats.{ 6 | SchemaFormat, 7 | ShaclFormat 8 | } 9 | import es.weso.rdfshape.server.api.format.dataFormats.{ 10 | DataFormat, 11 | RdfFormat, 12 | ShapeMapFormat 13 | } 14 | import es.weso.rdfshape.server.api.routes.data.logic.DataSource 15 | import es.weso.rdfshape.server.api.routes.data.logic.DataSource.DataSource 16 | import es.weso.rdfshape.server.api.routes.schema.logic.SchemaSource 17 | import es.weso.rdfshape.server.api.routes.schema.logic.SchemaSource.SchemaSource 18 | import es.weso.rdfshape.server.api.routes.shapemap.logic.ShapeMapSource 19 | import es.weso.rdfshape.server.api.routes.shapemap.logic.ShapeMapSource.ShapeMapSource 20 | import es.weso.schema.{ 21 | Schemas, 22 | ShapeMapTrigger, 23 | ValidationTrigger, 24 | Schema => SchemaW 25 | } 26 | import es.weso.shapemaps.ShapeMap 27 | 28 | /** Application-wide defaults 29 | */ 30 | case object ApiDefaults { 31 | 32 | /** [[DataFormat]] used when the format can be omitted or is needed but none was provided 33 | */ 34 | val defaultDataFormat: DataFormat = DataFormat.default 35 | 36 | /** [[RdfFormat]] used when the format can be omitted or is needed but none was provided 37 | */ 38 | val defaultRdfFormat: RdfFormat = RdfFormat.default 39 | 40 | /** [[SchemaFormat]] used when the format can be omitted or is needed but none was provided 41 | */ 42 | val defaultSchemaFormat: SchemaFormat = ShaclFormat.default 43 | 44 | /** [[ShapeMapFormat]] used when the format can be omitted or is needed but none was provided 45 | */ 46 | val defaultShapeMapFormat: ShapeMapFormat = ShapeMapFormat.default 47 | 48 | /** Schema engined ([[SchemaW]]) used when the engine can be omitted or is needed but none was provided 49 | */ 50 | val defaultSchemaEngine: SchemaW = Schemas.defaultSchema 51 | 52 | /** [[ValidationTrigger]] used when the trigger can be omitted or is needed but none was provided 53 | */ 54 | val defaultTriggerMode: ValidationTrigger = ShapeMapTrigger( 55 | ShapeMap.empty 56 | ) 57 | 58 | /** [[InferenceEngine]] used when the engine can be omitted or is needed but none was provided 59 | */ 60 | val defaultInferenceEngine: InferenceEngine = NONE 61 | 62 | /** [[DataSource]] used when the source can be omitted or is needed but none was provided 63 | */ 64 | val defaultDataSource: DataSource = DataSource.default 65 | 66 | /** [[SchemaSource]] used when the source can be omitted or is needed but none was provided 67 | */ 68 | val defaultSchemaSource: SchemaSource = 69 | SchemaSource.default 70 | 71 | /** [[ShapeMapSource]] used when the source can be omitted or is needed but none was provided 72 | */ 73 | val defaultShapeMapSource: ShapeMapSource = 74 | ShapeMapSource.default 75 | 76 | /** [[IRI]] used when the shape label can be omitted or is needed but none was provided 77 | */ 78 | val defaultShapeLabel: IRI = IRI("Shape") 79 | } 80 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/definitions/ApiDefinitions.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.definitions 2 | 3 | import es.weso.rdf.InferenceEngine 4 | import es.weso.rdf.nodes.IRI 5 | import es.weso.rdfshape.server.api.format.dataFormats.schemaFormats.SchemaFormat 6 | import es.weso.rdfshape.server.api.format.dataFormats.{ 7 | DataFormat, 8 | ShapeMapFormat 9 | } 10 | import es.weso.rdfshape.server.api.routes.schema.logic.trigger.{ 11 | TriggerMode, 12 | TriggerModeType 13 | } 14 | import es.weso.schema.{Schemas, Schema => SchemaW} 15 | import es.weso.shapemaps.ShapeMap 16 | import es.weso.utils.FileUtils 17 | import org.http4s.Uri 18 | import org.http4s.implicits.http4sLiteralsSyntax 19 | 20 | /** Global definitions used in the API 21 | */ 22 | case object ApiDefinitions { 23 | 24 | lazy val localBase: IRI = IRI(FileUtils.currentFolderURL) 25 | 26 | /** API route inside the web server 27 | */ 28 | val api = "api" 29 | 30 | /** [[List]] of [[DataFormat]]s accepted by the application 31 | */ 32 | val availableDataFormats: List[DataFormat] = DataFormat.availableFormats 33 | 34 | /** [[List]] of [[SchemaFormat]]s accepted by the application 35 | */ 36 | val availableSchemaFormats: List[SchemaFormat] = SchemaFormat.availableFormats 37 | 38 | /** [[List]] of [[SchemaW]]s used by the application 39 | */ 40 | val availableSchemaEngines: List[SchemaW] = Schemas.availableSchemas 41 | 42 | /** [[List]] of [[ShapeMapFormat]]s accepted by the application 43 | */ 44 | val availableShapeMapFormats: List[ShapeMapFormat] = 45 | ShapeMap.formats 46 | .map(f => ShapeMapFormat.fromString(f)) 47 | .filter(_.isRight) 48 | .map(_.getOrElse(ShapeMapFormat.default)) 49 | 50 | /** [[List]] of [[TriggerMode]]s accepted by the application, by name 51 | * 52 | * @note Must be coherent with [[es.weso.schema.Schemas.availableTriggerModes]] 53 | */ 54 | val availableTriggerModes: List[String] = 55 | List(TriggerModeType.SHAPEMAP, TriggerModeType.TARGET_DECLARATIONS) 56 | 57 | /** [[List]] of [[InferenceEngine]]s accepted by the application 58 | */ 59 | val availableInferenceEngines: List[InferenceEngine] = 60 | InferenceEngine.availableInferenceEngines 61 | 62 | /** [[IRI]] used as base for nodes created internally 63 | */ 64 | val relativeBase: Some[IRI] = Some(IRI("internal://base/")) 65 | 66 | /** [[Uri]] representation of Wikidata's base URL 67 | */ 68 | val wikidataUri: Uri = uri"https://www.wikidata.org" 69 | 70 | /** [[String]] representation of Wikidata's base URL 71 | */ 72 | val wikidataUrl: String = wikidataUri.renderString 73 | 74 | /** [[Uri]] representation of Wikidata's SPARQL endpoint 75 | */ 76 | val wikidataQueryUri: Uri = uri"https://query.wikidata.org/sparql" 77 | 78 | } 79 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/definitions/UmlDefinitions.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.definitions 2 | 3 | import es.weso.uml.PlantUMLOptions 4 | 5 | /** UML-generation related data 6 | */ 7 | case object UmlDefinitions { 8 | 9 | /** Additional options passed down to PlantUML when generating diagrams on the fly. 10 | */ 11 | val umlOptions: PlantUMLOptions = PlantUMLOptions( 12 | watermark = Some("Generated by [[https://rdfshape.weso.es rdfshape]]") 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/format/dataFormats/DataFormat.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.format.dataFormats 2 | 3 | import es.weso.rdfshape.server.api.format._ 4 | import es.weso.rdfshape.server.api.format.dataFormats.schemaFormats.SchemaFormat 5 | import org.http4s.MediaType 6 | 7 | /** Extension of the Format interface to represent generic data formats (RDF, schema, shapeMaps...) 8 | */ 9 | class DataFormat(formatName: String, formatMimeType: MediaType) extends Format { 10 | override val name: String = formatName 11 | override val mimeType: MediaType = formatMimeType 12 | } 13 | 14 | /** Companion object with all DataFormat static utilities 15 | */ 16 | object DataFormat extends FormatCompanion[DataFormat] { 17 | 18 | override lazy val availableFormats: List[DataFormat] = 19 | (RdfFormat.availableFormats ++ 20 | SchemaFormat.availableFormats ++ 21 | HtmlFormat.availableFormats ++ 22 | GraphicFormat.availableFormats ++ 23 | ShapeMapFormat.availableFormats ++ 24 | List(Json, Dot)).distinct 25 | override val default: DataFormat = Json 26 | } 27 | 28 | /** Represents the mime-type "application/json" 29 | */ 30 | case object Json 31 | extends DataFormat( 32 | formatName = "JSON", 33 | formatMimeType = new MediaType("application", "json") 34 | ) 35 | 36 | /** Represents the mime-type "text/vnd.graphviz", used by graphviz 37 | */ 38 | case object Dot 39 | extends DataFormat( 40 | formatName = "DOT", 41 | formatMimeType = new MediaType("text", "vnd.graphviz") 42 | ) 43 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/format/dataFormats/GraphicFormat.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.format.dataFormats 2 | 3 | import es.weso.rdfshape.server.api.format.{Format, FormatCompanion} 4 | import org.http4s.MediaType 5 | 6 | /** Dummy class to differentiate formats used for graphical representations from the more generic DataFormat 7 | * 8 | * @see {@link DataFormat} 9 | */ 10 | sealed class GraphicFormat(formatName: String, formatMimeType: MediaType) 11 | extends DataFormat(formatName, formatMimeType) { 12 | def this(format: Format) = { 13 | this(format.name, format.mimeType) 14 | } 15 | } 16 | 17 | /** Companion object with all RDFFormat static utilities 18 | */ 19 | object GraphicFormat extends FormatCompanion[GraphicFormat] { 20 | 21 | override lazy val availableFormats: List[GraphicFormat] = 22 | List(Svg, Png, PS) 23 | override val default: GraphicFormat = Svg 24 | 25 | } 26 | 27 | /** Represents the mime-type "image/svg+xml" 28 | */ 29 | case object Svg 30 | extends GraphicFormat( 31 | formatName = "SVG", 32 | formatMimeType = MediaType.image.`svg+xml` 33 | ) 34 | 35 | /** Represents the mime-type "image/png" 36 | */ 37 | case object Png 38 | extends GraphicFormat( 39 | formatName = "PNG", 40 | formatMimeType = MediaType.image.png 41 | ) 42 | 43 | /** Represents the mime-type "application/ps" 44 | */ 45 | case object PS 46 | extends GraphicFormat( 47 | formatName = "PS", 48 | formatMimeType = new MediaType("application", "ps") 49 | ) 50 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/format/dataFormats/HtmlFormat.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.format.dataFormats 2 | 3 | import es.weso.rdfshape.server.api.format.FormatCompanion 4 | import org.http4s.MediaType 5 | 6 | /** Dummy class to differentiate HTML-based formats from the more generic DataFormat 7 | * 8 | * @see {@link DataFormat} 9 | */ 10 | class HtmlFormat(formatName: String) 11 | extends DataFormat(formatName, MediaType.text.html) {} 12 | 13 | /** Companion object with all HtmlFormat static utilities 14 | */ 15 | object HtmlFormat extends FormatCompanion[HtmlFormat] { 16 | 17 | override lazy val availableFormats: List[HtmlFormat] = 18 | List( 19 | HtmlRdfa11, 20 | HtmlMicrodata 21 | ) 22 | override val default: HtmlFormat = HtmlRdfa11 23 | } 24 | 25 | /** Represents the mime-type "text/html" when used along rdfa11 26 | */ 27 | case object HtmlRdfa11 extends HtmlFormat(formatName = "html-rdfa11") 28 | 29 | /** Represents the mime-type "text/html" when used along microdata 30 | */ 31 | case object HtmlMicrodata extends HtmlFormat(formatName = "html-microdata") 32 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/format/dataFormats/RdfFormat.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.format.dataFormats 2 | 3 | import es.weso.rdfshape.server.api.format.FormatCompanion 4 | import es.weso.rdfshape.server.api.routes.data.logic.types.merged.DataCompound 5 | import org.http4s.MediaType 6 | 7 | /** Dummy class to differentiate RDF formats from the more generic DataFormat 8 | * 9 | * @see {@link DataFormat} 10 | */ 11 | sealed class RdfFormat(formatName: String, formatMimeType: MediaType) 12 | extends DataFormat(formatName, formatMimeType) 13 | 14 | /** Companion object with all RDFFormat static utilities 15 | */ 16 | object RdfFormat extends FormatCompanion[RdfFormat] { 17 | 18 | override lazy val availableFormats: List[RdfFormat] = 19 | List( 20 | Turtle, 21 | NTriples, 22 | NQuads, 23 | Trig, 24 | JsonLd, 25 | RdfXml, 26 | RdfJson, 27 | Mixed 28 | ) 29 | override val default: RdfFormat = Turtle 30 | } 31 | 32 | /** Represents the mime-type "text/turtle" 33 | */ 34 | case object Turtle 35 | extends RdfFormat( 36 | formatName = "Turtle", 37 | formatMimeType = new MediaType("text", "turtle") 38 | ) 39 | 40 | /** Represents the mime-type "application/n-triples" 41 | */ 42 | case object NTriples 43 | extends RdfFormat( 44 | formatName = "N-Triples", 45 | formatMimeType = new MediaType("application", "n-triples") 46 | ) 47 | 48 | /** Represents the mime-type "application/n-quads" 49 | */ 50 | case object NQuads 51 | extends RdfFormat( 52 | formatName = "N-Quads", 53 | formatMimeType = new MediaType("application", "n-quads") 54 | ) 55 | 56 | /** Represents the mime-type "application/trig" 57 | */ 58 | case object Trig 59 | extends RdfFormat( 60 | formatName = "TriG", 61 | formatMimeType = new MediaType("application", "trig") 62 | ) 63 | 64 | /** Represents the mime-type "application/ld+json" 65 | */ 66 | case object JsonLd 67 | extends RdfFormat( 68 | formatName = "JSON-LD", 69 | formatMimeType = new MediaType("application", "ld+json") 70 | ) 71 | 72 | /** Represents the mime-type "application/rdf+xml" 73 | */ 74 | case object RdfXml 75 | extends RdfFormat( 76 | formatName = "RDF/XML", 77 | formatMimeType = new MediaType("application", "rdf+xml") 78 | ) 79 | 80 | /** Represents the mime-type "application/json" 81 | */ 82 | case object RdfJson 83 | extends RdfFormat( 84 | formatName = "RDF/JSON", 85 | formatMimeType = MediaType.application.json 86 | ) 87 | 88 | /** Fictional format used in [[DataCompound]] instances 89 | */ 90 | case object Mixed 91 | extends RdfFormat( 92 | formatName = "mixed", 93 | formatMimeType = new MediaType("application", "mixed") 94 | ) 95 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/format/dataFormats/ShapeMapFormat.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.format.dataFormats 2 | 3 | import es.weso.rdfshape.server.api.format.{Format, FormatCompanion} 4 | import org.http4s.MediaType 5 | 6 | /** Dummy trait to differentiate shapemap formats from the more generic DataFormat 7 | * @see {@link DataFormat} 8 | */ 9 | class ShapeMapFormat(formatName: String, formatMimeType: MediaType) 10 | extends DataFormat(formatName, formatMimeType) { 11 | def this(format: Format) = { 12 | this(format.name, format.mimeType) 13 | } 14 | } 15 | 16 | /** Companion object with all SchemaFormat static utilities 17 | */ 18 | object ShapeMapFormat extends FormatCompanion[ShapeMapFormat] { 19 | 20 | override lazy val availableFormats: List[ShapeMapFormat] = 21 | List( 22 | Compact, 23 | new ShapeMapFormat(Json) 24 | ) 25 | override val default: ShapeMapFormat = Compact 26 | } 27 | 28 | /** Represents the mime-type "text/shex" 29 | */ 30 | case object Compact 31 | extends ShapeMapFormat( 32 | formatName = "Compact", 33 | formatMimeType = new MediaType("text", "shex") 34 | ) 35 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/format/dataFormats/schemaFormats/SchemaFormat.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.format.dataFormats.schemaFormats 2 | 3 | import es.weso.rdfshape.server.api.format.dataFormats._ 4 | import es.weso.rdfshape.server.api.format.{Format, FormatCompanion} 5 | import org.http4s.MediaType 6 | 7 | /** Dummy class to differentiate shapemap formats from the more generic DataFormat 8 | * 9 | * @see {@link DataFormat} 10 | */ 11 | class SchemaFormat(formatName: String, formatMimeType: MediaType) 12 | extends DataFormat(formatName, formatMimeType) { 13 | def this(format: Format) = { 14 | this(format.name, format.mimeType) 15 | } 16 | } 17 | 18 | /** Companion object with all SchemaFormat static utilities 19 | */ 20 | object SchemaFormat extends FormatCompanion[SchemaFormat] { 21 | 22 | override lazy val availableFormats: List[SchemaFormat] = { 23 | (ShExFormat.availableFormats ++ 24 | ShaclFormat.availableFormats).distinct 25 | } 26 | override val default: SchemaFormat = ShExC 27 | } 28 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/format/dataFormats/schemaFormats/ShExFormat.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.format.dataFormats.schemaFormats 2 | 3 | import es.weso.rdfshape.server.api.format.dataFormats._ 4 | import es.weso.rdfshape.server.api.format.{Format, FormatCompanion} 5 | import org.http4s.MediaType 6 | 7 | /** Dummy class to differentiate shapemap formats from the more generic DataFormat 8 | * @see {@link DataFormat} 9 | */ 10 | class ShExFormat(formatName: String, formatMimeType: MediaType) 11 | extends SchemaFormat(formatName, formatMimeType) { 12 | def this(format: Format) = { 13 | this(format.name, format.mimeType) 14 | } 15 | } 16 | 17 | /** Companion object with all SchemaFormat static utilities 18 | */ 19 | object ShExFormat extends FormatCompanion[ShExFormat] { 20 | 21 | override lazy val availableFormats: List[ShExFormat] = 22 | List( 23 | ShExC, 24 | ShExJ 25 | ) 26 | override val default: ShExFormat = ShExC 27 | } 28 | 29 | /** Represents the mime-type "text/shexc" 30 | */ 31 | case object ShExC 32 | extends ShExFormat( 33 | formatName = "ShExC", 34 | formatMimeType = new MediaType("text", "shexc") 35 | ) 36 | 37 | /** Represents the mime-type "text/shexj" 38 | */ 39 | case object ShExJ 40 | extends ShExFormat( 41 | formatName = "ShExJ", 42 | formatMimeType = new MediaType("text", "shexj") 43 | ) 44 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/format/dataFormats/schemaFormats/ShaclFormat.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.format.dataFormats.schemaFormats 2 | 3 | import es.weso.rdfshape.server.api.format.dataFormats._ 4 | import es.weso.rdfshape.server.api.format.{Format, FormatCompanion} 5 | import org.http4s.MediaType 6 | 7 | /** Dummy class to differentiate shapemap formats from the more generic DataFormat 8 | * 9 | * @see {@link DataFormat} 10 | */ 11 | class ShaclFormat(formatName: String, formatMimeType: MediaType) 12 | extends SchemaFormat(formatName, formatMimeType) { 13 | def this(format: Format) = { 14 | this(format.name, format.mimeType) 15 | } 16 | } 17 | 18 | /** Companion object with all SchemaFormat static utilities 19 | */ 20 | object ShaclFormat extends FormatCompanion[ShaclFormat] { 21 | 22 | override lazy val availableFormats: List[ShaclFormat] = 23 | List( 24 | new ShaclFormat(Turtle), 25 | new ShaclFormat(NTriples), 26 | new ShaclFormat(NQuads), 27 | new ShaclFormat(Trig), 28 | new ShaclFormat(JsonLd), 29 | new ShaclFormat(RdfXml) 30 | ) 31 | override val default: ShaclFormat = new ShaclFormat(Turtle) 32 | } 33 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/ApiService.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes 2 | 3 | /** Simple interface all API services should comply with 4 | * By convention, all services that are [[ApiService]]s are mounted behind 5 | * the "/api" 6 | * @see [[api]] 7 | */ 8 | trait ApiService { 9 | 10 | /** The service's characteristic verb, e.g.: "permalink", "data", "wikidata"... 11 | */ 12 | val verb: String 13 | } 14 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/api/service/BaseService.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.api.service 2 | 3 | import cats.effect._ 4 | import es.weso.rdfshape.server.api.routes.ApiService 5 | import org.http4s.client.Client 6 | import org.http4s.dsl.Http4sDsl 7 | import org.http4s.rho.RhoRoutes 8 | import org.http4s.rho.swagger.syntax.io._ 9 | 10 | /** API service to handle multiple general tasks (server status, etc.) 11 | * 12 | * @param client HTTP4S client object 13 | */ 14 | class BaseService(client: Client[IO]) extends Http4sDsl[IO] with ApiService { 15 | 16 | override val verb: String = "base" 17 | 18 | /** Describe the API routes handled by this service and the actions performed on each of them 19 | */ 20 | val routes: RhoRoutes[IO] = new RhoRoutes[IO] { 21 | "Perform a request to check if the API is up and running" ** 22 | GET / "health" |>> { () => 23 | Ok("Healthy") 24 | } 25 | } 26 | } 27 | 28 | object BaseService { 29 | 30 | /** Service factory 31 | * 32 | * @param client Underlying http4s client 33 | * @return A new API Service 34 | */ 35 | def apply(client: Client[IO]): BaseService = 36 | new BaseService(client) 37 | } 38 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/data/logic/DataSource.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.data.logic 2 | 3 | import es.weso.rdfshape.server.utils.other.MyEnum 4 | 5 | /** Enumeration of the different possible Data sources sent by the client. 6 | * The source sent indicates the API if the schema was sent in raw text, as a URL 7 | * to be fetched or as a text file containing the data. 8 | * In case the client submits the data in several formats, the selected source will indicate the preferred one. 9 | */ 10 | private[api] object DataSource extends MyEnum[String] { 11 | type DataSource = String 12 | val TEXT = "byText" 13 | val URL = "byUrl" 14 | val FILE = "byFile" 15 | val COMPOUND = "byCompound" 16 | 17 | val values: Set[DataSource] = 18 | Set(TEXT, URL, FILE, COMPOUND) 19 | val default: DataSource = TEXT 20 | } 21 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/data/logic/aux/InferenceCodecs.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.data.logic.aux 2 | 3 | import es.weso.rdf.InferenceEngine 4 | import es.weso.rdfshape.server.api.definitions.ApiDefaults 5 | import es.weso.rdfshape.server.api.routes.data.logic.types.Data 6 | import io.circe.{Decoder, Encoder, HCursor, Json} 7 | 8 | /** Implicit encoders and decoders for [[es.weso.rdf.InferenceEngine]] instances, 9 | * used when encoding and decoding [[Data]] instances 10 | */ 11 | private[api] object InferenceCodecs { 12 | 13 | /** Auxiliary encoder for data inference. 14 | */ 15 | implicit val encodeInference: Encoder[InferenceEngine] = 16 | (inference: InferenceEngine) => Json.fromString(inference.name) 17 | 18 | /** Auxiliary decoder for data inference 19 | */ 20 | implicit val decodeInference: Decoder[InferenceEngine] = 21 | (cursor: HCursor) => 22 | for { 23 | inferenceName <- cursor.value.as[String] 24 | inference = InferenceEngine 25 | .fromString(inferenceName) 26 | .toOption 27 | .getOrElse(ApiDefaults.defaultInferenceEngine) 28 | } yield inference 29 | 30 | } 31 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/data/logic/operations/DataInfo.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.data.logic.operations 2 | 3 | import cats.effect.IO 4 | import es.weso.rdf.PrefixMap 5 | import es.weso.rdf.nodes.IRI 6 | import es.weso.rdfshape.server.api.routes.data.logic.operations.DataInfo.{ 7 | DataInfoResult, 8 | successMessage 9 | } 10 | import es.weso.rdfshape.server.api.routes.data.logic.types.Data 11 | import es.weso.rdfshape.server.utils.json.JsonUtils._ 12 | import io.circe.syntax.EncoderOps 13 | import io.circe.{Encoder, Json} 14 | 15 | /** Data class representing the output of a data-information operation 16 | * 17 | * @param inputData RDF input data (contains content and format information) 18 | * @param result Object of type [[DataInfoResult]] containing the properties extracted from the data 19 | */ 20 | 21 | final case class DataInfo private ( 22 | override val inputData: Data, 23 | result: DataInfoResult 24 | ) extends DataOperation(successMessage, inputData) 25 | 26 | /** Static utilities to obtain information about RDF data 27 | */ 28 | private[api] object DataInfo { 29 | private val successMessage = "Well formed RDF" 30 | 31 | /** Given an input data, get information about it 32 | * 33 | * @param data Input Data instance of any type (Simple, Compound...) 34 | * @return A [[DataInfo]] instance with the information of the input data 35 | */ 36 | 37 | def dataInfo(data: Data): IO[DataInfo] = for { 38 | rdf <- data.toRdf() 39 | info <- rdf.use(rdf => 40 | for { 41 | nStatements <- rdf.getNumberOfStatements() 42 | predicates <- rdf.predicates().compile.toList 43 | prefixMap <- rdf.getPrefixMap 44 | } yield (nStatements, predicates, prefixMap) 45 | ) 46 | 47 | (nStatements, predicates, prefixMap) = info 48 | 49 | } yield DataInfo( 50 | inputData = data, 51 | result = DataInfoResult( 52 | data = data, 53 | numberOfStatements = nStatements, 54 | prefixMap = prefixMap, 55 | predicates = predicates.toSet 56 | ) 57 | ) 58 | 59 | /** Case class representing the results to be returned when performing a data-info operation 60 | * 61 | * @param data Data operated on 62 | * @param numberOfStatements Number of statements in the data 63 | * @param prefixMap Prefix map in the data 64 | * @param predicates Set of predicates in the data 65 | */ 66 | final case class DataInfoResult private ( 67 | data: Data, 68 | numberOfStatements: Int, 69 | prefixMap: PrefixMap, 70 | predicates: Set[IRI] 71 | ) 72 | 73 | /** Encoder for [[DataInfoResult]] 74 | */ 75 | private implicit val encodeDataInfoResult: Encoder[DataInfoResult] = 76 | (dataInfoResult: DataInfoResult) => 77 | Json.fromFields( 78 | List( 79 | ("numberOfStatements", dataInfoResult.numberOfStatements.asJson), 80 | ("format", dataInfoResult.data.format.asJson), 81 | ("prefixMap", prefixMap2JsonArray(dataInfoResult.prefixMap)), 82 | ( 83 | "predicates", 84 | Json.fromValues( 85 | dataInfoResult.predicates.map( 86 | iri2Json(_, Some(dataInfoResult.prefixMap)) 87 | ) 88 | ) 89 | ) 90 | ) 91 | ) 92 | 93 | /** Encoder for [[DataInfo]] 94 | */ 95 | implicit val encodeDataInfoOperation: Encoder[DataInfo] = 96 | (dataInfo: DataInfo) => 97 | Json.fromFields( 98 | List( 99 | ("message", Json.fromString(dataInfo.successMessage)), 100 | ("data", dataInfo.inputData.asJson), 101 | ("result", dataInfo.result.asJson) 102 | ) 103 | ) 104 | 105 | } 106 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/data/logic/operations/DataOperation.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.data.logic.operations 2 | 3 | import es.weso.rdfshape.server.api.routes.data.logic.types.Data 4 | 5 | /** General definition of operations that operate on Data 6 | * 7 | * @param successMessage Message attached to the result of the operation 8 | * @param inputData Data operated on 9 | */ 10 | private[operations] abstract class DataOperation( 11 | val successMessage: String = DataOperation.successMessage, 12 | val inputData: Data 13 | ) 14 | 15 | /** Static utils for Data operations 16 | */ 17 | private[operations] object DataOperation { 18 | 19 | /** Dummy success message 20 | */ 21 | private val successMessage = "Operation completed successfully" 22 | } 23 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/data/logic/operations/DataQuery.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.data.logic.operations 2 | 3 | import cats.effect.IO 4 | import es.weso.rdfshape.server.api.routes.data.logic.operations.DataQuery.{ 5 | DataQueryResult, 6 | successMessage 7 | } 8 | import es.weso.rdfshape.server.api.routes.data.logic.types.Data 9 | import es.weso.rdfshape.server.api.routes.endpoint.logic.query.SparqlQuery 10 | import io.circe.syntax.EncoderOps 11 | import io.circe.{Encoder, Json} 12 | 13 | /** Data class representing the output of a data-information operation 14 | * 15 | * @param inputData RDF input data (contains content and format information) 16 | * @param inputQuery Sparql query input 17 | * @param result Object of type [[DataQueryResult]] containing the properties extracted from the data 18 | */ 19 | 20 | final case class DataQuery private ( 21 | override val inputData: Data, 22 | inputQuery: SparqlQuery, 23 | result: DataQueryResult 24 | ) extends DataOperation(successMessage, inputData) {} 25 | 26 | /** Static utilities to perform SPARQL queries on RDF data 27 | */ 28 | private[api] object DataQuery { 29 | 30 | /** Encoder for [[DataQuery]] 31 | */ 32 | implicit val encodeDataQueryOperation: Encoder[DataQuery] = 33 | (dataQuery: DataQuery) => 34 | Json.fromFields( 35 | List( 36 | ("message", Json.fromString(dataQuery.successMessage)), 37 | ("data", dataQuery.inputData.asJson), 38 | ("query", dataQuery.inputQuery.asJson), 39 | ("result", dataQuery.result.json) 40 | ) 41 | ) 42 | private val successMessage = "Query executed successfully" 43 | 44 | /** Given an input data and query, perform the query on the data 45 | * 46 | * @param data Input data to be queried 47 | * @param query Input SPARQL query 48 | * @return A [[DataQuery]] object with the query results (see also [[DataQueryResult]]) 49 | */ 50 | 51 | def dataQuery(data: Data, query: SparqlQuery): IO[DataQuery] = 52 | for { 53 | rdf <- data.toRdf() // Get the RDF reader 54 | resultJson <- rdf.use( 55 | _.queryAsJson(query.raw) 56 | ) // Perform query 57 | } yield DataQuery( // Form the results object 58 | inputData = data, 59 | inputQuery = query, 60 | result = DataQueryResult( 61 | json = resultJson 62 | ) 63 | ) 64 | 65 | /** Case class representing the results to be returned when performing a data-query operation 66 | * @note Currently limited to JSON formatted results for convenience 67 | */ 68 | final case class DataQueryResult( 69 | json: Json 70 | ) 71 | 72 | } 73 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/data/service/operations/DataConvertInput.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.data.service.operations 2 | 3 | import es.weso.rdfshape.server.api.ServiceRouteOperation 4 | import es.weso.rdfshape.server.api.format.dataFormats.DataFormat 5 | import es.weso.rdfshape.server.api.routes.data.logic.types.Data 6 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters.{ 7 | DataParameter, 8 | TargetFormatParameter 9 | } 10 | import es.weso.rdfshape.server.utils.other.mapEitherToDecodeResult 11 | import io.circe.{Decoder, HCursor} 12 | 13 | /** Data class representing the inputs required when querying the server 14 | * for RDF data conversions 15 | * @param data RDF to be converted 16 | * @param targetFormat Conversion output format 17 | */ 18 | case class DataConvertInput(data: Data, targetFormat: DataFormat) 19 | 20 | object DataConvertInput extends ServiceRouteOperation[DataConvertInput] { 21 | override implicit val decoder: Decoder[DataConvertInput] = 22 | (cursor: HCursor) => { 23 | val decodeResult = for { 24 | maybeData <- cursor 25 | .downField(DataParameter.name) 26 | .as[Either[String, Data]] 27 | maybeTargetFormat <- cursor 28 | .downField(TargetFormatParameter.name) 29 | .as[Either[String, DataFormat]] 30 | 31 | maybeItems = for { 32 | data <- maybeData 33 | targetFormat <- maybeTargetFormat 34 | } yield (data, targetFormat) 35 | 36 | } yield maybeItems.map { case (data, targetFormat) => 37 | DataConvertInput(data, targetFormat) 38 | } 39 | 40 | mapEitherToDecodeResult(decodeResult) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/data/service/operations/DataExtractInput.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.data.service.operations 2 | 3 | import cats.implicits.catsSyntaxEitherId 4 | import es.weso.rdf.nodes.IRI 5 | import es.weso.rdfshape.server.api.ServiceRouteOperation 6 | import es.weso.rdfshape.server.api.routes.data.logic.types.Data 7 | import es.weso.rdfshape.server.api.routes.data.service.DataServiceError 8 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters.{ 9 | DataParameter, 10 | LabelParameter, 11 | NodeSelectorParameter 12 | } 13 | import es.weso.rdfshape.server.implicits.codecs.decodeIri 14 | import es.weso.rdfshape.server.utils.other.mapEitherToDecodeResult 15 | import io.circe.{Decoder, DecodingFailure, HCursor} 16 | 17 | /** Data class representing the inputs required when querying the server 18 | * to extract a schema from RDF data 19 | * 20 | * @param data RDF input 21 | * @param nodeSelector Node targeted in the extraction process 22 | * @param label Base label for the extracted shapes 23 | */ 24 | case class DataExtractInput( 25 | data: Data, 26 | nodeSelector: String, 27 | label: Option[IRI] 28 | ) 29 | 30 | object DataExtractInput extends ServiceRouteOperation[DataExtractInput] { 31 | override implicit val decoder: Decoder[DataExtractInput] = 32 | (cursor: HCursor) => { 33 | val decodeResult = for { 34 | maybeData <- cursor 35 | .downField(DataParameter.name) 36 | .as[Either[String, Data]] 37 | 38 | // Compulsory param, manually trigger decoding failure with message 39 | maybeNodeSelector <- cursor 40 | .downField(NodeSelectorParameter.name) 41 | .as[Option[String]] 42 | .map { 43 | case None => DataServiceError.noNodeSelector.asLeft 44 | case Some(value) if value.isBlank => 45 | DataServiceError.emptyNodeSelector.asLeft 46 | case Some(value) => value.asRight 47 | } 48 | 49 | // Optional param 50 | maybeLabel <- cursor 51 | .downField(LabelParameter.name) 52 | .as[Either[String, IRI]] match { 53 | // Failed to decode at cursor, no label was provided 54 | case Left(_) => None.asRight 55 | // Could decode 56 | case Right(labelDecodeResult) => 57 | labelDecodeResult match { 58 | // Decoded but an error message was returned, promote it 59 | case Left(err) => DecodingFailure(err, Nil).asLeft 60 | // Decoded and everything went OK 61 | case Right(label) => Some(label).asRight 62 | } 63 | } 64 | 65 | maybeItems = for { 66 | data <- maybeData 67 | nodeSelector <- maybeNodeSelector 68 | } yield (data, nodeSelector, maybeLabel) 69 | 70 | } yield maybeItems.map { case (data, nodeSelector, label) => 71 | DataExtractInput(data, nodeSelector, label) 72 | } 73 | 74 | mapEitherToDecodeResult(decodeResult) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/data/service/operations/DataInfoInput.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.data.service.operations 2 | 3 | import es.weso.rdfshape.server.api.ServiceRouteOperation 4 | import es.weso.rdfshape.server.api.routes.data.logic.types.Data 5 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters.DataParameter 6 | import es.weso.rdfshape.server.utils.other.mapEitherToDecodeResult 7 | import io.circe.{Decoder, HCursor} 8 | 9 | /** Data class representing the inputs required when querying the server 10 | * for RDF data information 11 | * @param data RDF to be inspected 12 | */ 13 | case class DataInfoInput(data: Data) 14 | 15 | object DataInfoInput extends ServiceRouteOperation[DataInfoInput] { 16 | override implicit val decoder: Decoder[DataInfoInput] = (cursor: HCursor) => { 17 | val decodeResult = for { 18 | maybeData <- cursor 19 | .downField(DataParameter.name) 20 | .as[Either[String, Data]] 21 | } yield maybeData.map(DataInfoInput(_)) 22 | 23 | mapEitherToDecodeResult(decodeResult) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/data/service/operations/DataQueryInput.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.data.service.operations 2 | 3 | import es.weso.rdfshape.server.api.ServiceRouteOperation 4 | import es.weso.rdfshape.server.api.routes.data.logic.types.Data 5 | import es.weso.rdfshape.server.api.routes.endpoint.logic.query.SparqlQuery 6 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters.{ 7 | DataParameter, 8 | QueryParameter 9 | } 10 | import es.weso.rdfshape.server.utils.other.mapEitherToDecodeResult 11 | import io.circe.{Decoder, HCursor} 12 | 13 | /** Data class representing the inputs required when querying the server 14 | * to perform a SPARQL query on RDF data 15 | * 16 | * @param data RDF to be queried 17 | * @param query SPARQL query to be performed 18 | */ 19 | case class DataQueryInput(data: Data, query: SparqlQuery) 20 | 21 | object DataQueryInput extends ServiceRouteOperation[DataQueryInput] { 22 | override implicit val decoder: Decoder[DataQueryInput] = 23 | (cursor: HCursor) => { 24 | val decodeResult = for { 25 | maybeData <- cursor 26 | .downField(DataParameter.name) 27 | .as[Either[String, Data]] 28 | maybeQuery <- cursor 29 | .downField(QueryParameter.name) 30 | .as[Either[String, SparqlQuery]] 31 | 32 | maybeItems = for { 33 | data <- maybeData 34 | query <- maybeQuery 35 | } yield (data, query) 36 | 37 | } yield maybeItems.map { case (data, query) => 38 | DataQueryInput(data, query) 39 | } 40 | 41 | mapEitherToDecodeResult(decodeResult) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/endpoint/logic/Endpoint.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.endpoint.logic 2 | 3 | import com.typesafe.scalalogging.LazyLogging 4 | import es.weso.rdf.RDFReader 5 | import es.weso.rdf.jena.{Endpoint => EndpointJena} 6 | import es.weso.rdfshape.server.api.routes.endpoint.logic.EndpointStatus.{ 7 | EndpointStatus, 8 | OFFLINE, 9 | ONLINE 10 | } 11 | import es.weso.rdfshape.server.utils.networking.NetworkingUtils.getUrlContents 12 | import es.weso.utils.IOUtils.{ESIO, io2es} 13 | import io.circe.syntax.EncoderOps 14 | import io.circe.{Encoder, Json} 15 | 16 | import java.net.URL 17 | 18 | /** Data class representing an endpoint 19 | * 20 | * @param response Message attached to the information/returned by the endpoint 21 | * @param status Status of the endpoint 22 | */ 23 | sealed case class Endpoint(response: String, status: EndpointStatus) 24 | 25 | /** Static utilities used by the {@link es.weso.rdfshape.server.api.routes.endpoint.service.EndpointService} 26 | * to operate on endpoints 27 | */ 28 | private[api] object Endpoint extends LazyLogging { 29 | 30 | /** Fetch information from an endpoint and return the RDF Reader to operate the information 31 | * 32 | * @param url Endpoint URL 33 | * @return An RDF Reader to operate the information in the endpoint 34 | */ 35 | def getEndpointAsRDFReader(url: URL): ESIO[RDFReader] = 36 | io2es(EndpointJena.fromString(url.toString)) 37 | 38 | /** Given an endpoint URL, fetch and return its data 39 | * 40 | * @param url Endpoint URL 41 | * @return An instance of Endpoint with the information contained in the endpoint 42 | */ 43 | def getEndpointInfo(url: URL): Endpoint = { 44 | logger.debug(s"Obtaining info of endpoint $url") 45 | getUrlContents(url.toString) match { 46 | case Left(errMsg) => Endpoint(errMsg, OFFLINE) 47 | case Right(response) => Endpoint(response, ONLINE) 48 | } 49 | } 50 | 51 | /** Encoder [[Endpoint]] => [[Json]] 52 | */ 53 | implicit val encoder: Encoder[Endpoint] = 54 | (endpoint: Endpoint) => 55 | Json.obj( 56 | ("online", (endpoint.status == ONLINE).asJson), 57 | ("response", endpoint.response.asJson) 58 | ) 59 | } 60 | 61 | /** Enumeration of the different possible Endpoint states. 62 | */ 63 | private[endpoint] object EndpointStatus extends Enumeration { 64 | type EndpointStatus = String 65 | 66 | val ONLINE = "online" 67 | val OFFLINE = "offline" 68 | 69 | } 70 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/endpoint/logic/Outgoing.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.endpoint.logic 2 | 3 | import cats.data.EitherT 4 | import cats.effect.IO 5 | import es.weso.rdf.jena.{Endpoint => EndpointJena} 6 | import es.weso.rdf.nodes.{IRI, RDFNode} 7 | import es.weso.rdf.triples.RDFTriple 8 | import es.weso.utils.IOUtils.{ESIO, stream2es} 9 | import io.circe.{Encoder, Json} 10 | 11 | import java.net.URL 12 | 13 | case class Outgoing(node: IRI, endpoint: IRI, children: Children) 14 | 15 | case class Children(m: Map[IRI, Vector[Value]]) 16 | 17 | case class Value(node: RDFNode, children: Children) 18 | 19 | object Outgoing { 20 | val noChildren: Children = Children(Map()) 21 | 22 | def getOutgoing( 23 | endpoint: URL, 24 | node: String, 25 | optLimit: Option[Int] = Some(1) 26 | ): EitherT[IO, String, Outgoing] = { 27 | for { 28 | endpointIRI <- EitherT.fromEither[IO]( 29 | IRI.fromString(endpoint.toString) 30 | ) 31 | node <- EitherT.fromEither[IO]( 32 | IRI.fromString(node) 33 | ) 34 | limit = optLimit.getOrElse(1) 35 | o <- outgoing(endpointIRI, node, limit) 36 | } yield o 37 | } 38 | 39 | def outgoing(endpoint: IRI, node: IRI, limit: Int): ESIO[Outgoing] = 40 | for { 41 | triples <- stream2es(EndpointJena(endpoint).triplesWithSubject(node)) 42 | } yield Outgoing.fromTriples(node, endpoint, triples.toSet) 43 | 44 | /** Creates an outgoing value from a set of triples. 45 | * It assumes all those triples have the same subject which is ignored 46 | * 47 | * @param ts Triple set 48 | * @return 49 | */ 50 | def fromTriples(node: IRI, endpoint: IRI, ts: Set[RDFTriple]): Outgoing = { 51 | val zero: Map[IRI, Vector[Value]] = Map() 52 | 53 | def cmb( 54 | m: Map[IRI, Vector[Value]], 55 | current: RDFTriple 56 | ): Map[IRI, Vector[Value]] = 57 | m.get(current.pred) 58 | .fold( 59 | m.updated(current.pred, Vector(Value(current.obj, noChildren))) 60 | )((vs: Vector[Value]) => 61 | m.updated(current.pred, vs :+ Value(current.obj, noChildren)) 62 | ) 63 | 64 | Outgoing(node, endpoint, Children(ts.foldLeft(zero)(cmb))) 65 | } 66 | 67 | implicit val encode: Encoder[Outgoing] = (outgoing: Outgoing) => 68 | Json.fromFields( 69 | List( 70 | ( 71 | "node", 72 | Json.fromString(outgoing.node.toString) 73 | ), 74 | ("endpoint", Json.fromString(outgoing.endpoint.toString)), 75 | ( 76 | "children", 77 | Json.fromValues( 78 | outgoing.children.m.map(pair => 79 | Json.fromFields( 80 | List( 81 | ("pred", Json.fromString(pair._1.toString)), 82 | ( 83 | "values", 84 | Json.fromValues( 85 | pair._2.toList.map(value => 86 | Json.fromString(value.node.toString) 87 | ) 88 | ) 89 | ) 90 | ) 91 | ) 92 | ) 93 | ) 94 | ) 95 | ) 96 | ) 97 | 98 | } 99 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/endpoint/logic/query/SparqlQuery.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.endpoint.logic.query 2 | 3 | import cats.implicits.toBifunctorOps 4 | import com.typesafe.scalalogging.LazyLogging 5 | import es.weso.rdfshape.server.api.routes.endpoint.logic.query.SparqlQuerySource.SparqlQuerySource 6 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters.{ 7 | ContentParameter, 8 | SourceParameter 9 | } 10 | import es.weso.rdfshape.server.utils.networking.NetworkingUtils.getUrlContents 11 | import io.circe._ 12 | import io.circe.syntax.EncoderOps 13 | 14 | import scala.util.Try 15 | 16 | /** Data class representing a SPARQL query and its current source 17 | * 18 | * @param content Query contents, as received before being processed depending on the [[source]] 19 | * @param source Active source, used to know which source the query comes from 20 | */ 21 | sealed case class SparqlQuery private ( 22 | private val content: String, 23 | source: SparqlQuerySource 24 | ) extends LazyLogging { 25 | 26 | // Non empty content 27 | assume(!content.isBlank, "Could not build the query from empty data") 28 | // Valid source 29 | assume( 30 | SparqlQuerySource.values.exists(_ equalsIgnoreCase source), 31 | s"Unknown query source: '$source'" 32 | ) 33 | 34 | /** Given the (user input) for the query and its source, fetch the Query contents using the input in the way the source needs it 35 | * (e.g.: for URLs, fetch the input with a web request; for files, decode the input; for raw data, do nothing) 36 | * 37 | * @return Either an error building the query text or a String containing the final text of the SPARQL query 38 | */ 39 | lazy val fetchedContents: Either[String, String] = 40 | if(source equalsIgnoreCase SparqlQuerySource.URL) 41 | getUrlContents(content) 42 | // Text or file 43 | else Right(content) 44 | 45 | assume( 46 | fetchedContents.isRight, 47 | fetchedContents.left.getOrElse("Unknown error") 48 | ) 49 | 50 | /** Raw query value, i.e.: the text forming the query 51 | * 52 | * @note It is safely extracted fromm [[fetchedContents]] after asserting 53 | * the contents are right 54 | */ 55 | val raw: String = fetchedContents.toOption.get 56 | 57 | } 58 | 59 | private[api] object SparqlQuery extends LazyLogging { 60 | 61 | /** Encoder [[SparqlQuery]] => [[Json]] 62 | */ 63 | implicit val encoder: Encoder[SparqlQuery] = 64 | (query: SparqlQuery) => 65 | Json.obj( 66 | ("content", query.raw.asJson), 67 | ("source", query.source.asJson) 68 | ) 69 | 70 | /** Decoder [[Json]] => [[SparqlQuery]] 71 | * @note Returns an either whose left contains specific errors building the query 72 | */ 73 | implicit val decoder: Decoder[Either[String, SparqlQuery]] = 74 | (cursor: HCursor) => { 75 | // Get request data 76 | val queryData = for { 77 | queryContent <- cursor 78 | .downField(ContentParameter.name) 79 | .as[String] 80 | .map(_.trim) 81 | querySource <- cursor 82 | .downField(SourceParameter.name) 83 | .as[SparqlQuerySource] 84 | 85 | } yield (queryContent, querySource) 86 | 87 | queryData.map { 88 | /* Destructure and try to build the object, catch the exception as error 89 | * message if needed */ 90 | case (content, source) => 91 | Try { 92 | SparqlQuery(content, source) 93 | }.toEither.leftMap(err => 94 | s"Could not build the SPARQL query from user data:\n ${err.getMessage}" 95 | ) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/endpoint/logic/query/SparqlQuerySource.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.endpoint.logic.query 2 | 3 | import es.weso.rdfshape.server.utils.other.MyEnum 4 | 5 | /** Enumeration of the different possible Query sources by the client. 6 | * The source sent indicates the API if the Query was sent in raw text, as a URL 7 | * to be fetched or as a text file containing the query. 8 | * In case the client submits the query in several formats, the selected source will indicate the one format. 9 | */ 10 | private[api] object SparqlQuerySource extends MyEnum[String] { 11 | type SparqlQuerySource = String 12 | 13 | val TEXT = "byText" 14 | val URL = "byUrl" 15 | val FILE = "byFile" 16 | 17 | val values: Set[SparqlQuerySource] = Set(TEXT, URL, FILE) 18 | val default: SparqlQuerySource = TEXT 19 | } 20 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/endpoint/service/EndpointService.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.endpoint.service 2 | 3 | import cats.effect._ 4 | import com.typesafe.scalalogging.LazyLogging 5 | import es.weso.rdfshape.server.api.routes.ApiService 6 | import es.weso.rdfshape.server.api.routes.endpoint.logic.Endpoint.getEndpointInfo 7 | import es.weso.rdfshape.server.api.routes.endpoint.logic.Outgoing._ 8 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters._ 9 | import es.weso.rdfshape.server.implicits.query_parsers.urlQueryParser 10 | import io.circe.syntax.EncoderOps 11 | import org.http4s.circe._ 12 | import org.http4s.client.Client 13 | import org.http4s.dsl._ 14 | import org.http4s.rho.RhoRoutes 15 | import org.http4s.rho.swagger.syntax.io._ 16 | 17 | import java.net.URL 18 | 19 | /** API service to handle endpoints and operations targeted to them (queries, etc.) 20 | * 21 | * @param client HTTP4S client object 22 | */ 23 | class EndpointService(client: Client[IO]) 24 | extends Http4sDsl[IO] 25 | with ApiService 26 | with LazyLogging { 27 | 28 | override val verb: String = "endpoint" 29 | 30 | /** Describe the API routes handled by this service and the actions performed on each of them 31 | */ 32 | def routes: RhoRoutes[IO] = new RhoRoutes[IO] { 33 | 34 | "Check the existence of an endpoint and get its response, if any" ** 35 | GET / `verb` / "info" +? 36 | param[URL](EndpointParameter.name) |>> { (endpointUrl: URL) => 37 | Ok(getEndpointInfo(endpointUrl).asJson) 38 | } 39 | 40 | "Attempt to contact a wikibase endpoint and return the data (triplets) about a node in it" ** 41 | GET / `verb` / "outgoing" +? 42 | param[URL](EndpointParameter.name) & 43 | param[String](NodeParameter.name) & 44 | param[Option[Int]](LimitParameter.name) |>> { 45 | (endpointUrl: URL, node: String, limit: Option[Int]) => 46 | for { 47 | eitherOutgoing <- getOutgoing( 48 | endpointUrl, 49 | node, 50 | limit 51 | ).value 52 | resp <- eitherOutgoing.fold( 53 | err => InternalServerError(s"Error: $err"), 54 | outgoing => Ok(outgoing.asJson) 55 | ) 56 | } yield resp 57 | } 58 | 59 | } 60 | } 61 | 62 | object EndpointService { 63 | 64 | /** Service factory 65 | * 66 | * @param client Underlying http4s client 67 | * @return A new Endpoint Service 68 | */ 69 | def apply(client: Client[IO]): EndpointService = 70 | new EndpointService(client) 71 | 72 | } 73 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/endpoint/service/operations/EndpointOutgoingInput.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.endpoint.service.operations 2 | 3 | import es.weso.rdfshape.server.api.ServiceRouteOperation 4 | import es.weso.rdfshape.server.api.routes.endpoint.logic.query.SparqlQuery 5 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters.{ 6 | EndpointParameter, 7 | QueryParameter 8 | } 9 | import es.weso.rdfshape.server.implicits.codecs.decodeUrl 10 | import es.weso.rdfshape.server.utils.other.mapEitherToDecodeResult 11 | import io.circe.{Decoder, HCursor} 12 | 13 | import java.net.URL 14 | 15 | case class EndpointOutgoingInput(endpoint: URL, query: SparqlQuery) 16 | 17 | object EndpointOutgoingInput 18 | extends ServiceRouteOperation[EndpointOutgoingInput] { 19 | 20 | override implicit val decoder: Decoder[EndpointOutgoingInput] = 21 | (cursor: HCursor) => { 22 | val decodeResult = for { 23 | maybeEndpoint <- cursor 24 | .downField(EndpointParameter.name) 25 | .as[Either[String, URL]] 26 | 27 | maybeQuery <- cursor 28 | .downField(QueryParameter.name) 29 | .as[Either[String, SparqlQuery]] 30 | 31 | decoded: Either[String, EndpointOutgoingInput] = for { 32 | endpoint <- maybeEndpoint 33 | query <- maybeQuery 34 | } yield EndpointOutgoingInput(endpoint, query) 35 | 36 | } yield decoded 37 | 38 | mapEitherToDecodeResult(decodeResult) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/fetch/service/FetchService.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.fetch.service 2 | 3 | import cats.effect._ 4 | import com.typesafe.scalalogging.LazyLogging 5 | import es.weso.rdfshape.server.api.routes.ApiService 6 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters.UrlParameter 7 | import es.weso.rdfshape.server.implicits.query_parsers.urlQueryParser 8 | import es.weso.rdfshape.server.utils.networking.NetworkingUtils.getUrlContents 9 | import org.http4s.client.Client 10 | import org.http4s.dsl.Http4sDsl 11 | import org.http4s.rho.RhoRoutes 12 | import org.http4s.rho.swagger.syntax.io._ 13 | 14 | import java.net.URL 15 | 16 | class FetchService() extends Http4sDsl[IO] with ApiService with LazyLogging { 17 | 18 | override val verb: String = "fetch" 19 | 20 | /** Describe the API routes handled by this service and the actions performed on each of them 21 | */ 22 | val routes: RhoRoutes[IO] = new RhoRoutes[IO] { 23 | "Query a given URL and return the response, acting as a proxy" ** 24 | GET / `verb` +? param[URL](UrlParameter.name) |>> { (url: URL) => 25 | getUrlContents(url) match { 26 | case Left(err) => InternalServerError(err) 27 | case Right(content) => Ok(content) 28 | } 29 | } 30 | } 31 | 32 | case class RequestData(domain: String, url: String) 33 | } 34 | 35 | object FetchService { 36 | 37 | /** Service factory 38 | * 39 | * @param client Underlying http4s client 40 | * @return A new Fetch Service 41 | */ 42 | def apply(client: Client[IO]): FetchService = 43 | new FetchService() 44 | } 45 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/permalink/logic/Permalink.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.permalink.logic 2 | 3 | import es.weso.rdfshape.server.utils.numeric.NumericUtils.getRandomInt 4 | import io.circe.syntax.EncoderOps 5 | import io.circe.{Decoder, Encoder, HCursor, Json} 6 | import mongo4cats.bson.ObjectId 7 | import mongo4cats.circe._ 8 | 9 | import java.time.Instant 10 | 11 | /** Data class representing a permalink 12 | * 13 | * @param _id Permalink ID (mongo) 14 | * @param longUrl Permalink target (only the path of the target URL) 15 | * @param urlCode Permalink identifying code 16 | * @param date Date of last usage of the permalink, defaults to its creation date 17 | */ 18 | final case class Permalink( 19 | _id: ObjectId = ObjectId(), 20 | longUrl: String, 21 | // Time based code 22 | urlCode: Long = s"${Instant.now.getEpochSecond}${getRandomInt()}".toLong, 23 | date: Instant = Instant.now 24 | ) { 25 | 26 | override def toString: String = 27 | s"Link '$urlCode' for '$longUrl' (used '$date')" 28 | } 29 | 30 | /** Companion object for [[Permalink]] including custom circe codecs 31 | */ 32 | private[permalink] object Permalink { 33 | // Forcibly implement codecs so that they are in implicit scope later 34 | implicit val encoder: Encoder[Permalink] = (pm: Permalink) => 35 | Json.obj( 36 | ("_id", pm._id.asJson), 37 | ("longUrl", pm.longUrl.asJson), 38 | ("urlCode", pm.urlCode.asJson), 39 | ("date", pm.date.asJson) 40 | ) 41 | implicit val decoder: Decoder[Permalink] = (cursor: HCursor) => 42 | for { 43 | id <- cursor.downField("_id").as[ObjectId] 44 | longUrl <- cursor.downField("longUrl").as[String] 45 | urlCode <- cursor.downField("urlCode").as[Long] 46 | date <- cursor.downField("date").as[Instant] 47 | 48 | decoded = Permalink( 49 | id, 50 | longUrl, 51 | urlCode, 52 | date 53 | ) 54 | } yield decoded 55 | } 56 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/schema/logic/SchemaSource.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.schema.logic 2 | 3 | import es.weso.rdfshape.server.utils.other.MyEnum 4 | 5 | /** Enumeration of the different possible Schema sources sent by the client. 6 | * The source sent indicates the API if the schema was sent in raw text, as a URL 7 | * to be fetched or as a text file containing the schema. 8 | * In case the client submits the schema in several formats, the selected source will indicate the preferred one. 9 | */ 10 | private[api] object SchemaSource extends MyEnum[String] { 11 | type SchemaSource = String 12 | 13 | val TEXT = "byText" 14 | val URL = "byUrl" 15 | val FILE = "byFile" 16 | 17 | override val values: Set[String] = Set(TEXT, URL, FILE) 18 | val default: SchemaSource = TEXT 19 | } 20 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/schema/logic/aux/SchemaAdapter.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.schema.logic.aux 2 | 3 | import cats.implicits.catsSyntaxEitherId 4 | import es.weso.rdfshape.server.api.routes.schema.logic.types.Schema 5 | import es.weso.schema.{Schemas, Schema => SchemaW} 6 | import io.circe.{Decoder, Encoder, HCursor, Json} 7 | 8 | /** Adapter, codecs and utils between the server's Schema class ([[Schema]]) 9 | * and shaclex Schema engine types ([[SchemaW]]) 10 | */ 11 | private[schema] object SchemaAdapter { 12 | 13 | /** Simple encoder for [[SchemaW]] instances, simplifying them to their name 14 | */ 15 | implicit val encodeEngine: Encoder[SchemaW] = (schemaEngine: SchemaW) => 16 | Json.fromString(schemaEngine.name) 17 | 18 | /** Auxiliary decoder for [[SchemaW]] 19 | */ 20 | implicit val decodeEngine: Decoder[Either[String, SchemaW]] = 21 | (cursor: HCursor) => 22 | for { 23 | engineName <- cursor.value.as[String] 24 | 25 | engine = Schemas.availableSchemas 26 | .find( 27 | _.name.toLowerCase == engineName.toLowerCase 28 | ) match { 29 | case Some(value) => value.asRight 30 | case None => s"The engine '$engineName' does not exist".asLeft 31 | } 32 | 33 | } yield engine 34 | 35 | /** For a given schema engine name, try to map it to the schema it represents 36 | * 37 | * @param engineName Name (String) of the given schema engine 38 | * @return The schema engine corresponding to the given name, if available 39 | */ 40 | def schemaEngineFromString(engineName: String): Option[SchemaW] = { 41 | Schemas.availableSchemas 42 | .find(schema => schema.name.toLowerCase == engineName.toLowerCase()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/schema/logic/operations/SchemaInfo.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.schema.logic.operations 2 | 3 | import cats.effect.IO 4 | import com.typesafe.scalalogging.LazyLogging 5 | import es.weso.rdf.PrefixMap 6 | import es.weso.rdfshape.server.api.routes.schema.logic.aux.SchemaAdapter._ 7 | import es.weso.rdfshape.server.api.routes.schema.logic.operations.SchemaInfo.SchemaInfoResult 8 | import es.weso.rdfshape.server.api.routes.schema.logic.types.Schema 9 | import es.weso.rdfshape.server.utils.json.JsonUtils.prefixMap2JsonArray 10 | import io.circe.syntax.EncoderOps 11 | import io.circe.{Encoder, Json} 12 | 13 | /** Data class representing the output of a schema-information operation 14 | * 15 | * @param inputSchema Schema used as input of the operation 16 | * @param result [[SchemaInfoResult]] containing the resulting schema information 17 | */ 18 | final case class SchemaInfo private ( 19 | override val inputSchema: Schema, 20 | result: SchemaInfoResult 21 | ) extends SchemaOperation(SchemaInfo.successMessage, inputSchema) 22 | 23 | private[api] object SchemaInfo extends LazyLogging { 24 | 25 | private val successMessage = "Well formed Schema" 26 | 27 | /** Given an input schema, get information about it 28 | * 29 | * @param schema Input schema instance of any type 30 | * @return A [[SchemaInfo]] instance with the information of the input schema 31 | */ 32 | 33 | def schemaInfo(schema: Schema): IO[SchemaInfo] = for { 34 | model <- schema.getSchema 35 | modelInfo = model.map(m => (m.shapes, m.pm)) 36 | 37 | results <- modelInfo match { 38 | case Right((shapes, prefixMap)) => 39 | IO { 40 | SchemaInfo( 41 | inputSchema = schema, 42 | result = SchemaInfoResult( 43 | schema = schema, 44 | shapes = shapes, 45 | prefixMap = prefixMap 46 | ) 47 | ) 48 | } 49 | case Left(err) => 50 | IO.raiseError(new RuntimeException(err)) 51 | } 52 | } yield results 53 | 54 | /** Case class representing the results to be returned when performing a schema-info operation 55 | * 56 | * @param schema Schema operated on 57 | * @param shapes Shapes in the schema 58 | * @param prefixMap Prefix map in the schema 59 | */ 60 | final case class SchemaInfoResult private ( 61 | schema: Schema, 62 | shapes: List[String], 63 | prefixMap: PrefixMap 64 | ) 65 | 66 | /** JSON encoder for [[SchemaInfoResult]] 67 | */ 68 | private implicit val encodeSchemaInfoResult: Encoder[SchemaInfoResult] = 69 | (schemaInfoResult: SchemaInfoResult) => 70 | Json.fromFields( 71 | List( 72 | ("format", schemaInfoResult.schema.format.asJson), 73 | ("engine", schemaInfoResult.schema.engine.asJson), 74 | ( 75 | "shapes", 76 | Json.fromValues(schemaInfoResult.shapes.map(Json.fromString)) 77 | ), 78 | ("prefixMap", prefixMap2JsonArray(schemaInfoResult.prefixMap)) 79 | ) 80 | ) 81 | 82 | /** JSON encoder for [[SchemaInfo]] 83 | */ 84 | implicit val encodeSchemaInfoOperation: Encoder[SchemaInfo] = 85 | (schemaInfo: SchemaInfo) => 86 | Json.fromFields( 87 | List( 88 | ("message", Json.fromString(schemaInfo.successMessage)), 89 | ("schema", schemaInfo.inputSchema.asJson), 90 | ("result", schemaInfo.result.asJson) 91 | ) 92 | ) 93 | } 94 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/schema/logic/operations/SchemaOperation.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.schema.logic.operations 2 | 3 | import es.weso.rdfshape.server.api.routes.schema.logic.types.Schema 4 | 5 | /** General definition of operations that operate on [[Schema]]s 6 | * 7 | * @param successMessage Message attached to the result of the operation 8 | * @param inputSchema Schema operated on 9 | */ 10 | private[operations] abstract class SchemaOperation( 11 | val successMessage: String = SchemaOperation.successMessage, 12 | val inputSchema: Schema 13 | ) 14 | 15 | /** Static utils for Schema operations 16 | */ 17 | private[operations] object SchemaOperation { 18 | 19 | /** Dummy success message 20 | */ 21 | private val successMessage = "Operation completed successfully" 22 | } 23 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/schema/logic/operations/stream/configuration/StreamValidationConfiguration.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.schema.logic.operations.stream.configuration 2 | 3 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters.{ 4 | ExtractorParameter, 5 | StreamParameter, 6 | ValidatorParameter 7 | } 8 | import es.weso.rdfshape.server.utils.other.mapEitherToDecodeResult 9 | import io.circe.{Decoder, Error, HCursor} 10 | 11 | /** Broad configuration class containing all the child configurations required to 12 | * perform a Stream validation with Comet 13 | * 14 | * @param validatorConfiguration Configuration used by the eventual validator 15 | * @param extractorConfiguration Configuration used by the eventual extractor 16 | * @param streamConfiguration Configuration used to fetch the input data stream 17 | */ 18 | sealed case class StreamValidationConfiguration( 19 | validatorConfiguration: StreamValidationValidatorConfiguration, 20 | extractorConfiguration: StreamValidationExtractorConfiguration, 21 | streamConfiguration: StreamValidationStreamConfiguration 22 | ) 23 | 24 | object StreamValidationConfiguration { 25 | 26 | /** Custom decoder fetching the correct parameter names in the incoming 27 | * JSON documents 28 | */ 29 | implicit val decoder: Decoder[StreamValidationConfiguration] = 30 | (cursor: HCursor) => { 31 | val decodeResult = for { 32 | validatorConfig <- cursor 33 | .downField(ValidatorParameter.name) 34 | .as[Either[String, StreamValidationValidatorConfiguration]] 35 | 36 | extractorConfig <- cursor 37 | .downField(ExtractorParameter.name) 38 | .as[Either[String, StreamValidationExtractorConfiguration]] 39 | 40 | streamConfig <- cursor 41 | .downField(StreamParameter.name) 42 | .as[Either[String, StreamValidationStreamConfiguration]] 43 | 44 | // Cumulative eithers 45 | configItems = for { 46 | vc <- validatorConfig 47 | ec <- extractorConfig 48 | sc <- streamConfig 49 | } yield (vc, ec, sc) 50 | 51 | } yield configItems.map { 52 | case (validatorConfig, extractorConfig, streamConfig) => 53 | StreamValidationConfiguration( 54 | validatorConfig, 55 | extractorConfig, 56 | streamConfig 57 | ) 58 | } 59 | 60 | // Map any error to a DecodingFailure in the final step of decoding 61 | mapEitherToDecodeResult(decodeResult) 62 | } 63 | 64 | /** Generic error message used as placeholder when no error cause is available 65 | */ 66 | private[configuration] val unknownErrorMessage: String = 67 | "Unknown error creating the configuration for stream validation" 68 | 69 | /** Error message used when a valid configuration can not be extracted from 70 | * the provided JSON data 71 | * 72 | * @param reason Error why the JSON decoding of the configuration failed 73 | * @return An informational text message containing some context and the 74 | * provided error 75 | */ 76 | private[schema] def invalidStreamValidationConfigurationReceived( 77 | reason: Option[Error] 78 | ) = { 79 | val formattedReasonText = 80 | reason.map(r => s": ${r.getMessage}").getOrElse("") 81 | s"Invalid configuration received for the stream validation$formattedReasonText" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/schema/logic/operations/stream/configuration/StreamValidationStreamConfiguration.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.schema.logic.operations.stream.configuration 2 | 3 | import cats.implicits.toBifunctorOps 4 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters.{ 5 | GroupIdParameter, 6 | PortParameter, 7 | ServerParameter, 8 | TopicParameter 9 | } 10 | import io.circe.{Decoder, HCursor} 11 | import org.ragna.comet.stream.extractors.kafka.{ 12 | KafkaExtractor, 13 | KafkaExtractorConfiguration 14 | } 15 | 16 | import scala.util.Try 17 | 18 | /** Minor configuration class, containing the information required for a 19 | * Comet Kafka extractor to fetch an incoming stream 20 | * 21 | * Everything but the server and topic is optional or has a default value 22 | * 23 | * @see [[KafkaExtractor]] 24 | * @see [[KafkaExtractorConfiguration]] 25 | */ 26 | final case class StreamValidationStreamConfiguration( 27 | server: String, 28 | port: Option[Int], 29 | topic: String, 30 | groupId: Option[String] 31 | ) 32 | 33 | object StreamValidationStreamConfiguration { 34 | 35 | /** Custom decoder fetching the correct parameter names in the incoming 36 | * JSON documents 37 | */ 38 | implicit val decoder 39 | : Decoder[Either[String, StreamValidationStreamConfiguration]] = 40 | (cursor: HCursor) => 41 | for { 42 | server <- cursor 43 | .downField(ServerParameter.name) 44 | .as[String] 45 | 46 | optPort <- cursor 47 | .downField(PortParameter.name) 48 | .as[Option[Int]] 49 | 50 | topic <- cursor 51 | .downField(TopicParameter.name) 52 | .as[String] 53 | 54 | groupId <- cursor 55 | .downField(GroupIdParameter.name) 56 | .as[Option[String]] 57 | } yield { 58 | Try { 59 | StreamValidationStreamConfiguration(server, optPort, topic, groupId) 60 | }.toEither.leftMap(err => 61 | s"Could not build the stream configuration from user data:\n ${err.getMessage}" 62 | ) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/schema/logic/operations/stream/configuration/StreamValidationValidatorConfiguration.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.schema.logic.operations.stream.configuration 2 | 3 | import cats.effect.unsafe.implicits.global 4 | import es.weso.rdfshape.server.api.routes.schema.logic.trigger.TriggerMode 5 | import es.weso.rdfshape.server.api.routes.schema.logic.types.Schema 6 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters._ 7 | import es.weso.schema.{Schema => SchemaW} 8 | import io.circe.{Decoder, HCursor} 9 | import org.ragna.comet.trigger.{ValidationTrigger => ValidationTriggerComet} 10 | import org.ragna.comet.validation.Validator 11 | import org.ragna.comet.validation.configuration.ValidatorConfiguration 12 | 13 | /** Minor configuration class, containing the information required for a 14 | * Comet validator to run 15 | * 16 | * The conversion from RDFShape Schemas ([[Schema]]) to SHAclEX schemas 17 | * ([[SchemaW]]) is attempted whilst decoding the configuration instances 18 | * from client data, the same goes for ValidationTriggers 19 | * 20 | * @see [[Validator]] 21 | * @see [[ValidatorConfiguration]] 22 | */ 23 | final case class StreamValidationValidatorConfiguration( 24 | schema: SchemaW, 25 | trigger: ValidationTriggerComet, 26 | haltOnInvalid: Option[Boolean], 27 | haltOnErrored: Option[Boolean], 28 | concurrentItems: Option[Int] 29 | ) 30 | 31 | object StreamValidationValidatorConfiguration { 32 | 33 | /** Custom decoder fetching the correct parameter names in the incoming 34 | * JSON documents 35 | * 36 | * Interprets the incoming timeout value as a number of milliseconds 37 | */ 38 | implicit val decoder 39 | : Decoder[Either[String, StreamValidationValidatorConfiguration]] = 40 | (cursor: HCursor) => { 41 | val configInfo = for { 42 | maybeSchema <- cursor 43 | .downField(SchemaParameter.name) 44 | .as[Either[String, Schema]] 45 | 46 | maybeTriggerMode <- cursor 47 | .downField(TriggerModeParameter.name) 48 | .as[Either[String, TriggerMode]]( 49 | TriggerMode.decode(None, maybeSchema.toOption) 50 | ) 51 | 52 | optHaltInvalid <- cursor 53 | .downField(HaltOnInvalidParameter.name) 54 | .as[Option[Boolean]] 55 | 56 | optHaltErrored <- cursor 57 | .downField(HaltOnErroredParameter.name) 58 | .as[Option[Boolean]] 59 | 60 | optConcurrentItems <- cursor 61 | .downField(ConcurrentItemsParameter.name) 62 | .as[Option[Int]] 63 | 64 | } yield ( 65 | maybeSchema, 66 | maybeTriggerMode, 67 | optHaltInvalid, 68 | optHaltErrored, 69 | optConcurrentItems 70 | ) 71 | 72 | configInfo.map { 73 | case ( 74 | maybeSchema, 75 | maybeTriggerMode, 76 | optHaltInvalid, 77 | optHaltErrored, 78 | optConcurrentItems 79 | ) => 80 | for { 81 | rdfShapeSchema <- maybeSchema 82 | rdfShapeTrigger <- maybeTriggerMode 83 | // Form final schema trigger used in the stream validation 84 | // Unsafe run due to SHAclEX API limitations 85 | cometSchema <- rdfShapeSchema.getSchema.unsafeRunSync() 86 | // Form final validation trigger used in the stream validation 87 | cometTrigger = rdfShapeTrigger.toStreamingTriggerMode 88 | } yield StreamValidationValidatorConfiguration( 89 | cometSchema, 90 | cometTrigger, 91 | optHaltInvalid, 92 | optHaltErrored, 93 | optConcurrentItems 94 | ) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/schema/logic/operations/stream/transformations/ValidationResultTransformations.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.schema.logic.operations.stream.transformations 2 | 3 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters.{ 4 | ContentParameter, 5 | TypeParameter 6 | } 7 | import io.circe.Json 8 | import io.circe.syntax.EncoderOps 9 | import org.http4s.websocket.WebSocketFrame 10 | import org.ragna.comet.validation.result.ValidationResult 11 | 12 | /** Object containing additional utilities added to the [[ValidationResult]] 13 | * class 14 | */ 15 | object ValidationResultTransformations { 16 | 17 | /** Implicit class providing extension methods for [[ValidationResult]] 18 | * instances 19 | * 20 | * @param validationResult Result being operated on 21 | */ 22 | implicit class ValidationResultOps(validationResult: ValidationResult) { 23 | 24 | /** From a given comet validation result, generate its JSON representation 25 | * and form a WebSocket frame containing it as text 26 | * 27 | * @return A textual [[WebSocketFrame]] with the JSON representation of 28 | * a [[ValidationResult]] 29 | */ 30 | def toWebSocketFrame: WebSocketFrame.Text = { 31 | val messageJson = Json.fromFields( 32 | List( 33 | (TypeParameter.name, "result".asJson), 34 | (ContentParameter.name, validationResult.asJson) 35 | ) 36 | ) 37 | WebSocketFrame.Text(messageJson.spaces2) 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/schema/logic/operations/stream/transformations/WebSocketTransformations.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.schema.logic.operations.stream.transformations 2 | 3 | import es.weso.rdfshape.server.utils.error.exceptions.UnexpectedWebSocketFrameException 4 | import io.circe.{Encoder, Json, ParsingFailure, parser} 5 | import org.http4s.websocket.WebSocketFrame 6 | 7 | /** Object containing additional utilities added to the [[WebSocketFrame]] 8 | * class 9 | */ 10 | object WebSocketTransformations { 11 | 12 | /** Custom encoder Encoder [[WebSocketFrame]] => [[Json]] 13 | * 14 | * If the frame contains a text message: attempt to parse to a JSON object, 15 | * else return an error 16 | * 17 | * @return A Circe Json object, parsed from the text inside the WebSockets 18 | * message 19 | * @throws UnexpectedWebSocketFrameException when the WebSocket message 20 | * received is not a text message 21 | * @throws ParsingFailure when the WebSocket message can't 22 | * be parsed to a JSON object 23 | */ 24 | implicit val encoder: Encoder[WebSocketFrame] = { 25 | case textMessage: WebSocketFrame.Text => 26 | parser.parse(textMessage.str) match { 27 | case Left(err) => throw err 28 | case Right(jsonObj) => jsonObj 29 | } 30 | case otherFrameType => 31 | throw UnexpectedWebSocketFrameException( 32 | Some(otherFrameType.getClass), 33 | Some(WebSocketFrame.Text.getClass) 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/schema/logic/trigger/TriggerModeType.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.schema.logic.trigger 2 | 3 | import es.weso.rdfshape.server.utils.other.MyEnum 4 | 5 | /** Enumeration of the different possible Validation Triggers sent by the client. 6 | * The trigger sent indicates the API how to proceed with validations 7 | */ 8 | private[api] object TriggerModeType extends MyEnum[String] { 9 | type TriggerModeType = String 10 | 11 | val SHAPEMAP = "ShapeMap" 12 | val TARGET_DECLARATIONS = "TargetDecls" 13 | 14 | val values: Set[String] = Set(SHAPEMAP, TARGET_DECLARATIONS) 15 | val default: TriggerModeType = SHAPEMAP 16 | } 17 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/schema/logic/trigger/TriggerTargetDeclarations.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.schema.logic.trigger 2 | 3 | import cats.implicits.catsSyntaxEitherId 4 | import com.typesafe.scalalogging.LazyLogging 5 | import es.weso.rdfshape.server.api.routes.data.logic.types.Data 6 | import es.weso.rdfshape.server.api.routes.schema.logic.trigger.TriggerModeType.TriggerModeType 7 | import es.weso.rdfshape.server.api.routes.schema.logic.types.Schema 8 | import es.weso.schema.{TargetDeclarations, ValidationTrigger} 9 | import io.circe.syntax.EncoderOps 10 | import io.circe.{Decoder, Encoder, HCursor, Json} 11 | 12 | /** Data class representing a validation trigger enabled by target declarations, 13 | * for SHACL validations. 14 | */ 15 | sealed case class TriggerTargetDeclarations private ( 16 | override val data: Option[Data] = None, 17 | override val schema: Option[Schema] = None 18 | ) extends TriggerMode 19 | with LazyLogging { 20 | 21 | override val `type`: TriggerModeType = 22 | TriggerModeType.TARGET_DECLARATIONS 23 | 24 | override def getValidationTrigger: ValidationTrigger = 25 | TargetDeclarations 26 | } 27 | 28 | private[api] object TriggerTargetDeclarations 29 | extends TriggerModeCompanion[TriggerTargetDeclarations] 30 | with LazyLogging { 31 | 32 | override implicit def decode( 33 | data: Option[Data], 34 | schema: Option[Schema] 35 | ): Decoder[Either[String, TriggerTargetDeclarations]] = 36 | (_: HCursor) => TriggerTargetDeclarations(data, schema).asRight.asRight 37 | 38 | override implicit val encode: Encoder[TriggerTargetDeclarations] = 39 | (tsm: TriggerTargetDeclarations) => 40 | Json.obj( 41 | ("type", tsm.`type`.asJson), 42 | ("data", tsm.data.asJson), 43 | ("schema", tsm.schema.asJson) 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/schema/service/operations/SchemaConvertInput.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.schema.service.operations 2 | 3 | import cats.implicits.catsSyntaxEitherId 4 | import es.weso.rdfshape.server.api.ServiceRouteOperation 5 | import es.weso.rdfshape.server.api.format.dataFormats.DataFormat 6 | import es.weso.rdfshape.server.api.routes.schema.logic.aux.SchemaAdapter.decodeEngine 7 | import es.weso.rdfshape.server.api.routes.schema.logic.types.Schema 8 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters.{ 9 | SchemaParameter, 10 | TargetEngineParameter, 11 | TargetFormatParameter 12 | } 13 | import es.weso.rdfshape.server.utils.other.mapEitherToDecodeResult 14 | import es.weso.schema.{Schema => SchemaW} 15 | import io.circe.{Decoder, HCursor} 16 | 17 | /** Data class representing the inputs required when querying the server 18 | * for a schema conversion 19 | * 20 | * @param schema Schema to be inspected 21 | * @param targetFormat Desired output format 22 | * @param targetEngine Desired output engine 23 | */ 24 | case class SchemaConvertInput( 25 | schema: Schema, 26 | targetFormat: DataFormat, 27 | targetEngine: SchemaW 28 | ) 29 | 30 | object SchemaConvertInput extends ServiceRouteOperation[SchemaConvertInput] { 31 | override implicit val decoder: Decoder[SchemaConvertInput] = 32 | (cursor: HCursor) => { 33 | val decodeResult = for { 34 | maybeSchema <- cursor 35 | .downField(SchemaParameter.name) 36 | .as[Either[String, Schema]] 37 | 38 | // Use data format because target formats range 39 | // from SchemaFormats to GraphicFormats, etc. 40 | maybeTargetFormat <- cursor 41 | .downField(TargetFormatParameter.name) 42 | .as[Either[String, DataFormat]] 43 | 44 | maybeTargetEngine <- cursor 45 | .downField(TargetEngineParameter.name) 46 | .as[Either[String, SchemaW]] match { 47 | // Decoding error: the param was not sent. Use input schema engine 48 | case Left(_) => maybeSchema.map(_.engine).asRight 49 | // Keep the result extracted from user input 50 | case other => other 51 | } 52 | 53 | maybeItems = for { 54 | schema <- maybeSchema 55 | targetFormat <- maybeTargetFormat 56 | targetEngine <- maybeTargetEngine 57 | } yield (schema, targetFormat, targetEngine) 58 | 59 | } yield maybeItems.map { case (schema, targetFormat, targetEngine) => 60 | SchemaConvertInput(schema, targetFormat, targetEngine) 61 | } 62 | 63 | mapEitherToDecodeResult(decodeResult) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/schema/service/operations/SchemaInfoInput.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.schema.service.operations 2 | 3 | import es.weso.rdfshape.server.api.ServiceRouteOperation 4 | import es.weso.rdfshape.server.api.routes.schema.logic.types.Schema 5 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters.SchemaParameter 6 | import es.weso.rdfshape.server.utils.other.mapEitherToDecodeResult 7 | import io.circe.{Decoder, HCursor} 8 | 9 | /** Data class representing the inputs required when querying the server 10 | * for a schema information 11 | * 12 | * @param schema Schema to be inspected 13 | */ 14 | case class SchemaInfoInput(schema: Schema) 15 | 16 | object SchemaInfoInput extends ServiceRouteOperation[SchemaInfoInput] { 17 | override implicit val decoder: Decoder[SchemaInfoInput] = (cursor: HCursor) => 18 | { 19 | val decodeResult = for { 20 | maybeSchema <- cursor 21 | .downField(SchemaParameter.name) 22 | .as[Either[String, Schema]] 23 | } yield maybeSchema.map(SchemaInfoInput(_)) 24 | 25 | mapEitherToDecodeResult(decodeResult) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/schema/service/operations/SchemaValidateInput.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.schema.service.operations 2 | 3 | import es.weso.rdfshape.server.api.ServiceRouteOperation 4 | import es.weso.rdfshape.server.api.routes.data.logic.types.Data 5 | import es.weso.rdfshape.server.api.routes.schema.logic.trigger.TriggerMode 6 | import es.weso.rdfshape.server.api.routes.schema.logic.types.Schema 7 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters._ 8 | import es.weso.rdfshape.server.utils.other.mapEitherToDecodeResult 9 | import io.circe.{Decoder, HCursor} 10 | 11 | /** Data class representing the inputs required when querying the server 12 | * for a schema validation of data 13 | * 14 | * @param data Data to be validated 15 | * @param schema Schema model for the validation 16 | * @param triggerMode Validation trigger, i.e.: how the validation was 17 | * started (shapeMap, target declarations...) 18 | */ 19 | case class SchemaValidateInput( 20 | data: Data, 21 | schema: Schema, 22 | triggerMode: TriggerMode 23 | ) 24 | 25 | object SchemaValidateInput extends ServiceRouteOperation[SchemaValidateInput] { 26 | override implicit val decoder: Decoder[SchemaValidateInput] = 27 | (cursor: HCursor) => { 28 | val decodeResult = for { 29 | maybeData <- cursor 30 | .downField(DataParameter.name) 31 | .as[Either[String, Data]] 32 | 33 | maybeSchema <- cursor 34 | .downField(SchemaParameter.name) 35 | .as[Either[String, Schema]] 36 | 37 | maybeTriggerMode <- cursor 38 | .downField(TriggerModeParameter.name) 39 | .as[Either[String, TriggerMode]]( 40 | TriggerMode.decode(maybeData.toOption, maybeSchema.toOption) 41 | ) 42 | 43 | maybeItems = for { 44 | data <- maybeData 45 | schema <- maybeSchema 46 | triggerMode <- maybeTriggerMode 47 | } yield (data, schema, triggerMode) 48 | 49 | } yield maybeItems.map { case (data, schema, triggerMode) => 50 | SchemaValidateInput(data, schema, triggerMode) 51 | } 52 | mapEitherToDecodeResult(decodeResult) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/schema/service/operations/SchemaValidateStreamInput.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.schema.service.operations 2 | 3 | import es.weso.rdfshape.server.api.ServiceRouteOperation 4 | import es.weso.rdfshape.server.api.routes.schema.logic.operations.stream.configuration.StreamValidationConfiguration 5 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters._ 6 | import io.circe.{Decoder, HCursor} 7 | 8 | /** Data class representing the inputs required when querying the server 9 | * for a schema validation of a stream of data using Comet 10 | * 11 | * @param configuration Configuration object with all settings needed by Comet 12 | * to fetch and validate a stream of data 13 | */ 14 | case class SchemaValidateStreamInput( 15 | configuration: StreamValidationConfiguration 16 | ) 17 | 18 | object SchemaValidateStreamInput 19 | extends ServiceRouteOperation[SchemaValidateStreamInput] { 20 | 21 | override implicit val decoder: Decoder[SchemaValidateStreamInput] = 22 | (cursor: HCursor) => { 23 | for { 24 | // Decode all of the validation settings to a domain object, 25 | // relying on the decoders of each underlying configuration class 26 | validationConfig <- cursor 27 | .downField(ConfigurationParameter.name) 28 | .as[StreamValidationConfiguration] 29 | } yield SchemaValidateStreamInput(validationConfig) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/shapemap/logic/ShapeMapSource.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.shapemap.logic 2 | 3 | import es.weso.rdfshape.server.utils.other.MyEnum 4 | 5 | /** Enumeration of the different possible ShapeMap sources sent by the client. 6 | * The source sent indicates the API if the shapemap was sent in raw text, as a URL 7 | * to be fetched or as a text file containing the shapemap. 8 | * In case the client submits the shapemap in several formats, the selected source will indicate the preferred one. 9 | */ 10 | private[api] object ShapeMapSource extends MyEnum[String] { 11 | type ShapeMapSource = String 12 | 13 | val TEXT = "byText" 14 | val URL = "byUrl" 15 | val FILE = "byFile" 16 | 17 | val values: Set[ShapeMapSource] = Set(TEXT, URL, FILE) 18 | val default: ShapeMapSource = TEXT 19 | } 20 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/shapemap/logic/operations/ShapeMapOperation.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.shapemap.logic.operations 2 | 3 | import es.weso.rdfshape.server.api.routes.shapemap.logic.ShapeMap 4 | 5 | /** General definition of operations that operate on [[ShapeMap]]s 6 | * 7 | * @param successMessage Message attached to the result of the operation 8 | * @param inputShapeMap ShapeMap operated on 9 | */ 10 | private[operations] abstract class ShapeMapOperation( 11 | val successMessage: String = ShapeMapOperation.successMessage, 12 | val inputShapeMap: ShapeMap 13 | ) 14 | 15 | /** Static utils for ShapeMap operations 16 | */ 17 | private[operations] object ShapeMapOperation { 18 | 19 | /** Dummy success message 20 | */ 21 | private val successMessage = "Operation completed successfully" 22 | } 23 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/shapemap/service/ShapeMapService.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.shapemap.service 2 | 3 | import cats.effect._ 4 | import com.typesafe.scalalogging.LazyLogging 5 | import es.weso.rdfshape.server.api.definitions.ApiDefinitions 6 | import es.weso.rdfshape.server.api.routes.ApiService 7 | import es.weso.rdfshape.server.api.routes.shapemap.logic.operations.ShapeMapInfo 8 | import es.weso.rdfshape.server.api.routes.shapemap.service.operations.ShapeMapInfoInput 9 | import io.circe._ 10 | import io.circe.syntax.EncoderOps 11 | import org.http4s.circe._ 12 | import org.http4s.client.Client 13 | import org.http4s.dsl.Http4sDsl 14 | import org.http4s.rho.RhoRoutes 15 | import org.http4s.rho.swagger.syntax.io._ 16 | 17 | /** API service to handle shapemap-related operations 18 | * 19 | * @param client HTTP4S client object 20 | */ 21 | class ShapeMapService(client: Client[IO]) 22 | extends Http4sDsl[IO] 23 | with ApiService 24 | with LazyLogging { 25 | 26 | override val verb: String = "shapemap" 27 | 28 | /** Describe the API routes handled by this service and the actions performed on each of them 29 | */ 30 | val routes: RhoRoutes[IO] = new RhoRoutes[IO] { 31 | 32 | "GET an array with the accepted ShapeMap formats" ** 33 | GET / `verb` / "formats" |>> { 34 | val formats = ApiDefinitions.availableShapeMapFormats 35 | val json = Json.fromValues(formats.map(f => Json.fromString(f.name))) 36 | Ok(json) 37 | } 38 | 39 | "Obtain information about a ShapeMap: number of associations, prefixes, model" ** 40 | POST / `verb` / "info" ^ jsonOf[IO, ShapeMapInfoInput] |>> { 41 | body: ShapeMapInfoInput => 42 | ShapeMapInfo 43 | .shapeMapInfo(body.shapeMap) 44 | .flatMap(info => Ok(info.asJson)) 45 | .handleErrorWith(err => InternalServerError(err.getMessage)) 46 | } 47 | } 48 | 49 | } 50 | 51 | object ShapeMapService { 52 | 53 | /** Service factory 54 | * 55 | * @param client Underlying http4s client 56 | * @return A new ShapeMap Service 57 | */ 58 | def apply(client: Client[IO]): ShapeMapService = new ShapeMapService(client) 59 | } 60 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/shapemap/service/operations/ShapeMapInfoInput.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.shapemap.service.operations 2 | 3 | import es.weso.rdfshape.server.api.ServiceRouteOperation 4 | import es.weso.rdfshape.server.api.routes.shapemap.logic.ShapeMap 5 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters.ShapeMapParameter 6 | import es.weso.rdfshape.server.utils.other.mapEitherToDecodeResult 7 | import io.circe.{Decoder, HCursor} 8 | 9 | /** Data class representing the inputs required when querying the server 10 | * for ShapeMap information 11 | * @param shapeMap ShapeMap to be inspected 12 | */ 13 | case class ShapeMapInfoInput(shapeMap: ShapeMap) 14 | 15 | object ShapeMapInfoInput extends ServiceRouteOperation[ShapeMapInfoInput] { 16 | 17 | override implicit val decoder: Decoder[ShapeMapInfoInput] = 18 | (cursor: HCursor) => { 19 | val decodeResult = for { 20 | maybeShapeMap <- cursor 21 | .downField(ShapeMapParameter.name) 22 | .as[Either[String, ShapeMap]] 23 | } yield maybeShapeMap.map(ShapeMapInfoInput(_)) 24 | 25 | mapEitherToDecodeResult(decodeResult) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/model/Wikibase.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.model 2 | 3 | import es.weso.rdfshape.server.implicits.codecs.encodeUri 4 | import io.circe.syntax.EncoderOps 5 | import io.circe.{Encoder, Json} 6 | import org.http4s._ 7 | import org.http4s.implicits.http4sLiteralsSyntax 8 | 9 | /** Abstract representation of a wikibase instance 10 | * 11 | * @param name Given name of the wikibase instance 12 | * @param baseUrl Base URL where the instance is deployed (e.g. [[https://www.wikidata.org/]]) 13 | * @param queryUrl SPARQL query endpoint of the wikibase instance, where SPARQL queries are targeted 14 | * It may vary depending on the instance. 15 | */ 16 | private[api] case class Wikibase( 17 | name: Option[String] = Option("wikibase instance"), 18 | baseUrl: Uri = uri"", 19 | queryUrl: Uri = uri"" 20 | ) { 21 | 22 | /** Base API endpoint of the wikibase instance (e.g. [[https://www.wikidata.org/w/api.php]]) 23 | * 24 | * @note Unlike [[queryUrl]], this can be inferred from [[baseUrl]] 25 | */ 26 | lazy val apiUrl: Uri = baseUrl / "w" / "api.php" 27 | 28 | /** Given a schema identifier, return it's location inside the wikibase instance 29 | * Default implementation is based on Wikidata's and should be overridden. 30 | * 31 | * @param schema String representation of the schema identifier 32 | * @return Uri where the schema can be accessed 33 | */ 34 | def schemaEntityUri(schema: String): Uri = 35 | baseUrl / "wiki" / "Special:EntitySchemaText" / schema 36 | 37 | /** Return whether if tow wikibase instances are the same or not 38 | * 39 | * @param other Other item being compared 40 | * @return True if the wikibase instances share a base URL, false otherwise 41 | */ 42 | override def equals(other: Any): Boolean = { 43 | other match { 44 | case wb: Wikibase => baseUrl == wb.baseUrl 45 | case _ => false 46 | } 47 | } 48 | } 49 | 50 | object Wikibase { 51 | 52 | /** JSON encoder for [[Wikibase]] 53 | */ 54 | implicit val encode: Encoder[Wikibase] = (wikibase: Wikibase) => 55 | Json.fromFields( 56 | List( 57 | ("name", wikibase.name.asJson), 58 | ("baseUrl", wikibase.baseUrl.asJson), 59 | ("queryUrl", wikibase.queryUrl.asJson), 60 | ("apiUrl", wikibase.apiUrl.asJson) 61 | ) 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/model/Wikidata.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.model 2 | 3 | import es.weso.rdf.PrefixMap 4 | import es.weso.rdf.nodes.IRI 5 | import org.http4s.implicits.http4sLiteralsSyntax 6 | 7 | /** A sub-instance of the more general [[Wikibase]] class, 8 | * configured to reference and access Wikidata 9 | * 10 | * @see [[https://www.wikidata.org/ Wikidata]] 11 | */ 12 | private[api] object Wikidata 13 | extends Wikibase( 14 | name = Option("wikidata"), 15 | baseUrl = uri"https://www.wikidata.org", 16 | queryUrl = uri"https://query.wikidata.org" / "sparql" 17 | ) { 18 | 19 | /** @return List of tuples with all the prefixes used by Wikidata 20 | * an their short-key values 21 | */ 22 | lazy val wikidataPrefixes: List[(String, String)] = { 23 | //noinspection HttpUrlsUsage,SpellCheckingInspection 24 | 25 | List( 26 | ("wikibase", "http://wikiba.se/ontology#"), 27 | ("bd", "http://www.bigdata.com/rdf#"), 28 | ("wd", "http://www.wikidata.org/entity/"), 29 | ("wdt", "http://www.wikidata.org/prop/direct/"), 30 | ("wdtn", "http://www.wikidata.org/prop/direct-normalized/"), 31 | ("wds", "http://www.wikidata.org/entity/statement/"), 32 | ("p", "http://www.wikidata.org/prop/"), 33 | ("wdref", "http://www.wikidata.org/reference/"), 34 | ("wdv", "http://www.wikidata.org/value/"), 35 | ("ps", "http://www.wikidata.org/prop/statement/"), 36 | ("psv", "http://www.wikidata.org/prop/statement/value/"), 37 | ("psn", "http://www.wikidata.org/prop/statement/value-normalized/"), 38 | ("pq", "http://www.wikidata.org/prop/qualifier/"), 39 | ("pqv", "http://www.wikidata.org/prop/qualifier/value/"), 40 | ("pqn", "http://www.wikidata.org/prop/qualifier/value-normalized/"), 41 | ("pr", "http://www.wikidata.org/prop/reference/"), 42 | ("prv", "http://www.wikidata.org/prop/reference/value/"), 43 | ("prn", "http://www.wikidata.org/prop/reference/value-normalized/"), 44 | ("wdno", "http://www.wikidata.org/prop/novalue/") 45 | ) 46 | } 47 | 48 | /** [[PrefixMap]] instance containing all Wikidata prefixes in [[wikidataPrefixes]] 49 | */ 50 | lazy val wikidataPrefixMap: PrefixMap = { 51 | PrefixMap.fromMap(wikidataPrefixes.toMap.view.mapValues(IRI.apply).toMap) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/model/objects/wikibase/WikibaseEntity.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.model.objects.wikibase 2 | 3 | import es.weso.rdfshape.server.api.routes.wikibase.logic.model.Wikibase 4 | import org.http4s.Uri 5 | 6 | /** Data class representing a Wikidata entity 7 | */ 8 | class WikibaseEntity( 9 | override val wikibase: Wikibase, 10 | override val entityUri: Uri 11 | ) extends WikibaseObject(wikibase, entityUri) { 12 | 13 | override val localName: String = 14 | entityUri.renderString.split("/").last.stripSuffix("#") 15 | 16 | override val contentUri: Uri = 17 | wikibase.baseUrl / "wiki" / "Special:EntityData" / (localName + ".ttl") 18 | } 19 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/model/objects/wikibase/WikibaseObject.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.model.objects.wikibase 2 | 3 | import es.weso.rdfshape.server.api.routes.wikibase.logic.model.Wikibase 4 | import es.weso.rdfshape.server.api.routes.wikibase.logic.model.objects.wikidata.WikidataObject 5 | import es.weso.rdfshape.server.utils.networking.NetworkingUtils 6 | import org.http4s.Uri 7 | 8 | /** General class representing any object (entity, schema...) living in a 9 | * wikibase instance. 10 | * Unlike [[WikidataObject]], where Wikidata is the target wikibase, 11 | * here a wikibase must be provided for context 12 | * 13 | * @param wikibase Wikibase where the object resides 14 | * @param entityUri URL where the object data can be found 15 | * 16 | * @note Currently limited to Entities/properties 17 | */ 18 | abstract private[api] class WikibaseObject( 19 | val wikibase: Wikibase, 20 | val entityUri: Uri 21 | ) { 22 | 23 | /** Either the raw contents of this object or the error 24 | * occurred while retrieving them 25 | */ 26 | lazy val contents: Either[String, String] = 27 | NetworkingUtils.getUrlContents(contentUri.renderString) 28 | 29 | /** Short name or identifier of the entity, e.g.: Q123, 30 | * normally this is the last part of [[entityUri]] 31 | */ 32 | val localName: String 33 | 34 | /** URL where the entity data can be found in raw form 35 | */ 36 | val contentUri: Uri 37 | } 38 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/model/objects/wikibase/WikibaseProperty.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.model.objects.wikibase 2 | 3 | import es.weso.rdfshape.server.api.routes.wikibase.logic.model.Wikibase 4 | import org.http4s.Uri 5 | 6 | /** Data class representing a Wikidata entity 7 | */ 8 | class WikibaseProperty( 9 | override val wikibase: Wikibase, 10 | override val entityUri: Uri 11 | ) extends WikibaseObject(wikibase, entityUri) { 12 | 13 | override val localName: String = 14 | entityUri.renderString.split(":").last.stripSuffix("#") 15 | 16 | override val contentUri: Uri = 17 | wikibase.baseUrl / "wiki" / "Special:EntityData" / (localName + ".ttl") 18 | } 19 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/model/objects/wikibase/WikibaseSchema.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.model.objects.wikibase 2 | 3 | import es.weso.rdfshape.server.api.routes.wikibase.logic.model.Wikibase 4 | import org.http4s.Uri 5 | 6 | /** Data class representing a Wikidata entity 7 | */ 8 | class WikibaseSchema( 9 | override val wikibase: Wikibase, 10 | override val entityUri: Uri 11 | ) extends WikibaseObject(wikibase, entityUri) { 12 | 13 | override val localName: String = 14 | entityUri.renderString.split(":").last.stripSuffix("#") 15 | 16 | override val contentUri: Uri = 17 | wikibase.baseUrl / "wiki" / "Special:EntitySchemaText" / localName 18 | } 19 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/model/objects/wikidata/WikidataEntity.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.model.objects.wikidata 2 | 3 | import es.weso.rdfshape.server.api.routes.wikibase.logic.model.Wikidata 4 | import es.weso.rdfshape.server.api.routes.wikibase.logic.model.objects.wikibase.WikibaseEntity 5 | import org.http4s.Uri 6 | 7 | import scala.util.matching.Regex 8 | 9 | /** Data class representing a Wikidata entity 10 | */ 11 | case class WikidataEntity( 12 | override val entityUri: Uri 13 | ) extends WikibaseEntity(Wikidata, entityUri) 14 | with WikidataObject { 15 | 16 | override val wikidataRegex: Regex = 17 | "(http(s)?://www.wikidata.org/entity/(.+))|(http(s)?://www.wikidata.org/wiki/(.+))".r 18 | 19 | checkUri() 20 | 21 | } 22 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/model/objects/wikidata/WikidataObject.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.model.objects.wikidata 2 | 3 | import org.http4s.Uri 4 | 5 | import scala.util.matching.Regex 6 | 7 | /** Trait defining several utilities and guidelines for wikibase items 8 | * residing in Wikidata 9 | */ 10 | trait WikidataObject { 11 | 12 | /** Uri unique to the wikidata object being identified 13 | */ 14 | val entityUri: Uri 15 | 16 | /** URL where the entity data can be found in raw form 17 | */ 18 | val contentUri: Uri 19 | 20 | /** Regular expression used to recognize wikidata objects of the required type 21 | * When using Wikidata, we know the format of the URIs we expect, so we can 22 | * define a regex to notice if the input URI is a valid Wikidata item or not 23 | */ 24 | val wikidataRegex: Regex 25 | 26 | /** Assert the given [[entityUri]] is a valid uri complying with 27 | * [[wikidataRegex]] 28 | */ 29 | def checkUri(): Unit = assume( 30 | wikidataRegex.matches(entityUri.renderString), 31 | s"Uri '${entityUri.renderString}' does not comply with '${wikidataRegex.regex}'" 32 | ) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/model/objects/wikidata/WikidataProperty.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.model.objects.wikidata 2 | 3 | import es.weso.rdfshape.server.api.routes.wikibase.logic.model.Wikidata 4 | import es.weso.rdfshape.server.api.routes.wikibase.logic.model.objects.wikibase.WikibaseProperty 5 | import org.http4s.Uri 6 | 7 | import scala.util.matching.Regex 8 | 9 | /** Data class representing a Wikidata schema 10 | */ 11 | case class WikidataProperty( 12 | override val entityUri: Uri 13 | ) extends WikibaseProperty(Wikidata, entityUri) 14 | with WikidataObject { 15 | 16 | override val wikidataRegex: Regex = 17 | "http(s)?://www.wikidata.org/wiki/Property:(.+)".r 18 | 19 | checkUri() 20 | } 21 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/model/objects/wikidata/WikidataSchema.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.model.objects.wikidata 2 | 3 | import es.weso.rdfshape.server.api.routes.wikibase.logic.model.Wikidata 4 | import es.weso.rdfshape.server.api.routes.wikibase.logic.model.objects.wikibase.WikibaseSchema 5 | import org.http4s.Uri 6 | 7 | import scala.util.matching.Regex 8 | 9 | /** Data class representing a Wikidata schema 10 | */ 11 | case class WikidataSchema( 12 | override val entityUri: Uri 13 | ) extends WikibaseSchema(Wikidata, entityUri) 14 | with WikidataObject { 15 | 16 | override val wikidataRegex: Regex = 17 | "http(s)?://www.wikidata.org/wiki/EntitySchema:(.+)".r 18 | 19 | checkUri() 20 | } 21 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/operations/WikibaseOperationFormats.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.operations 2 | 3 | import es.weso.rdfshape.server.utils.other.MyEnum 4 | 5 | /** Enumeration of the different formats that can be requested to wikibase's API. 6 | * The "fm" formats are meant for debugging 7 | * 8 | * @see [[https://www.mediawiki.org/wiki/Wikibase/API#Request_Format]] 9 | */ 10 | private[api] object WikibaseOperationFormats extends MyEnum[String] { 11 | type WikibaseOperationFormats = String 12 | 13 | val JSON = "json" 14 | val JSON_FM = "jsonfm" 15 | val XML = "xml" 16 | val XML_FM = "xmlfm" 17 | 18 | val values: Set[WikibaseOperationFormats] = Set(JSON, JSON_FM, XML, XML_FM) 19 | val default: WikibaseOperationFormats = JSON 20 | } 21 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/operations/WikibaseOperationResult.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.operations 2 | 3 | import es.weso.rdfshape.server.api.routes.wikibase.logic.model.Wikibase 4 | import io.circe.syntax.EncoderOps 5 | import io.circe.{Encoder, Json} 6 | 7 | /** Case class representing the results to be returned when performing a search 8 | * operation in a wikibase instance 9 | * 10 | * @param operationData Input data operated on 11 | * @param wikibase Target wikibase operated on 12 | * @param result Results returned by the operation, ready for API responses 13 | */ 14 | final case class WikibaseOperationResult private ( 15 | operationData: WikibaseOperationDetails, 16 | wikibase: Wikibase, 17 | result: Json 18 | ) 19 | 20 | /** Static codec utilities for the results 21 | */ 22 | private[api] object WikibaseOperationResult { 23 | 24 | /** JSON encoder for [[WikibaseOperationResult]]s 25 | */ 26 | implicit val encode: Encoder[WikibaseOperationResult] = 27 | (opResult: WikibaseOperationResult) => 28 | Json.fromFields( 29 | List( 30 | ("operationData", opResult.operationData.asJson), 31 | ("wikibase", opResult.wikibase.asJson), 32 | ("result", opResult.result) 33 | ) 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/operations/get/WikibaseGetLabels.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.operations.get 2 | 3 | import cats.effect.IO 4 | import es.weso.rdfshape.server.api.routes.wikibase.logic.operations.WikibaseOperationDetails 5 | import org.http4s.client.Client 6 | 7 | /** A [[WikibaseGetOperation]] returning the labels of the objects retrieved. 8 | * 9 | * @param operationData Data needed to perform the wikibase operation 10 | * @param client [[Client]] object to be used in requests to wikibase 11 | * @note All derived operations are based on [[https://www.wikidata.org/w/api.php?action=help&modules=wbgetentities]] 12 | */ 13 | sealed case class WikibaseGetLabels( 14 | override val operationData: WikibaseOperationDetails, 15 | override val client: Client[IO] 16 | ) extends WikibaseGetOperation( 17 | operationData, 18 | client, 19 | List(WikibasePropTypes.LABELS) 20 | ) 21 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/operations/get/WikibaseGetOperation.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.operations.get 2 | 3 | import cats.effect.IO 4 | import es.weso.rdfshape.server.api.routes.wikibase.logic.operations.get.WikibaseGetOperation.defaultResultLanguages 5 | import es.weso.rdfshape.server.api.routes.wikibase.logic.operations.get.WikibasePropTypes.WikibasePropTypes 6 | import es.weso.rdfshape.server.api.routes.wikibase.logic.operations.{ 7 | WikibaseOperation, 8 | WikibaseOperationDetails, 9 | WikibaseOperationFormats, 10 | WikibaseOperationResult 11 | } 12 | import es.weso.rdfshape.server.utils.error.exceptions.WikibaseServiceException 13 | import io.circe.Json 14 | import org.http4s.Uri 15 | import org.http4s.circe.jsonDecoder 16 | import org.http4s.client.Client 17 | 18 | /** Common class for wikibase operations based on retrieving entities. 19 | * Given an input [[WikibaseOperationDetails]], get items from a wikibase instance. 20 | * 21 | * @param operationData Data needed to perform the wikibase operation 22 | * @param client [[Client]] object to be used in requests to wikibase 23 | * @param props Properties to be returned from the objects retrieved 24 | * @note All derived operations are based on [[https://www.wikidata.org/w/api.php?action=help&modules=wbgetentities]] 25 | */ 26 | private[wikibase] class WikibaseGetOperation( 27 | override val operationData: WikibaseOperationDetails, 28 | override val client: Client[IO], 29 | props: List[WikibasePropTypes] = WikibasePropTypes.default 30 | ) extends WikibaseOperation( 31 | WikibaseGetOperation.successMessage, 32 | operationData, 33 | client 34 | ) { 35 | 36 | /** Target URL in the targeted wikibase instance. Already prepared with the 37 | * search action and given payload. 38 | */ 39 | override lazy val targetUri: Uri = { 40 | targetWikibase.apiUrl 41 | .withQueryParam("action", "wbgetentities") 42 | .withQueryParam("props", props.mkString("|")) 43 | .withQueryParam("ids", operationData.payload) 44 | .withQueryParam( 45 | "languages", 46 | operationData.resultLanguages 47 | .getOrElse(defaultResultLanguages) 48 | .mkString("|") 49 | ) 50 | .withQueryParam( 51 | "format", 52 | operationData.format 53 | .getOrElse(WikibaseOperationFormats.JSON) 54 | ) 55 | 56 | /* "props" parameter with the data to be returned to be defined in 57 | * sub-classes */ 58 | 59 | } 60 | 61 | override def performOperation: IO[WikibaseOperationResult] = { 62 | // Build the results item from the wikibase response, throwing errors 63 | for { 64 | eitherResponse <- super.performRequest[Json]() 65 | result <- eitherResponse match { 66 | case Left(err) => IO.raiseError(new WikibaseServiceException(err)) 67 | case Right(jsonResults) => 68 | IO { 69 | WikibaseOperationResult( 70 | operationData = operationData, 71 | wikibase = targetWikibase, 72 | result = jsonResults 73 | ) 74 | } 75 | } 76 | } yield result 77 | } 78 | 79 | } 80 | 81 | object WikibaseGetOperation { 82 | private val successMessage = "Get entities executed successfully" 83 | 84 | /** Languages in which the results will be returned when none has been specified 85 | */ 86 | private val defaultResultLanguages: List[String] = List("en") 87 | } 88 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/operations/get/WikibasePropTypes.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.operations.get 2 | 3 | /** Enumeration of the different properties of objects that can be requested to 4 | * wikibase's API in get operations. 5 | * 6 | * @see [[https://www.wikidata.org/w/api.php?action=help&modules=wbgetentitiess]] 7 | */ 8 | private[api] object WikibasePropTypes extends Enumeration { 9 | type WikibasePropTypes = String 10 | 11 | val ALIASES = "aliases" 12 | val CLAIMS = "claims" 13 | val DATATYPE = "datatype" 14 | val DESCRIPTIONS = "descriptions" 15 | val INFO = "info" 16 | val LABELS = "labels" 17 | val SITELINKS = "sitelinks" 18 | val SITELINKS_URLS = "sitelinks/urls" 19 | 20 | /** Default list of props request in get operations, mirroring the default used 21 | * by Mediawiki's API 22 | */ 23 | val default: List[WikibasePropTypes] = 24 | List(INFO, SITELINKS, ALIASES, LABELS, DESCRIPTIONS, CLAIMS, DATATYPE) 25 | } 26 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/operations/query/WikibaseQueryOperation.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.operations.query 2 | 3 | import cats.effect.IO 4 | import es.weso.rdfshape.server.api.routes.wikibase.logic.operations.{ 5 | WikibaseOperation, 6 | WikibaseOperationDetails, 7 | WikibaseOperationResult 8 | } 9 | import es.weso.rdfshape.server.utils.error.exceptions.WikibaseServiceException 10 | import io.circe.Json 11 | import org.http4s.circe.jsonDecoder 12 | import org.http4s.client.Client 13 | import org.http4s.headers.Accept 14 | import org.http4s.{Headers, MediaType, Request, Uri} 15 | 16 | /** Common class for wikibase operations based on querying a SPARQL endpoint. 17 | * Given an input [[WikibaseOperationDetails]], perform a query against a 18 | * wikibase instance. 19 | * 20 | * @param operationData Data needed to perform the wikibase operation 21 | * @param client [[Client]] object to be used in requests to wikibase 22 | * @note All derived operations are based on [[https://www.wikidata.org/w/api.php?action=help&modules=wbgetentities]] 23 | */ 24 | private[wikibase] case class WikibaseQueryOperation( 25 | override val operationData: WikibaseOperationDetails, 26 | override val client: Client[IO] 27 | ) extends WikibaseOperation( 28 | WikibaseQueryOperation.successMessage, 29 | operationData, 30 | client 31 | ) { 32 | 33 | /** Target URL in the targeted wikibase instance. Already prepared with the 34 | * endpoint and query. 35 | */ 36 | override lazy val targetUri: Uri = { 37 | targetWikibase.queryUrl 38 | .withQueryParam("query", operationData.payload) 39 | } 40 | 41 | /** Request for this operation. 42 | * Include the "Accept" header to get JSON responses 43 | */ 44 | override def request: Request[IO] = 45 | super.request.withHeaders(Headers(Accept(MediaType.application.`json`))) 46 | 47 | override def performOperation: IO[WikibaseOperationResult] = { 48 | // Build the results item from the wikibase response, throwing errors 49 | for { 50 | eitherResponse <- super.performRequest[Json]() 51 | result <- eitherResponse match { 52 | case Left(err) => IO.raiseError(new WikibaseServiceException(err)) 53 | case Right(jsonResults) => 54 | IO { 55 | WikibaseOperationResult( 56 | operationData = operationData, 57 | wikibase = targetWikibase, 58 | result = jsonResults 59 | ) 60 | } 61 | } 62 | } yield result 63 | } 64 | 65 | } 66 | 67 | object WikibaseQueryOperation { 68 | private val successMessage = "Query executed successfully" 69 | } 70 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/operations/schema/WikibaseSchemaContent.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.operations.schema 2 | 3 | import cats.effect.IO 4 | import es.weso.rdfshape.server.api.routes.wikibase.logic.operations.{ 5 | WikibaseOperation, 6 | WikibaseOperationDetails, 7 | WikibaseOperationResult 8 | } 9 | import es.weso.rdfshape.server.utils.error.exceptions.WikibaseServiceException 10 | import io.circe.syntax.EncoderOps 11 | import org.http4s.Uri 12 | import org.http4s.client.Client 13 | 14 | /** Given an input [[WikibaseOperationDetails]], search for schemas in a 15 | * wikibase instance 16 | * 17 | * @param operationData Data needed to perform the wikibase operation 18 | * @param client [[Client]] object to be used in requests to wikibase 19 | */ 20 | private[wikibase] case class WikibaseSchemaContent( 21 | override val operationData: WikibaseOperationDetails, 22 | override val client: Client[IO] 23 | ) extends WikibaseOperation( 24 | WikibaseSchemaContent.successMessage, 25 | operationData, 26 | client 27 | ) { 28 | 29 | /** Target URL in the targeted wikibase instance 30 | */ 31 | override lazy val targetUri: Uri = { 32 | targetWikibase.baseUrl / 33 | "wiki" / 34 | "Special:EntitySchemaText" / 35 | operationData.payload 36 | } 37 | 38 | override def performOperation: IO[WikibaseOperationResult] = { 39 | // Build the results item from the wikibase response, throwing errors 40 | for { 41 | eitherResponse <- super 42 | .performRequest[String]() 43 | result <- eitherResponse match { 44 | case Left(err) => IO.raiseError(new WikibaseServiceException(err)) 45 | case Right(jsonResults) => 46 | IO { 47 | WikibaseOperationResult( 48 | operationData = operationData, 49 | wikibase = targetWikibase, 50 | result = jsonResults.asJson 51 | ) 52 | } 53 | } 54 | } yield result 55 | } 56 | 57 | } 58 | 59 | object WikibaseSchemaContent { 60 | private val successMessage = "Schema contents fetched successfully" 61 | } 62 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/operations/search/WikibaseSearchEntity.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.operations.search 2 | 3 | import cats.effect.IO 4 | import es.weso.rdfshape.server.api.routes.wikibase.logic.operations.WikibaseOperationDetails 5 | import org.http4s.client.Client 6 | 7 | /** A [[WikibaseSearchOperation]] searching for entities in a wikibase instance. 8 | * Resorts to [[https://www.wikidata.org/w/api.php?action=help&modules=wbsearchentities]] 9 | * 10 | * @param operationData Data needed to perform the wikibase operation 11 | * @param client [[Client]] object to be used in requests to wikibase 12 | */ 13 | sealed case class WikibaseSearchEntity private ( 14 | override val operationData: WikibaseOperationDetails, 15 | override val client: Client[IO] 16 | ) extends WikibaseSearchOperation( 17 | operationData, 18 | client, 19 | WikibaseSearchTypes.ENTITY 20 | ) 21 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/operations/search/WikibaseSearchLexeme.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.operations.search 2 | 3 | import cats.effect.IO 4 | import es.weso.rdfshape.server.api.routes.wikibase.logic.operations.WikibaseOperationDetails 5 | import org.http4s.client.Client 6 | 7 | /** A [[WikibaseSearchOperation]] searching for lexemes in a wikibase instance. 8 | * Resorts to [[https://www.wikidata.org/w/api.php?action=help&modules=wbsearchentities]] 9 | * 10 | * @param operationData Data needed to perform the wikibase operation 11 | * @param client [[Client]] object to be used in requests to wikibase 12 | */ 13 | sealed case class WikibaseSearchLexeme private ( 14 | override val operationData: WikibaseOperationDetails, 15 | override val client: Client[IO] 16 | ) extends WikibaseSearchOperation( 17 | operationData, 18 | client, 19 | WikibaseSearchTypes.LEXEME 20 | ) 21 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/operations/search/WikibaseSearchProperty.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.operations.search 2 | 3 | import cats.effect.IO 4 | import es.weso.rdfshape.server.api.routes.wikibase.logic.operations.WikibaseOperationDetails 5 | import org.http4s.client.Client 6 | 7 | /** A [[WikibaseSearchOperation]] searching for properties in a wikibase instance. 8 | * Resorts to [[https://www.wikidata.org/w/api.php?action=help&modules=wbsearchentities]] 9 | * 10 | * @param operationData Data needed to perform the wikibase operation 11 | * @param client [[Client]] object to be used in requests to wikibase 12 | */ 13 | sealed case class WikibaseSearchProperty private ( 14 | override val operationData: WikibaseOperationDetails, 15 | override val client: Client[IO] 16 | ) extends WikibaseSearchOperation( 17 | operationData, 18 | client, 19 | WikibaseSearchTypes.PROPERTY 20 | ) 21 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/logic/operations/search/WikibaseSearchTypes.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.logic.operations.search 2 | 3 | import es.weso.rdfshape.server.utils.other.MyEnum 4 | 5 | /** Enumeration of the different types of objects that can be requested to 6 | * wikibase's API in search operations. 7 | * 8 | * @note "schema" is not a standard value for wbsearchentities, but a custom 9 | * value we use in some server operations that fetch schemas 10 | * @see [[https://www.wikidata.org/w/api.php?action=help&modules=wbsearchentities]] 11 | */ 12 | private[api] object WikibaseSearchTypes extends MyEnum[String] { 13 | type WikibaseSearchTypes = String 14 | 15 | val ENTITY = "item" 16 | val PROPERTY = "property" 17 | val LEXEME = "lexeme" 18 | val FORM = "form" 19 | val SENSE = "sense" 20 | val SCHEMA = "schema" 21 | 22 | val values: Set[WikibaseSearchTypes] = 23 | Set(ENTITY, PROPERTY, LEXEME, FORM, SENSE) 24 | val basicValues: Set[WikibaseSearchTypes] = 25 | Set(ENTITY, PROPERTY, LEXEME, SCHEMA) 26 | val default: WikibaseSearchTypes = ENTITY 27 | } 28 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/service/operations/WikibaseOperationInput.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.service.operations 2 | 3 | import es.weso.rdfshape.server.api.ServiceRouteOperation 4 | import es.weso.rdfshape.server.api.routes.wikibase.logic.operations.WikibaseOperationDetails 5 | import es.weso.rdfshape.server.utils.other.mapEitherToDecodeResult 6 | import io.circe.{Decoder, HCursor} 7 | 8 | /** Data class representing the inputs required when asking the server 9 | * for most wikibase operations 10 | * 11 | * @param operationDetails Information required to perform the operation 12 | * on the Wikibase 13 | */ 14 | case class WikibaseOperationInput(operationDetails: WikibaseOperationDetails) 15 | 16 | object WikibaseOperationInput 17 | extends ServiceRouteOperation[WikibaseOperationInput] { 18 | override implicit val decoder: Decoder[WikibaseOperationInput] = 19 | (cursor: HCursor) => { 20 | val decodeResult = for { 21 | maybeOperationDetails <- cursor 22 | .as[Either[String, WikibaseOperationDetails]] 23 | 24 | } yield maybeOperationDetails.map(WikibaseOperationInput(_)) 25 | 26 | mapEitherToDecodeResult(decodeResult) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/routes/wikibase/service/operations/WikibaseValidateInput.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.routes.wikibase.service.operations 2 | 3 | import es.weso.rdfshape.server.api.ServiceRouteOperation 4 | import es.weso.rdfshape.server.api.routes.schema.logic.types.Schema 5 | import es.weso.rdfshape.server.api.routes.wikibase.logic.operations.WikibaseOperationDetails 6 | import es.weso.rdfshape.server.api.utils.parameters.IncomingRequestParameters.SchemaParameter 7 | import es.weso.rdfshape.server.utils.other.mapEitherToDecodeResult 8 | import io.circe.{Decoder, HCursor} 9 | 10 | /** Data class representing the inputs required when asking the server 11 | * to validate Wikibase data 12 | * 13 | * @param operationDetails Information required to perform the operation 14 | * on the Wikibase 15 | * @param schema Schema used for validation 16 | */ 17 | case class WikibaseValidateInput( 18 | operationDetails: WikibaseOperationDetails, 19 | schema: Schema 20 | ) 21 | 22 | object WikibaseValidateInput 23 | extends ServiceRouteOperation[WikibaseValidateInput] { 24 | 25 | override implicit val decoder: Decoder[WikibaseValidateInput] = 26 | (cursor: HCursor) => { 27 | val decodeResult = for { 28 | maybeOperationDetails <- cursor 29 | .as[Either[String, WikibaseOperationDetails]] 30 | 31 | maybeSchema <- cursor 32 | .downField(SchemaParameter.name) 33 | .as[Either[String, Schema]] 34 | 35 | maybeItems = for { 36 | details <- maybeOperationDetails 37 | schema <- maybeSchema 38 | } yield (details, schema) 39 | 40 | } yield maybeItems.map { case (opDetails, schema) => 41 | WikibaseValidateInput(opDetails, schema) 42 | } 43 | 44 | mapEitherToDecodeResult(decodeResult) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/utils/Http4sUtils.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.utils 2 | 3 | import cats._ 4 | import cats.effect._ 5 | import cats.implicits._ 6 | import fs2.Stream 7 | import org.http4s.client.Client 8 | import org.http4s.client.middleware.{FollowRedirect, Logger} 9 | import org.http4s.{Method, Request, Response, Uri} 10 | 11 | /** Static utility methods to help work with http4s 12 | */ 13 | object Http4sUtils { 14 | 15 | /** Create a full-fledged http4s client from a base client object 16 | * 17 | * @param client Base http4s client 18 | * @tparam F Type of the data managed by the client 19 | * @return The client passed to the function with additional functionalities (follow redirects and logging) 20 | */ 21 | def mkClient[F[_]: Concurrent: Async](client: Client[F]): Client[F] = 22 | withRedirect(withLogging(client)) 23 | 24 | /** Create a redirecting http4s client from a base client object 25 | * 26 | * @param client Base http4s client 27 | * @param maxRedirects Maximum number of redirects the client will follow 28 | * @tparam F Type of the data managed by the client 29 | * @return The client passed to the function with additional functionalities (follow redirects) 30 | */ 31 | def withRedirect[F[_]: Concurrent]( 32 | client: Client[F], 33 | maxRedirects: Int = 10 34 | ): Client[F] = 35 | FollowRedirect(maxRedirects, _ => true)(client) 36 | 37 | /** Create a logging http4s client from a base client object 38 | * 39 | * @param client Base http4s client 40 | * @tparam F Type of the data managed by the client 41 | * @return The client passed to the function with additional functionalities (logging data) 42 | */ 43 | def withLogging[F[_]: Concurrent: Async](client: Client[F]): Client[F] = 44 | Logger(logHeaders = true, logBody = true, _ => false)(client) 45 | 46 | /** Given a URI and an http4s client, fetch the URI contents 47 | * 48 | * @param uri URI with the resource to be resolved 49 | * @param client Http4s client object that will fetch the resource 50 | * @tparam F Type of the data managed by the client 51 | * @return Either the body of the resource in the URI as a data Stream (using FS2) or an error message 52 | */ 53 | def resolveStream[F[_]: Monad: Concurrent]( 54 | uri: Uri, 55 | client: Client[F] 56 | ): F[Either[String, Stream[F, String]]] = { 57 | val req = Request[F](Method.GET, uri) 58 | client.toHttpApp(req).flatMap(resp => getBody(uri, resp)) 59 | } 60 | 61 | /** Given a client response, extract the response body from it 62 | * 63 | * @param uri URI 64 | * @param response Response object 65 | * @tparam F Type of the data contained in the response 66 | * @return Either the client's response body as plain text or an error message 67 | */ 68 | private def getBody[F[_]: Monad: Concurrent]( 69 | uri: Uri, 70 | response: Response[F] 71 | ): F[Either[String, Stream[F, String]]] = { 72 | if(response.status.isSuccess) response.bodyText.asRight.pure[F] 73 | else s"Status error fetching $uri: ${response.status}".asLeft.pure[F] 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/utils/OptEitherF.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.utils 2 | 3 | import cats._ 4 | import cats.data._ 5 | import cats.effect._ 6 | import cats.implicits._ 7 | 8 | /** Static utility methods to help work with Optional, Either or IO types 9 | */ 10 | object OptEitherF { 11 | 12 | /** Given an input optional value (type A) and a conversor function (A => Either(String, B)), 13 | * attempt to convert the data and return an optional value (type B) 14 | * 15 | * @param maybe Input data, optional value 16 | * @param function Conversion function from the input type to the output type 17 | * @tparam A Encapsulated type of the input data 18 | * @tparam B Encapsulated type of the output data 19 | * @return Optional value with the conversion result 20 | */ 21 | def optEither2f[A, B]( 22 | maybe: Option[A], 23 | function: A => Either[String, B] 24 | ): IO[Option[B]] = maybe match { 25 | case None => IO.pure(None) 26 | case Some(value) => 27 | ApplicativeError[IO, Throwable].fromEither( 28 | // "FUNCTION" returns an either from the value in the option. 29 | function(value) 30 | .map(Some(_)) // If Either is a right, it is mapped to an Option type 31 | .leftMap(e => 32 | new RuntimeException(s"Error: $e") 33 | ) // If Left, an exception in thrown 34 | ) 35 | } 36 | 37 | /** Given an input optional value (type A) and a conversor function (A => Either(String, B)), 38 | * attempt to convert the data and return an either value (type B) 39 | * 40 | * @param maybe Input data, optional value 41 | * @param function Conversion function from the input type to the output type 42 | * @tparam A Encapsulated type of the input data 43 | * @tparam B Encapsulated type of the output data 44 | * @return Either value with the conversion result/error 45 | */ 46 | def optEither2es[A, B]( 47 | maybe: Option[A], 48 | function: A => Either[String, B] 49 | ): EitherT[IO, String, Option[B]] = maybe match { 50 | case None => EitherT.pure(None) 51 | case Some(value) => EitherT.fromEither(function(value).map(Some(_))) 52 | } 53 | 54 | /** Given an Either, try to obtain an IO wrapping its value 55 | * @param either Input Either 56 | * @tparam A Type of value contained in the either and result 57 | * @return IO wrapping the Either value if right, an IO error if left 58 | */ 59 | def ioFromEither[A](either: Either[String, A]): IO[A] = { 60 | either.fold(err => IO.raiseError(new RuntimeException(err)), IO.pure) 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/api/utils/parameters/PartsMap.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api.utils.parameters 2 | 3 | import cats.effect.IO 4 | import cats.implicits._ 5 | import com.typesafe.scalalogging.LazyLogging 6 | import es.weso.rdfshape.server.api.format.Format 7 | import fs2.text.utf8.decode 8 | import org.http4s.multipart.Part 9 | 10 | /** Data class containing a map of a request's parameters with the form (param name: param content) 11 | * The data contained in a request parameter is handled via the {@link Part} class of HTT4s and extracted with the class methods 12 | * 13 | * @param map Map with the request's parameters information 14 | */ 15 | case class PartsMap private (map: Map[String, Part[IO]]) { 16 | 17 | /** Shorthand for extracting boolean values from a request parameter 18 | * 19 | * @param key Parameter key 20 | * @return Optionally, the boolean translation of the contents of the parameter 21 | */ 22 | def optPartValueBoolean(key: String): IO[Option[Boolean]] = for { 23 | maybeValue <- optPartValue(key) 24 | } yield maybeValue match { 25 | case Some("true") => Some(true) 26 | case Some("false") => Some(false) 27 | case _ => None 28 | 29 | } 30 | 31 | /** Extract the value from a request parameter, decoding it and handling errors 32 | * 33 | * @param key Parameter key 34 | * @param alt Alternative value to be returned when parameter value 35 | * is missing 36 | * @return Optionally, the String contents of the parameter 37 | */ 38 | def optPartValue(key: String, alt: Option[String] = None): IO[Option[String]] = 39 | map.get(key) match { 40 | case Some(part) => 41 | part.body.through(decode).compile.foldMonoid.map(Some.apply) 42 | case None => IO.pure(None) 43 | } 44 | 45 | /** Shorthand for extracting values from a request parameter with an informational error message 46 | * 47 | * @param key Parameter key 48 | * @return Either the String contents of the parameter or an error message 49 | */ 50 | def eitherPartValue(key: String): IO[Either[String, String]] = for { 51 | maybeValue <- optPartValue(key) 52 | } yield maybeValue match { 53 | case None => 54 | Left( 55 | s"Not found value for key $key\nKeys available: ${map.keySet.mkString(",")}" 56 | ) 57 | case Some(s) => Right(s) 58 | } 59 | } 60 | 61 | object PartsMap extends LazyLogging{ 62 | 63 | /** Instantiate a new {@link PartsMap} given a list of the inner parts 64 | * 65 | * @param ps List of parts 66 | * @return A new Parts map containing mapping each part's name to its contents 67 | */ 68 | def apply(ps: Vector[Part[IO]]): PartsMap = { 69 | PartsMap(ps.filter(_.name.isDefined).map(p => (p.name.get, p)).toMap) 70 | } 71 | 72 | /** Try to build a Format object from a request's parameters 73 | * 74 | * @param parameter Name of the parameter with the format name 75 | * @param parameterMap Request parameters 76 | * @return Optionally, a new generic Format instance with the format 77 | */ 78 | def getFormat( 79 | parameter: String, 80 | parameterMap: PartsMap 81 | ): IO[Option[Format]] = { 82 | for { 83 | maybeFormatName <- parameterMap.optPartValue(parameter) 84 | } yield maybeFormatName match { 85 | case None => 86 | logger.info(s"No valid format found for parameter \"$parameter\"") 87 | None 88 | case Some(formatNameParsed) => 89 | logger.info(s"Format value \"$formatNameParsed\" found in parameter \"$parameter\"") 90 | Format.fromString(formatNameParsed).toOption 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/html2rdf/RdfSourceTypes.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.html2rdf 2 | 3 | /** Enum listing the accepted sources from which RDF may be extracted. 4 | */ 5 | case object RdfSourceTypes extends Enumeration { 6 | val STRING: RdfSourceTypes.Value = Value("String") 7 | val URI: RdfSourceTypes.Value = Value("URI") 8 | } 9 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/implicits/codecs/package.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.implicits 2 | 3 | import cats.implicits.toBifunctorOps 4 | import es.weso.rdf.nodes.IRI 5 | import io.circe._ 6 | import org.http4s.Uri 7 | 8 | import java.net.URL 9 | import scala.util.Try 10 | 11 | /** Additional codecs used throughout the application 12 | */ 13 | package object codecs { 14 | 15 | /** JSON encoder for the [[Uri]] class used in http4s 16 | */ 17 | implicit val encodeUri: Encoder[Uri] = (uri: Uri) => 18 | Json.fromString(uri.renderString) 19 | 20 | /** JSON decoder for [[URL]]. Returns an option since the URL may be malformed 21 | * and thus None is decoded 22 | */ 23 | implicit val decodeUrl: Decoder[Either[String, URL]] = (cursor: HCursor) => 24 | for { 25 | urlStr <- cursor.value.as[String] 26 | } yield Try { 27 | new URL(urlStr) 28 | }.toEither.leftMap(_.getMessage) 29 | 30 | /** JSON decoder for [[URL]]. Returns an option since the URL may be malformed 31 | * and thus None is decoded 32 | */ 33 | implicit val decodeUri: Decoder[Either[String, Uri]] = (cursor: HCursor) => { 34 | for { 35 | urlStr <- cursor.value.as[String] 36 | maybeUri = Uri 37 | .fromString(urlStr) 38 | .leftMap(_.getMessage()) 39 | } yield maybeUri 40 | } 41 | 42 | /** JSON decoder for [[IRI]]. Returns an option since the URL may be malformed 43 | * and thus None is decoded 44 | */ 45 | implicit val decodeIri: Decoder[Either[String, IRI]] = (cursor: HCursor) => { 46 | for { 47 | urlStr <- cursor.value.as[String] 48 | } yield IRI.fromString(urlStr) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/implicits/query_parsers/package.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.implicits 2 | 3 | import cats.effect.IO 4 | import cats.effect.unsafe.implicits.global 5 | import es.weso.rdfshape.server.utils.json.JsonUtils.errorResponseJson 6 | import es.weso.schema.{Schemas, Schema => SchemaW} 7 | import org.http4s.Status._ 8 | import org.http4s.rho.bits.QueryParser.Params 9 | import org.http4s.rho.bits._ 10 | 11 | import java.net.{MalformedURLException, URL} 12 | import scala.util.{Failure, Success, Try} 13 | 14 | /** Custom query parsers used by Rho when parsing query parameters to complex data-types 15 | * @see https://github.com/http4s/rho/blob/main/core/src/main/scala/org/http4s/rho/bits/QueryParser.scala 16 | */ 17 | package object query_parsers { 18 | 19 | /** Parse the given URL parameter to an instance of [[java.net.URL]] 20 | * or return an error response 21 | */ 22 | implicit val urlQueryParser: QueryParser[IO, URL] = 23 | (name: String, params: Params, _: Option[URL]) => 24 | params.get(name) match { 25 | case Some(Seq(value, _*)) if !value.isBlank => 26 | Try { 27 | new URL(value) 28 | } match { 29 | case Success(urlObj) => SuccessResponse(urlObj) 30 | case Failure(exception) => 31 | exception match { 32 | case _: MalformedURLException => 33 | FailureResponse.pure( 34 | errorResponseJson( 35 | s"Invalid URL provided: $value", 36 | BadRequest 37 | ) 38 | ) 39 | case _ => 40 | FailureResponse.pure( 41 | errorResponseJson( 42 | s"Failed to parse the URL sent in the request: $value", 43 | InternalServerError 44 | ) 45 | ) 46 | } 47 | } 48 | case _ => 49 | FailureResponse.pure( 50 | errorResponseJson(s"Missing query param: $name", BadRequest) 51 | ) 52 | } 53 | 54 | /** Parse the given URL parameter to an instance of [[SchemaW]] 55 | * or return an error response 56 | */ 57 | implicit val schemaEngineQueryParser: QueryParser[IO, SchemaW] = 58 | (name: String, params: Params, _: Option[SchemaW]) => 59 | params.get(name) match { 60 | case Some(Seq(value, _*)) if !value.isBlank => 61 | Try { 62 | Schemas.lookupSchema(value).unsafeRunSync() 63 | } match { 64 | case Failure(exception) => 65 | FailureResponse.pure( 66 | errorResponseJson( 67 | exception.getMessage, 68 | InternalServerError 69 | ) 70 | ) 71 | case Success(schema) => SuccessResponse(schema) 72 | } 73 | 74 | case _ => 75 | FailureResponse.pure( 76 | errorResponseJson(s"Missing query param: $name", BadRequest) 77 | ) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/implicits/string_parsers/package.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.implicits 2 | 3 | import cats.Monad 4 | import cats.effect.IO 5 | import cats.effect.unsafe.implicits.global 6 | import es.weso.rdfshape.server.implicits.string_parsers.parsers.SchemaEngineParser 7 | import es.weso.schema.{Schemas, Schema => SchemaW} 8 | import org.http4s.rho.bits._ 9 | 10 | import scala.reflect.runtime.universe 11 | import scala.util.{Failure, Success, Try} 12 | 13 | /** Custom string parsers used by Rho when parsing path parameters to complex data-types 14 | * @see https://github.com/http4s/rho/blob/main/core/src/main/scala/org/http4s/rho/bits/StringParser.scala 15 | */ 16 | package object string_parsers { 17 | 18 | case object parsers { 19 | 20 | /** Parser capable of extracting a [[SchemaW]] from a URL path 21 | * @tparam F Monad type used 22 | */ 23 | class SchemaEngineParser[F[_]] extends StringParser[F, SchemaW] { 24 | override def parse(s: String)(implicit 25 | F: Monad[F] 26 | ): ResultResponse[F, SchemaW] = Try { 27 | Schemas.lookupSchema(s).unsafeRunSync() 28 | } match { 29 | case Failure(exception) => 30 | FailureResponse.pure[F](BadRequest.pure(exception.getMessage)) 31 | case Success(schema) => SuccessResponse(schema) 32 | } 33 | 34 | // Use String type tag 35 | override def typeTag: Option[universe.TypeTag[SchemaW]] = Some( 36 | implicitly[universe.TypeTag[SchemaW]] 37 | ) 38 | } 39 | } 40 | 41 | case object instances { 42 | implicit val schemaEngineParser: SchemaEngineParser[IO] = 43 | new SchemaEngineParser[IO] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/utils/error/ExitCodes.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.utils.error 2 | 3 | /** Enum classifying the accepted exit codes by their Int code. 4 | */ 5 | case object ExitCodes { 6 | type ExitCodes = Int 7 | 8 | /** Exit code on successful program execution */ 9 | val SUCCESS = 0 10 | // CLOSING SIGNAL 11 | /** Exit code on received signal: SIGINT, SIGTERM 12 | */ 13 | val SIGNAL_RECEIVED = 8 14 | // CLI ERRORS 15 | /** Exit code on CLI argument parsing error 16 | */ 17 | val ARGUMENTS_PARSE_ERROR = 101 18 | 19 | /** Exit code on invalid CLI arguments 20 | */ 21 | val ARGUMENTS_INVALID_ERROR = 102 22 | 23 | // Server startup errors 24 | /** Exit code on runtime error when trying to build and SSL Context for the app 25 | * 26 | * @see {@link es.weso.rdfshape.server.utils.secure.SSLHelper} 27 | */ 28 | val SSL_CONTEXT_CREATION_ERROR = 201 29 | } 30 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/utils/error/SysUtils.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.utils.error 2 | 3 | import com.typesafe.scalalogging.LazyLogging 4 | 5 | /** Static utility methods user for system-wide error operations, i.e., forcibly exiting the system and keeping track of 6 | * all custom error codes 7 | */ 8 | object SysUtils extends LazyLogging { 9 | 10 | /** Terminates the program with a given error code after logging a given error message 11 | * 12 | * @param code Exit code of the program 13 | * @param message Message to be printed before exiting 14 | */ 15 | def fatalError(code: Int, message: String): Unit = { 16 | logger.error(message) 17 | sys.exit(code) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/utils/error/exceptions/JsonConversionException.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.utils.error.exceptions 2 | 3 | import scala.util.control.NoStackTrace 4 | 5 | /** Custom exception thrown when a failure occurs while converting JSON data 6 | * 7 | * @param message Reason/explanation of why the exception occurred 8 | */ 9 | final case class JsonConversionException( 10 | private val message: String 11 | ) extends RuntimeException(message) 12 | with NoStackTrace 13 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/utils/error/exceptions/SSLContextCreationException.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.utils.error.exceptions 2 | 3 | /** Custom exception thrown when a failure occurs while trying to create an SSL Context from user's environment data 4 | * 5 | * @param message Reason/explanation of why the exception occurred 6 | * @param cause Nested exception that caused the SSL Context creation to fail 7 | */ 8 | final case class SSLContextCreationException( 9 | private val message: String, 10 | private val cause: Throwable 11 | ) extends Exception(message, cause) 12 | 13 | object SSLContextCreationException { 14 | 15 | /** Fixed message preceding the exception message 16 | */ 17 | private val prefix = 18 | "Could not create an SSL context with the specified configuration: " 19 | 20 | /** Factory method used for instantiating {@linkplain es.weso.rdfshape.server.utils.error.exceptions.SSLContextCreationException} 21 | * 22 | * @param message Message of the new exception 23 | * @param cause Cause of the new exception 24 | * @return A new {@linkplain es.weso.rdfshape.server.utils.error.exceptions.SSLContextCreationException SSLContextCreationException} with the given data. 25 | */ 26 | def apply( 27 | message: String, 28 | cause: Throwable = None.orNull 29 | ): SSLContextCreationException = { 30 | cause match { 31 | case cause: SSLContextCreationException => cause 32 | case _ => new SSLContextCreationException(s"$prefix$message", cause) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/utils/error/exceptions/UnexpectedWebSocketFrameException.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.utils.error.exceptions 2 | 3 | /** Custom exception thrown when a failure occurs because certain information 4 | * was expected via WebSockets but other arrived instead 5 | * 6 | * @param message Reason/explanation of why the exception occurred 7 | */ 8 | final case class UnexpectedWebSocketFrameException( 9 | private val message: String 10 | ) extends RuntimeException(message) 11 | 12 | object UnexpectedWebSocketFrameException { 13 | 14 | /** Factory of [[UnexpectedWebSocketFrameException]] 15 | * 16 | * @param received Optionally, the received frame that caused the exception 17 | * @param expected Optionally, the expected class of the received frame 18 | * @return A new [[UnexpectedWebSocketFrameException]] with a customized 19 | * error message 20 | */ 21 | def apply[A, B]( 22 | received: Option[Class[A]], 23 | expected: Option[Class[B]] 24 | ): UnexpectedWebSocketFrameException = { 25 | // Format the final messages to adapt to the cases where options are empty 26 | val formattedList = List(received, expected).map( 27 | _.map(_.getSimpleName).getOrElse("unspecified") 28 | ) 29 | val (expectedText, receivedText) = (formattedList.head, formattedList(1)) 30 | 31 | new UnexpectedWebSocketFrameException( 32 | s"Expected a WebSocket frame '$expectedText' but got '$receivedText'" 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/utils/error/exceptions/WikibaseServiceException.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.utils.error.exceptions 2 | 3 | import scala.util.control.NoStackTrace 4 | 5 | /** Custom exception thrown when a failure occurs while operating on Wikibase data 6 | * 7 | * @param message Reason/explanation of why the exception occurred 8 | */ 9 | final class WikibaseServiceException( 10 | private val message: String 11 | ) extends RuntimeException(message) 12 | with NoStackTrace { 13 | 14 | def this(throwable: Throwable) = { 15 | this(throwable.getMessage) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/utils/networking/NetworkingUtils.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.utils.networking 2 | 3 | import cats.effect.IO 4 | import cats.effect.kernel.Resource 5 | import cats.effect.unsafe.implicits.global 6 | import com.typesafe.scalalogging.LazyLogging 7 | import es.weso.rdfshape.server.Server 8 | import org.http4s.Uri 9 | import org.http4s.blaze.client.BlazeClientBuilder 10 | import org.http4s.client.Client 11 | import org.http4s.client.middleware.FollowRedirect 12 | 13 | import java.net.URL 14 | import scala.concurrent.duration.DurationInt 15 | import scala.util.{Failure, Success, Try} 16 | 17 | object NetworkingUtils extends LazyLogging { 18 | 19 | /** Http4s functional client used to fetch data contained in URLs 20 | * It has rich functionality and its tuned to follow redirects 21 | */ 22 | val httpClient: Resource[IO, Client[IO]] = 23 | BlazeClientBuilder[IO] 24 | .withRequestTimeout(Server.defaultRequestTimeout.minute) 25 | .withIdleTimeout(Server.defaultIdleTimeout.minute) 26 | .resource 27 | .map(FollowRedirect(3)(_)) 28 | 29 | /** Error-safe way of obtaining the raw contents in a given URL 30 | * 31 | * @param urlString URL to be fetched (String representation) 32 | * @return Either the contents if the URL or an error message 33 | */ 34 | def getUrlContents(urlString: String): Either[String, String] = { 35 | Try { 36 | val url = new URL(urlString) 37 | getUrlContents(url) 38 | } match { 39 | case Failure(exception) => Left(exception.getMessage) 40 | case Success(value) => value 41 | } 42 | } 43 | 44 | def getUrlContents(url: URL): Either[String, String] = { 45 | val strResponse = 46 | httpClient 47 | .use(client => 48 | // We use unsafe because we trust the java URL class syntax 49 | client.expect[String](Uri.unsafeFromString(url.toString)) 50 | ) 51 | Try { 52 | strResponse.unsafeRunSync() 53 | } match { 54 | case Success(urlContent) => Right(urlContent) 55 | case Failure(exception) => 56 | val msg = 57 | s"Could not obtain data from $url." 58 | logger.warn(s"$msg - ${exception.getMessage}") 59 | Left(msg) 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/utils/numeric/NumericUtils.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.utils.numeric 2 | 3 | import scala.util.{Random, Try} 4 | 5 | /** Utilities related to math 6 | */ 7 | case object NumericUtils { 8 | 9 | /** Random generator to help creating random amounts 10 | */ 11 | private lazy val random = Random 12 | 13 | /** Try to parse an integer 14 | * 15 | * @param str Text chain to be parsed 16 | * @return The resulting integer if the string was parsed, an error message otherwise 17 | */ 18 | def parseInt(str: String): Either[String, Int] = 19 | Try(str.toInt).map(Right(_)).getOrElse(Left(s"$str is not a number")) 20 | 21 | /** Generate a random integer in a given range 22 | * 23 | * @param min Lower limit (inclusive) 24 | * @param max Upper limit (exclusive) 25 | * @return A random Int in range [min-max) 26 | */ 27 | def getRandomInt(min: Int = 0, max: Int = 10): Int = { 28 | random.between(min, max) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/utils/other/MyEnum.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.utils.other 2 | 3 | /** Enumeration with support for free properties and values 4 | * 5 | * @tparam T Type contained in the enum 6 | */ 7 | trait MyEnum[T] { 8 | 9 | /** Set of values in the enum 10 | */ 11 | val values: Set[T] 12 | 13 | /** Default value to be used 14 | */ 15 | val default: T 16 | } 17 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/utils/other/package.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.utils 2 | 3 | import cats.implicits.catsSyntaxEitherId 4 | import io.circe._ 5 | 6 | package object other { 7 | 8 | /** Given a decoding operation whose result may contain an error, map the 9 | * erroring result to a [[DecodingFailure]] containing it as message 10 | * If a native decoding failure occurred, leave it as is, else if an error 11 | * occurred return it as a decoding failure , else return the value 12 | * 13 | * @param input Either resulting of a Circe decode operation 14 | * @tparam L Left type of input either 15 | * @tparam R Right type of input Either 16 | * @return A [[Decoder.Result]] containing a Decoding failure or an item of type [[R]] 17 | * @note This method fulfills a specific goal for this app, mainly 18 | */ 19 | def mapEitherToDecodeResult[L, R]( 20 | input: Either[DecodingFailure, Either[L, R]] 21 | ): Either[DecodingFailure, R] = { 22 | input.flatMap { 23 | case Left(err) => DecodingFailure(err.toString, Nil).asLeft 24 | case Right(value) => value.asRight 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /modules/server/src/main/scala/es/weso/rdfshape/server/utils/secure/SSLHelper.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.utils.secure 2 | 3 | import es.weso.rdfshape.server.utils.error.exceptions.SSLContextCreationException 4 | 5 | import java.io.{FileInputStream, IOException} 6 | import java.nio.file.Paths 7 | import java.security.{KeyStore, SecureRandom} 8 | import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManagerFactory} 9 | 10 | /** Static utilities for creating SSL Contexts to serve the API via HTTPS. 11 | * Pre-requisites: 12 | * - A valid certificate is expected to be found in a keystore. 13 | * - Some environment variables need to be set beforehand: 14 | * - KEYSTORE_PATH: location of the keystore storing the certificate. 15 | * - KEYSTORE_PASSWORD: password protecting the keystore (leave empty if there is none). 16 | * - KEYMANAGER_PASSWORD: password protecting the certificate (leave empty is there is none). 17 | * 18 | * @note The inner functionality needs to be able read the host's environment and filesystem 19 | * @note Further docs, see https://github.com/weso/rdfshape-api/wiki/Deploying-RDFShape-API-(SBT)#serving-with-https 20 | * @see {@link es.weso.rdfshape.server.Server} 21 | */ 22 | object SSLHelper { 23 | 24 | /** Password protecting the keystore, extracted from the host's environment. 25 | */ 26 | lazy val keyStorePassword: Option[String] = sys.env.get("KEYSTORE_PASSWORD") 27 | 28 | /** Password protecting the certificate, extracted from the host's environment. 29 | */ 30 | lazy val keyManagerPassword: Option[String] = 31 | sys.env.get("KEYMANAGER_PASSWORD") 32 | 33 | /** Location of the keystore storing the certificate, extracted from the host's environment. 34 | */ 35 | lazy val keyStorePath: Option[String] = sys.env.get("KEYSTORE_PATH") 36 | 37 | /** Try to build an SSL Context given that the certificate's location and credentials are in the PATH. 38 | * @throws es.weso.rdfshape.server.utils.error.exceptions.SSLContextCreationException On errors getting the certificate information 39 | * @throws java.io.IOException On errors accessing the filesystem 40 | * @return An SSLContext created from the user's certificate 41 | */ 42 | @throws(classOf[SSLContextCreationException]) 43 | @throws(classOf[IOException]) 44 | def getContext: SSLContext = { 45 | 46 | if( 47 | keyStorePassword.isEmpty || 48 | keyManagerPassword.isEmpty || 49 | keyStorePath.isEmpty 50 | ) { 51 | throw SSLContextCreationException( 52 | "Some environment variables are missing." 53 | ) 54 | } 55 | 56 | val keyStore = loadKeystore(keyStorePassword.get) 57 | val keyManagerFactory = getKeyManager(keyStore, keyStorePassword.get) 58 | val trustManagerFactory = getTrustManager(keyStore) 59 | 60 | val sslContext = SSLContext.getInstance("TLS") 61 | sslContext.init( 62 | keyManagerFactory.getKeyManagers, 63 | trustManagerFactory.getTrustManagers, 64 | new SecureRandom() 65 | ) 66 | sslContext 67 | } 68 | 69 | private def loadKeystore(keyStorePassword: String): KeyStore = { 70 | val in = new FileInputStream( 71 | Paths.get(keyStorePath.get).toAbsolutePath.toString 72 | ) 73 | val keyStore = KeyStore.getInstance(KeyStore.getDefaultType) 74 | keyStore.load(in, keyStorePassword.toCharArray) 75 | keyStore 76 | } 77 | 78 | private def getKeyManager(keyStore: KeyStore, keyStorePassword: String) = { 79 | val keyManagerFactory = 80 | KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm) 81 | keyManagerFactory.init(keyStore, keyStorePassword.toCharArray) 82 | keyManagerFactory 83 | } 84 | 85 | private def getTrustManager(keyStore: KeyStore) = { 86 | val trustManagerFactory = TrustManagerFactory.getInstance( 87 | TrustManagerFactory.getDefaultAlgorithm 88 | ) 89 | trustManagerFactory.init(keyStore) 90 | trustManagerFactory 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /modules/server/src/test/scala/es/weso/rdfshape/server/api/ResultMatcher.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api 2 | 3 | trait ResultMatcher { 4 | // TODO: Add a matcher for Json Results 5 | } 6 | -------------------------------------------------------------------------------- /modules/server/src/test/scala/es/weso/rdfshape/server/api/TestHttp4sTest.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.api 2 | import cats.effect._ 3 | import io.circe.Json 4 | import fs2._ 5 | import munit.CatsEffectSuite 6 | import org.http4s._ 7 | import org.http4s.circe._ 8 | import org.http4s.client.Client 9 | import org.http4s.dsl.Http4sDsl 10 | import org.http4s.ember.client.EmberClientBuilder 11 | import org.http4s.implicits._ 12 | 13 | /** API service for testing purposes 14 | * @param client HTTP4S client object 15 | */ 16 | class TestService(client: Client[IO]) extends Http4sDsl[IO] { 17 | 18 | val routes: HttpRoutes[IO] = HttpRoutes.of[IO] { case GET -> Root / "hi" => 19 | val json = Json.fromString("hello") 20 | Ok(json) 21 | } 22 | } 23 | object TestService { 24 | def apply(client: Client[IO]): TestService = 25 | new TestService(client) 26 | } 27 | 28 | class TestHttp4sTest extends CatsEffectSuite { 29 | 30 | val clientFixture: Fixture[Client[IO]] = ResourceSuiteLocalFixture( 31 | "client", 32 | EmberClientBuilder.default[IO].build 33 | ) 34 | 35 | override def munitFixtures = List(clientFixture) 36 | 37 | def checkRequest( 38 | request: Request[IO], 39 | expectedStatus: Status, 40 | expectedBody: Option[String] 41 | ): IO[Unit] = { 42 | val r: IO[(Status, String)] = for { 43 | client <- IO(clientFixture) 44 | response <- runReq(request, TestService(client.apply()).routes) 45 | body <- parseBody(response.body) 46 | } yield (response.status, body) 47 | r.map(pair => { 48 | val (status, body) = pair 49 | assertEquals(status, expectedStatus) 50 | expectedBody match { 51 | case None => () 52 | case Some(expectedStr) => assertEquals(body, expectedStr) 53 | } 54 | }) 55 | } 56 | 57 | test("Hi 42") { 58 | IO(42).map(n => assertEquals(n, 42)) 59 | } 60 | 61 | def runReq(req: Request[IO], routes: HttpRoutes[IO]): IO[Response[IO]] = 62 | routes.orNotFound(req) 63 | 64 | def parseBody(body: EntityBody[IO]): IO[String] = { 65 | body.through(text.utf8.decode).compile.toList.map(_.mkString) 66 | } 67 | 68 | test("Routes") { 69 | checkRequest(Request[IO]().withUri(uri"/hi"), Status.Ok, Some("\"hello\"")) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /modules/server/src/test/scala/es/weso/rdfshape/server/api/compoundData/CompoundDataTest.pending: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.server.server.compoundData 2 | 3 | import cats.implicits._ 4 | import es.weso.rdfshape.server.server.format._ 5 | import es.weso.rdfshape.server.server.merged.CompoundData._ 6 | import es.weso.rdfshape.server.server.merged.{CompoundData, DataElement, DataTextArea} 7 | import io.circe.parser._ 8 | import io.circe.syntax._ 9 | import munit._ 10 | class CompoundDataTest extends CatsEffectSuite { 11 | 12 | shouldParse( 13 | """|[{"data": "p", 14 | | "dataFormat": "Turtle", 15 | | "activeDataTab": "#dataTextArea" 16 | | } 17 | |]""".stripMargin, 18 | CompoundData(List(DataElement.empty.copy(data = Some("p"), dataFormat = Turtle, activeDataTab = DataTextArea))) 19 | ) 20 | shouldParse( 21 | """|[{"data": "p", 22 | | "dataFormat": "json-ld", 23 | | "activeDataTab": "#dataTextArea" 24 | | } 25 | |]""".stripMargin, 26 | CompoundData(List(DataElement.empty.copy(data = Some("p"), dataFormat = JsonLd, activeDataTab = DataTextArea))) 27 | ) 28 | shouldParse( 29 | """|[{"data": "p", 30 | | "dataFormat": "json-ld" 31 | | } 32 | |]""".stripMargin, 33 | CompoundData(List(DataElement.empty.copy(data = Some("p"), dataFormat = JsonLd, activeDataTab = DataTextArea))) 34 | ) 35 | 36 | // Wrong value in "activeDataTab" should default to "#dataTextArea" 37 | shouldParse("""|[{"data": "p", 38 | | "dataFormat": "Json", 39 | | "activeDataTab": "#asdf" 40 | | } 41 | |]""".stripMargin, 42 | CompoundData(List(DataElement(Some("p"), None, None, None, dataFormat = JsonDataFormat, DataTextArea))) 43 | ) 44 | 45 | shouldNotParse("""|[{"dataBadParam": "p", 46 | | "dataFormat": "Json", 47 | | "activeDataTab": "#asdf" 48 | | } 49 | |]""".stripMargin) 50 | 51 | def shouldParse(str: String, expected: CompoundData): Unit = { 52 | test(s"Should parse\n$str\n and obtain \n${expected.asJson.spaces2}") { 53 | val r: Either[String, CompoundData] = for { 54 | json <- parse(str).leftMap(pe => s"Parser error: ${pe.getMessage()}") 55 | cd <- json.as[CompoundData].leftMap(e => s"Error: ${e.getMessage()}") 56 | } yield cd 57 | 58 | r.fold(e => fail(s"Error: $e"), cd => if (cd == expected) 59 | info(s"Equal to expected") else 60 | fail(s"Parsed \n${cd.asJson.spaces2}\n is different to \n${expected.asJson.spaces2}\n")) 61 | } 62 | } 63 | 64 | def shouldNotParse(str: String): Unit = { 65 | test(s"Should not parse $str") { 66 | val r: Either[String, CompoundData] = for { 67 | json <- parse(str).leftMap(pe => s"Parser error: ${pe.getMessage()}") 68 | cd <- json.as[CompoundData].leftMap(e => s"Error: ${e.getMessage()}") 69 | } yield cd 70 | 71 | r.fold(e => info(s"Error as expected: $e"), cd => fail(s"Parsed as $cd but should have failed")) 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.10.1 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.14.2") 2 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.2.0") 3 | addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") 4 | addSbtPlugin("com.github.sbt" % "sbt-unidoc" % "0.5.0") 5 | addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") 6 | addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.9") 7 | addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1") 8 | addSbtPlugin("org.lyranthe.sbt" % "partial-unification" % "1.1.2") 9 | // addSbtPlugin("com.simplytyped" % "sbt-antlr4" % "0.8.1") 10 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0") 11 | addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.5") 12 | -------------------------------------------------------------------------------- /src/main/scala/es/weso/rdfshape/Main.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape 2 | 3 | import com.typesafe.scalalogging._ 4 | import es.weso.rdfshape.cli.ArgumentsData.unapply 5 | import es.weso.rdfshape.cli.{ArgumentsData, CliManager} 6 | import es.weso.rdfshape.logging.LoggingManager 7 | import es.weso.rdfshape.logging.LoggingManager.systemPropertyVerbosity 8 | import es.weso.rdfshape.server.Server 9 | import es.weso.rdfshape.server.Server.systemPropertyStreamTimeout 10 | 11 | object Main extends App with LazyLogging { 12 | 13 | run(args) 14 | 15 | /** Entrypoint to the application. Parse arguments and start the API server 16 | * @param args Command line arguments entered when launching the application 17 | * @see {@link es.weso.rdfshape.server.Server} 18 | */ 19 | private def run(args: Array[String]): Unit = { 20 | // Parse arguments 21 | val argumentsData = parseArguments(args) 22 | val (port, https, verbosity, silent, streamTimeout) = unapply(argumentsData) 23 | // Set up the logging framework depending on the verbose argument 24 | setUpLogger(verbosity, silent) 25 | // Set up timeout for streaming validations 26 | setUpStreamTimeout(streamTimeout) 27 | // Start the server module 28 | CliManager.showBanner() 29 | Server(port, https) 30 | } 31 | 32 | /** Parse and validate the user-entered arguments 33 | * 34 | * @param args Array of arguments passed to the executable 35 | * @return An {@linkplain es.weso.rdfshape.cli.ArgumentsData inmutable instance} that provides access to the arguments 36 | */ 37 | private def parseArguments(args: Array[String]): ArgumentsData = { 38 | val cliManager = new CliManager(args) 39 | ArgumentsData( 40 | port = cliManager.port.apply(), 41 | https = cliManager.https.apply(), 42 | verbosity = cliManager.verbose.apply(), 43 | silent = cliManager.silent.apply(), 44 | streamTimeout = cliManager.streamTimeout.apply() 45 | ) 46 | } 47 | 48 | /** Let [[LoggingManager]] prepare the logging framework according to the user's CLI arguments and inform the user 49 | * @param verbosity Verbosity level introduced by the user 50 | */ 51 | private def setUpLogger(verbosity: Int, silent: Boolean = false): Unit = { 52 | LoggingManager.setUp(verbosity, silent) 53 | if(!silent) 54 | logger.info( 55 | s"Console logging filter set to ${System.getProperty(systemPropertyVerbosity)}" 56 | ) 57 | } 58 | 59 | /** Set up the system property storing the stream timeout 60 | * @param timeout Timeout in seconds 61 | */ 62 | private def setUpStreamTimeout( 63 | timeout: Int 64 | ): Unit = { 65 | System.setProperty( 66 | systemPropertyStreamTimeout, 67 | timeout.toString 68 | ) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/scala/es/weso/rdfshape/cli/ArgumentsData.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.cli 2 | 3 | /** Data class encapsulating the values read from the arguments passed to the application 4 | * @param port Port number read from CLI 5 | * @param https HTTPS value read from CLI (true or false) 6 | * @param verbosity Verbosity level read from CLI 7 | */ 8 | sealed case class ArgumentsData( 9 | port: Int, 10 | https: Boolean, 11 | verbosity: Int, 12 | silent: Boolean, 13 | streamTimeout: Int 14 | ) 15 | 16 | object ArgumentsData { 17 | def unapply( 18 | argumentsData: ArgumentsData 19 | ): (Int, Boolean, Int, Boolean, Int) = { 20 | ( 21 | argumentsData.port, 22 | argumentsData.https, 23 | argumentsData.verbosity, 24 | argumentsData.silent, 25 | argumentsData.streamTimeout 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/scala/es/weso/rdfshape/logging/LoggingLevels.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.logging 2 | 3 | /** Enumeration classifying the accepted logging levels by their String representation. 4 | */ 5 | case object LoggingLevels { 6 | type LoggingLevels = String 7 | 8 | val ERROR = "ERROR" 9 | val WARN = "WARN" 10 | val INFO = "INFO" 11 | val DEBUG = "DEBUG" 12 | val TRACE = "TRACE" 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/es/weso/rdfshape/logging/LoggingManager.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.logging 2 | 3 | import ch.qos.logback.classic.Level 4 | import ch.qos.logback.classic.util.ContextInitializer 5 | 6 | /** Utilities related to the logging system used by the application to log information on the console and the logs folder 7 | * @see {@link es.weso.rdfshape.logging.LoggingManager} 8 | */ 9 | final class LoggingManager 10 | 11 | object LoggingManager { 12 | 13 | /** System property checked by logback to know how much console-output it should filter 14 | */ 15 | val systemPropertyVerbosity = "rdfshape.api.verbosity.level" 16 | 17 | /** Location of logback's configuration file inside the "resources" folder 18 | * @note Resorts to [[ContextInitializer.AUTOCONFIG_FILE]], i.e.: logback.xml 19 | */ 20 | private val logbackConfigurationFile = 21 | ContextInitializer.AUTOCONFIG_FILE 22 | 23 | /** Set the System Properties that will be read in logback's configuration file to define logback's behavior 24 | * @see setUpLogbackConfiguration 25 | * @see setUpLogbackLogLevel 26 | */ 27 | def setUp( 28 | verbosity: Int, 29 | silent: Boolean, 30 | logbackConfigurationFile: String = logbackConfigurationFile 31 | ): Unit = { 32 | setUpLogbackLogLevel(verbosity, silent) 33 | setUpLogbackConfiguration(logbackConfigurationFile) 34 | } 35 | 36 | /** Set the system property which defines the configuration file that logback uses. 37 | * @param configurationFile filename of the logback configuration file (relative to the "resources" folder) 38 | */ 39 | private def setUpLogbackConfiguration(configurationFile: String): Unit = { 40 | System.setProperty( 41 | ContextInitializer.CONFIG_FILE_PROPERTY, 42 | configurationFile 43 | ) 44 | } 45 | 46 | /** Set the system property which defines the amount of logs that will appear 47 | * in console (see logback configuration file). 48 | * @param verbosity numeric representation of the required verbosity level 49 | */ 50 | private def setUpLogbackLogLevel(verbosity: Int, silent: Boolean): Unit = { 51 | val logLevel = 52 | if(silent) Level.OFF else mapVerbosityValueToLogLevel(verbosity) 53 | System.setProperty( 54 | systemPropertyVerbosity, 55 | logLevel.toString 56 | ) 57 | } 58 | 59 | /** Given a verbosity numeric value, map it to its corresponding logging level 60 | * @param verbosity Verbosity numeric value 61 | * @return A string representing the minimum level of the logs to be shown on console 62 | */ 63 | def mapVerbosityValueToLogLevel(verbosity: Int): Level = { 64 | verbosity match { 65 | case 0 => Level.ERROR // No verbose argument. Show errors. 66 | case 1 => Level.WARN // -v. Show warnings. 67 | case 2 => Level.INFO // -vv. Show info. 68 | case _ => Level.DEBUG // -vvv and forth. Show debug information. 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/scala/es/weso/rdfshape/logging/filters/VerbosityFilter.scala: -------------------------------------------------------------------------------- 1 | package es.weso.rdfshape.logging.filters 2 | 3 | import ch.qos.logback.classic.Level 4 | import ch.qos.logback.classic.spi.ILoggingEvent 5 | import ch.qos.logback.core.filter.Filter 6 | import ch.qos.logback.core.spi.FilterReply 7 | import es.weso.rdfshape.logging.LoggingManager.systemPropertyVerbosity 8 | 9 | /** Decide whether a logging event should be performed or not based on 10 | * the user-selected verbosity when run through the CLI 11 | */ 12 | class VerbosityFilter extends Filter[ILoggingEvent] { 13 | 14 | /** Represents the user selected verbosity of the app, used to filter 15 | * console log output. It is retrieved from a custom System property. 16 | * @note Use lazy to delay the property computation until it is set and needed 17 | */ 18 | lazy val userVerbosity: Level = 19 | Level.toLevel(System.getProperty(systemPropertyVerbosity), Level.ERROR) 20 | 21 | /** Decide whether a logging event should be performed or not based on user preferences 22 | * @param event Input logging event 23 | * @return Accept the logging request if it does comply with the user verbosity level, 24 | * else deny 25 | */ 26 | override def decide(event: ILoggingEvent): FilterReply = 27 | userVerbosity match { 28 | case Level.OFF => FilterReply.DENY 29 | case _ => 30 | if(event.getLevel.isGreaterOrEqual(userVerbosity)) FilterReply.ACCEPT 31 | else FilterReply.DENY 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "0.2.14" 2 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | 22 | # Generated scaladoc 23 | static/api 24 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /website/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | const deployUrl = "https://weso.github.io" 2 | const baseUrl = "/rdfshape-api/" 3 | const scalaDocUrl = `${deployUrl}${baseUrl}api/es/weso/rdfshape/` 4 | const apiDocsUrl = "https://app.swaggerhub.com/apis-docs/weso/RDFShape/" 5 | 6 | const lightCodeTheme = require("prism-react-renderer/themes/github"); 7 | const darkCodeTheme = require("prism-react-renderer/themes/dracula"); 8 | 9 | /** @type {import("@docusaurus/types").DocusaurusConfig} */ 10 | module.exports = { 11 | title: "RDFShape API", 12 | tagline: "Processing and validation of RDF with ShEx, SHACL and more", 13 | organizationName: "weso", // GitHub org/user name. 14 | projectName: "rdfshape-api", // Repo name. 15 | url: deployUrl, 16 | baseUrl: baseUrl, 17 | onBrokenLinks: "throw", 18 | onBrokenMarkdownLinks: "warn", 19 | favicon: "favicon.ico", 20 | trailingSlash: true, 21 | customFields: { 22 | scalaDocUrl, apiDocsUrl 23 | }, 24 | themeConfig: { 25 | image: "img/preview.png", 26 | colorMode: { 27 | defaultMode: "light", 28 | disableSwitch: false, 29 | respectPrefersColorScheme: true 30 | }, 31 | navbar: { 32 | title: "RDFShape API", logo: { 33 | alt: "RDFShape API - WESO", src: "img/logo-weso.png" 34 | }, items: [// Web docs 35 | { 36 | to: "/docs", label: "Web docs", position: "left" 37 | }, // Scaladoc 38 | { 39 | href: scalaDocUrl, label: "Scaladoc", position: "left" 40 | }, // API Docs in SwaggerHub 41 | { 42 | href: "https://app.swaggerhub.com/apis-docs/weso/RDFShape", 43 | label: "SwaggerHub", 44 | position: "right" 45 | }, // Link to repo 46 | { 47 | href: "https://github.com/weso/rdfshape-api", 48 | label: "GitHub", 49 | position: "right" 50 | }] 51 | }, footer: { 52 | style: "light", 53 | logo: { 54 | alt: "RDFShape API - WESO", 55 | src: "img/logo-weso-footer.png", 56 | href: "https://www.weso.es/" 57 | }, 58 | links: [{ 59 | title: "About us", items: [{ 60 | label: "WESO Research Group", to: "https://www.weso.es/" 61 | }, { 62 | label: "University of Oviedo", to: "https://www.uniovi.es/" 63 | }] 64 | }, { 65 | title: "Community", items: [{ 66 | label: "GitHub", to: "https://github.com/weso" 67 | }, { 68 | label: "Twitter", to: "https://twitter.com/wesoviedo" 69 | }] 70 | }, { 71 | title: "Further work", items: [{ 72 | label: "RDFShape project", to: "https://github.com/weso/rdfshape" 73 | }, { 74 | label: "More software by WESO", to: "https://www.weso.es/#software" 75 | }] 76 | }], 77 | copyright: `Copyright © ${new Date().getFullYear()} WESO Research Group` 78 | }, 79 | prism: { 80 | theme: lightCodeTheme, 81 | darkTheme: darkCodeTheme 82 | } 83 | }, 84 | presets: [["@docusaurus/preset-classic", { 85 | docs: { 86 | path: "../rdfshape-docs/target/mdoc", 87 | sidebarPath: require.resolve("./sidebars.js") 88 | }, blog: false, theme: { 89 | customCss: require.resolve("./src/css/custom.css") 90 | }, sitemap: {} 91 | }]] 92 | }; 93 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rdfshape-api-webdocs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "publish-gh-pages": "docusaurus deploy", 11 | "deploy": "docusaurus deploy", 12 | "clear": "docusaurus clear", 13 | "serve": "docusaurus serve", 14 | "write-translations": "docusaurus write-translations", 15 | "write-heading-ids": "docusaurus write-heading-ids" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "^2.0.0-beta.18", 19 | "@docusaurus/preset-classic": "^2.0.0-beta.18", 20 | "@mdx-js/react": "^1.6.21", 21 | "@svgr/webpack": "^5.5.0", 22 | "clsx": "^1.1.1", 23 | "file-loader": "^6.2.0", 24 | "prism-react-renderer": "^1.3.1", 25 | "react": "^17.0.1", 26 | "react-dom": "^17.0.1", 27 | "url-loader": "^4.1.1" 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.5%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /website/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /website/sidebars.js: -------------------------------------------------------------------------------- 1 | // Cannot be imported outside of module 2 | const scalaDocUrl = "https://www.weso.es/rdfshape-api/api/es/weso/rdfshape/" 3 | const apiDocsUrl = "https://app.swaggerhub.com/apis-docs/weso/RDFShape/" 4 | 5 | module.exports = { 6 | // Generate a sidebar from the docs folder structure 7 | // sidebar: [{type: "autogenerated", dirName: "."}], 8 | 9 | // Create a sidebar manually 10 | docsSidebar: [/* Home */ 11 | { 12 | type: "doc", id: "home", label: "Welcome" 13 | }, 14 | 15 | /* Category: deployment */ 16 | { 17 | type: "category", 18 | label: "API Deployment", 19 | items: ["api-deployment/deployment_overview", "api-deployment/deployment_manual", "api-deployment/deployment_docker"], 20 | collapsed: false 21 | }, 22 | 23 | /* Category: usage */ 24 | { 25 | type: "category", 26 | label: "API Usage", 27 | items: ["api-usage/usage_cli", "api-usage/streaming"], 28 | collapsed: false 29 | }, 30 | 31 | /* Category: testing */ 32 | { 33 | type: "category", 34 | label: "API Testing and Auditing", 35 | items: ["api-testing-auditing/testing-auditing_munit", "api-testing-auditing/testing-auditing_integration", "api-testing-auditing/testing-auditing_logs"], 36 | collapsed: true 37 | }, 38 | 39 | /* Category: documentation */ 40 | { 41 | type: "category", label: "Additional documentation", items: [ 42 | { 43 | type: "link", label: "Scaladoc", href: scalaDocUrl 44 | }, { 45 | type: "link", label: "API Docs (Swagger Hub)", href: apiDocsUrl 46 | }], collapsed: false 47 | }, 48 | 49 | /* Webpage information */ 50 | { 51 | type: "doc", id: "webpage/webpage_info", label: "About this webpage" 52 | }] 53 | 54 | }; 55 | -------------------------------------------------------------------------------- /website/src/components/HomepageFeatures.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import clsx from "clsx"; 3 | import styles from "./HomepageFeatures.module.css"; 4 | import Link from "@docusaurus/core/lib/client/exports/Link"; 5 | import {customFields} from "../../docusaurus.config" 6 | 7 | const {scalaDocUrl, apiDocsUrl} = customFields 8 | 9 | const FeatureList = [{ 10 | title: "Scaladoc", 11 | Svg: require("../../static/img/scala-icon.svg").default, 12 | description: (<> 13 | Check out the automatically generated Scaladoc, up to date with our 14 | latest stable 15 | build 16 | ), 17 | link: scalaDocUrl 18 | }, { 19 | title: "Web documentation", 20 | Svg: require("../../static/img/webdocs.svg").default, 21 | description: (<> 22 | Friendly guides and short articles related to the project and the usage 23 | of the API 24 | ), 25 | link: "/docs" 26 | }, { 27 | title: "API Docs", 28 | Svg: require("../../static/img/rocket.svg").default, 29 | description: (<> 30 | Browse the API Docs and test the API directly in Swagger Hub without 31 | having to learn about the 32 | underlying infrastructure 33 | ), 34 | link: apiDocsUrl 35 | },]; 36 | 37 | function Feature({Svg, title, description, link}) { 38 | return (
39 |
40 | 41 |
42 |
43 |

{title}

44 |

{description}

45 |
46 |
); 47 | } 48 | 49 | export default function HomepageFeatures() { 50 | return (
51 |
52 |
53 | {FeatureList.map((props, idx) => ())} 54 |
55 |
56 |
); 57 | } 58 | -------------------------------------------------------------------------------- /website/src/components/HomepageFeatures.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | .features { 4 | display: flex; 5 | align-items: center; 6 | padding: 2rem 0; 7 | width: 100%; 8 | } 9 | 10 | .featureSvg { 11 | height: 200px; 12 | width: 200px; 13 | border-radius: 10%; 14 | margin-bottom: 10px; 15 | } 16 | -------------------------------------------------------------------------------- /website/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * Any CSS included here will be global. The classic template 4 | * bundles Infima by default. Infima is a CSS framework designed to 5 | * work well for content-centric websites. 6 | */ 7 | 8 | /* You can override the default Infima variables here. */ 9 | :root { 10 | --ifm-color-primary: #388697; 11 | --ifm-color-primary-dark: #2B84AD; 12 | --ifm-color-primary-darker: #08415C; 13 | --ifm-color-primary-darkest: #152F3B; 14 | --ifm-color-primary-light: #57C6FA; 15 | --ifm-color-primary-lighter: #73D3FF; 16 | --ifm-color-primary-lightest: #BFEDEF; 17 | --ifm-code-font-size: 95%; 18 | } 19 | 20 | /* noinspection ALL */ 21 | .docusaurus-highlight-code-line { 22 | background-color: rgb(72, 77, 91); 23 | display: block; 24 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 25 | padding: 0 var(--ifm-pre-padding); 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /website/src/pages/docs.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Redirect} from '@docusaurus/router' 3 | import useBaseUrl from '@docusaurus/useBaseUrl' 4 | 5 | // Redirect to docs intro 6 | const RdfShapeApi = () => { 7 | const redirectDocsIntro = useBaseUrl("/docs/home") 8 | return 9 | } 10 | 11 | export default RdfShapeApi -------------------------------------------------------------------------------- /website/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import clsx from "clsx"; 3 | import Layout from "@theme/Layout"; 4 | import Link from "@docusaurus/Link"; 5 | import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; 6 | import styles from "./index.module.css"; 7 | import HomepageFeatures from "../components/HomepageFeatures"; 8 | 9 | function HomepageHeader() { 10 | const {siteConfig} = useDocusaurusContext(); 11 | return (
12 |
13 |

{siteConfig.title}

14 |

{siteConfig.tagline}

15 |
16 | 19 | RDFShape Client DEMO 20 | 21 |
22 |
23 |
); 24 | } 25 | 26 | export default function Home() { 27 | const {siteConfig} = useDocusaurusContext(); 28 | return ( 31 | 32 |
33 | 34 |
35 |
); 36 | } 37 | -------------------------------------------------------------------------------- /website/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | /** 4 | * CSS files with the .module.css suffix will be treated as CSS modules 5 | * and scoped locally. 6 | */ 7 | 8 | .heroBanner { 9 | padding: 4rem 0; 10 | text-align: center; 11 | position: relative; 12 | overflow: hidden; 13 | } 14 | 15 | @media screen and (max-width: 966px) { 16 | .heroBanner { 17 | padding: 2rem; 18 | } 19 | } 20 | 21 | .buttons { 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | } 26 | -------------------------------------------------------------------------------- /website/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weso/rdfshape-api/6c31f73583bf14b53a5ab063ee47a5135a368640/website/static/.nojekyll -------------------------------------------------------------------------------- /website/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weso/rdfshape-api/6c31f73583bf14b53a5ab063ee47a5135a368640/website/static/favicon.ico -------------------------------------------------------------------------------- /website/static/img/logo-weso-footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weso/rdfshape-api/6c31f73583bf14b53a5ab063ee47a5135a368640/website/static/img/logo-weso-footer.png -------------------------------------------------------------------------------- /website/static/img/logo-weso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weso/rdfshape-api/6c31f73583bf14b53a5ab063ee47a5135a368640/website/static/img/logo-weso.png -------------------------------------------------------------------------------- /website/static/img/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weso/rdfshape-api/6c31f73583bf14b53a5ab063ee47a5135a368640/website/static/img/preview.png -------------------------------------------------------------------------------- /website/static/img/scala-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 22 | 23 | -------------------------------------------------------------------------------- /website/static/img/scala.svg: -------------------------------------------------------------------------------- 1 | scala-full-color -------------------------------------------------------------------------------- /website/static/img/tutorial/docsVersionDropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weso/rdfshape-api/6c31f73583bf14b53a5ab063ee47a5135a368640/website/static/img/tutorial/docsVersionDropdown.png -------------------------------------------------------------------------------- /website/static/img/tutorial/localeDropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weso/rdfshape-api/6c31f73583bf14b53a5ab063ee47a5135a368640/website/static/img/tutorial/localeDropdown.png --------------------------------------------------------------------------------