Skip to content
This repository has been archived by the owner on Aug 13, 2024. It is now read-only.

Commit

Permalink
Add configuration options for self-describing JSON Schema (close #17, c…
Browse files Browse the repository at this point in the history
…lose #19)
  • Loading branch information
chuwy committed Jun 29, 2015
1 parent 747fc2e commit d91700c
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 18 deletions.
53 changes: 42 additions & 11 deletions src/main/scala/com.snowplowanalytics/schemaguru/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,42 @@ object Main extends App with FileSystemJsonGetters {
val schemaByArgument = parser.option[String](List("schema-by"), "JSON Path", "Path of Schema title")
val outputDirArgument = parser.option[String](List("output-dir"), "directory", "Directory path for multiple Schemas")

// self-describing schema arguments
val vendorArgument = parser.option[String](List("vendor"), "name", "Vendor name for self-describing schema")
val nameArgument = parser.option[String](List("name"), "name", "Schema name for self-describing schema")
val versionArgument = parser.option[String](List("schemaver"), "version", "Schema version (in SchemaVer format) for self-describing schema")


parser.parse(args)

// Get arguments for JSON Path segmentation and validate it
// Get arguments for JSON Path segmentation and validate them
val segmentSchema = (schemaByArgument.value, outputDirArgument.value) match {
case (Some(jsonPath), Some(dirPath)) => Some((jsonPath, dirPath))
case (None, None) => None
case _ => parser.usage("--schema-by and --output-dir arguments need to be used in conjunction")
case (None, None) => None
case _ => parser.usage("--schema-by and --output-dir arguments need to be used in conjunction.")
}

// Get arguments for self-describing schema and validate them
val selfDescribing = (vendorArgument.value, nameArgument.value, versionArgument.value) match {
case (Some(vendor), Some(name), version) => {
if (!vendor.matches("([A-Za-z0-9\\-\\_\\.]+)")) {
parser.usage("--vendor argument must consist of only letters, numbers, hyphens, underscores and dots")
} else if (!name.matches("([A-Za-z0-9\\-\\_]+)")) {
parser.usage("--name argument must consist of only letters, numbers, hyphens and underscores")
} else if (version.isDefined && !version.get.matches("\\d+\\-\\d+\\-\\d+")) {
parser.usage("--schemaver argument must be in SchemaVer format (example: 1-1-0)")
}
Some(SelfDescribingSchema(vendor, name, version))
}
case (None, None, None) => None
case _ => parser.usage("--vendor, --name and --schemaver arguments need to be used in conjunction.")
}

val enumCardinality = cardinalityArgument.value.getOrElse(0)

// Check whether provided path exists
List(directoryArgument.value, fileArgument.value).flatten.headOption match {
case None => parser.usage("either --dir or --file argument must be provided")
case None => parser.usage("Either --dir or --file argument must be provided.")
case Some(path) => {
if (Files.exists(Paths.get(path))) () // everything is OK
else parser.usage(s"Path $path does exists")
Expand All @@ -73,7 +95,7 @@ object Main extends App with FileSystemJsonGetters {
case _ => getJsonsFromFolder(dir)
}
case None => fileArgument.value match {
case None => parser.usage("either --dir or --file argument must be provided")
case None => parser.usage("Either --dir or --file argument must be provided.")
case Some(file) => ndjsonFlag.value match {
case Some(true) => getJsonFromNDFile(file)
case _ => List(getJsonFromFile(file))
Expand All @@ -87,7 +109,7 @@ object Main extends App with FileSystemJsonGetters {
segmentSchema match {
case None => {
val result = SchemaGuru.convertsJsonsToSchema(someJsons, enumCardinality)
outputResult(result, outputFileArgument.value)
outputResult(result, outputFileArgument.value, selfDescribing)
}
case Some((path, dir)) => {
val nameToJsonsMapping = JsonPathExtractor.mapByPath(path, jsonList)
Expand All @@ -98,7 +120,7 @@ object Main extends App with FileSystemJsonGetters {
val file =
if (key == "$SchemaGuruFailed") None
else Some(new File(dir, fileName).getAbsolutePath)
outputResult(result, file)
outputResult(result, file, selfDescribing)
}
}
}
Expand All @@ -107,18 +129,27 @@ object Main extends App with FileSystemJsonGetters {
}

/**
* Prints Schema, warnings and errors
* Print Schema, warnings and errors
*
* @param result Schema Guru result containing all information
* @param outputFile optional path to file for schema output
* @param selfDescribingInfo optional info to make shema self-describing
*/
def outputResult(result: SchemaGuruResult, outputFile: Option[String]): Unit = {
def outputResult(result: SchemaGuruResult, outputFile: Option[String], selfDescribingInfo: Option[SelfDescribingSchema]): Unit = {
// Make schema self-describing if necessary
val schema: JValue = selfDescribingInfo match {
case None => result.schema
case Some(description) => description.descriptSchema(result.schema)
}

// Print JsonSchema to file or stdout
outputFile match {
case Some(file) => {
val output = new java.io.PrintWriter(file)
output.write(pretty(render(result.schema)))
output.write(pretty(render(schema)))
output.close()
}
case None => println(pretty(render(result.schema)))
case None => println(pretty(render(schema)))
}

// Print errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import Scalaz._
// json4s
import org.json4s._
import org.json4s.JsonDSL._
import org.json4s.jackson.JsonMethods.pretty

// This library
import json.SchemaHelpers._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ package com.snowplowanalytics.schemaguru
package json

// json4s

import org.json4s._
import org.json4s.JsonDSL._

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,14 @@ object JsonPathExtractor {

val mapper = new ObjectMapper

def parseJson(s: String) =
mapper.readValue(s, classOf[Object])
/**
* Parse JSON string as raw JVM object to work with jackson
*
* @param json string containing JSON
* @return raw JVM object representing JSON
*/
def parseJson(json: String): Object =
mapper.readValue(json, classOf[Object])

/**
* Add default values for some exceptional cases and
Expand All @@ -46,11 +52,11 @@ object JsonPathExtractor {
val key = try {
str.toString
} catch {
case _: NullPointerException => "null"
case _: NullPointerException => "unmatched"
}
if (key.trim.length == 0) "Empty"
if (key.trim.length == 0) "unmatched"
else key.slice(0, 30).replaceAll("[^a-zA-Z0-9.-]", "_")
case None => "NoSuchPath"
case None => "unmatched"
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2015 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
package com.snowplowanalytics.schemaguru.utils

// json4s
import org.json4s._
import org.json4s.JsonDSL._

case class SelfDescribingSchema(vendor: String, name: String, version: Option[String]) {
val selfDescribingSchemaURI = "http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0#"

def descriptSchema(schema: JValue): JValue = {
val uri: JObject = ("$schema", selfDescribingSchemaURI)

val selfObject: JObject = ("self",
("vendor", vendor) ~
("name", name) ~
("version", version.getOrElse("0-1-0")) ~
("format", "jsonschema"))

uri.merge(selfObject).merge(schema)
}
}

0 comments on commit d91700c

Please sign in to comment.