forked from loafle/openapi-generator-original
scala-cask fix: Added support for 'additionalProperties:true' (#19767)
* Added support for 'additionalProperties:true' to scala-cask generator additionalProperties means the request can contain arbitrary additional properties, and so this change adds an 'additionalProperties' field to request objects which is a json type. * fixed warning in example scala-cli project * updated samples * addressed codegen comments
This commit is contained in:
parent
d60200de38
commit
31be9b9207
@ -3158,7 +3158,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
additionalPropertiesIsAnyType = true;
|
||||
}
|
||||
} else {
|
||||
// if additioanl properties is set (e.g. free form object, any type, string, etc)
|
||||
// if additional properties is set (e.g. free form object, any type, string, etc)
|
||||
addPropProp = fromProperty(getAdditionalPropertiesName(), (Schema) schema.getAdditionalProperties(), false);
|
||||
additionalPropertiesIsAnyType = true;
|
||||
}
|
||||
|
@ -39,6 +39,10 @@ import static org.openapitools.codegen.utils.StringUtils.camelize;
|
||||
public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements CodegenConfig {
|
||||
public static final String PROJECT_NAME = "projectName";
|
||||
|
||||
// this is our opinionated json type - ujson.Value - which is a first-class
|
||||
// citizen of cask
|
||||
private static final String AdditionalPropertiesType = "Value";
|
||||
|
||||
private final Logger LOGGER = LoggerFactory.getLogger(ScalaCaskServerCodegen.class);
|
||||
|
||||
@Override
|
||||
@ -115,6 +119,8 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
|
||||
|
||||
typeMapping.put("integer", "Int");
|
||||
typeMapping.put("long", "Long");
|
||||
typeMapping.put("AnyType", AdditionalPropertiesType);
|
||||
|
||||
//TODO binary should be mapped to byte array
|
||||
// mapped to String as a workaround
|
||||
typeMapping.put("binary", "String");
|
||||
@ -241,6 +247,7 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
|
||||
importMapping.put("OffsetDateTime", "java.time.OffsetDateTime");
|
||||
importMapping.put("LocalTime", "java.time.LocalTime");
|
||||
importMapping.put("Value", "ujson.Value");
|
||||
importMapping.put(AdditionalPropertiesType, "ujson.Value");
|
||||
}
|
||||
|
||||
static boolean consumesMimetype(CodegenOperation op, String mimetype) {
|
||||
@ -614,7 +621,7 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
|
||||
if (p.getIsEnumOrRef()) {
|
||||
p.defaultValue = "null";
|
||||
} else {
|
||||
p.defaultValue = defaultValueNonOption(p);
|
||||
p.defaultValue = defaultValueNonOption(p, "null");
|
||||
}
|
||||
} else if (p.defaultValue.contains("Seq.empty")) {
|
||||
p.defaultValue = "Nil";
|
||||
@ -767,6 +774,23 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
|
||||
return defaultValueNonOption(p, fallbackDefaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* the subtypes of IJsonSchemaValidationProperties have an 'isNumeric', but that's not a method on IJsonSchemaValidationProperties.
|
||||
*
|
||||
* This helper method tries to isolate that noisy logic in a safe way so we can ask 'is this IJsonSchemaValidationProperties numeric'?
|
||||
* @param p the property
|
||||
* @return true if the property is numeric
|
||||
*/
|
||||
private static boolean isNumeric(IJsonSchemaValidationProperties p) {
|
||||
if (p instanceof CodegenParameter) {
|
||||
return ((CodegenParameter)p).isNumeric;
|
||||
} else if (p instanceof CodegenProperty) {
|
||||
return ((CodegenProperty)p).isNumeric;
|
||||
} else {
|
||||
return p.getIsNumber() || p.getIsFloat() || p.getIsDecimal() || p.getIsDouble() || p.getIsInteger() || p.getIsLong() || p.getIsUnboundedInteger();
|
||||
}
|
||||
}
|
||||
|
||||
private static String defaultValueNonOption(IJsonSchemaValidationProperties p, String fallbackDefaultValue) {
|
||||
if (p.getIsArray()) {
|
||||
if (p.getUniqueItems()) {
|
||||
@ -777,7 +801,7 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
|
||||
if (p.getIsMap()) {
|
||||
return "Map.empty";
|
||||
}
|
||||
if (p.getIsNumber()) {
|
||||
if (isNumeric(p)) {
|
||||
return "0";
|
||||
}
|
||||
if (p.getIsEnum()) {
|
||||
@ -792,37 +816,12 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
|
||||
if (p.getIsString()) {
|
||||
return "\"\"";
|
||||
}
|
||||
if (fallbackDefaultValue != null && !fallbackDefaultValue.trim().isEmpty()) {
|
||||
return fallbackDefaultValue;
|
||||
}
|
||||
|
||||
private static String defaultValueNonOption(CodegenProperty p) {
|
||||
if (p.getIsArray()) {
|
||||
return "Nil";
|
||||
}
|
||||
if (p.getIsMap()) {
|
||||
return "Map.empty";
|
||||
}
|
||||
if (p.isNumber || p.isNumeric) {
|
||||
return "0";
|
||||
}
|
||||
if (p.isBoolean) {
|
||||
return "false";
|
||||
}
|
||||
if (p.isUuid) {
|
||||
return "java.util.UUID.randomUUID()";
|
||||
}
|
||||
if (p.isModel) {
|
||||
return "null";
|
||||
}
|
||||
if (p.isDate || p.isDateTime) {
|
||||
return "null";
|
||||
}
|
||||
if (p.isString) {
|
||||
return "\"\"";
|
||||
}
|
||||
return p.defaultValue;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public CodegenProperty fromProperty(String name, Schema schema) {
|
||||
@ -847,9 +846,9 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
|
||||
|
||||
@Override
|
||||
public String toModelImport(String name) {
|
||||
final String result = super.toModelImport(name);
|
||||
String result = super.toModelImport(name);
|
||||
if (importMapping.containsKey(name)) {
|
||||
return importMapping.get(name);
|
||||
result = importMapping.get(name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import java.time.LocalDate
|
||||
import java.util.UUID
|
||||
import scala.reflect.ClassTag
|
||||
import scala.util.*
|
||||
import upickle.default.*
|
||||
|
||||
// needed for BigDecimal params
|
||||
given cask.endpoints.QueryParamReader.SimpleParam[BigDecimal](BigDecimal.apply)
|
||||
@ -143,6 +144,15 @@ extension (request: cask.Request) {
|
||||
|
||||
def bodyAsString = new String(request.readAllBytes(), "UTF-8")
|
||||
|
||||
def bodyAsJson : Try[ujson.Value] = {
|
||||
val jason = bodyAsString
|
||||
try {
|
||||
Success(read[ujson.Value](jason))
|
||||
} catch {
|
||||
case scala.util.control.NonFatal(e) => sys.error(s"Error parsing json '$jason': $e")
|
||||
}
|
||||
}
|
||||
|
||||
def pathValue(index: Int, paramName: String, required : Boolean): Parsed[String] = {
|
||||
request
|
||||
.remainingPathSegments
|
||||
|
@ -44,7 +44,7 @@ class {{classname}}Routes(service : {{classname}}Service) extends cask.Routes {
|
||||
|
||||
val result = {{>parseHttpParams}}
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
{{#responses}}
|
||||
{{#dataType}}
|
||||
|
@ -1,6 +1,6 @@
|
||||
//> using scala "3.3.1"
|
||||
//> using lib "com.lihaoyi::cask:0.9.2"
|
||||
//> using lib "com.lihaoyi::scalatags:0.8.2"
|
||||
//> using dep "com.lihaoyi::cask:0.9.2"
|
||||
//> using dep "com.lihaoyi::scalatags:0.8.2"
|
||||
{{>licenseInfo}}
|
||||
|
||||
// this file was generated from app.mustache
|
||||
@ -11,11 +11,21 @@ package {{packageName}}
|
||||
import _root_.{{modelPackage}}.*
|
||||
import _root_.{{apiPackage}}.*
|
||||
|
||||
/** an example of how you can add your own additional routes to your app */
|
||||
object MoreRoutes extends cask.Routes {
|
||||
@cask.get("/echo")
|
||||
def more(request: cask.Request) = s"request was ${request.bodyAsString}"
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an example of how you might extends BaseApp for a runnable application.
|
||||
*
|
||||
* See the README.md for how to create your own app
|
||||
*/
|
||||
object ExampleApp extends BaseApp() {
|
||||
// override to include our additional route
|
||||
override def allRoutes = super.allRoutes ++ Option(MoreRoutes)
|
||||
start()
|
||||
}
|
||||
|
@ -17,26 +17,28 @@ case class {{classname}}(
|
||||
/* {{{description}}} */
|
||||
{{/description}}
|
||||
{{name}}: {{#isEnum}}{{^required}}Option[{{/required}}{{classname}}.{{datatypeWithEnum}}{{^required}}]{{/required}}{{/isEnum}}{{^isEnum}}{{{vendorExtensions.x-datatype-model}}}{{/isEnum}}{{^required}} = {{{vendorExtensions.x-defaultValue-model}}} {{/required}}{{^-last}},{{/-last}}
|
||||
{{/vars}}
|
||||
|
||||
{{/vars}}) {
|
||||
{{#isAdditionalPropertiesTrue}}, additionalProperties : ujson.Value = ujson.Null{{/isAdditionalPropertiesTrue}}
|
||||
) {
|
||||
|
||||
def asJson: String = asData.asJson
|
||||
def asJsonString: String = asData.asJsonString
|
||||
def asJson: ujson.Value = asData.asJson
|
||||
|
||||
def asData : {{classname}}Data = {
|
||||
{{classname}}Data(
|
||||
{{#vars}}
|
||||
{{name}} = {{name}}{{#vendorExtensions.x-map-asModel}}.map(_.asData){{/vendorExtensions.x-map-asModel}}{{#vendorExtensions.x-wrap-in-optional}}.getOrElse({{{defaultValue}}}){{/vendorExtensions.x-wrap-in-optional}}{{^-last}},{{/-last}}
|
||||
{{/vars}}
|
||||
{{#isAdditionalPropertiesTrue}}, additionalProperties{{/isAdditionalPropertiesTrue}}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object {{classname}} {
|
||||
given RW[{{classname}}] = summon[RW[ujson.Value]].bimap[{{classname}}](_.asJson, json => read[{{classname}}Data](json).asModel)
|
||||
|
||||
given RW[{{classname}}] = {{classname}}Data.readWriter.bimap[{{classname}}](_.asData, _.asModel)
|
||||
|
||||
enum Fields(fieldName : String) extends Field(fieldName) {
|
||||
enum Fields(val fieldName : String) extends Field(fieldName) {
|
||||
{{#vars}}
|
||||
case {{name}} extends Fields("{{name}}")
|
||||
{{/vars}}
|
||||
|
@ -21,16 +21,28 @@ case class {{classname}}Data(
|
||||
/* {{{description}}} */
|
||||
{{/description}}
|
||||
{{name}}: {{#isEnum}}{{classname}}.{{datatypeWithEnum}}{{/isEnum}}{{^isEnum}}{{{vendorExtensions.x-datatype-data}}}{{/isEnum}}{{^required}} = {{{vendorExtensions.x-defaultValue-data}}} {{/required}}{{^-last}},{{/-last}}
|
||||
{{/vars}}
|
||||
{{#isAdditionalPropertiesTrue}}, additionalProperties : ujson.Value = ujson.Null{{/isAdditionalPropertiesTrue}}
|
||||
|
||||
{{/vars}}) {
|
||||
) derives RW {
|
||||
|
||||
def asJson: String = write(this)
|
||||
def asJsonString: String = asJson.toString()
|
||||
|
||||
def asJson : ujson.Value = {
|
||||
val jason = writeJs(this)
|
||||
{{#isAdditionalPropertiesTrue}}
|
||||
jason.obj.remove("additionalProperties")
|
||||
jason.mergeWith(additionalProperties)
|
||||
{{/isAdditionalPropertiesTrue}}
|
||||
{{^isAdditionalPropertiesTrue}}
|
||||
jason
|
||||
{{/isAdditionalPropertiesTrue}}
|
||||
}
|
||||
|
||||
def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = {
|
||||
val errors = scala.collection.mutable.ListBuffer[ValidationError]()
|
||||
{{#vars}}
|
||||
// ==================
|
||||
// {{name}}
|
||||
// ================== {{name}} validation ==================
|
||||
{{#pattern}}
|
||||
// validate against pattern '{{{pattern}}}'
|
||||
if (errors.isEmpty || !failFast) {
|
||||
@ -39,7 +51,6 @@ case class {{classname}}Data(
|
||||
errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"value '${{name}}' doesn't match pattern $regex")
|
||||
}
|
||||
{{/pattern}}
|
||||
|
||||
{{#minimum}}
|
||||
// validate against {{#exclusiveMinimum}}exclusive {{/exclusiveMinimum}}minimum {{minimum}}
|
||||
if (errors.isEmpty || !failFast) {
|
||||
@ -47,7 +58,6 @@ case class {{classname}}Data(
|
||||
errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"value '${{name}}' is not greater than the {{#exclusiveMinimum}}exclusive {{/exclusiveMinimum}}minimum value {{minimum}}")
|
||||
}
|
||||
{{/minimum}}
|
||||
|
||||
{{#maximum}}
|
||||
// validate against {{#exclusiveMaximum}}exclusive {{/exclusiveMaximum}}maximum {{maximum}}
|
||||
if (errors.isEmpty || !failFast) {
|
||||
@ -55,7 +65,6 @@ case class {{classname}}Data(
|
||||
errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"value '${{name}}' is not greater than the {{#exclusiveMaximum}}exclusive {{/exclusiveMaximum}}maximum value {{maximum}}")
|
||||
}
|
||||
{{/maximum}}
|
||||
|
||||
{{#minLength}}
|
||||
// validate min length {{minLength}}
|
||||
if (errors.isEmpty || !failFast) {
|
||||
@ -65,7 +74,6 @@ case class {{classname}}Data(
|
||||
}
|
||||
}
|
||||
{{/minLength}}
|
||||
|
||||
{{#maxLength}}
|
||||
// validate max length {{maxLength}}
|
||||
if (errors.isEmpty || !failFast) {
|
||||
@ -75,7 +83,6 @@ case class {{classname}}Data(
|
||||
}
|
||||
}
|
||||
{{/maxLength}}
|
||||
|
||||
{{#isEmail}}
|
||||
// validate {{name}} is a valid email address
|
||||
if (errors.isEmpty || !failFast) {
|
||||
@ -86,7 +93,6 @@ case class {{classname}}Data(
|
||||
}
|
||||
}
|
||||
{{/isEmail}}
|
||||
|
||||
{{#required}}{{^isPrimitiveType}}
|
||||
if (errors.isEmpty || !failFast) {
|
||||
if ({{name}} == null) {
|
||||
@ -94,7 +100,6 @@ case class {{classname}}Data(
|
||||
}
|
||||
}
|
||||
{{/isPrimitiveType}}{{/required}}
|
||||
|
||||
{{#uniqueItems}}
|
||||
// validate {{name}} has unique items
|
||||
if (errors.isEmpty || !failFast) {
|
||||
@ -111,7 +116,6 @@ case class {{classname}}Data(
|
||||
}
|
||||
}
|
||||
{{/uniqueItems}}
|
||||
|
||||
{{#multipleOf}}
|
||||
if (errors.isEmpty || !failFast) {
|
||||
// validate {{name}} multiple of {{multipleOf}}
|
||||
@ -123,7 +127,6 @@ case class {{classname}}Data(
|
||||
}
|
||||
}
|
||||
{{/multipleOf}}
|
||||
|
||||
{{#minItems}}
|
||||
// validate min items {{minItems}}
|
||||
if (errors.isEmpty || !failFast) {
|
||||
@ -133,7 +136,6 @@ case class {{classname}}Data(
|
||||
}
|
||||
}
|
||||
{{/minItems}}
|
||||
|
||||
{{#maxItems}}
|
||||
// validate min items {{maxItems}}
|
||||
if (errors.isEmpty || !failFast) {
|
||||
@ -143,15 +145,8 @@ case class {{classname}}Data(
|
||||
}
|
||||
}
|
||||
{{/maxItems}}
|
||||
|
||||
{{#minProperties}}
|
||||
TODO - minProperties
|
||||
{{/minProperties}}
|
||||
|
||||
{{#maxProperties}}
|
||||
TODO - maxProperties
|
||||
{{/maxProperties}}
|
||||
|
||||
{{#minProperties}} TODO - minProperties {{/minProperties}}
|
||||
{{#maxProperties}} TODO - maxProperties {{/maxProperties}}
|
||||
{{#items}}{{#isModel}}
|
||||
if (errors.isEmpty || !failFast) {
|
||||
if ({{name}} != null) {
|
||||
@ -192,19 +187,35 @@ case class {{classname}}Data(
|
||||
{{#vendorExtensions.x-wrap-in-optional}}){{/vendorExtensions.x-wrap-in-optional}}
|
||||
{{#vendorExtensions.x-map-asModel}}.map(_.asModel){{/vendorExtensions.x-map-asModel}}{{^-last}},{{/-last}}
|
||||
{{/vars}}
|
||||
{{#isAdditionalPropertiesTrue}}, additionalProperties{{/isAdditionalPropertiesTrue}}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object {{classname}}Data {
|
||||
|
||||
given readWriter : RW[{{classname}}Data] = macroRW
|
||||
def fromJson(jason : ujson.Value) : {{classname}}Data = try {
|
||||
val data = read[{{classname}}Data](jason)
|
||||
{{^isAdditionalPropertiesTrue}}
|
||||
data
|
||||
{{/isAdditionalPropertiesTrue}}
|
||||
{{#isAdditionalPropertiesTrue}}
|
||||
val obj = jason.obj
|
||||
{{classname}}.Fields.values.foreach(v => obj.value.subtractOne(v.fieldName))
|
||||
data.copy(additionalProperties = obj)
|
||||
{{/isAdditionalPropertiesTrue}}
|
||||
} catch {
|
||||
case NonFatal(e) => sys.error(s"Error creating {{classname}}Data from json '$jason': $e")
|
||||
}
|
||||
|
||||
def fromJsonString(jason : String) : {{classname}}Data = try {
|
||||
read[{{classname}}Data](jason)
|
||||
def fromJsonString(jason : String) : {{classname}}Data = {
|
||||
val parsed = try {
|
||||
read[ujson.Value](jason)
|
||||
} catch {
|
||||
case NonFatal(e) => sys.error(s"Error parsing json '$jason': $e")
|
||||
}
|
||||
fromJson(parsed)
|
||||
}
|
||||
|
||||
def manyFromJsonString(jason : String) : Seq[{{classname}}Data] = try {
|
||||
read[List[{{classname}}Data]](jason)
|
||||
|
@ -51,3 +51,20 @@ given ReadWriter[OffsetDateTime] = readwriter[String].bimap[OffsetDateTime](
|
||||
OffsetDateTime.parse(str, DateTimeFormatter.ISO_INSTANT)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
extension (json: ujson.Value) {
|
||||
def mergeWith(other: ujson.Value): ujson.Value = (json, other) match {
|
||||
case (ujson.Obj(aMap), ujson.Obj(bMap)) =>
|
||||
val mergedMap: scala.collection.mutable.Map[String, ujson.Value] = aMap ++ bMap.map {
|
||||
case (k, v) => k -> (aMap.get(k) match {
|
||||
case Some(aValue) => aValue.mergeWith(v)
|
||||
case None => v
|
||||
})
|
||||
}
|
||||
ujson.Obj.from(mergedMap)
|
||||
case (ujson.Arr(aArray), ujson.Arr(bArray)) => ujson.Arr(aArray ++ bArray)
|
||||
case (aValue, ujson.Null) => aValue
|
||||
case (_, bValue) => bValue
|
||||
}
|
||||
}
|
@ -63,7 +63,7 @@ class OpenApiRoutes(localPort: Int, swaggerUrl: Option[String]) extends cask.Rou
|
||||
val urlConn = new URL(url).openConnection()
|
||||
urlConn.setRequestProperty("User-Agent", "Mozilla/5.0")
|
||||
|
||||
Using(urlConn.getInputStream) { inputStream =>
|
||||
val extracted = Using(urlConn.getInputStream) { inputStream =>
|
||||
val zipIn = new ZipInputStream(new BufferedInputStream(inputStream))
|
||||
LazyList.continually(zipIn.getNextEntry).takeWhile(_ != null).foreach { entry =>
|
||||
|
||||
@ -77,6 +77,12 @@ class OpenApiRoutes(localPort: Int, swaggerUrl: Option[String]) extends cask.Rou
|
||||
zipIn.closeEntry()
|
||||
}
|
||||
}
|
||||
|
||||
if (extracted.isFailure) {
|
||||
println(s"Error extracting swagger: ${extracted}")
|
||||
} else {
|
||||
println(s"Extracting swagger: ${extracted}")
|
||||
}
|
||||
}
|
||||
|
||||
def extractFile(name: String, zipIn: ZipInputStream, filePath: String): Unit = {
|
||||
|
@ -38,7 +38,8 @@
|
||||
{{/vendorExtensions.x-deserialize-asModelMap}}
|
||||
{{/isMap}}
|
||||
{{^isMap}}
|
||||
{{paramName}}Data <- Parsed.eval({{vendorExtensions.x-container-type}}Data.fromJsonString(request.bodyAsString)).mapError(e => s"Error parsing json as {{vendorExtensions.x-container-type}} from >${request.bodyAsString}< : ${e}") /* not array or map */
|
||||
{{paramName}}Json <- Parsed.fromTry(request.bodyAsJson)
|
||||
{{paramName}}Data <- Parsed.eval({{vendorExtensions.x-container-type}}Data.fromJson({{paramName}}Json)) /* not array or map */
|
||||
{{paramName}} <- Parsed.fromTry({{paramName}}Data.validated(failFast))
|
||||
{{/isMap}}
|
||||
{{/isArray}}
|
||||
|
@ -1,6 +1,6 @@
|
||||
//> using scala "3.3.1"
|
||||
//> using lib "com.lihaoyi::cask:0.9.2"
|
||||
//> using lib "com.lihaoyi::scalatags:0.8.2"
|
||||
//> using dep "com.lihaoyi::cask:0.9.2"
|
||||
//> using dep "com.lihaoyi::scalatags:0.8.2"
|
||||
/**
|
||||
* OpenAPI Petstore
|
||||
* This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||
@ -21,11 +21,21 @@ package cask.groupId.server
|
||||
import _root_.sample.cask.model.*
|
||||
import _root_.sample.cask.api.*
|
||||
|
||||
/** an example of how you can add your own additional routes to your app */
|
||||
object MoreRoutes extends cask.Routes {
|
||||
@cask.get("/echo")
|
||||
def more(request: cask.Request) = s"request was ${request.bodyAsString}"
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an example of how you might extends BaseApp for a runnable application.
|
||||
*
|
||||
* See the README.md for how to create your own app
|
||||
*/
|
||||
object ExampleApp extends BaseApp() {
|
||||
// override to include our additional route
|
||||
override def allRoutes = super.allRoutes ++ Option(MoreRoutes)
|
||||
start()
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ class OpenApiRoutes(localPort: Int, swaggerUrl: Option[String]) extends cask.Rou
|
||||
val urlConn = new URL(url).openConnection()
|
||||
urlConn.setRequestProperty("User-Agent", "Mozilla/5.0")
|
||||
|
||||
Using(urlConn.getInputStream) { inputStream =>
|
||||
val extracted = Using(urlConn.getInputStream) { inputStream =>
|
||||
val zipIn = new ZipInputStream(new BufferedInputStream(inputStream))
|
||||
LazyList.continually(zipIn.getNextEntry).takeWhile(_ != null).foreach { entry =>
|
||||
|
||||
@ -89,6 +89,12 @@ class OpenApiRoutes(localPort: Int, swaggerUrl: Option[String]) extends cask.Rou
|
||||
zipIn.closeEntry()
|
||||
}
|
||||
}
|
||||
|
||||
if (extracted.isFailure) {
|
||||
println(s"Error extracting swagger: ${extracted}")
|
||||
} else {
|
||||
println(s"Extracting swagger: ${extracted}")
|
||||
}
|
||||
}
|
||||
|
||||
def extractFile(name: String, zipIn: ZipInputStream, filePath: String): Unit = {
|
||||
|
@ -60,12 +60,13 @@ class PetRoutes(service : PetService) extends cask.Routes {
|
||||
def failFast = request.queryParams.keySet.contains("failFast")
|
||||
|
||||
val result = for {
|
||||
petData <- Parsed.eval(PetData.fromJsonString(request.bodyAsString)).mapError(e => s"Error parsing json as Pet from >${request.bodyAsString}< : ${e}") /* not array or map */
|
||||
petJson <- Parsed.fromTry(request.bodyAsJson)
|
||||
petData <- Parsed.eval(PetData.fromJson(petJson)) /* not array or map */
|
||||
pet <- Parsed.fromTry(petData.validated(failFast))
|
||||
result <- Parsed.eval(service.addPet(pet))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(value : Pet) => cask.Response(data = write(value), 200, headers = Seq("Content-Type" -> "application/json"))
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
@ -86,7 +87,7 @@ class PetRoutes(service : PetService) extends cask.Routes {
|
||||
result <- Parsed.eval(service.deletePet(petId, apiKey))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
}
|
||||
@ -104,7 +105,7 @@ class PetRoutes(service : PetService) extends cask.Routes {
|
||||
result <- Parsed.eval(service.findPetsByStatus(status))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(value : List[Pet]) => cask.Response(data = write(value), 200, headers = Seq("Content-Type" -> "application/json"))
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
@ -123,7 +124,7 @@ class PetRoutes(service : PetService) extends cask.Routes {
|
||||
result <- Parsed.eval(service.findPetsByTags(tags))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(value : List[Pet]) => cask.Response(data = write(value), 200, headers = Seq("Content-Type" -> "application/json"))
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
@ -143,7 +144,7 @@ class PetRoutes(service : PetService) extends cask.Routes {
|
||||
result <- Parsed.eval(service.getPetById(petId))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(value : Pet) => cask.Response(data = write(value), 200, headers = Seq("Content-Type" -> "application/json"))
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
@ -159,12 +160,13 @@ class PetRoutes(service : PetService) extends cask.Routes {
|
||||
def failFast = request.queryParams.keySet.contains("failFast")
|
||||
|
||||
val result = for {
|
||||
petData <- Parsed.eval(PetData.fromJsonString(request.bodyAsString)).mapError(e => s"Error parsing json as Pet from >${request.bodyAsString}< : ${e}") /* not array or map */
|
||||
petJson <- Parsed.fromTry(request.bodyAsJson)
|
||||
petData <- Parsed.eval(PetData.fromJson(petJson)) /* not array or map */
|
||||
pet <- Parsed.fromTry(petData.validated(failFast))
|
||||
result <- Parsed.eval(service.updatePet(pet))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(value : Pet) => cask.Response(data = write(value), 200, headers = Seq("Content-Type" -> "application/json"))
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
@ -186,7 +188,7 @@ class PetRoutes(service : PetService) extends cask.Routes {
|
||||
result <- Parsed.eval(service.updatePetWithForm(petId, name, status))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
}
|
||||
@ -207,7 +209,7 @@ class PetRoutes(service : PetService) extends cask.Routes {
|
||||
result <- Parsed.eval(service.uploadFile(petId, additionalMetadata, file))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(value : ApiResponse) => cask.Response(data = write(value), 200, headers = Seq("Content-Type" -> "application/json"))
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
|
@ -41,7 +41,7 @@ class StoreRoutes(service : StoreService) extends cask.Routes {
|
||||
result <- Parsed.eval(service.deleteOrder(orderId))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
}
|
||||
@ -59,7 +59,7 @@ class StoreRoutes(service : StoreService) extends cask.Routes {
|
||||
result <- Parsed.eval(service.getInventory())
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(value : Map[String, Int]) => cask.Response(data = write(value), 200, headers = Seq("Content-Type" -> "application/json"))
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
@ -78,7 +78,7 @@ class StoreRoutes(service : StoreService) extends cask.Routes {
|
||||
result <- Parsed.eval(service.getOrderById(orderId))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(value : Order) => cask.Response(data = write(value), 200, headers = Seq("Content-Type" -> "application/json"))
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
@ -93,12 +93,13 @@ class StoreRoutes(service : StoreService) extends cask.Routes {
|
||||
def failFast = request.queryParams.keySet.contains("failFast")
|
||||
|
||||
val result = for {
|
||||
orderData <- Parsed.eval(OrderData.fromJsonString(request.bodyAsString)).mapError(e => s"Error parsing json as Order from >${request.bodyAsString}< : ${e}") /* not array or map */
|
||||
orderJson <- Parsed.fromTry(request.bodyAsJson)
|
||||
orderData <- Parsed.eval(OrderData.fromJson(orderJson)) /* not array or map */
|
||||
order <- Parsed.fromTry(orderData.validated(failFast))
|
||||
result <- Parsed.eval(service.placeOrder(order))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(value : Order) => cask.Response(data = write(value), 200, headers = Seq("Content-Type" -> "application/json"))
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
|
@ -49,12 +49,13 @@ class UserRoutes(service : UserService) extends cask.Routes {
|
||||
def failFast = request.queryParams.keySet.contains("failFast")
|
||||
|
||||
val result = for {
|
||||
userData <- Parsed.eval(UserData.fromJsonString(request.bodyAsString)).mapError(e => s"Error parsing json as User from >${request.bodyAsString}< : ${e}") /* not array or map */
|
||||
userJson <- Parsed.fromTry(request.bodyAsJson)
|
||||
userData <- Parsed.eval(UserData.fromJson(userJson)) /* not array or map */
|
||||
user <- Parsed.fromTry(userData.validated(failFast))
|
||||
result <- Parsed.eval(service.createUser(user))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
}
|
||||
@ -73,7 +74,7 @@ class UserRoutes(service : UserService) extends cask.Routes {
|
||||
result <- Parsed.eval(service.createUsersWithArrayInput(user))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
}
|
||||
@ -92,7 +93,7 @@ class UserRoutes(service : UserService) extends cask.Routes {
|
||||
result <- Parsed.eval(service.createUsersWithListInput(user))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
}
|
||||
@ -111,7 +112,7 @@ class UserRoutes(service : UserService) extends cask.Routes {
|
||||
result <- Parsed.eval(service.deleteUser(username))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
}
|
||||
@ -129,7 +130,7 @@ class UserRoutes(service : UserService) extends cask.Routes {
|
||||
result <- Parsed.eval(service.getUserByName(username))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(value : User) => cask.Response(data = write(value), 200, headers = Seq("Content-Type" -> "application/json"))
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
@ -147,7 +148,7 @@ class UserRoutes(service : UserService) extends cask.Routes {
|
||||
result <- Parsed.eval(service.loginUser(username, password))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(value : String) => cask.Response(data = write(value), 200, headers = Seq("Content-Type" -> "application/json"))
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
@ -166,7 +167,7 @@ class UserRoutes(service : UserService) extends cask.Routes {
|
||||
result <- Parsed.eval(service.logoutUser())
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
}
|
||||
@ -182,12 +183,13 @@ class UserRoutes(service : UserService) extends cask.Routes {
|
||||
|
||||
val result = for {
|
||||
username <- Parsed(username)
|
||||
userData <- Parsed.eval(UserData.fromJsonString(request.bodyAsString)).mapError(e => s"Error parsing json as User from >${request.bodyAsString}< : ${e}") /* not array or map */
|
||||
userJson <- Parsed.fromTry(request.bodyAsJson)
|
||||
userData <- Parsed.eval(UserData.fromJson(userJson)) /* not array or map */
|
||||
user <- Parsed.fromTry(userData.validated(failFast))
|
||||
result <- Parsed.eval(service.updateUser(username, user))
|
||||
} yield result
|
||||
|
||||
result match {
|
||||
(result : @unchecked) match {
|
||||
case Left(error) => cask.Response(error, 500)
|
||||
case Right(other) => cask.Response(s"$other", 200)
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import java.time.LocalDate
|
||||
import java.util.UUID
|
||||
import scala.reflect.ClassTag
|
||||
import scala.util.*
|
||||
import upickle.default.*
|
||||
|
||||
// needed for BigDecimal params
|
||||
given cask.endpoints.QueryParamReader.SimpleParam[BigDecimal](BigDecimal.apply)
|
||||
@ -155,6 +156,15 @@ extension (request: cask.Request) {
|
||||
|
||||
def bodyAsString = new String(request.readAllBytes(), "UTF-8")
|
||||
|
||||
def bodyAsJson : Try[ujson.Value] = {
|
||||
val jason = bodyAsString
|
||||
try {
|
||||
Success(read[ujson.Value](jason))
|
||||
} catch {
|
||||
case scala.util.control.NonFatal(e) => sys.error(s"Error parsing json '$jason': $e")
|
||||
}
|
||||
}
|
||||
|
||||
def pathValue(index: Int, paramName: String, required : Boolean): Parsed[String] = {
|
||||
request
|
||||
.remainingPathSegments
|
||||
|
@ -21,30 +21,29 @@ import upickle.default.*
|
||||
|
||||
case class ApiResponse(
|
||||
code: Option[Int] = None ,
|
||||
|
||||
`type`: Option[String] = None ,
|
||||
|
||||
message: Option[String] = None
|
||||
|
||||
|
||||
) {
|
||||
|
||||
def asJson: String = asData.asJson
|
||||
def asJsonString: String = asData.asJsonString
|
||||
def asJson: ujson.Value = asData.asJson
|
||||
|
||||
def asData : ApiResponseData = {
|
||||
ApiResponseData(
|
||||
code = code.getOrElse(0),
|
||||
`type` = `type`.getOrElse(""),
|
||||
message = message.getOrElse("")
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object ApiResponse {
|
||||
given RW[ApiResponse] = summon[RW[ujson.Value]].bimap[ApiResponse](_.asJson, json => read[ApiResponseData](json).asModel)
|
||||
|
||||
given RW[ApiResponse] = ApiResponseData.readWriter.bimap[ApiResponse](_.asData, _.asModel)
|
||||
|
||||
enum Fields(fieldName : String) extends Field(fieldName) {
|
||||
enum Fields(val fieldName : String) extends Field(fieldName) {
|
||||
case code extends Fields("code")
|
||||
case `type` extends Fields("`type`")
|
||||
case message extends Fields("message")
|
||||
|
@ -25,66 +25,34 @@ import upickle.default.*
|
||||
*/
|
||||
case class ApiResponseData(
|
||||
code: Int = 0 ,
|
||||
|
||||
`type`: String = "" ,
|
||||
|
||||
message: String = ""
|
||||
|
||||
) {
|
||||
|
||||
def asJson: String = write(this)
|
||||
) derives RW {
|
||||
|
||||
def asJsonString: String = asJson.toString()
|
||||
|
||||
def asJson : ujson.Value = {
|
||||
val jason = writeJs(this)
|
||||
jason
|
||||
}
|
||||
|
||||
def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = {
|
||||
val errors = scala.collection.mutable.ListBuffer[ValidationError]()
|
||||
// ==================
|
||||
// code
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// `type`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// message
|
||||
|
||||
// ================== code validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== `type` validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== message validation ==================
|
||||
|
||||
|
||||
|
||||
@ -115,19 +83,28 @@ case class ApiResponseData(
|
||||
message
|
||||
)
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object ApiResponseData {
|
||||
|
||||
given readWriter : RW[ApiResponseData] = macroRW
|
||||
def fromJson(jason : ujson.Value) : ApiResponseData = try {
|
||||
val data = read[ApiResponseData](jason)
|
||||
data
|
||||
} catch {
|
||||
case NonFatal(e) => sys.error(s"Error creating ApiResponseData from json '$jason': $e")
|
||||
}
|
||||
|
||||
def fromJsonString(jason : String) : ApiResponseData = try {
|
||||
read[ApiResponseData](jason)
|
||||
def fromJsonString(jason : String) : ApiResponseData = {
|
||||
val parsed = try {
|
||||
read[ujson.Value](jason)
|
||||
} catch {
|
||||
case NonFatal(e) => sys.error(s"Error parsing json '$jason': $e")
|
||||
}
|
||||
fromJson(parsed)
|
||||
}
|
||||
|
||||
def manyFromJsonString(jason : String) : Seq[ApiResponseData] = try {
|
||||
read[List[ApiResponseData]](jason)
|
||||
|
@ -21,27 +21,27 @@ import upickle.default.*
|
||||
|
||||
case class Category(
|
||||
id: Option[Long] = None ,
|
||||
|
||||
name: Option[String] = None
|
||||
|
||||
|
||||
) {
|
||||
|
||||
def asJson: String = asData.asJson
|
||||
def asJsonString: String = asData.asJsonString
|
||||
def asJson: ujson.Value = asData.asJson
|
||||
|
||||
def asData : CategoryData = {
|
||||
CategoryData(
|
||||
id = id.getOrElse(0),
|
||||
name = name.getOrElse("")
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object Category {
|
||||
given RW[Category] = summon[RW[ujson.Value]].bimap[Category](_.asJson, json => read[CategoryData](json).asModel)
|
||||
|
||||
given RW[Category] = CategoryData.readWriter.bimap[Category](_.asData, _.asModel)
|
||||
|
||||
enum Fields(fieldName : String) extends Field(fieldName) {
|
||||
enum Fields(val fieldName : String) extends Field(fieldName) {
|
||||
case id extends Fields("id")
|
||||
case name extends Fields("name")
|
||||
}
|
||||
|
@ -25,35 +25,27 @@ import upickle.default.*
|
||||
*/
|
||||
case class CategoryData(
|
||||
id: Long = 0 ,
|
||||
|
||||
name: String = ""
|
||||
|
||||
) {
|
||||
|
||||
def asJson: String = write(this)
|
||||
) derives RW {
|
||||
|
||||
def asJsonString: String = asJson.toString()
|
||||
|
||||
def asJson : ujson.Value = {
|
||||
val jason = writeJs(this)
|
||||
jason
|
||||
}
|
||||
|
||||
def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = {
|
||||
val errors = scala.collection.mutable.ListBuffer[ValidationError]()
|
||||
// ==================
|
||||
// id
|
||||
// ================== id validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// name
|
||||
// ================== name validation ==================
|
||||
// validate against pattern '^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$'
|
||||
if (errors.isEmpty || !failFast) {
|
||||
val regex = """^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$"""
|
||||
@ -65,17 +57,6 @@ case class CategoryData(
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
errors.toSeq
|
||||
}
|
||||
|
||||
@ -97,19 +78,28 @@ case class CategoryData(
|
||||
name
|
||||
)
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object CategoryData {
|
||||
|
||||
given readWriter : RW[CategoryData] = macroRW
|
||||
def fromJson(jason : ujson.Value) : CategoryData = try {
|
||||
val data = read[CategoryData](jason)
|
||||
data
|
||||
} catch {
|
||||
case NonFatal(e) => sys.error(s"Error creating CategoryData from json '$jason': $e")
|
||||
}
|
||||
|
||||
def fromJsonString(jason : String) : CategoryData = try {
|
||||
read[CategoryData](jason)
|
||||
def fromJsonString(jason : String) : CategoryData = {
|
||||
val parsed = try {
|
||||
read[ujson.Value](jason)
|
||||
} catch {
|
||||
case NonFatal(e) => sys.error(s"Error parsing json '$jason': $e")
|
||||
}
|
||||
fromJson(parsed)
|
||||
}
|
||||
|
||||
def manyFromJsonString(jason : String) : Seq[CategoryData] = try {
|
||||
read[List[CategoryData]](jason)
|
||||
|
@ -22,21 +22,18 @@ import upickle.default.*
|
||||
|
||||
case class Order(
|
||||
id: Option[Long] = None ,
|
||||
|
||||
petId: Option[Long] = None ,
|
||||
|
||||
quantity: Option[Int] = None ,
|
||||
|
||||
shipDate: Option[OffsetDateTime] = None ,
|
||||
|
||||
/* Order Status */
|
||||
status: Option[Order.StatusEnum] = None ,
|
||||
|
||||
complete: Option[Boolean] = None
|
||||
|
||||
|
||||
) {
|
||||
|
||||
def asJson: String = asData.asJson
|
||||
def asJsonString: String = asData.asJsonString
|
||||
def asJson: ujson.Value = asData.asJson
|
||||
|
||||
def asData : OrderData = {
|
||||
OrderData(
|
||||
@ -46,16 +43,15 @@ case class Order(
|
||||
shipDate = shipDate.getOrElse(null),
|
||||
status = status.getOrElse(null),
|
||||
complete = complete.getOrElse(false)
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object Order {
|
||||
given RW[Order] = summon[RW[ujson.Value]].bimap[Order](_.asJson, json => read[OrderData](json).asModel)
|
||||
|
||||
given RW[Order] = OrderData.readWriter.bimap[Order](_.asData, _.asModel)
|
||||
|
||||
enum Fields(fieldName : String) extends Field(fieldName) {
|
||||
enum Fields(val fieldName : String) extends Field(fieldName) {
|
||||
case id extends Fields("id")
|
||||
case petId extends Fields("petId")
|
||||
case quantity extends Fields("quantity")
|
||||
|
@ -26,127 +26,56 @@ import upickle.default.*
|
||||
*/
|
||||
case class OrderData(
|
||||
id: Long = 0 ,
|
||||
|
||||
petId: Long = 0 ,
|
||||
|
||||
quantity: Int = 0 ,
|
||||
|
||||
shipDate: OffsetDateTime = null ,
|
||||
|
||||
/* Order Status */
|
||||
status: Order.StatusEnum = null ,
|
||||
|
||||
complete: Boolean = false
|
||||
|
||||
) {
|
||||
|
||||
def asJson: String = write(this)
|
||||
) derives RW {
|
||||
|
||||
def asJsonString: String = asJson.toString()
|
||||
|
||||
def asJson : ujson.Value = {
|
||||
val jason = writeJs(this)
|
||||
jason
|
||||
}
|
||||
|
||||
def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = {
|
||||
val errors = scala.collection.mutable.ListBuffer[ValidationError]()
|
||||
// ==================
|
||||
// id
|
||||
// ================== id validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== petId validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== quantity validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// petId
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// quantity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// shipDate
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// status
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// complete
|
||||
|
||||
// ================== shipDate validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== status validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== complete validation ==================
|
||||
|
||||
|
||||
|
||||
@ -189,19 +118,28 @@ case class OrderData(
|
||||
complete
|
||||
)
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object OrderData {
|
||||
|
||||
given readWriter : RW[OrderData] = macroRW
|
||||
def fromJson(jason : ujson.Value) : OrderData = try {
|
||||
val data = read[OrderData](jason)
|
||||
data
|
||||
} catch {
|
||||
case NonFatal(e) => sys.error(s"Error creating OrderData from json '$jason': $e")
|
||||
}
|
||||
|
||||
def fromJsonString(jason : String) : OrderData = try {
|
||||
read[OrderData](jason)
|
||||
def fromJsonString(jason : String) : OrderData = {
|
||||
val parsed = try {
|
||||
read[ujson.Value](jason)
|
||||
} catch {
|
||||
case NonFatal(e) => sys.error(s"Error parsing json '$jason': $e")
|
||||
}
|
||||
fromJson(parsed)
|
||||
}
|
||||
|
||||
def manyFromJsonString(jason : String) : Seq[OrderData] = try {
|
||||
read[List[OrderData]](jason)
|
||||
|
@ -23,21 +23,18 @@ import upickle.default.*
|
||||
|
||||
case class Pet(
|
||||
id: Option[Long] = None ,
|
||||
|
||||
category: Option[Category] = None ,
|
||||
|
||||
name: String,
|
||||
|
||||
photoUrls: Seq[String],
|
||||
|
||||
tags: Seq[Tag] = Nil ,
|
||||
|
||||
/* pet status in the store */
|
||||
status: Option[Pet.StatusEnum] = None
|
||||
|
||||
|
||||
) {
|
||||
|
||||
def asJson: String = asData.asJson
|
||||
def asJsonString: String = asData.asJsonString
|
||||
def asJson: ujson.Value = asData.asJson
|
||||
|
||||
def asData : PetData = {
|
||||
PetData(
|
||||
@ -47,16 +44,15 @@ case class Pet(
|
||||
photoUrls = photoUrls,
|
||||
tags = tags.map(_.asData),
|
||||
status = status.getOrElse(null)
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object Pet {
|
||||
given RW[Pet] = summon[RW[ujson.Value]].bimap[Pet](_.asJson, json => read[PetData](json).asModel)
|
||||
|
||||
given RW[Pet] = PetData.readWriter.bimap[Pet](_.asData, _.asModel)
|
||||
|
||||
enum Fields(fieldName : String) extends Field(fieldName) {
|
||||
enum Fields(val fieldName : String) extends Field(fieldName) {
|
||||
case id extends Fields("id")
|
||||
case category extends Fields("category")
|
||||
case name extends Fields("name")
|
||||
|
@ -27,55 +27,32 @@ import upickle.default.*
|
||||
*/
|
||||
case class PetData(
|
||||
id: Long = 0 ,
|
||||
|
||||
category: CategoryData = null ,
|
||||
|
||||
name: String,
|
||||
|
||||
photoUrls: Seq[String],
|
||||
|
||||
tags: Seq[TagData] = Nil ,
|
||||
|
||||
/* pet status in the store */
|
||||
status: Pet.StatusEnum = null
|
||||
|
||||
) {
|
||||
|
||||
def asJson: String = write(this)
|
||||
) derives RW {
|
||||
|
||||
def asJsonString: String = asJson.toString()
|
||||
|
||||
def asJson : ujson.Value = {
|
||||
val jason = writeJs(this)
|
||||
jason
|
||||
}
|
||||
|
||||
def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = {
|
||||
val errors = scala.collection.mutable.ListBuffer[ValidationError]()
|
||||
// ==================
|
||||
// id
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// category
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== id validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== category validation ==================
|
||||
|
||||
|
||||
|
||||
@ -85,55 +62,19 @@ case class PetData(
|
||||
if category != null then errors ++= category.validationErrors(path :+ Pet.Fields.category, failFast)
|
||||
}
|
||||
|
||||
// ==================
|
||||
// name
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// photoUrls
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// tags
|
||||
|
||||
// ================== name validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== photoUrls validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== tags validation ==================
|
||||
|
||||
|
||||
|
||||
@ -151,19 +92,7 @@ case class PetData(
|
||||
}
|
||||
|
||||
|
||||
// ==================
|
||||
// status
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== status validation ==================
|
||||
|
||||
|
||||
|
||||
@ -206,19 +135,28 @@ case class PetData(
|
||||
status
|
||||
)
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object PetData {
|
||||
|
||||
given readWriter : RW[PetData] = macroRW
|
||||
def fromJson(jason : ujson.Value) : PetData = try {
|
||||
val data = read[PetData](jason)
|
||||
data
|
||||
} catch {
|
||||
case NonFatal(e) => sys.error(s"Error creating PetData from json '$jason': $e")
|
||||
}
|
||||
|
||||
def fromJsonString(jason : String) : PetData = try {
|
||||
read[PetData](jason)
|
||||
def fromJsonString(jason : String) : PetData = {
|
||||
val parsed = try {
|
||||
read[ujson.Value](jason)
|
||||
} catch {
|
||||
case NonFatal(e) => sys.error(s"Error parsing json '$jason': $e")
|
||||
}
|
||||
fromJson(parsed)
|
||||
}
|
||||
|
||||
def manyFromJsonString(jason : String) : Seq[PetData] = try {
|
||||
read[List[PetData]](jason)
|
||||
|
@ -21,27 +21,27 @@ import upickle.default.*
|
||||
|
||||
case class Tag(
|
||||
id: Option[Long] = None ,
|
||||
|
||||
name: Option[String] = None
|
||||
|
||||
|
||||
) {
|
||||
|
||||
def asJson: String = asData.asJson
|
||||
def asJsonString: String = asData.asJsonString
|
||||
def asJson: ujson.Value = asData.asJson
|
||||
|
||||
def asData : TagData = {
|
||||
TagData(
|
||||
id = id.getOrElse(0),
|
||||
name = name.getOrElse("")
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object Tag {
|
||||
given RW[Tag] = summon[RW[ujson.Value]].bimap[Tag](_.asJson, json => read[TagData](json).asModel)
|
||||
|
||||
given RW[Tag] = TagData.readWriter.bimap[Tag](_.asData, _.asModel)
|
||||
|
||||
enum Fields(fieldName : String) extends Field(fieldName) {
|
||||
enum Fields(val fieldName : String) extends Field(fieldName) {
|
||||
case id extends Fields("id")
|
||||
case name extends Fields("name")
|
||||
}
|
||||
|
@ -25,46 +25,27 @@ import upickle.default.*
|
||||
*/
|
||||
case class TagData(
|
||||
id: Long = 0 ,
|
||||
|
||||
name: String = ""
|
||||
|
||||
) {
|
||||
|
||||
def asJson: String = write(this)
|
||||
) derives RW {
|
||||
|
||||
def asJsonString: String = asJson.toString()
|
||||
|
||||
def asJson : ujson.Value = {
|
||||
val jason = writeJs(this)
|
||||
jason
|
||||
}
|
||||
|
||||
def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = {
|
||||
val errors = scala.collection.mutable.ListBuffer[ValidationError]()
|
||||
// ==================
|
||||
// id
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// name
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== id validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== name validation ==================
|
||||
|
||||
|
||||
|
||||
@ -91,19 +72,28 @@ case class TagData(
|
||||
name
|
||||
)
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object TagData {
|
||||
|
||||
given readWriter : RW[TagData] = macroRW
|
||||
def fromJson(jason : ujson.Value) : TagData = try {
|
||||
val data = read[TagData](jason)
|
||||
data
|
||||
} catch {
|
||||
case NonFatal(e) => sys.error(s"Error creating TagData from json '$jason': $e")
|
||||
}
|
||||
|
||||
def fromJsonString(jason : String) : TagData = try {
|
||||
read[TagData](jason)
|
||||
def fromJsonString(jason : String) : TagData = {
|
||||
val parsed = try {
|
||||
read[ujson.Value](jason)
|
||||
} catch {
|
||||
case NonFatal(e) => sys.error(s"Error parsing json '$jason': $e")
|
||||
}
|
||||
fromJson(parsed)
|
||||
}
|
||||
|
||||
def manyFromJsonString(jason : String) : Seq[TagData] = try {
|
||||
read[List[TagData]](jason)
|
||||
|
@ -21,25 +21,20 @@ import upickle.default.*
|
||||
|
||||
case class User(
|
||||
id: Option[Long] = None ,
|
||||
|
||||
username: Option[String] = None ,
|
||||
|
||||
firstName: Option[String] = None ,
|
||||
|
||||
lastName: Option[String] = None ,
|
||||
|
||||
email: Option[String] = None ,
|
||||
|
||||
password: Option[String] = None ,
|
||||
|
||||
phone: Option[String] = None ,
|
||||
|
||||
/* User Status */
|
||||
userStatus: Option[Int] = None
|
||||
|
||||
|
||||
) {
|
||||
|
||||
def asJson: String = asData.asJson
|
||||
def asJsonString: String = asData.asJsonString
|
||||
def asJson: ujson.Value = asData.asJson
|
||||
|
||||
def asData : UserData = {
|
||||
UserData(
|
||||
@ -51,16 +46,15 @@ case class User(
|
||||
password = password.getOrElse(""),
|
||||
phone = phone.getOrElse(""),
|
||||
userStatus = userStatus.getOrElse(0)
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object User {
|
||||
given RW[User] = summon[RW[ujson.Value]].bimap[User](_.asJson, json => read[UserData](json).asModel)
|
||||
|
||||
given RW[User] = UserData.readWriter.bimap[User](_.asData, _.asModel)
|
||||
|
||||
enum Fields(fieldName : String) extends Field(fieldName) {
|
||||
enum Fields(val fieldName : String) extends Field(fieldName) {
|
||||
case id extends Fields("id")
|
||||
case username extends Fields("username")
|
||||
case firstName extends Fields("firstName")
|
||||
|
@ -25,167 +25,70 @@ import upickle.default.*
|
||||
*/
|
||||
case class UserData(
|
||||
id: Long = 0 ,
|
||||
|
||||
username: String = "" ,
|
||||
|
||||
firstName: String = "" ,
|
||||
|
||||
lastName: String = "" ,
|
||||
|
||||
email: String = "" ,
|
||||
|
||||
password: String = "" ,
|
||||
|
||||
phone: String = "" ,
|
||||
|
||||
/* User Status */
|
||||
userStatus: Int = 0
|
||||
|
||||
) {
|
||||
|
||||
def asJson: String = write(this)
|
||||
) derives RW {
|
||||
|
||||
def asJsonString: String = asJson.toString()
|
||||
|
||||
def asJson : ujson.Value = {
|
||||
val jason = writeJs(this)
|
||||
jason
|
||||
}
|
||||
|
||||
def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = {
|
||||
val errors = scala.collection.mutable.ListBuffer[ValidationError]()
|
||||
// ==================
|
||||
// id
|
||||
// ================== id validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== username validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== firstName validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== lastName validation ==================
|
||||
|
||||
// ==================
|
||||
// username
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== email validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== password validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// firstName
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// lastName
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// email
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// password
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// phone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// userStatus
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== phone validation ==================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ================== userStatus validation ==================
|
||||
|
||||
|
||||
|
||||
@ -236,19 +139,28 @@ case class UserData(
|
||||
userStatus
|
||||
)
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object UserData {
|
||||
|
||||
given readWriter : RW[UserData] = macroRW
|
||||
def fromJson(jason : ujson.Value) : UserData = try {
|
||||
val data = read[UserData](jason)
|
||||
data
|
||||
} catch {
|
||||
case NonFatal(e) => sys.error(s"Error creating UserData from json '$jason': $e")
|
||||
}
|
||||
|
||||
def fromJsonString(jason : String) : UserData = try {
|
||||
read[UserData](jason)
|
||||
def fromJsonString(jason : String) : UserData = {
|
||||
val parsed = try {
|
||||
read[ujson.Value](jason)
|
||||
} catch {
|
||||
case NonFatal(e) => sys.error(s"Error parsing json '$jason': $e")
|
||||
}
|
||||
fromJson(parsed)
|
||||
}
|
||||
|
||||
def manyFromJsonString(jason : String) : Seq[UserData] = try {
|
||||
read[List[UserData]](jason)
|
||||
|
@ -63,3 +63,20 @@ given ReadWriter[OffsetDateTime] = readwriter[String].bimap[OffsetDateTime](
|
||||
OffsetDateTime.parse(str, DateTimeFormatter.ISO_INSTANT)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
extension (json: ujson.Value) {
|
||||
def mergeWith(other: ujson.Value): ujson.Value = (json, other) match {
|
||||
case (ujson.Obj(aMap), ujson.Obj(bMap)) =>
|
||||
val mergedMap: scala.collection.mutable.Map[String, ujson.Value] = aMap ++ bMap.map {
|
||||
case (k, v) => k -> (aMap.get(k) match {
|
||||
case Some(aValue) => aValue.mergeWith(v)
|
||||
case None => v
|
||||
})
|
||||
}
|
||||
ujson.Obj.from(mergedMap)
|
||||
case (ujson.Arr(aArray), ujson.Arr(bArray)) => ujson.Arr(aArray ++ bArray)
|
||||
case (aValue, ujson.Null) => aValue
|
||||
case (_, bValue) => bValue
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user