├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── main │ ├── resources │ │ └── META-INF │ │ │ └── gradle-plugins │ │ │ ├── json-validator.properties │ │ │ └── cz.alenkacz.gradle.jsonvalidator.properties │ └── groovy │ │ └── cz │ │ └── alenkacz │ │ └── gradle │ │ └── jsonvalidator │ │ ├── PluginExtension.groovy │ │ ├── JsonSchemaValidationException.groovy │ │ ├── JsonValidatorPlugin.groovy │ │ ├── InvalidJsonException.groovy │ │ ├── ValidateJsonSchemaSyntaxTask.groovy │ │ └── ValidateJsonTask.groovy └── test │ └── groovy │ └── cz │ └── alenkacz │ └── gradle │ └── jsonvalidator │ ├── JsonValidatorPluginTest.groovy │ ├── ValidateJsonSchemaSyntaxTaskTest.groovy │ ├── ProjectMother.groovy │ └── ValidateJsonTaskTest.groovy ├── Dockerfile ├── .gitignore ├── .travis.yml ├── LICENSE ├── gradlew.bat ├── README.md └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'json-validator' 2 | 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alenkacz/gradle-json-validator/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/json-validator.properties: -------------------------------------------------------------------------------- 1 | implementation-class=cz.alenkacz.gradle.jsonvalidator.JsonValidatorPlugin -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/cz.alenkacz.gradle.jsonvalidator.properties: -------------------------------------------------------------------------------- 1 | implementation-class=cz.alenkacz.gradle.jsonvalidator.JsonValidatorPlugin -------------------------------------------------------------------------------- /src/main/groovy/cz/alenkacz/gradle/jsonvalidator/PluginExtension.groovy: -------------------------------------------------------------------------------- 1 | package cz.alenkacz.gradle.jsonvalidator 2 | 3 | class PluginExtension { 4 | def String schemaFolder 5 | } 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This is Dockerfile that defines build environment. 2 | FROM java:8u66-jdk 3 | MAINTAINER varkockova.a@gmail.com 4 | 5 | VOLUME /build 6 | WORKDIR /build 7 | 8 | ENTRYPOINT ["/build/gradlew"] 9 | CMD ["test"] -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Feb 19 17:31:04 CET 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /src/main/groovy/cz/alenkacz/gradle/jsonvalidator/JsonSchemaValidationException.groovy: -------------------------------------------------------------------------------- 1 | package cz.alenkacz.gradle.jsonvalidator 2 | 3 | import org.gradle.api.GradleException 4 | 5 | class JsonSchemaValidationException extends GradleException { 6 | public JsonSchemaValidationException(List schemaFiles) { 7 | super("Invalid schema files: " + schemaFiles.join(",")) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/groovy/cz/alenkacz/gradle/jsonvalidator/JsonValidatorPlugin.groovy: -------------------------------------------------------------------------------- 1 | package cz.alenkacz.gradle.jsonvalidator 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.Plugin 5 | 6 | class JsonValidatorPlugin implements Plugin { 7 | void apply(Project target) { 8 | target.task('validateJson', type: ValidateJsonTask) 9 | ValidateJsonSchemaSyntaxTask validateSchemaTask = target.task('validateJsonSchema', type: ValidateJsonSchemaSyntaxTask) 10 | PluginExtension extension = target.extensions.create('jsonSchema', PluginExtension) 11 | 12 | validateSchemaTask.setPluginExtension(extension) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | .gradle 15 | build/ 16 | 17 | # Ignore Gradle GUI config 18 | gradle-app.setting 19 | 20 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 21 | !gradle-wrapper.jar 22 | 23 | # Cache of project 24 | .gradletasknamecache 25 | 26 | # IntelliJ IDEA 27 | **/.idea/workspace.xml 28 | **/.idea/tasks.xml 29 | **/.idea/dataSources.ids 30 | **/.idea/dataSources.xml 31 | **/.idea/sqlDataSources.xml 32 | **/.idea/dynamic.xml 33 | **/.idea/uiDesigner.xml 34 | **/.idea/dictionaries/ 35 | **/.idea/** 36 | .idea/workspace.xml 37 | *.ipr 38 | *.iws 39 | *.iml 40 | out/ 41 | # generated by JIRA plugin 42 | atlassian-ide-plugin.xml 43 | -------------------------------------------------------------------------------- /src/test/groovy/cz/alenkacz/gradle/jsonvalidator/JsonValidatorPluginTest.groovy: -------------------------------------------------------------------------------- 1 | package cz.alenkacz.gradle.jsonvalidator 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class JsonValidatorPluginTest extends Specification { 8 | def "add tasks to the project"() { 9 | when: 10 | Project project = ProjectBuilder.builder().build() 11 | project.plugins.apply "cz.alenkacz.gradle.jsonvalidator" 12 | 13 | then: 14 | project.tasks.validateJson instanceof ValidateJsonTask 15 | } 16 | 17 | def "add tasks to the project for short plugin name"() { 18 | when: 19 | Project project = ProjectBuilder.builder().build() 20 | project.plugins.apply "json-validator" 21 | 22 | then: 23 | project.tasks.validateJson instanceof ValidateJsonTask 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | 3 | sudo: required 4 | services: 5 | - docker 6 | 7 | before_cache: 8 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 9 | cache: 10 | directories: 11 | - $HOME/.gradle/caches/ 12 | - $HOME/.gradle/wrapper/ 13 | - $HOME/.m2 14 | 15 | before_install: 16 | - docker build --tag=build-image . 17 | 18 | script: 19 | - docker run -v $(pwd):/build -v $HOME/.m2:/root/.m2 -v $HOME/.gradle:/root/.gradle -v /var/run/docker.sock:/var/run/docker.sock build-image --info 20 | 21 | after_success: 22 | - test $TRAVIS_PULL_REQUEST == "false" && test "$TRAVIS_TAG" != "" && test $TRAVIS_REPO_SLUG == "alenkacz/gradle-json-validator" && sudo docker run -v $(pwd):/build -v $HOME/.m2:/root/.m2 -v $HOME/.gradle:/root/.gradle -v /var/run/docker.sock:/var/run/docker.sock -e "BINTRAY_USER=$BINTRAY_USER" -e "BINTRAY_KEY=$BINTRAY_KEY" -e "GRADLE_PORTAL_KEY=$GRADLE_PORTAL_KEY" -e "GRADLE_PORTAL_SECRET=$GRADLE_PORTAL_SECRET" build-image bintrayUpload publishPlugins -Pversion="$TRAVIS_TAG" --info -------------------------------------------------------------------------------- /src/main/groovy/cz/alenkacz/gradle/jsonvalidator/InvalidJsonException.groovy: -------------------------------------------------------------------------------- 1 | package cz.alenkacz.gradle.jsonvalidator 2 | 3 | import org.gradle.api.GradleException 4 | 5 | class InvalidJsonException extends GradleException { 6 | public InvalidJsonException(List validationErrors, String jsonSchemaPath) { 7 | super(getMessage(validationErrors, jsonSchemaPath)) 8 | } 9 | 10 | static getMessage(List validationErrors, String jsonSchemaPath) { 11 | def errorMessage = new StringBuilder("One or more validation errors found against schema '$jsonSchemaPath': ${System.getProperty("line.separator")}") 12 | errorMessage.append(validationErrors.collect { 13 | "File '$it.jsonFilePath' has ${it.violations.size()} violations: ${System.getProperty("line.separator")}${it.violations.join(System.getProperty("line.separator"))}" 14 | }.join(System.getProperty("line.separator"))) 15 | errorMessage.toString() 16 | } 17 | } 18 | 19 | class ValidationError { 20 | String jsonFilePath 21 | List violations 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Alena Varkockova 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 | -------------------------------------------------------------------------------- /src/test/groovy/cz/alenkacz/gradle/jsonvalidator/ValidateJsonSchemaSyntaxTaskTest.groovy: -------------------------------------------------------------------------------- 1 | package cz.alenkacz.gradle.jsonvalidator 2 | 3 | import org.gradle.testfixtures.ProjectBuilder 4 | import spock.lang.Specification 5 | 6 | class ValidateJsonSchemaSyntaxTaskTest extends Specification { 7 | def "say that the schema is valid"() { 8 | given: 9 | def schemaRoot = ProjectMother.validSchemaProject() 10 | def project = ProjectBuilder.builder().build() 11 | project.plugins.apply 'scala' 12 | project.plugins.apply 'json-validator' 13 | def extension = (PluginExtension) project.extensions.findByName('jsonSchema') 14 | extension.setSchemaFolder(schemaRoot.absolutePath) 15 | 16 | when: 17 | project.tasks.validateJsonSchema.validateSchema() 18 | 19 | then: 20 | noExceptionThrown() 21 | } 22 | 23 | def "say that the schema is invalid"() { 24 | given: 25 | def schemaRoot = ProjectMother.invalidSchemaProject() 26 | def project = ProjectBuilder.builder().build() 27 | project.plugins.apply 'scala' 28 | project.plugins.apply 'json-validator' 29 | def extension = (PluginExtension) project.extensions.findByName('jsonSchema') 30 | extension.setSchemaFolder(schemaRoot.absolutePath) 31 | 32 | when: 33 | project.tasks.validateJsonSchema.validateSchema() 34 | 35 | then: 36 | thrown JsonSchemaValidationException 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/groovy/cz/alenkacz/gradle/jsonvalidator/ProjectMother.groovy: -------------------------------------------------------------------------------- 1 | package cz.alenkacz.gradle.jsonvalidator 2 | 3 | import java.nio.file.Files 4 | import java.nio.file.Paths 5 | 6 | class ProjectMother { 7 | static def File validSchemaProject() { 8 | def projectRoot = null 9 | File.createTempDir().with { 10 | deleteOnExit() 11 | def srcFile = Files.createFile(Paths.get(absoluteFile.absolutePath, "schema.json")) 12 | srcFile.write """{ 13 | "title": "Example Schema", 14 | "type": "object", 15 | "properties": { 16 | "firstName": { 17 | "type": "string" 18 | }, 19 | "lastName": { 20 | "type": "string" 21 | }, 22 | "age": { 23 | "description": "Age in years", 24 | "type": "integer", 25 | "minimum": 0 26 | } 27 | }, 28 | "required": ["firstName", "lastName"] 29 | }""".stripMargin() 30 | projectRoot = absoluteFile 31 | } 32 | return projectRoot 33 | } 34 | 35 | static def File invalidSchemaProject() { 36 | def projectRoot = null 37 | File.createTempDir().with { 38 | deleteOnExit() 39 | def srcFile = Files.createFile(Paths.get(absoluteFile.absolutePath, "schema.json")) 40 | srcFile.write """{ 41 | "title": "Example Schema", 42 | "type": "object", 43 | "properties": { 44 | "firstName": { 45 | "type": 1 46 | } 47 | }, 48 | "required": ["firstName", "lastName"] 49 | }""".stripMargin() 50 | projectRoot = absoluteFile 51 | } 52 | return projectRoot 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/groovy/cz/alenkacz/gradle/jsonvalidator/ValidateJsonSchemaSyntaxTask.groovy: -------------------------------------------------------------------------------- 1 | package cz.alenkacz.gradle.jsonvalidator 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import com.github.fge.jackson.JacksonUtils 5 | import com.github.fge.jsonschema.cfg.ValidationConfiguration 6 | import com.github.fge.jsonschema.processors.syntax.SyntaxValidator 7 | import groovy.io.FileType 8 | import org.gradle.api.DefaultTask 9 | import org.gradle.api.tasks.TaskAction 10 | 11 | /** 12 | * Recursively search for all files in 'schemaFolder' and validates that the file is syntactically valid json schema 13 | * If invalid json schema found, it prints out the name of that file and the whole task ends with a failure 14 | */ 15 | class ValidateJsonSchemaSyntaxTask extends DefaultTask { 16 | private static final ObjectMapper MAPPER = JacksonUtils.newMapper(); 17 | private static final def validator = new SyntaxValidator(ValidationConfiguration.byDefault()) 18 | 19 | PluginExtension pluginExtension 20 | 21 | @TaskAction 22 | def validateSchema() { 23 | if (pluginExtension.schemaFolder == null) { 24 | throw new IllegalArgumentException("You need to provide 'schemaFolder' property, otherwise the plugin cannot validate your json files.") 25 | } 26 | File schemaFolder = new File(pluginExtension.schemaFolder) 27 | if (!schemaFolder.exists() || !schemaFolder.isDirectory()) { 28 | throw new IllegalArgumentException("Provided schema folder '${pluginExtension.schemaFolder}' does not exist or is not a directory") 29 | } 30 | def schemaFiles = [] 31 | schemaFolder.eachFileRecurse (FileType.FILES) { file -> 32 | schemaFiles << file 33 | } 34 | def invalidSchemaFiles = [] 35 | schemaFiles.each { sf -> 36 | def rootNode = MAPPER.readTree(sf) 37 | if (!validator.schemaIsValid(rootNode)) { 38 | invalidSchemaFiles << sf.toString() 39 | } 40 | } 41 | if (!invalidSchemaFiles.empty) { 42 | throw new JsonSchemaValidationException(invalidSchemaFiles) 43 | } else { 44 | logger.info("All schema files are valid.") 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gradle-json-validator 2 | 3 | [![Build Status](https://travis-ci.org/alenkacz/gradle-json-validator.svg)](https://travis-ci.org/alenkacz/gradle-json-validator) [ ![Download](https://api.bintray.com/packages/alenkacz/maven/gradle-json-validator/images/download.svg) ](https://bintray.com/alenkacz/maven/gradle-json-validator/_latestVersion) 4 | 5 | Provides json validation as a part of your gradle build pipeline. 6 | 7 | This plugin implements a custom task type, [ValidateJsonTask](https://github.com/alenkacz/gradle-json-validator/blob/master/src/main/groovy/cz/alenkacz/gradle/jsonvalidator/ValidateJsonTask.groovy). This task expects two properties - *targetJsonFile* and *jsonSchemaFile* (instead of *targetJsonFile* you can use *targetJsonDirectory* and then all files in that directory will be validated). If that directory contains both json and non-json files, the task will fail for non-json files. If you want to validate only files with .json extension use the `onlyWithJsonExtension` property. If you need to validate more jsons as a part of one build, you will have to create as many custom tasks as the number of json schema files (see *validateCustomJson* in the example below). 8 | 9 | Usage 10 | ==================== 11 | 12 | buildscript { 13 | repositories { 14 | jcenter() 15 | } 16 | dependencies { 17 | classpath 'cz.alenkacz.gradle:json-validator:FILL_VERSION_HERE' 18 | } 19 | } 20 | 21 | apply plugin: 'json-validator' 22 | 23 | import cz.alenkacz.gradle.jsonvalidator.ValidateJsonTask 24 | 25 | task validateCustomJson(type: ValidateJsonTask) { 26 | targetJsonFile = file("target.json") // only one of targetJsonFile or targetJsonDirectory can be specified 27 | targetJsonDirectory = file("directoryWithJsons") // only one of targetJsonFile or targetJsonDirectory can be specified 28 | jsonSchema = file("schema.json") 29 | onlyWithJsonExtension = true // default is false, this makes sense only when using targetJsonDirectory, it ignores all files apart from those who do not have .json file extension 30 | } 31 | 32 | JSON schema syntax check 33 | ==================== 34 | For some build pipelines it might be useful to be able to check schema files for syntax errors as a part of the build. To make that work, use the following setup and run the task **validateJsonSchema**: 35 | 36 | 37 | buildscript { 38 | repositories { 39 | jcenter() 40 | } 41 | dependencies { 42 | classpath 'cz.alenkacz.gradle:json-validator:FILL_VERSION_HERE' 43 | } 44 | } 45 | 46 | apply plugin: 'json-validator' 47 | 48 | jsonSchema { 49 | schemaFolder = PATH_TO_YOUR_FOLDER_WITH_JSON_SCHEMAS 50 | } 51 | -------------------------------------------------------------------------------- /src/main/groovy/cz/alenkacz/gradle/jsonvalidator/ValidateJsonTask.groovy: -------------------------------------------------------------------------------- 1 | package cz.alenkacz.gradle.jsonvalidator 2 | 3 | import groovy.io.FileType 4 | import org.everit.json.schema.ArraySchema 5 | import org.everit.json.schema.Schema 6 | import org.everit.json.schema.ValidationException 7 | import org.everit.json.schema.loader.SchemaLoader 8 | import org.gradle.api.DefaultTask 9 | import org.gradle.api.tasks.InputDirectory 10 | import org.gradle.api.tasks.InputFile 11 | import org.gradle.api.tasks.Optional 12 | import org.gradle.api.tasks.TaskAction 13 | import org.json.JSONArray 14 | import org.json.JSONException 15 | import org.json.JSONObject 16 | import org.json.JSONTokener 17 | 18 | import java.util.stream.Collectors 19 | 20 | class ValidateJsonTask extends DefaultTask { 21 | @InputFile 22 | File jsonSchema 23 | @InputFile 24 | @Optional 25 | File targetJsonFile 26 | @InputDirectory 27 | @Optional 28 | File targetJsonDirectory 29 | @Input 30 | @Optional 31 | Boolean onlyWithJsonExtension 32 | 33 | @TaskAction 34 | def validateJson() { 35 | // check files exist 36 | if (onlyWithJsonExtension != null && targetJsonDirectory == null) { 37 | throw new IllegalArgumentException("Cannot use onlyWithJsonExtension property without using targetJsonDirectory.") 38 | } 39 | if (onlyWithJsonExtension == null) { 40 | onlyWithJsonExtension = false 41 | } 42 | new FileInputStream(jsonSchema).withStream { 43 | JSONObject rawSchema = new JSONObject(new JSONTokener(it)) 44 | Schema schema = SchemaLoader.load(rawSchema) 45 | List targetJsonFilesList = [] 46 | if (targetJsonDirectory != null) { 47 | targetJsonDirectory.eachFileRecurse (FileType.FILES) { file -> 48 | targetJsonFilesList << file 49 | } 50 | } else { 51 | targetJsonFilesList << targetJsonFile 52 | } 53 | List violations = [] 54 | targetJsonFilesList.each { 55 | try { 56 | if (schema instanceof ArraySchema) { 57 | schema.validate(new JSONArray(new FileInputStream(it).getText())) 58 | } else { 59 | schema.validate(new JSONObject(new FileInputStream(it).getText())) 60 | } 61 | } catch (ValidationException e) { 62 | violations << new ValidationError(jsonFilePath: it.absolutePath, violations: getViolations(e)) 63 | } catch (JSONException e) { 64 | if (!onlyWithJsonExtension || hasJsonExtension(it)) { 65 | // invalid json file is considered a violation only when onlyWithJsonExtension is not used or if the file does not have json extension 66 | violations << new ValidationError(jsonFilePath: it.absolutePath, violations: ["File is not valid json: ${e.message}"]) 67 | } 68 | } 69 | } 70 | if (!violations.empty) { 71 | throw new InvalidJsonException(violations, jsonSchema.absolutePath) 72 | } 73 | } 74 | } 75 | 76 | def hasJsonExtension(File file) { 77 | file.name.contains(".") && file.name.take(file.name.lastIndexOf('.')) == "json" 78 | } 79 | 80 | List getViolations(ValidationException e) { 81 | if (e.causingExceptions.empty) { 82 | [e.message] 83 | } else { 84 | e.causingExceptions.stream().map{ ex -> ex.message }.collect(Collectors.toList()) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 165 | if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then 166 | cd "$(dirname "$0")" 167 | fi 168 | 169 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 170 | -------------------------------------------------------------------------------- /src/test/groovy/cz/alenkacz/gradle/jsonvalidator/ValidateJsonTaskTest.groovy: -------------------------------------------------------------------------------- 1 | package cz.alenkacz.gradle.jsonvalidator 2 | 3 | 4 | import org.gradle.testkit.runner.GradleRunner 5 | import org.gradle.testkit.runner.TaskOutcome 6 | import org.junit.Rule 7 | import org.junit.rules.TemporaryFolder 8 | import spock.lang.Specification 9 | 10 | class ValidateJsonTaskTest extends Specification { 11 | @Rule final TemporaryFolder targetProjectDir = new TemporaryFolder() 12 | String pluginClasspath 13 | File gradleBuildFile 14 | File jsonSchemaFile 15 | File targetJsonFile 16 | File targetJsonFolder 17 | 18 | def setup() { 19 | gradleBuildFile = targetProjectDir.newFile('build.gradle') 20 | pluginClasspath = sourceCodeOfThisPluginClasspath() 21 | gradleBuildFile << getSingleFileGradleFile() 22 | 23 | jsonSchemaFile = targetProjectDir.newFile('schema.json') 24 | targetJsonFile = targetProjectDir.newFile('target.json') 25 | 26 | targetJsonFolder = targetProjectDir.newFolder("json") 27 | final FileTreeBuilder treeBuilder = new FileTreeBuilder(targetJsonFolder) 28 | treeBuilder.file("test.json", getInvalidJson()) 29 | treeBuilder.file("test2.json", getInvalidJson()) 30 | } 31 | 32 | def "succeed on valid json"() { 33 | given: 34 | gradleBuildFile.text = getSingleFileGradleFile() 35 | jsonSchemaFile << getJsonSchema() 36 | targetJsonFile << getCorrectJson() 37 | when: 38 | def actual = GradleRunner.create() 39 | .withProjectDir(targetProjectDir.root) 40 | .withArguments(':validateCustomJson', '--stacktrace') 41 | .build() 42 | 43 | then: 44 | actual.task(":validateCustomJson").outcome == TaskOutcome.SUCCESS 45 | } 46 | 47 | def "fail on invalid json"() { 48 | given: 49 | gradleBuildFile.text = getSingleFileGradleFile() 50 | jsonSchemaFile << getJsonSchema() 51 | targetJsonFile << getInvalidJson() 52 | when: 53 | def actual = GradleRunner.create() 54 | .withProjectDir(targetProjectDir.root) 55 | .withArguments(':validateCustomJson', '--stacktrace') 56 | .buildAndFail() 57 | 58 | then: 59 | actual.output.contains("One or more validation errors found") 60 | actual.task(":validateCustomJson").outcome == TaskOutcome.FAILED 61 | } 62 | 63 | def "succeed on valid json with array as top level object"() { 64 | given: 65 | gradleBuildFile.text = getSingleFileGradleFile() 66 | jsonSchemaFile << getArrayJsonSchema() 67 | targetJsonFile << getValidJsonArray() 68 | when: 69 | def actual = GradleRunner.create() 70 | .withProjectDir(targetProjectDir.root) 71 | .withArguments(':validateCustomJson', '--stacktrace') 72 | .build() 73 | 74 | then: 75 | actual.task(":validateCustomJson").outcome == TaskOutcome.SUCCESS 76 | } 77 | 78 | def "succeed on valid json empty array"() { 79 | given: 80 | gradleBuildFile.text = getSingleFileGradleFile() 81 | jsonSchemaFile << getArrayJsonSchema() 82 | targetJsonFile << getValidEmptyJsonArray() 83 | when: 84 | def actual = GradleRunner.create() 85 | .withProjectDir(targetProjectDir.root) 86 | .withArguments(':validateCustomJson', '--stacktrace') 87 | .build() 88 | 89 | then: 90 | actual.task(":validateCustomJson").outcome == TaskOutcome.SUCCESS 91 | } 92 | 93 | def "fail on invalid json with array as top level object"() { 94 | given: 95 | gradleBuildFile.text = getSingleFileGradleFile() 96 | jsonSchemaFile << getArrayJsonSchema() 97 | targetJsonFile << getInvalidJsonArray() 98 | when: 99 | def actual = GradleRunner.create() 100 | .withProjectDir(targetProjectDir.root) 101 | .withArguments(':validateCustomJson', '--stacktrace') 102 | .buildAndFail() 103 | 104 | then: 105 | actual.output.contains("One or more validation errors found") 106 | actual.task(":validateCustomJson").outcome == TaskOutcome.FAILED 107 | } 108 | 109 | def "accept also folder with json files and fail on invalid file in that folder"() { 110 | given: 111 | gradleBuildFile.text = getFolderGradleFile() 112 | jsonSchemaFile << getJsonSchema() 113 | 114 | when: 115 | def actual = GradleRunner.create() 116 | .withProjectDir(targetProjectDir.root) 117 | .withArguments(':validateCustomJson', '--stacktrace') 118 | .buildAndFail() 119 | 120 | then: 121 | actual.output.contains("One or more validation errors found") 122 | actual.output.contains("test.json") 123 | actual.output.contains("test2.json") 124 | actual.task(":validateCustomJson").outcome == TaskOutcome.FAILED 125 | } 126 | 127 | def "fail on non-json file"() { 128 | given: 129 | final FileTreeBuilder treeBuilder = new FileTreeBuilder(targetProjectDir.newFolder("non-json-folder")) 130 | treeBuilder.file("test.json", getInvalidJson()) 131 | treeBuilder.file("nonjson.txt", "randomtext") 132 | gradleBuildFile.text = getFolderGradleFile("non-json-folder") 133 | jsonSchemaFile << getJsonSchema() 134 | 135 | when: 136 | def actual = GradleRunner.create() 137 | .withProjectDir(targetProjectDir.root) 138 | .withArguments(':validateCustomJson', '--stacktrace') 139 | .buildAndFail() 140 | 141 | then: 142 | actual.output.contains("File is not valid json") 143 | actual.output.contains("nonjson.txt") 144 | actual.task(":validateCustomJson").outcome == TaskOutcome.FAILED 145 | } 146 | 147 | def "not fail on non-json file when using onlyWithJsonExtension"() { 148 | given: 149 | final FileTreeBuilder treeBuilder = new FileTreeBuilder(targetProjectDir.newFolder("non-json-folder")) 150 | treeBuilder.file("test.json", getCorrectJson()) 151 | treeBuilder.file("nonjson.txt", "randomtext") 152 | gradleBuildFile.text = getFolderGradleFile("non-json-folder", true) 153 | jsonSchemaFile << getJsonSchema() 154 | 155 | when: 156 | def actual = GradleRunner.create() 157 | .withProjectDir(targetProjectDir.root) 158 | .withArguments(':validateCustomJson', '--stacktrace') 159 | .build() 160 | 161 | then: 162 | print(actual.output) 163 | actual.task(":validateCustomJson").outcome == TaskOutcome.SUCCESS 164 | } 165 | 166 | def getCorrectJson() { 167 | ''' 168 | { 169 | "id": 1, 170 | "name": "A green door", 171 | "price": 12.50, 172 | "tags": ["home", "green"] 173 | } 174 | ''' 175 | } 176 | 177 | def getInvalidJson() { 178 | ''' 179 | { 180 | "name": 1, 181 | "price": 12.50, 182 | "tags": ["home", "green"] 183 | } 184 | ''' 185 | } 186 | 187 | 188 | def getValidJsonArray() { 189 | ''' 190 | [{ 191 | "id": 1, 192 | "name": "A green door", 193 | "price": 12.50, 194 | "tags": ["home", "green"] 195 | },{ 196 | "id": 2, 197 | "name": "Rocket", 198 | "price": 3500.0, 199 | "tags": ["rocket", "explosive"] 200 | }] 201 | ''' 202 | } 203 | 204 | def getValidEmptyJsonArray() { 205 | ''' 206 | [] 207 | ''' 208 | } 209 | 210 | def getInvalidJsonArray() { 211 | ''' 212 | [{ 213 | "name": "A green door", 214 | "price": 12.50, 215 | "tags": ["home", "green"] 216 | }] 217 | ''' 218 | } 219 | 220 | 221 | def getArrayJsonSchema() { 222 | ''' 223 | { 224 | "$schema": "http://json-schema.org/draft-04/schema#", 225 | "title": "Products", 226 | "description": "Acme's catalog", 227 | "type": "array", 228 | "items": { 229 | "type": "object", 230 | "properties": { 231 | "id": { 232 | "description": "The unique identifier for a product", 233 | "type": "integer" 234 | }, 235 | "name": { 236 | "description": "Name of the person", 237 | "type": "string" 238 | } 239 | }, 240 | "required": ["id"] 241 | } 242 | } 243 | ''' 244 | } 245 | 246 | def getJsonSchema() { 247 | ''' 248 | { 249 | "$schema": "http://json-schema.org/draft-04/schema#", 250 | "title": "Product", 251 | "description": "A product from Acme's catalog", 252 | "type": "object", 253 | "properties": { 254 | "id": { 255 | "description": "The unique identifier for a product", 256 | "type": "integer" 257 | }, 258 | "name": { 259 | "description": "Name of the person", 260 | "type": "string" 261 | } 262 | }, 263 | "required": ["id"] 264 | } 265 | ''' 266 | } 267 | 268 | def getSingleFileGradleFile() { 269 | """ 270 | buildscript { 271 | dependencies { 272 | classpath files($pluginClasspath) 273 | } 274 | } 275 | apply plugin: 'cz.alenkacz.gradle.jsonvalidator' 276 | 277 | import cz.alenkacz.gradle.jsonvalidator.ValidateJsonTask 278 | 279 | task validateCustomJson(type: ValidateJsonTask) { 280 | targetJsonFile = file("target.json") 281 | jsonSchema = file("schema.json") 282 | } 283 | """ 284 | } 285 | 286 | def getFolderGradleFile(folderName = "json", onlyWithJsonExtension = false) { 287 | """ 288 | buildscript { 289 | dependencies { 290 | classpath files($pluginClasspath) 291 | } 292 | } 293 | apply plugin: 'cz.alenkacz.gradle.jsonvalidator' 294 | 295 | import cz.alenkacz.gradle.jsonvalidator.ValidateJsonTask 296 | 297 | task validateCustomJson(type: ValidateJsonTask) { 298 | targetJsonDirectory = file("$folderName") 299 | jsonSchema = project.file("schema.json") 300 | onlyWithJsonExtension = ${onlyWithJsonExtension ? "true" : "false"} 301 | } 302 | """ 303 | } 304 | /* 305 | This is needed to get the current plugin to the classpath. See https://docs.gradle.org/current/userguide/test_kit.html 306 | */ 307 | def sourceCodeOfThisPluginClasspath() { 308 | def pluginClasspathResource = getClass().classLoader.findResource("plugin-classpath.txt") 309 | if (pluginClasspathResource == null) { 310 | throw new IllegalStateException("Did not find plugin classpath resource, run `testClasses` build task.") 311 | } 312 | 313 | pluginClasspathResource.readLines() 314 | .collect { it.replace('\\', '\\\\') } // escape backslashes in Windows paths 315 | .collect { "'$it'" } 316 | .join(", ") 317 | } 318 | } 319 | --------------------------------------------------------------------------------