Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3154,7 +3154,7 @@ protected void setAddProps(Schema schema, IJsonSchemaValidationProperties proper
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements CodegenConfig {
public static final String PROJECT_NAME = "projectName";

private static final String AdditionalPropertiesType = "Additional Properties Type";
private final Logger LOGGER = LoggerFactory.getLogger(ScalaCaskServerCodegen.class);

@Override
Expand Down Expand Up @@ -115,6 +116,8 @@ public ScalaCaskServerCodegen() {

typeMapping.put("integer", "Int");
typeMapping.put("long", "Long");
typeMapping.put("AnyType", AdditionalPropertiesType);
Comment thread
aaronp marked this conversation as resolved.

//TODO binary should be mapped to byte array
// mapped to String as a workaround
typeMapping.put("binary", "String");
Expand Down Expand Up @@ -241,6 +244,7 @@ public void processOpts() {
importMapping.put("OffsetDateTime", "java.time.OffsetDateTime");
importMapping.put("LocalTime", "java.time.LocalTime");
importMapping.put("Value", "ujson.Value");
importMapping.put(AdditionalPropertiesType, "ujson.Value // additional props");
}

static boolean consumesMimetype(CodegenOperation op, String mimetype) {
Expand Down Expand Up @@ -614,7 +618,7 @@ private void setDefaultValueForCodegenProperty(CodegenProperty p) {
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";
Expand Down Expand Up @@ -777,7 +781,7 @@ private static String defaultValueNonOption(IJsonSchemaValidationProperties p, S
if (p.getIsMap()) {
return "Map.empty";
}
if (p.getIsNumber()) {
if (p.getIsNumber() || p.getIsFloat() || p.getIsDecimal() || p.getIsDouble() || p.getIsInteger() || p.getIsLong() || p.getIsUnboundedInteger()) {
Comment thread
aaronp marked this conversation as resolved.
Outdated
return "0";
}
if (p.getIsEnum()) {
Expand All @@ -792,37 +796,12 @@ private static String defaultValueNonOption(IJsonSchemaValidationProperties p, S
if (p.getIsString()) {
return "\"\"";
}
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 "\"\"";
if (fallbackDefaultValue != null && !fallbackDefaultValue.trim().isEmpty()) {
return fallbackDefaultValue;
}
return p.defaultValue;
}

return "null";
}

@Override
public CodegenProperty fromProperty(String name, Schema schema) {
Expand All @@ -847,9 +826,9 @@ public String getTypeDeclaration(Schema schema) {

@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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -142,6 +143,15 @@ extension (request: cask.Request) {
def headerManyValues(paramName: String, required: Boolean): Parsed[List[String]] = Parsed.manyValues(request.headers, paramName, required)

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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}}] = {{classname}}Data.readWriter.bimap[{{classname}}](_.asData, _.asModel)
object {{classname}} {
given RW[{{classname}}] = summon[RW[ujson.Value]].bimap[{{classname}}](_.asJson, json => read[{{classname}}Data](json).asModel)

enum Fields(fieldName : String) extends Field(fieldName) {
enum Fields(val fieldName : String) extends Field(fieldName) {
{{#vars}}
case {{name}} extends Fields("{{name}}")
{{/vars}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -39,23 +51,20 @@ 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) {
if !({{name}} >{{^exclusiveMinimum}}={{/exclusiveMinimum}} {{minimum}}) then
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) {
if !({{name}} <{{^exclusiveMaximum}}={{/exclusiveMaximum}} {{maximum}}) then
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) {
Expand All @@ -65,7 +74,6 @@ case class {{classname}}Data(
}
}
{{/minLength}}

{{#maxLength}}
// validate max length {{maxLength}}
if (errors.isEmpty || !failFast) {
Expand All @@ -75,7 +83,6 @@ case class {{classname}}Data(
}
}
{{/maxLength}}

{{#isEmail}}
// validate {{name}} is a valid email address
if (errors.isEmpty || !failFast) {
Expand All @@ -86,15 +93,13 @@ case class {{classname}}Data(
}
}
{{/isEmail}}

{{#required}}{{^isPrimitiveType}}
if (errors.isEmpty || !failFast) {
if ({{name}} == null) {
errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, "{{name}} is a required field and cannot be null")
}
}
{{/isPrimitiveType}}{{/required}}

{{#uniqueItems}}
// validate {{name}} has unique items
if (errors.isEmpty || !failFast) {
Expand All @@ -111,7 +116,6 @@ case class {{classname}}Data(
}
}
{{/uniqueItems}}

{{#multipleOf}}
if (errors.isEmpty || !failFast) {
// validate {{name}} multiple of {{multipleOf}}
Expand All @@ -123,7 +127,6 @@ case class {{classname}}Data(
}
}
{{/multipleOf}}

{{#minItems}}
// validate min items {{minItems}}
if (errors.isEmpty || !failFast) {
Expand All @@ -133,7 +136,6 @@ case class {{classname}}Data(
}
}
{{/minItems}}

{{#maxItems}}
// validate min items {{maxItems}}
if (errors.isEmpty || !failFast) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
} catch {
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)
Expand Down
Loading