Skip to content

Commit be2ba14

Browse files
authored
[NAVAND-1714] Upgrade services-cli (#1583)
* handle string input: -j "example json file" * exit with code 1 when failed verification * exit also with code 1 on back conversion: -c * pretty print result: -p * updated tests
1 parent 9bd9b7f commit be2ba14

File tree

8 files changed

+272
-38
lines changed

8 files changed

+272
-38
lines changed

.circleci/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ commands:
5151
- run:
5252
name: Build command line interface
5353
command: make build-cli
54+
- store_artifacts:
55+
path: services-cli/build/libs/services-cli.jar
56+
destination: services-cli.jar
5457

5558
run-tests:
5659
steps:

services-cli/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ and we can help. Also, pull requests are always welcome!
1111
From the command line
1212
1. cd mapbox-java
1313
1. Build with ./gradlew shadowJar, or make build-cli
14-
1. Run with java -jar services-cli/build/libs/services-cli-all.jar
14+
1. Run with java -jar services-cli/build/libs/services-cli.jar
1515

1616
From Android Studio
1717
1. Open mapbox-java with Android Studio
Lines changed: 91 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.mapbox.services.cli
22

3-
import com.google.gson.Gson
3+
import com.google.gson.GsonBuilder
44
import com.mapbox.services.cli.validator.DirectionsResponseValidator
5+
import com.mapbox.services.cli.validator.ValidatorInput
6+
import com.mapbox.services.cli.validator.ValidatorResult
57
import org.apache.commons.cli.CommandLine
68
import org.apache.commons.cli.DefaultParser
79
import org.apache.commons.cli.HelpFormatter
@@ -14,22 +16,58 @@ import org.apache.commons.cli.ParseException
1416
*/
1517
object MapboxJavaCli {
1618

17-
private const val COMMAND_FILE_INPUT = "f"
1819
private const val COMMAND_HELP = "h"
20+
private const val COMMAND_FILE_INPUT = "f"
21+
private const val COMMAND_JSON_INPUT = "j"
22+
private const val COMMAND_PRETTY_PRINT = "p"
23+
private const val COMMAND_FAIL_ON_CONVERT_BACK = "c"
1924

2025
@JvmStatic
2126
fun main(args: Array<String>) {
2227
val options = Options()
23-
.addOption(Option.builder(COMMAND_HELP)
24-
.longOpt("help")
25-
.desc("Shows this help message")
26-
.build())
27-
.addOption(Option.builder(COMMAND_FILE_INPUT)
28-
.longOpt("file")
29-
.hasArg(true)
30-
.desc("Path to a json file or directory")
31-
.required()
32-
.build())
28+
.addOption(
29+
Option.builder(COMMAND_HELP)
30+
.longOpt("help")
31+
.desc("Shows this help message")
32+
.build()
33+
)
34+
.addOption(
35+
Option.builder(COMMAND_FILE_INPUT)
36+
.longOpt("file")
37+
.hasArg(true)
38+
.desc("Path to a json file or directory. If directory is provided " +
39+
"all files in it will be processed.")
40+
.required(false)
41+
.build()
42+
)
43+
.addOption(
44+
Option.builder(COMMAND_JSON_INPUT)
45+
.longOpt("json")
46+
.hasArg(true)
47+
.desc("String containing the json. Instead of providing files it " +
48+
"is possible to relay the json directly.")
49+
.required(false)
50+
.build()
51+
)
52+
.addOption(
53+
Option.builder(COMMAND_PRETTY_PRINT)
54+
.longOpt("pretty")
55+
.desc("Pretty printing of results. Colored and indented JSON " +
56+
"output. It consist of many characters which are not visible in the " +
57+
"console - that why it might be hard to parse such JSON. If you want " +
58+
"to parse the result it's recommended not use this option.")
59+
.required(false)
60+
.build()
61+
)
62+
.addOption(
63+
Option.builder(COMMAND_FAIL_ON_CONVERT_BACK)
64+
.longOpt("convert-fail")
65+
.desc("Exit also with code 1 if the conversion back to JSON fails. " +
66+
"This is useful to check if mapbox-java produces the same JSON file " +
67+
"that was provided as an input from its internal structures.")
68+
.required(false)
69+
.build()
70+
)
3371

3472
try {
3573
val commandLine = DefaultParser().parse(options, args)
@@ -41,18 +79,53 @@ object MapboxJavaCli {
4179
}
4280

4381
private fun parseCommands(commandLine: CommandLine, options: Options) {
44-
if (commandLine.hasOption(COMMAND_HELP)) {
82+
if (commandLine.hasOption(COMMAND_HELP) || commandLine.options.isEmpty()) {
4583
printHelp(options)
84+
return
4685
}
4786

48-
val fileInput = commandLine.getOptionValue(COMMAND_FILE_INPUT)
4987
val directionsResponseValidator = DirectionsResponseValidator()
50-
val results = directionsResponseValidator.parse(fileInput)
51-
print(Gson().toJson(results))
88+
val results = mutableListOf<ValidatorResult>()
89+
90+
when {
91+
commandLine.hasOption(COMMAND_JSON_INPUT) -> {
92+
val jsonInput = commandLine.getOptionValue(COMMAND_JSON_INPUT)
93+
results.add(directionsResponseValidator.parseJson(jsonInput))
94+
}
95+
96+
commandLine.hasOption(COMMAND_FILE_INPUT) -> {
97+
val fileInput = commandLine.getOptionValue(COMMAND_FILE_INPUT)
98+
results.addAll(directionsResponseValidator.parseFile(fileInput))
99+
}
100+
}
101+
102+
val failure = !results.all { it.success } ||
103+
(commandLine.hasOption(COMMAND_FAIL_ON_CONVERT_BACK) && !results.all { it.convertsBack })
104+
105+
printResult(results, failure, commandLine.hasOption(COMMAND_PRETTY_PRINT))
106+
107+
if (failure) {
108+
System.exit(1)
109+
}
52110
}
53111

54112
private fun printHelp(options: Options) {
55-
val syntax = "java -jar services-cli/build/libs/services-cli-all.jar"
56-
HelpFormatter().printHelp(syntax, options)
113+
val syntax = "java -jar services-cli/build/libs/services-cli.jar <option>"
114+
val header = "\nMapbox Java CLI. Validates DirectionsApi responses." +
115+
"CHeck correctness of the provided JSON (files or strings). " +
116+
"Process with code 1 on parse errors or (optionally) on conversion " +
117+
"back. Exits with code 0 on success. Returns JSON formatted result."
118+
HelpFormatter().printHelp(syntax, header, options, "")
119+
}
120+
121+
private fun printResult(
122+
results: List<ValidatorResult>, failure: Boolean, prettyPrint: Boolean
123+
) {
124+
var message = GsonBuilder().also { if (prettyPrint) it.setPrettyPrinting() }
125+
.registerTypeAdapterFactory(ValidatorInput.gsonAdapter()).create().toJson(results)
126+
if (prettyPrint) {
127+
message = (if (failure) "\u001B[31m" else "\u001B[32m") + "$message\u001B[0m\n"
128+
}
129+
print(message)
57130
}
58131
}

services-cli/src/main/kotlin/com/mapbox/services/cli/validator/DirectionsResponseValidator.kt

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,50 @@ class DirectionsResponseValidator {
1313
* @param filePath path to the json file or directory
1414
* @return results for all the files
1515
*/
16-
fun parse(filePath: String): List<ValidatorResult> {
16+
fun parseFile(filePath: String): List<ValidatorResult> {
1717
val inputFile = File(filePath)
1818

1919
val results = mutableListOf<ValidatorResult>()
2020
inputFile.forEachFile { file ->
21-
val result = validateJson(file)
21+
val result = validateFile(file, ValidatorInput.File(file.name))
2222
results.add(result)
2323
}
2424
return results
2525
}
2626

27+
/**
28+
* @param json JSON formatted string
29+
* @return results parsed from the JSON
30+
*/
31+
fun parseJson(json: String): ValidatorResult = validateJson(json, ValidatorInput.Json)
32+
2733
private fun File.forEachFile(function: (File) -> Unit) = walk()
2834
.filter { !it.isDirectory }
2935
.forEach(function)
3036

31-
private fun validateJson(file: File): ValidatorResult {
37+
private fun validateFile(file: File, input: ValidatorInput): ValidatorResult {
3238
val json = file.readText(UTF_8)
39+
return validateJson(json, input)
40+
}
41+
42+
private fun validateJson(json: String, input: ValidatorInput): ValidatorResult {
3343
return try {
3444
val directionsResponse = DirectionsResponse.fromJson(json)
3545
val toJson = directionsResponse.toJson()
3646
val convertsBack = json == toJson
3747
ValidatorResult(
38-
filename = file.name,
48+
input = input,
3949
success = true,
4050
convertsBack = convertsBack
4151
)
4252
} catch (throwable: Throwable) {
4353
ValidatorResult(
44-
filename = file.name,
54+
input = input,
4555
success = false,
4656
convertsBack = false,
47-
throwable = throwable
57+
error = throwable.message
4858
)
4959
}
5060
}
61+
5162
}
Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,35 @@
11
package com.mapbox.services.cli.validator
22

33
import com.google.gson.annotations.SerializedName
4+
import com.mapbox.geojson.internal.typeadapters.RuntimeTypeAdapterFactory
45

56
data class ValidatorResult(
6-
val filename: String,
7+
val input: ValidatorInput,
78
val success: Boolean,
89
@SerializedName("converts_back")
910
val convertsBack: Boolean,
10-
val throwable: Throwable? = null
11+
val error: String? = null
1112
)
13+
14+
15+
sealed class ValidatorInput {
16+
data class File(val name: String) : ValidatorInput()
17+
object Json : ValidatorInput()
18+
19+
companion object {
20+
fun gsonAdapter(): RuntimeTypeAdapterFactory<ValidatorInput> {
21+
return RuntimeTypeAdapterFactory
22+
.of<ValidatorInput>(ValidatorInput::class.java, "type")
23+
.registerSubtype(
24+
File::class.java,
25+
File::class.simpleName
26+
)
27+
.registerSubtype(
28+
Json::class.java,
29+
Json::class.simpleName
30+
)
31+
}
32+
}
33+
}
34+
35+

services-cli/src/test/kotlin/com/mapbox/services/cli/validator/DirectionsResponseValidatorTest.kt

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.mapbox.services.cli.validator
22

33
import com.google.common.truth.Truth.assertThat
44
import org.junit.Test
5+
import java.io.File
56
import kotlin.test.assertTrue
67

78
class DirectionsResponseValidatorTest {
@@ -12,30 +13,55 @@ class DirectionsResponseValidatorTest {
1213
fun `should successfully read file with DirectionsResponse json`() {
1314
val testFile = "./src/test/resources/directions_v5.json"
1415

15-
val results = directionsResponseValidator.parse(testFile)
16+
val results = directionsResponseValidator.parseFile(testFile)
1617

1718
assertTrue(results[0].success)
1819
assertThat(results[0].filename).isEqualTo("directions_v5.json")
19-
assertThat(results[0].throwable).isNull()
20+
assertThat(results[0].error).isNull()
21+
}
22+
23+
@Test
24+
fun `should successfully read string with correct DirectionsResponse json`() {
25+
val testText = File("./src/test/resources/directions_v5.json").readText(Charsets.UTF_8)
26+
27+
val result = directionsResponseValidator.parseJson(testText)
28+
29+
assertTrue(result.success)
30+
assertThat(result.input).isEqualTo(ValidatorInput.Json)
31+
assertThat(result.error).isNull()
2032
}
2133

2234
@Test
2335
fun `should detect if file is not DirectionsResponse json`() {
2436
val testFile = "./src/test/resources/geojson_feature.json"
2537

26-
val results = directionsResponseValidator.parse(testFile)
38+
val results = directionsResponseValidator.parseFile(testFile)
2739

2840
assertThat(results[0].success).isFalse()
2941
assertThat(results[0].filename).isEqualTo("geojson_feature.json")
30-
assertThat(results[0].throwable).isNotNull()
42+
assertThat(results[0].error).isNotNull()
3143
assertThat(results[0].convertsBack).isFalse()
3244
}
3345

46+
@Test
47+
fun `should detect if provided string is not DirectionsResponse formatted json`() {
48+
val testText = File("./src/test/resources/geojson_feature.json")
49+
.readText(Charsets.UTF_8)
50+
51+
52+
val result = directionsResponseValidator.parseJson(testText)
53+
54+
assertThat(result.success).isFalse()
55+
assertThat(result.input).isEqualTo(ValidatorInput.Json)
56+
assertThat(result.error).isNotNull()
57+
assertThat(result.convertsBack).isFalse()
58+
}
59+
3460
@Test(expected = Exception::class)
35-
fun `should crash when json does not exist`() {
61+
fun `should crash when json file does not exist`() {
3662
val testFile = "not a real file path"
3763

38-
val results = directionsResponseValidator.parse(testFile)
64+
val results = directionsResponseValidator.parseFile(testFile)
3965

4066
assertTrue(results[0].success)
4167
}
@@ -44,8 +70,10 @@ class DirectionsResponseValidatorTest {
4470
fun `should parse every file in the directory`() {
4571
val testFile = "./src/test/resources"
4672

47-
val results = directionsResponseValidator.parse(testFile)
73+
val results = directionsResponseValidator.parseFile(testFile)
4874

4975
assertThat(results.size).isGreaterThan(1)
5076
}
77+
78+
private val ValidatorResult.filename: String? get() = (input as? ValidatorInput.File)?.name
5179
}

0 commit comments

Comments
 (0)