Skip to content

Commit

Permalink
Add basic server
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelmior committed Mar 4, 2025
0 parents commit 654d36f
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
logs
target
/.bsp
/.idea
/.idea_modules
/.classpath
/.project
/.settings
/RUNNING_PID
137 changes: 137 additions & 0 deletions app/controllers/JsonoidController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package controllers

import scala.collection.mutable.Map

import javax.inject._
import play.api._
import play.api.libs.json._
import play.api.mvc._

import io.github.dataunitylab.jsonoid.discovery.{DiscoverSchema, JsonoidParams}
import io.github.dataunitylab.jsonoid.discovery.schemas.{
JsonSchema,
PropertySets,
ZeroSchema
}

import play.api.libs.{ json => pjson }
import org.{ json4s => j4s }

object Conversions {
def toJson4s(json: play.api.libs.json.JsValue):org.json4s.JValue = json match {
case pjson.JsString(str) => j4s.JString(str)
case pjson.JsNull => j4s.JNull
case pjson.JsBoolean(value) => j4s.JBool(value)
case pjson.JsNumber(value) => j4s.JDecimal(value)
case pjson.JsArray(items) => j4s.JArray(items.map(toJson4s(_)).toList)
case pjson.JsObject(items) => j4s.JObject(items.map { case (k, v) => k -> toJson4s(v)}.toList)
}

def toPlayJson(json: org.json4s.JValue): play.api.libs.json.JsValue = json match {
case j4s.JString(str) => pjson.JsString(str)
case j4s.JNothing => pjson.JsNull
case j4s.JNull => pjson.JsNull
case j4s.JDecimal(value) => pjson.JsNumber(value)
case j4s.JDouble(value) => pjson.JsNumber(value)
case j4s.JInt(value) => pjson.JsNumber(BigDecimal(value))
case j4s.JLong(value) => pjson.JsNumber(BigDecimal(value))
case j4s.JBool(value) => pjson.JsBoolean(value)
case j4s.JSet(fields) => pjson.JsArray(fields.toList.map(toPlayJson(_)))
case j4s.JArray(fields) => pjson.JsArray(fields.map(toPlayJson(_)))
case j4s.JObject(fields) => pjson.JsObject(fields.map { case (k, v) => k -> toPlayJson(v)}.toMap)
}

def toPlayJson(json: org.json4s.JObject): play.api.libs.json.JsObject =
pjson.JsObject(json.obj.map { case (k, v) => k -> toPlayJson(v)}.toMap)
}

final case class SchemaParams(name: String, propSet: Option[String] = None)
object SchemaParams {
implicit val schemaParamsRead: Reads[SchemaParams] = Json.reads[SchemaParams]
}
import SchemaParams._

/**
* This controller creates an `Action` to handle HTTP requests to the
* application's home page.
*/
@Singleton
class JsonoidController @Inject()(val cc: ControllerComponents) extends AbstractController(cc) {
private val schemas = Map.empty[String, JsonSchema[_]]
private val jsonoidParams = Map.empty[String, JsonoidParams]

def status() = Action { implicit request: Request[AnyContent] =>
Ok(Json.obj("status" -> "ok"))
}

def postSchema() = Action(parse.json) { implicit request =>
val schemaParamsResult = request.body.validate[SchemaParams]
schemaParamsResult.fold(
errors => {
BadRequest(Json.obj("status" -> "error", "error" -> JsError.toJson(errors)))
},
schemaParams => {
if (schemas.contains(schemaParams.name)) {
Conflict(Json.obj("error" -> "Schema already exists"))
} else {
val maybePropSet = schemaParams.propSet match {
case Some("All") =>
Some(PropertySets.AllProperties)
case Some("Simple") =>
Some(PropertySets.SimpleProperties)
case Some("Min") =>
Some(PropertySets.MinProperties)
case Some(_) =>
None
case None =>
Some(PropertySets.AllProperties)
}
maybePropSet match {
case Some(propSet) =>
schemas.put(schemaParams.name, ZeroSchema())
jsonoidParams.put(
schemaParams.name,
JsonoidParams().withPropertySet(propSet)
)
Created(Json.obj("status" -> "ok"))
case None =>
BadRequest(Json.obj("error" -> "Invalid property set"))
}
}
}
)
}

def deleteSchema(name: String) = Action { implicit request =>
if (schemas.contains(name)) {
schemas.remove(name)
Ok(Json.obj("status" -> "ok"))
} else {
NotFound(Json.obj("error" -> "Schema not found"))
}
}

def getSchema(name: String) = Action { implicit request =>
schemas.get(name) match {
case Some(schema) => Ok(Json.obj("schema" -> Conversions.toPlayJson(schema.toJson())))
case None => NotFound(Json.obj("error" -> "Schema not found"))
}
}

def putSchema(name: String) = Action(parse.json) { implicit request =>
schemas.get(name) match {
case Some(schema) =>
assert(jsonoidParams.contains(name))
val p = jsonoidParams.get(name).get
val newSchema = DiscoverSchema.discoverFromValue(Conversions.toJson4s(request.body))(p)
newSchema match {
case Some(newSchema) =>
schemas.put(name, schema.merge(newSchema)(p))
Ok(Json.obj("status" -> "ok"))
case None =>
BadRequest(Json.obj("error" -> "Invalid schema"))
}
case None => NotFound(Json.obj("error" -> "Schema not found"))
}
}
}
20 changes: 20 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name := """jsonoid-server"""
organization := "io.github.dataunitylab.jsonoid"

version := "1.0-SNAPSHOT"

lazy val root = (project in file(".")).enablePlugins(PlayScala)

scalaVersion := "2.13.16"

libraryDependencies += guice
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "7.0.1" % Test
libraryDependencies += "io.github.dataunitylab" %% "jsonoid-discovery" % "0.30.1"
libraryDependencies += "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.17.1"
libraryDependencies += "com.fasterxml.jackson.core" % "jackson-databind" % "2.17.1"

// Adds additional packages into Twirl
//TwirlKeys.templateImports += "io.github.dataunitylab.jsonoid.controllers._"

// Adds additional packages into conf/routes
// play.sbt.routes.RoutesKeys.routesImport += "io.github.dataunitylab.jsonoid.binders._"
11 changes: 11 additions & 0 deletions build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import mill._
import $ivy.`com.lihaoyi::mill-contrib-playlib:`, mill.playlib._

object jsonoidserver extends RootModule with PlayModule {

def scalaVersion = "2.13.16"
def playVersion = "3.0.6"
def twirlVersion = "2.0.1"

object test extends PlayTests
}
4 changes: 4 additions & 0 deletions conf/application.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# https://www.playframework.com/documentation/latest/Configuration
play.filters.hosts {
allowed = ["."]
}
50 changes: 50 additions & 0 deletions conf/logback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" ?>

<!-- https://www.playframework.com/documentation/latest/SettingsLogger -->

<!DOCTYPE configuration>

<configuration>
<import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
<import class="ch.qos.logback.classic.AsyncAppender"/>
<import class="ch.qos.logback.core.FileAppender"/>
<import class="ch.qos.logback.core.ConsoleAppender"/>

<appender name="FILE" class="FileAppender">
<file>${application.home:-.}/logs/application.log</file>
<encoder class="PatternLayoutEncoder">
<charset>UTF-8</charset>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %cyan(%logger{36}) %magenta(%X{pekkoSource}) %msg%n</pattern>
</encoder>
</appender>

<appender name="STDOUT" class="ConsoleAppender">
<!--
On Windows, enabling Jansi is recommended to benefit from color code interpretation on DOS command prompts,
which otherwise risk being sent ANSI escape sequences that they cannot interpret.
See https://logback.qos.ch/manual/layouts.html#coloring
-->
<!-- <withJansi>true</withJansi> -->
<encoder class="PatternLayoutEncoder">
<charset>UTF-8</charset>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %cyan(%logger{36}) %magenta(%X{pekkoSource}) %msg%n</pattern>
</encoder>
</appender>

<appender name="ASYNCFILE" class="AsyncAppender">
<appender-ref ref="FILE"/>
</appender>

<appender name="ASYNCSTDOUT" class="AsyncAppender">
<appender-ref ref="STDOUT"/>
</appender>

<logger name="play" level="INFO"/>
<logger name="application" level="DEBUG"/>

<root level="WARN">
<appender-ref ref="ASYNCFILE"/>
<appender-ref ref="ASYNCSTDOUT"/>
</root>

</configuration>
1 change: 1 addition & 0 deletions conf/messages
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# https://www.playframework.com/documentation/latest/ScalaI18N
10 changes: 10 additions & 0 deletions conf/routes
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Routes
# This file defines all application routes (Higher priority routes first)
# https://www.playframework.com/documentation/latest/ScalaRouting
# ~~~~

GET /status controllers.JsonoidController.status()
POST /schemas controllers.JsonoidController.postSchema()
DELETE /schemas/:name controllers.JsonoidController.deleteSchema(name: String)
GET /schemas/:name controllers.JsonoidController.getSchema(name: String)
PUT /schemas/:name controllers.JsonoidController.putSchema(name: String)
1 change: 1 addition & 0 deletions project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version=1.10.9
2 changes: 2 additions & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
addSbtPlugin("org.playframework" % "sbt-plugin" % "3.0.6")
addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.17.0")

0 comments on commit 654d36f

Please sign in to comment.