diff --git a/build.sbt b/build.sbt index 5bb05e8..3465445 100644 --- a/build.sbt +++ b/build.sbt @@ -41,6 +41,7 @@ lazy val root = (project in file(".")) "org.scala-lang" % "scala-library" % Versions.scala, "org.scala-lang" % "scala-compiler" % Versions.scala, "org.scala-lang.modules" %% "scala-parser-combinators" % Versions.scalaParsers, + "io.spray" %% "spray-json" % Versions.sprayJson, "org.scalaz" %% "scalaz-core" % Versions.scalaz, "com.novocode" % "junit-interface" % "0.11" % "test", "org.scalacheck" %% "scalacheck" % "1.13.4" % "test", diff --git a/project/Versions.scala b/project/Versions.scala index 888dad8..c480710 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -3,4 +3,5 @@ object Versions { val scalaz = "7.2.20" val scalaParsers = "1.1.0" val idea = "2019.1" + val sprayJson = "1.3.5" } \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index c1b7362..df1949a 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -1,7 +1,7 @@ org.psliwa.idea.composer PHP composer.json support - 1.0.27 + 1.0.27-spray-json psliwa Some(a) + private def tryJsonObject(a: JsValue): Option[JsObject] = a match { + case a@JsObject(_) => Some(a) case _ => None } - private def tryJsonArray(a: Any): Option[JSONArray] = a match { - case a@JSONArray(_) => Some(a) + private def tryJsonArray(a: JsValue): Option[JsArray] = a match { + case a@JsArray(_) => Some(a) case _ => None } - private def tryJsonString(a: Any): Option[String] = a match { - case a: String => Some(a) + private def tryJsonString(a: JsValue): Option[String] = a match { + case JsString(a) => Some(a) case _ => None } def parseVersions(data: String): Try[Seq[String]] = { val versions = for { - result <- JSON.parseRaw(data) + result <- JSON.parse(data) o <- tryJsonObject(result) - pkg <- o.obj.get("package") + pkg <- o.fields.get("package") pkg <- tryJsonObject(pkg) - versions <- pkg.obj.get("versions") + versions <- pkg.fields.get("versions") versions <- tryJsonObject(versions) - } yield versions.obj.keys.toList + } yield versions.fields.keys.toList versions.map(Try(_)).getOrElse(Failure(new ParseException())) } @@ -56,11 +58,11 @@ object JsonParsers { import scalaz.Scalaz._ def parse(property: String, dev: Boolean) = for { - result <- JSON.parseRaw(data) + result <- JSON.parse(data) o <- tryJsonObject(result) - packagesElement <- o.obj.get(property) + packagesElement <- o.fields.get(property) packagesArray <- tryJsonArray(packagesElement) - packages <- packagesArray.list.traverse(createLockPackage(dev)) + packages <- packagesArray.elements.traverse(createLockPackage(dev)) } yield packages val packages = for { @@ -74,23 +76,23 @@ object JsonParsers { } } - private def createLockPackage(dev: Boolean)(maybeJsonObject: Any): Option[ComposerPackage] = { + private def createLockPackage(dev: Boolean)(maybeJsonObject: JsValue): Option[ComposerPackage] = { for { jsonObject <- tryJsonObject(maybeJsonObject) - name <- jsonObject.obj.get("name").map(_.toString) - version <- jsonObject.obj.get("version").map(_.toString) - homepage = jsonObject.obj.get("homepage").map(_.toString) + name <- jsonObject.fields.get("name").flatMap(tryJsonString) + version <- jsonObject.fields.get("version").flatMap(tryJsonString) + homepage = jsonObject.fields.get("homepage").flatMap(tryJsonString) } yield ComposerPackage(name, version, dev, homepage) } private def parsePackagesFromPackagesJson(data: String): Try[RepositoryPackages] = { - def getPackagesFrom(json: Any): Option[Map[String,Seq[String]]] = { + def getPackagesFrom(json: JsValue): Option[Map[String,Seq[String]]] = { val packages: Map[String,Seq[String]] = (for { obj <- tryJsonObject(json).toList - packageName <- obj.obj.keys - packageObject <- obj.obj.get(packageName) + packageName <- obj.fields.keys + packageObject <- obj.fields.get(packageName) packageObject <- tryJsonObject(packageObject) - versions <- Option(packageObject.obj.keys.toSeq) + versions <- Option(packageObject.fields.keys.toSeq) } yield packageName -> versions).toMap Option(packages) @@ -100,32 +102,32 @@ object JsonParsers { val packages = for { root <- maybeRoot - packagesElement <- root.obj.get("packages") + packagesElement <- root.fields.get("packages") packages <- getPackagesFrom(packagesElement) } yield packages val includes = for { root <- maybeRoot.toList - includesElement <- root.obj.get("includes").toList + includesElement <- root.fields.get("includes").toList includesElement <- tryJsonObject(includesElement).toList - include <- includesElement.obj.keys.toList + include <- includesElement.fields.keys.toList } yield include packages.map(pkgs => Try(RepositoryPackages(pkgs, includes))).getOrElse(tryMonoid.zero) } - private def parse(data: String): Option[JSONObject] = for { - result <- JSON.parseRaw(data) + private def parse(data: String): Option[JsObject] = for { + result <- JSON.parse(data) o <- tryJsonObject(result) } yield o private def parsePackageFromComposerJson(data: String): Try[RepositoryPackages] = { (for { root <- parse(data) - name <- root.obj.get("name") + name <- root.fields.get("name") name <- tryJsonString(name) versions <- (for { - version <- root.obj.get("version") + version <- root.fields.get("version") version <- tryJsonString(version) } yield version).map(Seq(_)).orElse(Some(Seq.empty)) } yield RepositoryPackages(Map(name -> versions), Seq.empty)).map(Success(_)).getOrElse(tryMonoid.zero) diff --git a/src/main/scala/org/psliwa/idea/composerJson/json/Schema.scala b/src/main/scala/org/psliwa/idea/composerJson/json/Schema.scala index eb66172..8fef0c1 100644 --- a/src/main/scala/org/psliwa/idea/composerJson/json/Schema.scala +++ b/src/main/scala/org/psliwa/idea/composerJson/json/Schema.scala @@ -1,9 +1,11 @@ package org.psliwa.idea.composerJson.json -import scala.language.{postfixOps, higherKinds, implicitConversions} +import org.psliwa.idea.composerJson.util.parsers.JSON + +import scala.language.{higherKinds, implicitConversions, postfixOps} import scala.util.Try import scala.util.matching.Regex -import scala.util.parsing.json.{JSON, JSONArray, JSONObject, JSONType} +import spray.json._ import scalaz.Scalaz._ sealed trait Schema @@ -63,56 +65,56 @@ object Schema { def parse(s: String): Option[Schema] = { for { - rawJsonElement <- JSON.parseRaw(s) + rawJsonElement <- JSON.parse(s) jsonObject <- ensureJsonObject(rawJsonElement) - definitions <- tryDefinitions(jsonObject).orElse(Option(Map[String,Schema]())) + definitions <- tryDefinitions(jsonObject).orElse(Option(Map[String, Schema]())) schema <- jsonTypeToSchema(jsonObject, definitions) } yield schema } - private def tryDefinitions(jsonObject: JSONObject): Option[Map[String,Schema]] = for { - rawDefinitions <- jsonObject.obj.get("definitions").flatMap(ensureJsonObject) - resolvedDefinitions = resolveDefinitions(rawDefinitions.obj) - definitions = parseDefinitions(JSONObject(resolvedDefinitions)) + private def tryDefinitions(jsonObject: JsObject): Option[Map[String,Schema]] = for { + rawDefinitions <- jsonObject.fields.get("definitions").flatMap(ensureJsonObject) + resolvedDefinitions = resolveDefinitions(rawDefinitions.fields) + definitions = parseDefinitions(JsObject(resolvedDefinitions)) } yield definitions - private def resolveDefinitions(definitions: Map[String, Any]): Map[String, Any] = { + private def resolveDefinitions(definitions: Map[String, JsValue]): Map[String, JsValue] = { definitions.map { case(name, obj) => name -> resolveDefinition(obj, definitions)} } // performance is not perfect, because one definition may be resolved many times. If needed: FIXME - private def resolveDefinition(definition: Any, definitions: Map[String, Any]): Any = { - def definitionByReference(ref: Any): Option[Any] = for { + private def resolveDefinition(definition: JsValue, definitions: Map[String, JsValue]): JsValue = { + def definitionByReference(ref: JsValue): Option[JsValue] = for { refName <- ensureString(ref) name <- resolveRefName(refName) definition <- definitions.get(name).map(resolveDefinition(_, definitions)) } yield definition - def loop(json: Any): Any = json match { - case obj: JSONObject if obj.obj.contains("$ref") => obj.obj.get("$ref").flatMap(definitionByReference).getOrElse(obj) - case obj: JSONObject => JSONObject(obj.obj.map { case(name, value) => name -> loop(value) }) - case array: JSONArray => JSONArray(array.list.map(loop)) + def loop(json: JsValue): JsValue = json match { + case obj: JsObject if obj.fields.contains("$ref") => obj.fields.get("$ref").flatMap(definitionByReference).getOrElse(obj) + case obj: JsObject => JsObject(obj.fields.map { case(name, value) => name -> loop(value) }) + case array: JsArray => JsArray(array.elements.map(loop)) case other => other } loop(definition) } - private implicit def converterOps[A<:JSONType](c: Converter[A]): ConverterOps[A] = ConverterOps(c) - private type Converter[A<:JSONType] = (A, Map[String,Schema]) => Option[Schema] + private implicit def converterOps[A<:JsValue](c: Converter[A]): ConverterOps[A] = ConverterOps(c) + private type Converter[A<:JsValue] = (A, Map[String,Schema]) => Option[Schema] - private def jsonTypeToSchema: Converter[JSONType] = (t, defs) => t match { - case o@JSONObject(_) => jsonObjectToObjectSchema(o, defs) + private def jsonTypeToSchema: Converter[JsValue] = (t, defs) => t match { + case o@JsObject(_) => jsonObjectToObjectSchema(o, defs) case _ => None } - private def ensureJsonObject(t: Any): Option[JSONObject] = t match { - case o: JSONObject => Some(o) + private def ensureJsonObject(t: JsValue): Option[JsObject] = t match { + case o: JsObject => Some(o) case _ => None } - private def parseDefinitions(jsonObject: JSONObject): Map[String,Schema] = { - jsonObject.obj.flatMap{ case(name, definition) => { + private def parseDefinitions(jsonObject: JsObject): Map[String,Schema] = { + jsonObject.fields.flatMap{ case(name, definition) => { val maybeSchema = for { obj <- ensureJsonObject(definition) schema <- jsonObjectToSchema(obj, Map()) @@ -124,62 +126,63 @@ object Schema { import OptionOps._ - private def jsonObjectToStringSchema: Converter[JSONObject] = (o, defs) => { + private def jsonObjectToStringSchema: Converter[JsObject] = (o, defs) => { for { o <- ensureType(o, "string") - format <- o.obj.get("format").orElse(Some("any")) - pattern = Try { o.obj.get("pattern").map(_.toString.r) }.toOption.flatten.map(new PatternFormat(_)) - } yield pattern.map(SString).getOrElse(stringsForFormat.getOrElse(format.toString, SAnyString)) + formatValue <- o.fields.get("format").orElse(Some(JsString("any"))) + format <- ensureString(formatValue) + pattern = Try { o.fields.get("pattern").flatMap(ensureString).map(_.r) }.toOption.flatten.map(new PatternFormat(_)) + } yield pattern.map(SString).getOrElse(stringsForFormat.getOrElse(format, SAnyString)) } - private def jsonObjectToNumberSchema: Converter[JSONObject] = (o, _) => jsonObjectTo(SNumber, "integer")(o) - private def jsonObjectToBooleanSchema: Converter[JSONObject] = (o, _) => jsonObjectTo(SBoolean, "boolean")(o) + private def jsonObjectToNumberSchema: Converter[JsObject] = (o, _) => jsonObjectTo(SNumber, "integer")(o) + private def jsonObjectToBooleanSchema: Converter[JsObject] = (o, _) => jsonObjectTo(SBoolean, "boolean")(o) - private def jsonObjectTo(s: Schema, t: String)(o: JSONObject): Option[Schema] = ensureType(o, t).map(_ => s) - private def ensureType(o: JSONObject, t: String) = o.obj.get("type").filter(_ == t).map(_ => o) + private def jsonObjectTo(s: Schema, t: String)(o: JsObject): Option[Schema] = ensureType(o, t).map(_ => s) + private def ensureType(o: JsObject, t: String) = o.fields.get("type").filter(_ == JsString(t)).map(_ => o) - private def jsonObjectToSchema: Converter[JSONObject] = ( + private def jsonObjectToSchema: Converter[JsObject] = ( jsonObjectToObjectSchema | jsonObjectToStringSchema | jsonObjectToNumberSchema | jsonObjectToBooleanSchema | jsonObjectToArraySchema | jsonObjectToEnum | jsonObjectToOr | jsonObjectToPackagesSchema | jsonObjectToFilePathSchema | jsonObjectToFilePathsSchema | jsonObjectToRef ) - private def jsonObjectToOr: Converter[JSONObject] = jsonObjectToComplexOr | jsonObjectToSimpleOr + private def jsonObjectToOr: Converter[JsObject] = jsonObjectToComplexOr | jsonObjectToSimpleOr - private def jsonObjectToComplexOr: Converter[JSONObject] = (t, defs) => { - if(t.obj.contains("oneOf")) { + private def jsonObjectToComplexOr: Converter[JsObject] = (t, defs) => { + if(t.fields.contains("oneOf")) { for { - arr <- t.obj.get("oneOf").flatMap(tryJsonArray) - alternatives <- arr.list.traverse(tryJsonObject(_).flatMap(jsonObjectToSchema(_, defs))) + arr <- t.fields.get("oneOf").flatMap(tryJsonArray) + alternatives <- arr.elements.toList.traverse(tryJsonObject(_).flatMap(jsonObjectToSchema(_, defs))) } yield SOr(alternatives) } else { None } } - private def jsonObjectToSimpleOr: Converter[JSONObject] = (t, defs) => { + private def jsonObjectToSimpleOr: Converter[JsObject] = (t, defs) => { for { - arr <- t.obj.get("type").flatMap(tryJsonArray) - stringValues <- arr.list.traverse(tryString) - schemaValues <- stringValues.traverse[Option,Schema](s => jsonObjectToSchema(JSONObject(Map("type" -> s)), defs)) + arr <- t.fields.get("type").flatMap(tryJsonArray) + stringValues <- arr.elements.toList.traverse(tryString) + schemaValues <- stringValues.traverse[Option,Schema](s => jsonObjectToSchema(JsObject(Map("type" -> JsString(s))), defs)) } yield SOr(schemaValues) } - private def jsonObjectToEnum: Converter[JSONObject] = (t, defs) => { - if(t.obj.contains("enum")) { + private def jsonObjectToEnum: Converter[JsObject] = (t, defs) => { + if(t.fields.contains("enum")) { for { - arr <- t.obj.get("enum").flatMap(tryJsonArray) - values <- arr.list.traverse(tryString) + arr <- t.fields.get("enum").flatMap(tryJsonArray) + values <- arr.elements.toList.traverse(tryString) } yield SStringChoice(values) } else { None } } - private def jsonObjectToRef: Converter[JSONObject] = (t, defs) => { + private def jsonObjectToRef: Converter[JsObject] = (t, defs) => { for { - ref <- t.obj.get("$ref") + ref <- t.fields.get("$ref") ref <- ensureString(ref) name <- resolveRefName(ref) schema <- defs.get(name) @@ -192,63 +195,65 @@ object Schema { else None } - private def ensureString(a: Any) = a match { - case x: String => Some(x) + private def ensureString(a: JsValue) = a match { + case JsString(x) => Some(x) case _ => None } - private def jsonObjectToObjectSchema: Converter[JSONObject] = (t, defs) => { - if(t.obj.get("type").contains("object")) { - val propertiesObject = t.obj.getOrElse("properties", JSONObject(Map())) - val patternPropertiesObject = t.obj.getOrElse("patternProperties", JSONObject(Map())) + private def jsonObjectToObjectSchema: Converter[JsObject] = (t, defs) => { + if(t.fields.get("type").contains(JsString("object"))) { + val propertiesObject = t.fields.getOrElse("properties", JsObject(Map.empty[String, JsValue])) + val patternPropertiesObject = t.fields.getOrElse("patternProperties", JsObject(Map.empty[String, JsValue])) for { properties <- jsonObjectToProperties(propertiesObject, defs) patternProperties <- jsonObjectToPatternProperties(patternPropertiesObject, defs) - additionalProperties <- booleanProperty("additionalProperties")(t.obj).orElse(Some(true)) + additionalProperties <- booleanProperty("additionalProperties")(t.fields).orElse(Some(true)) } yield SObject(new Properties(properties, patternProperties), additionalProperties) } else { None } } - private def jsonObjectToProperties(maybeJsonObject: Any, defs: Map[String,Schema]): Option[Map[String,Property]] = { + private def jsonObjectToProperties(maybeJsonObject: JsValue, defs: Map[String,Schema]): Option[Map[String,Property]] = { for { jsonObject <- tryJsonObject(maybeJsonObject) - properties <- jsonObject.obj.traverse(tryProperty(defs)) + properties <- jsonObject.fields.traverse(tryProperty(defs)) } yield properties } - private def tryProperty[K](defs: Map[String,Schema])(jsonObject: Any): Option[Property] = jsonObject match { - case o@JSONObject(_) => for { - required <- booleanProperty("required")(o.obj).orElse(Some(false)) - description <- stringProperty("description")(o.obj).orElse(Some("")) + private def tryProperty[K](defs: Map[String,Schema])(jsonObject: JsValue): Option[Property] = jsonObject match { + case o@JsObject(_) => for { + required <- booleanProperty("required")(o.fields).orElse(Some(false)) + description <- stringProperty("description")(o.fields).orElse(Some("")) property <- jsonObjectToSchema(o, defs).map(Property(_, required, description)) } yield property case _ => None } - private def jsonObjectToPatternProperties(maybeJsonObject: Any, defs: Map[String,Schema]): Option[Map[Regex,Property]] = { + private def jsonObjectToPatternProperties(maybeJsonObject: JsValue, defs: Map[String,Schema]): Option[Map[Regex,Property]] = { for { jsonObject <- tryJsonObject(maybeJsonObject) - properties <- jsonObject.obj.map(prop => prop._1.r -> prop._2).traverse(tryProperty(defs)) + properties <- jsonObject.fields.map(prop => prop._1.r -> prop._2).traverse(tryProperty(defs)) } yield properties } - private def booleanProperty(k: String)(map: Map[String,Any]): Option[Boolean] = map.get(k).flatMap(toBoolean) - private def stringProperty(k: String)(map: Map[String,Any]): Option[String] = map.get(k).map(_.toString) + private def booleanProperty(k: String)(map: Map[String,JsValue]): Option[Boolean] = map.get(k).flatMap(toBoolean) + private def stringProperty(k: String)(map: Map[String,JsValue]): Option[String] = map.get(k).flatMap { + case JsString(s) => Some(s) + case _ => None + } - private def toBoolean(o: Any): Option[Boolean] = { - try { - Some(o.toString.toBoolean) - } catch { - case _: IllegalArgumentException => None + private def toBoolean(o: JsValue): Option[Boolean] = { + o match { + case JsBoolean(f) => Some(f) + case _ => None } } - private def jsonObjectToArraySchema: Converter[JSONObject] = (t, defs) => { - if(t.obj.get("type").contains("array")) { - val maybeItems = t.obj.get("items") + private def jsonObjectToArraySchema: Converter[JsObject] = (t, defs) => { + if(t.fields.get("type").contains(JsString("array"))) { + val maybeItems = t.fields.get("items") val maybeItemsType = for { items <- maybeItems @@ -262,44 +267,44 @@ object Schema { } } - private def jsonObjectToPackagesSchema: Converter[JSONObject] = (t, defs) => { - if(t.obj.get("type").contains("packages")) { + private def jsonObjectToPackagesSchema: Converter[JsObject] = (t, defs) => { + if(t.fields.get("type").contains(JsString("packages"))) { Some(SPackages) } else { None } } - private def jsonObjectToFilePathSchema: Converter[JSONObject] = (o, _) => jsonObjectToSchemaWithBooleanArg(SFilePath, "filePath", "existingFilePath")(o) + private def jsonObjectToFilePathSchema: Converter[JsObject] = (o, _) => jsonObjectToSchemaWithBooleanArg(SFilePath, "filePath", "existingFilePath")(o) - private def jsonObjectToSchemaWithBooleanArg(f: Boolean => Schema, typeProp: String, booleanProp: String)(jsonObject: JSONObject): Option[Schema] = { - if(jsonObject.obj.get("type").contains(typeProp)) { - Some(f(booleanProperty(booleanProp)(jsonObject.obj).getOrElse(true))) + private def jsonObjectToSchemaWithBooleanArg(f: Boolean => Schema, typeProp: String, booleanProp: String)(jsonObject: JsObject): Option[Schema] = { + if(jsonObject.fields.get("type").contains(JsString(typeProp))) { + Some(f(booleanProperty(booleanProp)(jsonObject.fields).getOrElse(true))) } else { None } } - private def jsonObjectToFilePathsSchema: Converter[JSONObject] = (o, _) => jsonObjectToSchemaWithBooleanArg(SFilePaths, "filePaths", "existingFilePath")(o) + private def jsonObjectToFilePathsSchema: Converter[JsObject] = (o, _) => jsonObjectToSchemaWithBooleanArg(SFilePaths, "filePaths", "existingFilePath")(o) private object OptionOps { - def tryJsonObject(a: Any): Option[JSONObject] = a match { - case o@JSONObject(_) => Some(o) + def tryJsonObject(a: JsValue): Option[JsObject] = a match { + case o@JsObject(_) => Some(o) case _ => None } - def tryJsonArray(a: Any): Option[JSONArray] = a match { - case a@JSONArray(_) => Some(a) + def tryJsonArray(a: JsValue): Option[JsArray] = a match { + case a@JsArray(_) => Some(a) case _ => None } - def tryString(a: Any): Option[String] = a match { - case s: String => Some(s) + def tryString(a: JsValue): Option[String] = a match { + case s: JsString => Some(s.value) case _ => None } } - private case class ConverterOps[A<:JSONType](c: Converter[A]) { + private case class ConverterOps[A<:JsValue](c: Converter[A]) { def |(c2: Converter[A]): Converter[A] = (t, defs) => c(t, defs).orElse(c2(t, defs)) } } \ No newline at end of file diff --git a/src/main/scala/org/psliwa/idea/composerJson/util/parsers/JSON.scala b/src/main/scala/org/psliwa/idea/composerJson/util/parsers/JSON.scala new file mode 100644 index 0000000..6ffd8ec --- /dev/null +++ b/src/main/scala/org/psliwa/idea/composerJson/util/parsers/JSON.scala @@ -0,0 +1,9 @@ +package org.psliwa.idea.composerJson.util.parsers + +import spray.json.{JsValue, JsonParser} + +import scala.util.Try + +object JSON { + def parse(data: String): Option[JsValue] = Try { JsonParser(data) }.toOption +} diff --git a/src/test/scala/org/psliwa/idea/composerJson/composer/parsers/JsonParsersTest.scala b/src/test/scala/org/psliwa/idea/composerJson/composer/parsers/JsonParsersTest.scala index f56bd68..5940f3e 100644 --- a/src/test/scala/org/psliwa/idea/composerJson/composer/parsers/JsonParsersTest.scala +++ b/src/test/scala/org/psliwa/idea/composerJson/composer/parsers/JsonParsersTest.scala @@ -55,7 +55,7 @@ class JsonParsersTest { val result = JsonParsers.parseVersions(json) assertTrue(result.isSuccess) - assertEquals(List("dev-master", "1.0.0", "1.0.1"), result.get) + assertEquals(Set("dev-master", "1.0.0", "1.0.1"), result.get.toSet) } @Test