From 2a7944eca92fa5512edf6c3cc3dfeb6a96f6f72f Mon Sep 17 00:00:00 2001 From: "aaron.pritzlaff" Date: Sun, 20 Oct 2024 11:53:23 +0100 Subject: [PATCH 1/2] added support for 'oneOf' types represented as unions also updated libs and an 'errors' field rename to address name clashes with likely/popular field names --- .../languages/ScalaCaskServerCodegen.java | 102 ++++++++++++++---- .../resources/scala-cask/apiRoutes.mustache | 4 + .../main/resources/scala-cask/model.mustache | 24 +++-- .../resources/scala-cask/modelClass.mustache | 2 +- .../resources/scala-cask/modelData.mustache | 29 +++++ .../scala-cask/modelDataClass.mustache | 60 ++++++----- .../resources/scala-cask/modelTest.mustache | 3 +- .../scala-cask/parseHttpParams.mustache | 5 +- .../scala-cask/project/build.properties | 2 +- .../resources/scala-cask/project/plugins.sbt | 2 +- .../scala/sample/cask/api/PetRoutes.scala | 16 ++- .../scala/sample/cask/api/StoreRoutes.scala | 8 +- .../scala/sample/cask/api/UserRoutes.scala | 13 ++- .../scala-cask/project/build.properties | 2 +- .../petstore/scala-cask/project/plugins.sbt | 2 +- .../scala/sample/cask/model/ApiResponse.scala | 3 +- .../sample/cask/model/ApiResponseData.scala | 6 +- .../scala/sample/cask/model/Category.scala | 3 +- .../sample/cask/model/CategoryData.scala | 10 +- .../main/scala/sample/cask/model/Order.scala | 3 +- .../scala/sample/cask/model/OrderData.scala | 6 +- .../main/scala/sample/cask/model/Pet.scala | 3 +- .../scala/sample/cask/model/PetData.scala | 16 +-- .../main/scala/sample/cask/model/Tag.scala | 3 +- .../scala/sample/cask/model/TagData.scala | 6 +- .../main/scala/sample/cask/model/User.scala | 3 +- .../scala/sample/cask/model/UserData.scala | 6 +- 27 files changed, 249 insertions(+), 93 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaCaskServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaCaskServerCodegen.java index 1ac6e0a3feab..b257e2eba22d 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaCaskServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaCaskServerCodegen.java @@ -18,6 +18,7 @@ import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.parameters.RequestBody; import org.apache.commons.io.FileUtils; import org.openapitools.codegen.*; import org.openapitools.codegen.model.ModelMap; @@ -394,6 +395,12 @@ public void processOpenAPI(OpenAPI openAPI) { } + /** + * This class is used in pathExtractorParams.mustache. + * + * It exposes some methods which make it more readable + * for that mustache snippet, and also isolates the logic needed for the path extractors + */ public static class ParamPart { final CodegenParameter param; final String name; @@ -416,7 +423,9 @@ public ParamPart(String name, CodegenParameter param) { } /** - * Cask will compile but 'initialize' can throw a route overlap exception: + * This data structure is here to manually identify and fix routes which will overlap (e.g. GET /foo/bar and GET /foo/bazz) + * + * If we added these as individual routes, then Cask itself will compile, but calling 'initialize' throws a route overlap exception: *

* {{{ * Routes overlap with wildcards: get /user/logout, get /user/:username, get /user/login @@ -672,9 +681,12 @@ private void postProcessModel(CodegenModel model) { model.getVars().forEach(this::postProcessProperty); model.getAllVars().forEach(this::postProcessProperty); + + + model.vendorExtensions.put("x-has-one-of", model.oneOf != null && !model.oneOf.isEmpty()); } - private static void postProcessOperation(CodegenOperation op) { + private static void postProcessOperation(final CodegenOperation op) { // force http method to lower case op.httpMethod = op.httpMethod.toLowerCase(Locale.ROOT); @@ -710,9 +722,33 @@ private static void postProcessOperation(CodegenOperation op) { .collect(Collectors.toCollection(LinkedHashSet::new)); var responseType = responses.isEmpty() ? "Unit" : String.join(" | ", responses); + op.vendorExtensions.put("x-import-response-implicits", importResponseImplicits(op)); op.vendorExtensions.put("x-response-type", responseType); } + /** + * We need to bring the response type into scope in order to use the upickle implicits + * only if the response type has a 'oneOf' type, which means it's a union type with a + * companion object containing the ReadWriter + * + * @param op + * @return true if we need to provide an import + */ + private static boolean importResponseImplicits(final CodegenOperation op) { + final Set importBlacklist = Set.of("File"); + + boolean doImport = false; + for (var response : op.responses) { + // we should ignore generic types like Seq[...] or Map[..] types + var isPolymorphic = response.dataType != null && response.dataType.contains("["); + if (response.isModel && !importBlacklist.contains(response.dataType) && !isPolymorphic) { + doImport = true; + break; + } + } + return doImport; + } + /** * primitive or enum types don't have Data representations * @param p the property @@ -747,6 +783,10 @@ private static boolean isByteArray(final CodegenProperty p) { return "byte".equalsIgnoreCase(p.dataFormat); // && } + private static boolean wrapInOptional(CodegenProperty p) { + return !p.required && !p.isArray && !p.isMap; + } + /** * this parameter is used to create the function: * {{{ @@ -761,19 +801,18 @@ private static boolean isByteArray(final CodegenProperty p) { * and then back again */ private static String asDataCode(final CodegenProperty p, final Set typesWhichDoNotNeedMapping) { - final var wrapInOptional = !p.required && !p.isArray && !p.isMap; String code = ""; String dv = defaultValueNonOption(p, p.defaultValue); if (doesNotNeedMapping(p, typesWhichDoNotNeedMapping)) { - if (wrapInOptional) { + if (wrapInOptional(p)) { code = String.format(Locale.ROOT, "%s.getOrElse(%s) /* 1 */", p.name, dv); } else { code = String.format(Locale.ROOT, "%s /* 2 */", p.name); } } else { - if (wrapInOptional) { + if (wrapInOptional(p)) { if (isByteArray(p)) { code = String.format(Locale.ROOT, "%s.getOrElse(%s) /* 3 */", p.name, dv); } else { @@ -782,11 +821,15 @@ private static String asDataCode(final CodegenProperty p, final Set type } else if (p.isArray) { if (isByteArray(p)) { code = String.format(Locale.ROOT, "%s /* 5 */", p.name); + } else if (!isObjectArray(p)) { + code = String.format(Locale.ROOT, "%s /* 5.1 */", p.name); } else { code = String.format(Locale.ROOT, "%s.map(_.asData) /* 6 */", p.name); } + } else if (p.isMap) { + code = String.format(Locale.ROOT, "%s /* 7 */", p.name); } else { - code = String.format(Locale.ROOT, "%s.asData /* 7 */", p.name); + code = String.format(Locale.ROOT, "%s.asData /* 8 */", p.name); } } return code; @@ -807,17 +850,16 @@ private static String asDataCode(final CodegenProperty p, final Set type * @return */ private static String asModelCode(final CodegenProperty p, final Set typesWhichDoNotNeedMapping) { - final var wrapInOptional = !p.required && !p.isArray && !p.isMap; String code = ""; if (doesNotNeedMapping(p, typesWhichDoNotNeedMapping)) { - if (wrapInOptional) { + if (wrapInOptional(p)) { code = String.format(Locale.ROOT, "Option(%s) /* 1 */", p.name); } else { code = String.format(Locale.ROOT, "%s /* 2 */", p.name); } } else { - if (wrapInOptional) { + if (wrapInOptional(p)) { if (isByteArray(p)) { code = String.format(Locale.ROOT, "Option(%s) /* 3 */", p.name); } else { @@ -825,6 +867,8 @@ private static String asModelCode(final CodegenProperty p, final Set typ } } else if (p.isArray) { code = String.format(Locale.ROOT, "%s.map(_.asModel) /* 5 */", p.name); + } else if (p.isMap) { + code = String.format(Locale.ROOT, "%s /* 5.1 */", p.name); } else { code = String.format(Locale.ROOT, "%s.asModel /* 6 */", p.name); } @@ -863,8 +907,17 @@ private String ensureNonKeyword(String text) { return text; } + private static boolean hasItemModel(final CodegenProperty p) { + return p.items != null && p.items.isModel; + } + + private static boolean isObjectArray(final CodegenProperty p) { + return p.isArray && hasItemModel(p); + } + private void postProcessProperty(final CodegenProperty p) { - p.vendorExtensions.put("x-datatype-model", asScalaDataType(p, p.required, false)); + + p.vendorExtensions.put("x-datatype-model", asScalaDataType(p, p.required, false, wrapInOptional(p))); p.vendorExtensions.put("x-defaultValue-model", defaultValue(p, p.required, p.defaultValue)); final String dataTypeData = asScalaDataType(p, p.required, true); p.vendorExtensions.put("x-datatype-data", dataTypeData); @@ -878,7 +931,7 @@ private void postProcessProperty(final CodegenProperty p) { p._enum = p._enum.stream().map(this::ensureNonKeyword).collect(Collectors.toList()); } - /** + /* * This is a fix for the enum property "type" declared like this: * {{{ * type: @@ -908,6 +961,9 @@ private void postProcessProperty(final CodegenProperty p) { )).collect(Collectors.toSet()); typesWhichShouldNotBeMapped.add("byte"); + // when deserialising map objects, the logic is tricky. + p.vendorExtensions.put("x-deserialize-asModelMap", p.isMap && hasItemModel(p)); + // the 'asModel' logic for modelData.mustache // // if it's optional (not required), then wrap the value in Option() @@ -916,16 +972,6 @@ private void postProcessProperty(final CodegenProperty p) { p.vendorExtensions.put("x-asData", asDataCode(p, typesWhichShouldNotBeMapped)); p.vendorExtensions.put("x-asModel", asModelCode(p, typesWhichShouldNotBeMapped)); - // if it's an array or optional, we need to map it as a model -- unless it's a map, - // in which case we have to map the values - boolean hasItemModel = p.items != null && p.items.isModel; - boolean isObjectArray = p.isArray && hasItemModel; - boolean isOptionalObj = !p.required && p.isModel; - p.vendorExtensions.put("x-map-asModel", (isOptionalObj || isObjectArray) && !p.isMap); - - // when deserialising map objects, the logic is tricky. - p.vendorExtensions.put("x-deserialize-asModelMap", p.isMap && hasItemModel); - // for some reason, an openapi spec with pattern field like this: // pattern: '^[A-Za-z]+$' // will result in the pattern property text of @@ -934,6 +980,20 @@ private void postProcessProperty(final CodegenProperty p) { p.pattern = p.pattern.substring(1, p.pattern.length() - 1); } + // in our model class definition laid out in modelClass.mustache, we use 'Option' for non-required + // properties only when they don't have a sensible 'empty' value (e.g. maps and lists). + // + // that is to say, we're trying to avoid having: + // + // someOptionalField : Option[Seq[Foo]] + // + // when we could just have e.g. + // + // someOptionalField : Seq[Foo] + // + // with an empty value + p.vendorExtensions.put("x-model-needs-option", wrapInOptional(p)); + } diff --git a/modules/openapi-generator/src/main/resources/scala-cask/apiRoutes.mustache b/modules/openapi-generator/src/main/resources/scala-cask/apiRoutes.mustache index 53dc07104945..b2167d0cd530 100644 --- a/modules/openapi-generator/src/main/resources/scala-cask/apiRoutes.mustache +++ b/modules/openapi-generator/src/main/resources/scala-cask/apiRoutes.mustache @@ -45,6 +45,10 @@ class {{classname}}Routes(service : {{classname}}Service[Try]) extends cask.Rout val result = {{>parseHttpParams}} + {{#vendorExtensions.x-import-response-implicits}} + import {{vendorExtensions.x-response-type}}.{given, *} // this brings in upickle in the case of union (oneOf) types + {{/vendorExtensions.x-import-response-implicits}} + (result : @unchecked) match { case Left(error) => cask.Response(error, 500) {{#responses}} diff --git a/modules/openapi-generator/src/main/resources/scala-cask/model.mustache b/modules/openapi-generator/src/main/resources/scala-cask/model.mustache index 6e8551c5b059..31b487598775 100644 --- a/modules/openapi-generator/src/main/resources/scala-cask/model.mustache +++ b/modules/openapi-generator/src/main/resources/scala-cask/model.mustache @@ -13,12 +13,24 @@ import upickle.default.* {{#models}} {{#model}} -{{#isEnum}} -{{>modelEnum}} -{{/isEnum}} -{{^isEnum}} -{{>modelClass}} -{{/isEnum}} +{{#vendorExtensions.x-has-one-of}} + +type {{classname}} = {{#oneOf}}{{{.}}}{{^-last}} | {{/-last}}{{/oneOf}} +object {{{classname}}} { + + given RW[{{{classname}}}] = RW.merge({{#oneOf}}summon[RW[{{{.}}}]]{{^-last}}, {{/-last}}{{/oneOf}}) +} + +{{/vendorExtensions.x-has-one-of}} +{{^vendorExtensions.x-has-one-of}} + {{#isEnum}} + {{>modelEnum}} + {{/isEnum}} + {{^isEnum}} + {{>modelClass}} + {{/isEnum}} +{{/vendorExtensions.x-has-one-of}} + {{/model}} {{/models}} diff --git a/modules/openapi-generator/src/main/resources/scala-cask/modelClass.mustache b/modules/openapi-generator/src/main/resources/scala-cask/modelClass.mustache index 9cf9cf3102fc..12f4ced6b084 100644 --- a/modules/openapi-generator/src/main/resources/scala-cask/modelClass.mustache +++ b/modules/openapi-generator/src/main/resources/scala-cask/modelClass.mustache @@ -4,7 +4,7 @@ case class {{classname}}( {{#description}} /* {{{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}} + {{name}}: {{#isEnum}}{{#vendorExtensions.x-model-needs-option}}Option[{{/vendorExtensions.x-model-needs-option}}{{classname}}.{{datatypeWithEnum}}{{#vendorExtensions.x-model-needs-option}}]{{/vendorExtensions.x-model-needs-option}}{{/isEnum}}{{^isEnum}}{{{vendorExtensions.x-datatype-model}}}{{/isEnum}}{{^required}} = {{{vendorExtensions.x-defaultValue-model}}} {{/required}}{{^-last}},{{/-last}} {{/vars}} {{#isAdditionalPropertiesTrue}}, additionalProperties : ujson.Value = ujson.Null{{/isAdditionalPropertiesTrue}} diff --git a/modules/openapi-generator/src/main/resources/scala-cask/modelData.mustache b/modules/openapi-generator/src/main/resources/scala-cask/modelData.mustache index 14a8e12ee91a..91c580352a19 100644 --- a/modules/openapi-generator/src/main/resources/scala-cask/modelData.mustache +++ b/modules/openapi-generator/src/main/resources/scala-cask/modelData.mustache @@ -13,11 +13,40 @@ import upickle.default.* {{#models}} {{#model}} +{{#vendorExtensions.x-has-one-of}} +type {{{classname}}}Data = {{#oneOf}}{{{.}}}Data{{^-last}} | {{/-last}}{{/oneOf}} + +object {{{classname}}}Data { + + def validated(d8a : {{{classname}}}Data, failFast: Boolean) : Try[{{{classname}}}] = { + d8a match { + {{#oneOf}} + case value : {{{.}}}Data => value.validated(failFast) + {{/oneOf}} + } + } + + def fromJsonString(jason : String) = fromJson { + try { + read[ujson.Value](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason': $e") + } + } + + def fromJson(jason : ujson.Value) : {{{classname}}}Data = { + val attempt = {{#oneOf}}{{^-first}}.orElse({{/-first}} Try({{{.}}}Data.fromJson(jason)) {{^-first}}) /* not first */{{/-first}} {{/oneOf}} + attempt.get + } +} +{{/vendorExtensions.x-has-one-of}} +{{^vendorExtensions.x-has-one-of}} {{#isEnum}} {{>modelDataEnum}} {{/isEnum}} {{^isEnum}} {{>modelDataClass}} {{/isEnum}} +{{/vendorExtensions.x-has-one-of}} {{/model}} {{/models}} diff --git a/modules/openapi-generator/src/main/resources/scala-cask/modelDataClass.mustache b/modules/openapi-generator/src/main/resources/scala-cask/modelDataClass.mustache index 72017e635637..a61b223fb1da 100644 --- a/modules/openapi-generator/src/main/resources/scala-cask/modelDataClass.mustache +++ b/modules/openapi-generator/src/main/resources/scala-cask/modelDataClass.mustache @@ -26,73 +26,73 @@ case class {{classname}}Data( } def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = { - val errors = scala.collection.mutable.ListBuffer[ValidationError]() + val _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]() {{#vars}} // ================== {{name}} validation ================== {{#pattern}} // validate against pattern '{{{pattern}}}' - if (errors.isEmpty || !failFast) { + if (_allValidationErrors.isEmpty || !failFast) { val regex = """{{{pattern}}}""" if {{name}} == null || !regex.r.matches({{name}}) then - errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"value '${{name}}' doesn't match pattern $regex") + _allValidationErrors += 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 (_allValidationErrors.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}}") + _allValidationErrors += 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 (_allValidationErrors.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}}") + _allValidationErrors += 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) { + if (_allValidationErrors.isEmpty || !failFast) { val len = if {{name}} == null then 0 else {{name}}.length if (len < {{minLength}}) { - errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"length $len is shorter than the min length {{minLength}}") + _allValidationErrors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"length $len is shorter than the min length {{minLength}}") } } {{/minLength}} {{#maxLength}} // validate max length {{maxLength}} - if (errors.isEmpty || !failFast) { + if (_allValidationErrors.isEmpty || !failFast) { val len = if {{name}} == null then 0 else {{name}}.length if (len < {{maxLength}}) { - errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"length $len is longer than the max length {{maxLength}}") + _allValidationErrors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"length $len is longer than the max length {{maxLength}}") } } {{/maxLength}} {{#isEmail}} // validate {{name}} is a valid email address - if (errors.isEmpty || !failFast) { + if (_allValidationErrors.isEmpty || !failFast) { val emailRegex = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$""" // validate {{name}} is email if ({{name}} == null || !emailRegex.r.matches({{name}})) { - errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"${{name}} is not a valid email address according to the pattern $emailRegex") + _allValidationErrors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"${{name}} is not a valid email address according to the pattern $emailRegex") } } {{/isEmail}} {{#required}}{{^isPrimitiveType}} - if (errors.isEmpty || !failFast) { + if (_allValidationErrors.isEmpty || !failFast) { if ({{name}} == null) { - errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, "{{name}} is a required field and cannot be null") + _allValidationErrors += 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) { + if (_allValidationErrors.isEmpty || !failFast) { if ({{name}} != null) { {{name}}.foldLeft(Set[{{{vendorExtensions.x-containertype-data}}}]()) { case (set, next) if set.contains(next) => - errors += ValidationError( + _allValidationErrors += ValidationError( path :+ {{classname}}.Fields.{{name}}, s"duplicate value: $next" ) @@ -103,10 +103,10 @@ case class {{classname}}Data( } {{/uniqueItems}} {{#multipleOf}} - if (errors.isEmpty || !failFast) { + if (_allValidationErrors.isEmpty || !failFast) { // validate {{name}} multiple of {{multipleOf}} if ({{name}} % {{multipleOf}} != 0) { - errors += ValidationError( + _allValidationErrors += ValidationError( path :+ {{classname}}.Fields.{{name}}, s"${{name}} is not a multiple of {{multipleOf}}" ) @@ -115,30 +115,30 @@ case class {{classname}}Data( {{/multipleOf}} {{#minItems}} // validate min items {{minItems}} - if (errors.isEmpty || !failFast) { + if (_allValidationErrors.isEmpty || !failFast) { val len = if {{name}} == null then 0 else {{name}}.size if (len < {{minItems}}) { - errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"{{name}} has $len, which is less than the min items {{minItems}}") + _allValidationErrors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"{{name}} has $len, which is less than the min items {{minItems}}") } } {{/minItems}} {{#maxItems}} // validate min items {{maxItems}} - if (errors.isEmpty || !failFast) { + if (_allValidationErrors.isEmpty || !failFast) { val len = if {{name}} == null then 0 else {{name}}.size if (len > {{maxItems}}) { - errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"{{name}} has $len, which is greater than the max items {{maxItems}}") + _allValidationErrors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"{{name}} has $len, which is greater than the max items {{maxItems}}") } } {{/maxItems}} {{#minProperties}} TODO - minProperties {{/minProperties}} {{#maxProperties}} TODO - maxProperties {{/maxProperties}} {{#items}}{{#isModel}} - if (errors.isEmpty || !failFast) { + if (_allValidationErrors.isEmpty || !failFast) { if ({{name}} != null) { {{name}}.zipWithIndex.foreach { - case (value, i) if errors.isEmpty || !failFast => - errors ++= value.validationErrors( + case (value, i) if _allValidationErrors.isEmpty || !failFast => + _allValidationErrors ++= value.validationErrors( path :+ {{classname}}.Fields.{{name}} :+ Field(i.toString), failFast) case (value, i) => @@ -148,13 +148,13 @@ case class {{classname}}Data( {{/isModel}}{{/items}} {{#isModel}} // validating {{name}} - if (errors.isEmpty || !failFast) { - if {{name}} != null then errors ++= {{name}}.validationErrors(path :+ {{classname}}.Fields.{{name}}, failFast) + if (_allValidationErrors.isEmpty || !failFast) { + if {{name}} != null then _allValidationErrors ++= {{name}}.validationErrors(path :+ {{classname}}.Fields.{{name}}, failFast) } {{/isModel}} {{/vars}} - errors.toSeq + _allValidationErrors.toSeq } /** @@ -180,6 +180,8 @@ case class {{classname}}Data( object {{classname}}Data { + def validated(d8a : {{classname}}Data, failFast : Boolean) : scala.util.Try[{{classname}}] = d8a.validated(failFast) + def fromJson(jason : ujson.Value) : {{classname}}Data = try { val data = read[{{classname}}Data](jason) {{^isAdditionalPropertiesTrue}} diff --git a/modules/openapi-generator/src/main/resources/scala-cask/modelTest.mustache b/modules/openapi-generator/src/main/resources/scala-cask/modelTest.mustache index 7b1d95b5aaba..bed1a908d2ea 100644 --- a/modules/openapi-generator/src/main/resources/scala-cask/modelTest.mustache +++ b/modules/openapi-generator/src/main/resources/scala-cask/modelTest.mustache @@ -28,7 +28,8 @@ class {{classname}}Test extends AnyWordSpec with Matchers { err.getMessage should startWith ("Error parsing json 'invalid jason'") } """parse {{example}}""" ignore { - val Failure(err : ValidationErrors) = {{classname}}Data.fromJsonString("""{{example}}""").validated() + val d8a = {{classname}}Data.fromJsonString("""{{example}}""") + val Failure(err : ValidationErrors) = {{classname}}Data.validated(d8a, true) sys.error("TODO") } diff --git a/modules/openapi-generator/src/main/resources/scala-cask/parseHttpParams.mustache b/modules/openapi-generator/src/main/resources/scala-cask/parseHttpParams.mustache index ebbd63e2c05e..7957edeb5bb7 100644 --- a/modules/openapi-generator/src/main/resources/scala-cask/parseHttpParams.mustache +++ b/modules/openapi-generator/src/main/resources/scala-cask/parseHttpParams.mustache @@ -40,7 +40,10 @@ {{^isMap}} {{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)) + {{paramName}}{{^required}}Opt{{/required}} <- Parsed.fromTry({{vendorExtensions.x-container-type}}Data.validated({{paramName}}Data, failFast)) + {{^required}} + {{paramName}} = Option({{paramName}}Opt) + {{/required}} {{/isMap}} {{/isArray}} {{/vendorExtensions.x-consumes-json}} diff --git a/modules/openapi-generator/src/main/resources/scala-cask/project/build.properties b/modules/openapi-generator/src/main/resources/scala-cask/project/build.properties index 04267b14af69..bc7390601f4e 100644 --- a/modules/openapi-generator/src/main/resources/scala-cask/project/build.properties +++ b/modules/openapi-generator/src/main/resources/scala-cask/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.9 +sbt.version=1.10.3 diff --git a/modules/openapi-generator/src/main/resources/scala-cask/project/plugins.sbt b/modules/openapi-generator/src/main/resources/scala-cask/project/plugins.sbt index ece317dfad9b..9c4d009e2906 100644 --- a/modules/openapi-generator/src/main/resources/scala-cask/project/plugins.sbt +++ b/modules/openapi-generator/src/main/resources/scala-cask/project/plugins.sbt @@ -1,4 +1,4 @@ addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.2.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.17.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.2.0") \ No newline at end of file diff --git a/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/PetRoutes.scala b/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/PetRoutes.scala index da02bbb338d6..972b15d30e98 100644 --- a/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/PetRoutes.scala +++ b/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/PetRoutes.scala @@ -63,11 +63,13 @@ class PetRoutes(service : PetService[Try]) extends cask.Routes { val result = for { petJson <- Parsed.fromTry(request.bodyAsJson) petData <- Parsed.eval(PetData.fromJson(petJson)) /* not array or map */ - pet <- Parsed.fromTry(petData.validated(failFast)) + pet <- Parsed.fromTry(PetData.validated(petData, failFast)) resultTry <- Parsed.eval(service.addPet(pet)) result <- Parsed.fromTry(resultTry) } yield result + import Pet.{given, *} // this brings in upickle in the case of union (oneOf) types + (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")) @@ -90,6 +92,7 @@ class PetRoutes(service : PetService[Try]) extends cask.Routes { result <- Parsed.fromTry(resultTry) } yield result + (result : @unchecked) match { case Left(error) => cask.Response(error, 500) case Right(other) => cask.Response(s"$other", 200) @@ -109,6 +112,7 @@ class PetRoutes(service : PetService[Try]) extends cask.Routes { result <- Parsed.fromTry(resultTry) } yield result + (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")) @@ -129,6 +133,7 @@ class PetRoutes(service : PetService[Try]) extends cask.Routes { result <- Parsed.fromTry(resultTry) } yield result + (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")) @@ -150,6 +155,8 @@ class PetRoutes(service : PetService[Try]) extends cask.Routes { result <- Parsed.fromTry(resultTry) } yield result + import Pet.{given, *} // this brings in upickle in the case of union (oneOf) types + (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")) @@ -168,11 +175,13 @@ class PetRoutes(service : PetService[Try]) extends cask.Routes { val result = for { petJson <- Parsed.fromTry(request.bodyAsJson) petData <- Parsed.eval(PetData.fromJson(petJson)) /* not array or map */ - pet <- Parsed.fromTry(petData.validated(failFast)) + pet <- Parsed.fromTry(PetData.validated(petData, failFast)) resultTry <- Parsed.eval(service.updatePet(pet)) result <- Parsed.fromTry(resultTry) } yield result + import Pet.{given, *} // this brings in upickle in the case of union (oneOf) types + (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")) @@ -196,6 +205,7 @@ class PetRoutes(service : PetService[Try]) extends cask.Routes { result <- Parsed.fromTry(resultTry) } yield result + (result : @unchecked) match { case Left(error) => cask.Response(error, 500) case Right(other) => cask.Response(s"$other", 200) @@ -218,6 +228,8 @@ class PetRoutes(service : PetService[Try]) extends cask.Routes { result <- Parsed.fromTry(resultTry) } yield result + import ApiResponse.{given, *} // this brings in upickle in the case of union (oneOf) types + (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")) diff --git a/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/StoreRoutes.scala b/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/StoreRoutes.scala index 5c3414564e5f..5cea1d14b35c 100644 --- a/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/StoreRoutes.scala +++ b/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/StoreRoutes.scala @@ -43,6 +43,7 @@ class StoreRoutes(service : StoreService[Try]) extends cask.Routes { result <- Parsed.fromTry(resultTry) } yield result + (result : @unchecked) match { case Left(error) => cask.Response(error, 500) case Right(other) => cask.Response(s"$other", 200) @@ -62,6 +63,7 @@ class StoreRoutes(service : StoreService[Try]) extends cask.Routes { result <- Parsed.fromTry(resultTry) } yield result + (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")) @@ -82,6 +84,8 @@ class StoreRoutes(service : StoreService[Try]) extends cask.Routes { result <- Parsed.fromTry(resultTry) } yield result + import Order.{given, *} // this brings in upickle in the case of union (oneOf) types + (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")) @@ -99,11 +103,13 @@ class StoreRoutes(service : StoreService[Try]) extends cask.Routes { val result = for { orderJson <- Parsed.fromTry(request.bodyAsJson) orderData <- Parsed.eval(OrderData.fromJson(orderJson)) /* not array or map */ - order <- Parsed.fromTry(orderData.validated(failFast)) + order <- Parsed.fromTry(OrderData.validated(orderData, failFast)) resultTry <- Parsed.eval(service.placeOrder(order)) result <- Parsed.fromTry(resultTry) } yield result + import Order.{given, *} // this brings in upickle in the case of union (oneOf) types + (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")) diff --git a/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/UserRoutes.scala b/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/UserRoutes.scala index 63c01ea5583e..5cb1b56288c0 100644 --- a/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/UserRoutes.scala +++ b/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/UserRoutes.scala @@ -52,11 +52,12 @@ class UserRoutes(service : UserService[Try]) extends cask.Routes { val result = for { userJson <- Parsed.fromTry(request.bodyAsJson) userData <- Parsed.eval(UserData.fromJson(userJson)) /* not array or map */ - user <- Parsed.fromTry(userData.validated(failFast)) + user <- Parsed.fromTry(UserData.validated(userData, failFast)) resultTry <- Parsed.eval(service.createUser(user)) result <- Parsed.fromTry(resultTry) } yield result + (result : @unchecked) match { case Left(error) => cask.Response(error, 500) case Right(other) => cask.Response(s"$other", 200) @@ -77,6 +78,7 @@ class UserRoutes(service : UserService[Try]) extends cask.Routes { result <- Parsed.fromTry(resultTry) } yield result + (result : @unchecked) match { case Left(error) => cask.Response(error, 500) case Right(other) => cask.Response(s"$other", 200) @@ -97,6 +99,7 @@ class UserRoutes(service : UserService[Try]) extends cask.Routes { result <- Parsed.fromTry(resultTry) } yield result + (result : @unchecked) match { case Left(error) => cask.Response(error, 500) case Right(other) => cask.Response(s"$other", 200) @@ -117,6 +120,7 @@ class UserRoutes(service : UserService[Try]) extends cask.Routes { result <- Parsed.fromTry(resultTry) } yield result + (result : @unchecked) match { case Left(error) => cask.Response(error, 500) case Right(other) => cask.Response(s"$other", 200) @@ -136,6 +140,8 @@ class UserRoutes(service : UserService[Try]) extends cask.Routes { result <- Parsed.fromTry(resultTry) } yield result + import User.{given, *} // this brings in upickle in the case of union (oneOf) types + (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")) @@ -155,6 +161,7 @@ class UserRoutes(service : UserService[Try]) extends cask.Routes { result <- Parsed.fromTry(resultTry) } yield result + (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")) @@ -175,6 +182,7 @@ class UserRoutes(service : UserService[Try]) extends cask.Routes { result <- Parsed.fromTry(resultTry) } yield result + (result : @unchecked) match { case Left(error) => cask.Response(error, 500) case Right(other) => cask.Response(s"$other", 200) @@ -193,11 +201,12 @@ class UserRoutes(service : UserService[Try]) extends cask.Routes { username <- Parsed(username) userJson <- Parsed.fromTry(request.bodyAsJson) userData <- Parsed.eval(UserData.fromJson(userJson)) /* not array or map */ - user <- Parsed.fromTry(userData.validated(failFast)) + user <- Parsed.fromTry(UserData.validated(userData, failFast)) resultTry <- Parsed.eval(service.updateUser(username, user)) result <- Parsed.fromTry(resultTry) } yield result + (result : @unchecked) match { case Left(error) => cask.Response(error, 500) case Right(other) => cask.Response(s"$other", 200) diff --git a/samples/server/petstore/scala-cask/project/build.properties b/samples/server/petstore/scala-cask/project/build.properties index 04267b14af69..bc7390601f4e 100644 --- a/samples/server/petstore/scala-cask/project/build.properties +++ b/samples/server/petstore/scala-cask/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.9 +sbt.version=1.10.3 diff --git a/samples/server/petstore/scala-cask/project/plugins.sbt b/samples/server/petstore/scala-cask/project/plugins.sbt index ece317dfad9b..9c4d009e2906 100644 --- a/samples/server/petstore/scala-cask/project/plugins.sbt +++ b/samples/server/petstore/scala-cask/project/plugins.sbt @@ -1,4 +1,4 @@ addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.2.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.17.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.2.0") \ No newline at end of file diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ApiResponse.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ApiResponse.scala index cafa77116452..0307a91039da 100644 --- a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ApiResponse.scala +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ApiResponse.scala @@ -21,7 +21,7 @@ import upickle.default.{ReadWriter => RW, macroRW} import upickle.default.* - + case class ApiResponse( code: Option[Int] = None , `type`: Option[String] = None , @@ -55,3 +55,4 @@ enum Fields(val fieldName : String) extends Field(fieldName) { } + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ApiResponseData.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ApiResponseData.scala index 4c80e1c743fe..df95d64e1412 100644 --- a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ApiResponseData.scala +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ApiResponseData.scala @@ -40,7 +40,7 @@ case class ApiResponseData( } def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = { - val errors = scala.collection.mutable.ListBuffer[ValidationError]() + val _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]() // ================== code validation ================== @@ -59,7 +59,7 @@ case class ApiResponseData( - errors.toSeq + _allValidationErrors.toSeq } /** @@ -85,6 +85,8 @@ case class ApiResponseData( object ApiResponseData { + def validated(d8a : ApiResponseData, failFast : Boolean) : scala.util.Try[ApiResponse] = d8a.validated(failFast) + def fromJson(jason : ujson.Value) : ApiResponseData = try { val data = read[ApiResponseData](jason) data diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Category.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Category.scala index e89cbd7e81e6..34eb6b98be43 100644 --- a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Category.scala +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Category.scala @@ -21,7 +21,7 @@ import upickle.default.{ReadWriter => RW, macroRW} import upickle.default.* - + case class Category( id: Option[Long] = None , name: Option[String] = None @@ -52,3 +52,4 @@ enum Fields(val fieldName : String) extends Field(fieldName) { } + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/CategoryData.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/CategoryData.scala index 7eb8a9489701..ab187bf5f669 100644 --- a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/CategoryData.scala +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/CategoryData.scala @@ -39,7 +39,7 @@ case class CategoryData( } def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = { - val errors = scala.collection.mutable.ListBuffer[ValidationError]() + val _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]() // ================== id validation ================== @@ -48,17 +48,17 @@ case class CategoryData( // ================== name validation ================== // validate against pattern '^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$' - if (errors.isEmpty || !failFast) { + if (_allValidationErrors.isEmpty || !failFast) { val regex = """^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$""" if name == null || !regex.r.matches(name) then - errors += ValidationError(path :+ Category.Fields.name, s"value '$name' doesn't match pattern $regex") + _allValidationErrors += ValidationError(path :+ Category.Fields.name, s"value '$name' doesn't match pattern $regex") } - errors.toSeq + _allValidationErrors.toSeq } /** @@ -83,6 +83,8 @@ case class CategoryData( object CategoryData { + def validated(d8a : CategoryData, failFast : Boolean) : scala.util.Try[Category] = d8a.validated(failFast) + def fromJson(jason : ujson.Value) : CategoryData = try { val data = read[CategoryData](jason) data diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Order.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Order.scala index 77dbcd6793b4..03b50eae2a9c 100644 --- a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Order.scala +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Order.scala @@ -22,7 +22,7 @@ import upickle.default.{ReadWriter => RW, macroRW} import upickle.default.* - + case class Order( id: Option[Long] = None , petId: Option[Long] = None , @@ -73,3 +73,4 @@ enum Fields(val fieldName : String) extends Field(fieldName) { } + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OrderData.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OrderData.scala index 1330c2111c5a..2ef08f14aa69 100644 --- a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OrderData.scala +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OrderData.scala @@ -45,7 +45,7 @@ case class OrderData( } def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = { - val errors = scala.collection.mutable.ListBuffer[ValidationError]() + val _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]() // ================== id validation ================== @@ -82,7 +82,7 @@ case class OrderData( - errors.toSeq + _allValidationErrors.toSeq } /** @@ -111,6 +111,8 @@ case class OrderData( object OrderData { + def validated(d8a : OrderData, failFast : Boolean) : scala.util.Try[Order] = d8a.validated(failFast) + def fromJson(jason : ujson.Value) : OrderData = try { val data = read[OrderData](jason) data diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Pet.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Pet.scala index 58725e10dccc..041aab7f6ed8 100644 --- a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Pet.scala +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Pet.scala @@ -21,7 +21,7 @@ import upickle.default.{ReadWriter => RW, macroRW} import upickle.default.* - + case class Pet( id: Option[Long] = None , category: Option[Category] = None , @@ -72,3 +72,4 @@ enum Fields(val fieldName : String) extends Field(fieldName) { } + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/PetData.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/PetData.scala index 755d33c288d6..126c81abe885 100644 --- a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/PetData.scala +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/PetData.scala @@ -44,7 +44,7 @@ case class PetData( } def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = { - val errors = scala.collection.mutable.ListBuffer[ValidationError]() + val _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]() // ================== id validation ================== @@ -57,8 +57,8 @@ case class PetData( // validating category - if (errors.isEmpty || !failFast) { - if category != null then errors ++= category.validationErrors(path :+ Pet.Fields.category, failFast) + if (_allValidationErrors.isEmpty || !failFast) { + if category != null then _allValidationErrors ++= category.validationErrors(path :+ Pet.Fields.category, failFast) } // ================== name validation ================== @@ -78,11 +78,11 @@ case class PetData( - if (errors.isEmpty || !failFast) { + if (_allValidationErrors.isEmpty || !failFast) { if (tags != null) { tags.zipWithIndex.foreach { - case (value, i) if errors.isEmpty || !failFast => - errors ++= value.validationErrors( + case (value, i) if _allValidationErrors.isEmpty || !failFast => + _allValidationErrors ++= value.validationErrors( path :+ Pet.Fields.tags :+ Field(i.toString), failFast) case (value, i) => @@ -97,7 +97,7 @@ case class PetData( - errors.toSeq + _allValidationErrors.toSeq } /** @@ -126,6 +126,8 @@ case class PetData( object PetData { + def validated(d8a : PetData, failFast : Boolean) : scala.util.Try[Pet] = d8a.validated(failFast) + def fromJson(jason : ujson.Value) : PetData = try { val data = read[PetData](jason) data diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Tag.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Tag.scala index fa6805f6c33c..05ff4fcc9d6b 100644 --- a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Tag.scala +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/Tag.scala @@ -21,7 +21,7 @@ import upickle.default.{ReadWriter => RW, macroRW} import upickle.default.* - + case class Tag( id: Option[Long] = None , name: Option[String] = None @@ -52,3 +52,4 @@ enum Fields(val fieldName : String) extends Field(fieldName) { } + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/TagData.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/TagData.scala index 972b21dd518f..35db0cdb3828 100644 --- a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/TagData.scala +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/TagData.scala @@ -39,7 +39,7 @@ case class TagData( } def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = { - val errors = scala.collection.mutable.ListBuffer[ValidationError]() + val _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]() // ================== id validation ================== @@ -52,7 +52,7 @@ case class TagData( - errors.toSeq + _allValidationErrors.toSeq } /** @@ -77,6 +77,8 @@ case class TagData( object TagData { + def validated(d8a : TagData, failFast : Boolean) : scala.util.Try[Tag] = d8a.validated(failFast) + def fromJson(jason : ujson.Value) : TagData = try { val data = read[TagData](jason) data diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/User.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/User.scala index 55614de4f873..5e432c0b0430 100644 --- a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/User.scala +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/User.scala @@ -21,7 +21,7 @@ import upickle.default.{ReadWriter => RW, macroRW} import upickle.default.* - + case class User( id: Option[Long] = None , username: Option[String] = None , @@ -71,3 +71,4 @@ enum Fields(val fieldName : String) extends Field(fieldName) { } + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/UserData.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/UserData.scala index 07b5cb1de417..dea3a5927b13 100644 --- a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/UserData.scala +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/UserData.scala @@ -46,7 +46,7 @@ case class UserData( } def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = { - val errors = scala.collection.mutable.ListBuffer[ValidationError]() + val _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]() // ================== id validation ================== @@ -95,7 +95,7 @@ case class UserData( - errors.toSeq + _allValidationErrors.toSeq } /** @@ -126,6 +126,8 @@ case class UserData( object UserData { + def validated(d8a : UserData, failFast : Boolean) : scala.util.Try[User] = d8a.validated(failFast) + def fromJson(jason : ujson.Value) : UserData = try { val data = read[UserData](jason) data From 8162903f2a3417fb14d6060773a403fedc744595 Mon Sep 17 00:00:00 2001 From: "aaron.pritzlaff" Date: Mon, 11 Nov 2024 10:18:00 +0000 Subject: [PATCH 2/2] Created cask-specific petstore example which Includes a oneOf and allOf example --- bin/configs/scala-cask-petstore.yaml | 2 +- .../resources/3_0/scala-cask/petstore.yaml | 905 ++++++++++++++++++ .../scala-cask/.openapi-generator/FILES | 33 + .../petstore/scala-cask/example/Server.scala | 4 +- .../jvm/src/main/resources/openapi.json | 230 +++++ .../main/scala/sample/cask/AppRoutes.scala | 4 + .../src/main/scala/sample/cask/BaseApp.scala | 2 + .../sample/cask/api/ComplexRouteRoutes.scala | 59 ++ .../cask/model/AllOfRequestDetailsTest.scala | 36 + .../sample/cask/model/AllOfRequestTest.scala | 36 + ...xRequestArrayOfObjectsFieldInnerTest.scala | 36 + .../ComplexRequestNestedObjectTest.scala | 36 + .../cask/model/ComplexRequestTest.scala | 41 + ...eOfRequestAndResponse200ResponseTest.scala | 37 + .../OneOfRequestAndResponseRequestTest.scala | 40 + .../sample/cask/model/SomeResponse1Test.scala | 36 + .../cask/model/SomeResponse2DetailsTest.scala | 36 + .../sample/cask/model/SomeResponse2Test.scala | 37 + .../sample/cask/api/ComplexRouteService.scala | 118 +++ .../sample/cask/model/AllOfRequest.scala | 58 ++ .../sample/cask/model/AllOfRequestData.scala | 149 +++ .../cask/model/AllOfRequestDetails.scala | 55 ++ .../cask/model/AllOfRequestDetailsData.scala | 137 +++ .../sample/cask/model/ComplexRequest.scala | 127 +++ ...mplexRequestArrayOfObjectsFieldInner.scala | 55 ++ ...xRequestArrayOfObjectsFieldInnerData.scala | 137 +++ .../cask/model/ComplexRequestData.scala | 338 +++++++ .../model/ComplexRequestNestedObject.scala | 57 ++ .../ComplexRequestNestedObjectData.scala | 139 +++ .../OneOfRequestAndResponse200Response.scala | 33 + ...eOfRequestAndResponse200ResponseData.scala | 48 + .../OneOfRequestAndResponseRequest.scala | 36 + .../OneOfRequestAndResponseRequestData.scala | 51 + .../sample/cask/model/SomeResponse1.scala | 55 ++ .../sample/cask/model/SomeResponse1Data.scala | 137 +++ .../sample/cask/model/SomeResponse2.scala | 59 ++ .../sample/cask/model/SomeResponse2Data.scala | 156 +++ .../cask/model/SomeResponse2Details.scala | 55 ++ .../cask/model/SomeResponse2DetailsData.scala | 137 +++ 39 files changed, 3745 insertions(+), 2 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/3_0/scala-cask/petstore.yaml create mode 100644 samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/ComplexRouteRoutes.scala create mode 100644 samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/AllOfRequestDetailsTest.scala create mode 100644 samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/AllOfRequestTest.scala create mode 100644 samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/ComplexRequestArrayOfObjectsFieldInnerTest.scala create mode 100644 samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/ComplexRequestNestedObjectTest.scala create mode 100644 samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/ComplexRequestTest.scala create mode 100644 samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/OneOfRequestAndResponse200ResponseTest.scala create mode 100644 samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/OneOfRequestAndResponseRequestTest.scala create mode 100644 samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/SomeResponse1Test.scala create mode 100644 samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/SomeResponse2DetailsTest.scala create mode 100644 samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/SomeResponse2Test.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/api/ComplexRouteService.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequest.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequestData.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequestDetails.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequestDetailsData.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequest.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestArrayOfObjectsFieldInner.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestArrayOfObjectsFieldInnerData.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestData.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestNestedObject.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestNestedObjectData.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponse200Response.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponse200ResponseData.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponseRequest.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponseRequestData.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse1.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse1Data.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2Data.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2Details.scala create mode 100644 samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2DetailsData.scala diff --git a/bin/configs/scala-cask-petstore.yaml b/bin/configs/scala-cask-petstore.yaml index bb37d3587e7f..283d643de95b 100644 --- a/bin/configs/scala-cask-petstore.yaml +++ b/bin/configs/scala-cask-petstore.yaml @@ -1,6 +1,6 @@ generatorName: scala-cask outputDir: samples/server/petstore/scala-cask -inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml +inputSpec: modules/openapi-generator/src/test/resources/3_0/scala-cask/petstore.yaml templateDir: modules/openapi-generator/src/main/resources/scala-cask additionalProperties: hideGenerationTimestamp: "true" diff --git a/modules/openapi-generator/src/test/resources/3_0/scala-cask/petstore.yaml b/modules/openapi-generator/src/test/resources/3_0/scala-cask/petstore.yaml new file mode 100644 index 000000000000..2249f5342c8e --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/scala-cask/petstore.yaml @@ -0,0 +1,905 @@ +openapi: 3.0.0 +servers: + - url: 'http://petstore.swagger.io/v2' +info: + description: >- + This is a sample server Petstore server. For this sample, you can use the api key + `special-key` to test the authorization filters. + version: 1.0.0 + title: OpenAPI Petstore + license: + name: Apache-2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0.html' +tags: + - name: pet + description: Everything about your Pets + - name: store + description: Access to Petstore orders + - name: user + description: Operations about user +paths: + /complex-types: + post: + tags: + - complexRoute + operationId: oneOfRequestAndResponse + summary: Example route with 'oneOf' content and response + requestBody: + required: true + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/ComplexRequest' + - $ref: '#/components/schemas/AllOfRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/SomeResponse1' + - $ref: '#/components/schemas/SomeResponse2' + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + $ref: '#/components/requestBodies/Pet' + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + externalDocs: + url: "http://petstore.swagger.io/v2/doc/updatePet" + description: "API documentation for the updatePet operation" + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + $ref: '#/components/requestBodies/Pet' + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + style: form + explode: false + deprecated: true + schema: + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'read:pets' + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: >- + Multiple tags can be provided with comma separated strings. Use tag1, + tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + style: form + explode: false + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - 'read:pets' + deprecated: true + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/pet/{petId}/uploadImage': + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + additionalMetadata: + description: Additional data to pass to server + type: string + file: + description: file to upload + type: string + format: binary + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid Order + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + description: order placed for purchasing the pet + required: true + '/store/order/{orderId}': + get: + tags: + - store + summary: Find purchase order by ID + description: >- + For valid response try integer IDs with value <= 5 or > 10. Other values + will generate exceptions + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + type: integer + format: int64 + minimum: 1 + maximum: 5 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: >- + For valid response try integer IDs with value < 1000. Anything above + 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Created user object + required: true + /user/createWithArray: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithArrayInput + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithListInput + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: true + schema: + type: string + pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' + - name: password + in: query + description: The password for login in clear text + required: true + schema: + type: string + responses: + '200': + description: successful operation + headers: + Set-Cookie: + description: >- + Cookie authentication key for use with the `api_key` + apiKey authentication. + schema: + type: string + example: AUTH_KEY=abcde12345; Path=/; HttpOnly + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + responses: + default: + description: successful operation + security: + - api_key: [] + '/user/{username}': + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: The name that needs to be fetched. Use user1 for testing. + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + security: + - api_key: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Updated user object + required: true + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found + security: + - api_key: [] +externalDocs: + description: Find out more about Swagger + url: 'http://swagger.io' +components: + requestBodies: + UserArray: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + description: List of user object + required: true + Pet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + required: true + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog' + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + type: apiKey + name: api_key + in: header + schemas: + Order: + title: Pet Order + description: An order for a pets from the pet store + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + Category: + title: Pet category + description: A category for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' + xml: + name: Category + User: + title: a User + description: A User who is purchasing from the pet store + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Tag: + title: Pet Tag + description: A tag for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + title: a Pet + description: A pet for sale in the pet store + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + deprecated: true + enum: + - available + - pending + - sold + xml: + name: Pet + ApiResponse: + title: An uploaded response + description: Describes the result of uploading an image resource + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + ComplexRequest: + type: object + required: + - type + - requiredField + properties: + type: + type: string + enum: + - someEnum + requiredField: + type: string + description: A required string field + stringField: + type: string + minLength: 5 + maxLength: 100 + description: String with length constraints + integerField: + type: integer + minimum: 1 + maximum: 100 + description: Integer with a range from 1 to 100 + numberField: + type: number + exclusiveMinimum: 0.0 + exclusiveMaximum: 100.0 + description: Number with exclusive minimum and maximum constraints + booleanField: + type: boolean + description: A boolean field + emailField: + type: string + format: email + description: A valid email address + uuidField: + type: string + format: uuid + description: A valid UUID + dateField: + type: string + format: date + description: A date field (YYYY-MM-DD) + dateTimeField: + type: string + format: date-time + description: A date-time field (RFC 3339 format) + arrayField: + type: array + items: + type: string + minItems: 1 + maxItems: 10 + description: An array of strings with min and max items constraints + enumField: + type: string + enum: + - value1 + - value2 + - value3 + description: A string field with enum validation + nestedObject: + type: object + properties: + nestedField1: + type: string + description: A field within a nested object + nestedField2: + type: integer + description: Another field in a nested object + arrayOfObjectsField: + type: array + items: + type: object + properties: + objectField1: + type: string + objectField2: + type: integer + description: An array of objects + patternField: + type: string + pattern: '^[a-zA-Z0-9]{3,10}$' + description: A string field with regex pattern validation + nullableField: + type: string + nullable: true + description: A string field that allows null values + additionalProperties: + type: object + description: Additional properties are allowed beyond the defined fields + + AllOfRequest: + type: object + properties: + name: + type: string + age: + type: integer + details: + allOf: + - type: object + properties: + address: + type: string + - type: object + properties: + contact_number: + type: string + required: + - name + - age + SomeResponse1: + type: object + properties: + status: + type: string + message: + type: string + required: + - status + - message + + SomeResponse2: + type: object + properties: + id: + type: string + created_at: + type: string + format: date-time + details: + type: object + properties: + description: + type: string + status: + type: string + required: + - id + - created_at \ No newline at end of file diff --git a/samples/server/petstore/scala-cask/.openapi-generator/FILES b/samples/server/petstore/scala-cask/.openapi-generator/FILES index e3a6308f3d25..f3d799a105ce 100644 --- a/samples/server/petstore/scala-cask/.openapi-generator/FILES +++ b/samples/server/petstore/scala-cask/.openapi-generator/FILES @@ -10,6 +10,8 @@ example/Server.scala jvm/src/main/scala/sample/cask/AppRoutes.scala jvm/src/main/scala/sample/cask/BaseApp.scala jvm/src/main/scala/sample/cask/ExampleApp.scala +jvm/src/main/scala/sample/cask/api/ComplexRouteRoutes.scala +jvm/src/main/scala/sample/cask/api/ComplexRouteRoutes.scala jvm/src/main/scala/sample/cask/api/OpenApiRoutes.scala jvm/src/main/scala/sample/cask/api/PetRoutes.scala jvm/src/main/scala/sample/cask/api/PetRoutes.scala @@ -19,19 +21,50 @@ jvm/src/main/scala/sample/cask/api/UserRoutes.scala jvm/src/main/scala/sample/cask/api/UserRoutes.scala jvm/src/main/scala/sample/cask/api/package.scala jvm/src/main/scala/sample/cask/package.scala +jvm/src/test/scala/sample/cask/model/AllOfRequestDetailsTest.scala +jvm/src/test/scala/sample/cask/model/AllOfRequestTest.scala +jvm/src/test/scala/sample/cask/model/ComplexRequestArrayOfObjectsFieldInnerTest.scala +jvm/src/test/scala/sample/cask/model/ComplexRequestNestedObjectTest.scala +jvm/src/test/scala/sample/cask/model/ComplexRequestTest.scala +jvm/src/test/scala/sample/cask/model/OneOfRequestAndResponse200ResponseTest.scala +jvm/src/test/scala/sample/cask/model/OneOfRequestAndResponseRequestTest.scala +jvm/src/test/scala/sample/cask/model/SomeResponse1Test.scala +jvm/src/test/scala/sample/cask/model/SomeResponse2DetailsTest.scala +jvm/src/test/scala/sample/cask/model/SomeResponse2Test.scala project/build.properties project/plugins.sbt +shared/src/main/scala/sample/cask/api/ComplexRouteService.scala shared/src/main/scala/sample/cask/api/PetService.scala shared/src/main/scala/sample/cask/api/StoreService.scala shared/src/main/scala/sample/cask/api/UserService.scala +shared/src/main/scala/sample/cask/model/AllOfRequest.scala +shared/src/main/scala/sample/cask/model/AllOfRequestData.scala +shared/src/main/scala/sample/cask/model/AllOfRequestDetails.scala +shared/src/main/scala/sample/cask/model/AllOfRequestDetailsData.scala shared/src/main/scala/sample/cask/model/ApiResponse.scala shared/src/main/scala/sample/cask/model/ApiResponseData.scala shared/src/main/scala/sample/cask/model/Category.scala shared/src/main/scala/sample/cask/model/CategoryData.scala +shared/src/main/scala/sample/cask/model/ComplexRequest.scala +shared/src/main/scala/sample/cask/model/ComplexRequestArrayOfObjectsFieldInner.scala +shared/src/main/scala/sample/cask/model/ComplexRequestArrayOfObjectsFieldInnerData.scala +shared/src/main/scala/sample/cask/model/ComplexRequestData.scala +shared/src/main/scala/sample/cask/model/ComplexRequestNestedObject.scala +shared/src/main/scala/sample/cask/model/ComplexRequestNestedObjectData.scala +shared/src/main/scala/sample/cask/model/OneOfRequestAndResponse200Response.scala +shared/src/main/scala/sample/cask/model/OneOfRequestAndResponse200ResponseData.scala +shared/src/main/scala/sample/cask/model/OneOfRequestAndResponseRequest.scala +shared/src/main/scala/sample/cask/model/OneOfRequestAndResponseRequestData.scala shared/src/main/scala/sample/cask/model/Order.scala shared/src/main/scala/sample/cask/model/OrderData.scala shared/src/main/scala/sample/cask/model/Pet.scala shared/src/main/scala/sample/cask/model/PetData.scala +shared/src/main/scala/sample/cask/model/SomeResponse1.scala +shared/src/main/scala/sample/cask/model/SomeResponse1Data.scala +shared/src/main/scala/sample/cask/model/SomeResponse2.scala +shared/src/main/scala/sample/cask/model/SomeResponse2Data.scala +shared/src/main/scala/sample/cask/model/SomeResponse2Details.scala +shared/src/main/scala/sample/cask/model/SomeResponse2DetailsData.scala shared/src/main/scala/sample/cask/model/Tag.scala shared/src/main/scala/sample/cask/model/TagData.scala shared/src/main/scala/sample/cask/model/User.scala diff --git a/samples/server/petstore/scala-cask/example/Server.scala b/samples/server/petstore/scala-cask/example/Server.scala index cd6650b83a40..99ba19e0d46c 100644 --- a/samples/server/petstore/scala-cask/example/Server.scala +++ b/samples/server/petstore/scala-cask/example/Server.scala @@ -47,6 +47,7 @@ import sample.cask.model.* import java.io.File // TODO - write your business logic for your services here (the defaults all return 'not implemented'): +val myComplexRouteService : ComplexRouteService = ComplexRouteService() // <-- replace this with your implementation val myPetService : PetService = PetService() // <-- replace this with your implementation val myStoreService : StoreService = StoreService() // <-- replace this with your implementation val myUserService : UserService = UserService() // <-- replace this with your implementation @@ -54,7 +55,8 @@ val myUserService : UserService = UserService() // <-- replace this with your im /** This is your main entry point for your REST service * It extends BaseApp which defines the business logic for your services */ -object Server extends BaseApp(appPetService = myPetService, +object Server extends BaseApp(appComplexRouteService = myComplexRouteService, +appPetService = myPetService, appStoreService = myStoreService, appUserService = myUserService): start() diff --git a/samples/server/petstore/scala-cask/jvm/src/main/resources/openapi.json b/samples/server/petstore/scala-cask/jvm/src/main/resources/openapi.json index 5c3f8dec9aa7..6ef6a493170f 100644 --- a/samples/server/petstore/scala-cask/jvm/src/main/resources/openapi.json +++ b/samples/server/petstore/scala-cask/jvm/src/main/resources/openapi.json @@ -27,6 +27,35 @@ "name" : "user" } ], "paths" : { + "/complex-types" : { + "post" : { + "operationId" : "oneOfRequestAndResponse", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/oneOfRequestAndResponse_request" + } + } + }, + "required" : true + }, + "responses" : { + "200" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/oneOfRequestAndResponse_200_response" + } + } + }, + "description" : "Success" + } + }, + "summary" : "Example route with 'oneOf' content and response", + "tags" : [ "complexRoute" ] + } + }, "/pet" : { "post" : { "description" : "", @@ -981,6 +1010,154 @@ "title" : "An uploaded response", "type" : "object" }, + "ComplexRequest" : { + "additionalProperties" : { + "description" : "Additional properties are allowed beyond the defined fields", + "type" : "object" + }, + "properties" : { + "type" : { + "enum" : [ "someEnum" ], + "type" : "string" + }, + "requiredField" : { + "description" : "A required string field", + "type" : "string" + }, + "stringField" : { + "description" : "String with length constraints", + "maxLength" : 100, + "minLength" : 5, + "type" : "string" + }, + "integerField" : { + "description" : "Integer with a range from 1 to 100", + "maximum" : 100, + "minimum" : 1, + "type" : "integer" + }, + "numberField" : { + "description" : "Number with exclusive minimum and maximum constraints", + "type" : "number" + }, + "booleanField" : { + "description" : "A boolean field", + "type" : "boolean" + }, + "emailField" : { + "description" : "A valid email address", + "format" : "email", + "type" : "string" + }, + "uuidField" : { + "description" : "A valid UUID", + "format" : "uuid", + "type" : "string" + }, + "dateField" : { + "description" : "A date field (YYYY-MM-DD)", + "format" : "date", + "type" : "string" + }, + "dateTimeField" : { + "description" : "A date-time field (RFC 3339 format)", + "format" : "date-time", + "type" : "string" + }, + "arrayField" : { + "description" : "An array of strings with min and max items constraints", + "items" : { + "type" : "string" + }, + "maxItems" : 10, + "minItems" : 1, + "type" : "array" + }, + "enumField" : { + "description" : "A string field with enum validation", + "enum" : [ "value1", "value2", "value3" ], + "type" : "string" + }, + "nestedObject" : { + "$ref" : "#/components/schemas/ComplexRequest_nestedObject" + }, + "arrayOfObjectsField" : { + "items" : { + "$ref" : "#/components/schemas/ComplexRequest_arrayOfObjectsField_inner" + }, + "type" : "array" + }, + "patternField" : { + "description" : "A string field with regex pattern validation", + "pattern" : "^[a-zA-Z0-9]{3,10}$", + "type" : "string" + }, + "nullableField" : { + "description" : "A string field that allows null values", + "nullable" : true, + "type" : "string" + } + }, + "required" : [ "requiredField", "type" ], + "type" : "object" + }, + "AllOfRequest" : { + "properties" : { + "name" : { + "type" : "string" + }, + "age" : { + "type" : "integer" + }, + "details" : { + "$ref" : "#/components/schemas/AllOfRequest_details" + } + }, + "required" : [ "age", "name" ], + "type" : "object" + }, + "SomeResponse1" : { + "properties" : { + "status" : { + "type" : "string" + }, + "message" : { + "type" : "string" + } + }, + "required" : [ "message", "status" ], + "type" : "object" + }, + "SomeResponse2" : { + "properties" : { + "id" : { + "type" : "string" + }, + "created_at" : { + "format" : "date-time", + "type" : "string" + }, + "details" : { + "$ref" : "#/components/schemas/SomeResponse2_details" + } + }, + "required" : [ "created_at", "id" ], + "type" : "object" + }, + "oneOfRequestAndResponse_request" : { + "oneOf" : [ { + "$ref" : "#/components/schemas/ComplexRequest" + }, { + "$ref" : "#/components/schemas/AllOfRequest" + } ] + }, + "oneOfRequestAndResponse_200_response" : { + "oneOf" : [ { + "$ref" : "#/components/schemas/SomeResponse1" + }, { + "$ref" : "#/components/schemas/SomeResponse2" + } ] + }, "updatePetWithForm_request" : { "properties" : { "name" : { @@ -1007,6 +1184,59 @@ } }, "type" : "object" + }, + "ComplexRequest_nestedObject" : { + "properties" : { + "nestedField1" : { + "description" : "A field within a nested object", + "type" : "string" + }, + "nestedField2" : { + "description" : "Another field in a nested object", + "type" : "integer" + } + }, + "type" : "object" + }, + "ComplexRequest_arrayOfObjectsField_inner" : { + "description" : "An array of objects", + "properties" : { + "objectField1" : { + "type" : "string" + }, + "objectField2" : { + "type" : "integer" + } + }, + "type" : "object" + }, + "AllOfRequest_details" : { + "allOf" : [ { + "properties" : { + "address" : { + "type" : "string" + } + }, + "type" : "object" + }, { + "properties" : { + "contact_number" : { + "type" : "string" + } + }, + "type" : "object" + } ] + }, + "SomeResponse2_details" : { + "properties" : { + "description" : { + "type" : "string" + }, + "status" : { + "type" : "string" + } + }, + "type" : "object" } }, "securitySchemes" : { diff --git a/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/AppRoutes.scala b/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/AppRoutes.scala index f519b3ca30fa..07a0586bd3c6 100644 --- a/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/AppRoutes.scala +++ b/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/AppRoutes.scala @@ -37,6 +37,9 @@ import _root_.sample.cask.api.* * More typically, however, you would extend the 'BaseApp' class */ trait AppRoutes { + def appComplexRouteService : ComplexRouteService[Try] = ComplexRouteService() + def routeForComplexRoute : ComplexRouteRoutes = ComplexRouteRoutes(appComplexRouteService) + def appPetService : PetService[Try] = PetService() def routeForPet : PetRoutes = PetRoutes(appPetService) @@ -48,6 +51,7 @@ trait AppRoutes { def appRoutes = Seq( + routeForComplexRoute , routeForPet , routeForStore , routeForUser diff --git a/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/BaseApp.scala b/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/BaseApp.scala index 7ed98e88bc96..e4aa3992979f 100644 --- a/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/BaseApp.scala +++ b/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/BaseApp.scala @@ -27,6 +27,8 @@ import _root_.sample.cask.api.* * passing in the custom business logic services */ class BaseApp( + override val appComplexRouteService : ComplexRouteService[Try] = ComplexRouteService(), + override val appPetService : PetService[Try] = PetService(), override val appStoreService : StoreService[Try] = StoreService(), diff --git a/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/ComplexRouteRoutes.scala b/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/ComplexRouteRoutes.scala new file mode 100644 index 000000000000..cb77f67ee8bd --- /dev/null +++ b/samples/server/petstore/scala-cask/jvm/src/main/scala/sample/cask/api/ComplexRouteRoutes.scala @@ -0,0 +1,59 @@ +//> using scala "3.3.1" +//> using lib "com.lihaoyi::cask:0.8.3" +//> using lib "com.lihaoyi::scalatags:0.12.0" +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + + +// this is generated from apiRoutes.mustache +package sample.cask.api + +import sample.cask.model.* + +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* +import scala.util.Try + +import sample.cask.model.OneOfRequestAndResponse200Response +import sample.cask.model.OneOfRequestAndResponseRequest + +class ComplexRouteRoutes(service : ComplexRouteService[Try]) extends cask.Routes { + + + /** Example route with 'oneOf' content and response + * + */ + @cask.post("/complex-types") + def oneOfRequestAndResponse(request: cask.Request) = { + + def failFast = request.queryParams.keySet.contains("failFast") + + val result = for { + oneOfRequestAndResponseRequestJson <- Parsed.fromTry(request.bodyAsJson) + oneOfRequestAndResponseRequestData <- Parsed.eval(OneOfRequestAndResponseRequestData.fromJson(oneOfRequestAndResponseRequestJson)) /* not array or map */ + oneOfRequestAndResponseRequest <- Parsed.fromTry(OneOfRequestAndResponseRequestData.validated(oneOfRequestAndResponseRequestData, failFast)) + resultTry <- Parsed.eval(service.oneOfRequestAndResponse(oneOfRequestAndResponseRequest)) + result <- Parsed.fromTry(resultTry) + } yield result + + import OneOfRequestAndResponse200Response.{given, *} // this brings in upickle in the case of union (oneOf) types + + (result : @unchecked) match { + case Left(error) => cask.Response(error, 500) + case Right(value : OneOfRequestAndResponse200Response) => cask.Response(data = write(value), 200, headers = Seq("Content-Type" -> "application/json")) + case Right(other) => cask.Response(s"$other", 200) + } + } + + initialize() +} diff --git a/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/AllOfRequestDetailsTest.scala b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/AllOfRequestDetailsTest.scala new file mode 100644 index 000000000000..fd04c762efc8 --- /dev/null +++ b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/AllOfRequestDetailsTest.scala @@ -0,0 +1,36 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelTest.mustache +package sample.cask.model + +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import scala.util.* + +class AllOfRequestDetailsTest extends AnyWordSpec with Matchers { + + + "AllOfRequestDetails.fromJson" should { + """not parse invalid json""" in { + val Failure(err) = Try(AllOfRequestDetailsData.fromJsonString("invalid jason")) + err.getMessage should startWith ("Error parsing json 'invalid jason'") + } + """parse """ ignore { + val d8a = AllOfRequestDetailsData.fromJsonString("""""") + val Failure(err : ValidationErrors) = AllOfRequestDetailsData.validated(d8a, true) + + sys.error("TODO") + } + } +} diff --git a/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/AllOfRequestTest.scala b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/AllOfRequestTest.scala new file mode 100644 index 000000000000..13a8aba1a177 --- /dev/null +++ b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/AllOfRequestTest.scala @@ -0,0 +1,36 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelTest.mustache +package sample.cask.model + +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import scala.util.* + +class AllOfRequestTest extends AnyWordSpec with Matchers { + + + "AllOfRequest.fromJson" should { + """not parse invalid json""" in { + val Failure(err) = Try(AllOfRequestData.fromJsonString("invalid jason")) + err.getMessage should startWith ("Error parsing json 'invalid jason'") + } + """parse """ ignore { + val d8a = AllOfRequestData.fromJsonString("""""") + val Failure(err : ValidationErrors) = AllOfRequestData.validated(d8a, true) + + sys.error("TODO") + } + } +} diff --git a/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/ComplexRequestArrayOfObjectsFieldInnerTest.scala b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/ComplexRequestArrayOfObjectsFieldInnerTest.scala new file mode 100644 index 000000000000..3ab544d8567f --- /dev/null +++ b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/ComplexRequestArrayOfObjectsFieldInnerTest.scala @@ -0,0 +1,36 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelTest.mustache +package sample.cask.model + +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import scala.util.* + +class ComplexRequestArrayOfObjectsFieldInnerTest extends AnyWordSpec with Matchers { + + + "ComplexRequestArrayOfObjectsFieldInner.fromJson" should { + """not parse invalid json""" in { + val Failure(err) = Try(ComplexRequestArrayOfObjectsFieldInnerData.fromJsonString("invalid jason")) + err.getMessage should startWith ("Error parsing json 'invalid jason'") + } + """parse """ ignore { + val d8a = ComplexRequestArrayOfObjectsFieldInnerData.fromJsonString("""""") + val Failure(err : ValidationErrors) = ComplexRequestArrayOfObjectsFieldInnerData.validated(d8a, true) + + sys.error("TODO") + } + } +} diff --git a/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/ComplexRequestNestedObjectTest.scala b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/ComplexRequestNestedObjectTest.scala new file mode 100644 index 000000000000..ac1f00ffc518 --- /dev/null +++ b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/ComplexRequestNestedObjectTest.scala @@ -0,0 +1,36 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelTest.mustache +package sample.cask.model + +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import scala.util.* + +class ComplexRequestNestedObjectTest extends AnyWordSpec with Matchers { + + + "ComplexRequestNestedObject.fromJson" should { + """not parse invalid json""" in { + val Failure(err) = Try(ComplexRequestNestedObjectData.fromJsonString("invalid jason")) + err.getMessage should startWith ("Error parsing json 'invalid jason'") + } + """parse """ ignore { + val d8a = ComplexRequestNestedObjectData.fromJsonString("""""") + val Failure(err : ValidationErrors) = ComplexRequestNestedObjectData.validated(d8a, true) + + sys.error("TODO") + } + } +} diff --git a/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/ComplexRequestTest.scala b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/ComplexRequestTest.scala new file mode 100644 index 000000000000..c353c9dac58d --- /dev/null +++ b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/ComplexRequestTest.scala @@ -0,0 +1,41 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelTest.mustache +package sample.cask.model +import java.time.LocalDate +import java.time.OffsetDateTime +import java.util.UUID +import scala.math.BigDecimal +import ujson.Value + +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import scala.util.* + +class ComplexRequestTest extends AnyWordSpec with Matchers { + + + "ComplexRequest.fromJson" should { + """not parse invalid json""" in { + val Failure(err) = Try(ComplexRequestData.fromJsonString("invalid jason")) + err.getMessage should startWith ("Error parsing json 'invalid jason'") + } + """parse """ ignore { + val d8a = ComplexRequestData.fromJsonString("""""") + val Failure(err : ValidationErrors) = ComplexRequestData.validated(d8a, true) + + sys.error("TODO") + } + } +} diff --git a/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/OneOfRequestAndResponse200ResponseTest.scala b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/OneOfRequestAndResponse200ResponseTest.scala new file mode 100644 index 000000000000..4fd4e0eace2c --- /dev/null +++ b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/OneOfRequestAndResponse200ResponseTest.scala @@ -0,0 +1,37 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelTest.mustache +package sample.cask.model +import java.time.OffsetDateTime + +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import scala.util.* + +class OneOfRequestAndResponse200ResponseTest extends AnyWordSpec with Matchers { + + + "OneOfRequestAndResponse200Response.fromJson" should { + """not parse invalid json""" in { + val Failure(err) = Try(OneOfRequestAndResponse200ResponseData.fromJsonString("invalid jason")) + err.getMessage should startWith ("Error parsing json 'invalid jason'") + } + """parse """ ignore { + val d8a = OneOfRequestAndResponse200ResponseData.fromJsonString("""""") + val Failure(err : ValidationErrors) = OneOfRequestAndResponse200ResponseData.validated(d8a, true) + + sys.error("TODO") + } + } +} diff --git a/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/OneOfRequestAndResponseRequestTest.scala b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/OneOfRequestAndResponseRequestTest.scala new file mode 100644 index 000000000000..33d5aa9b87c6 --- /dev/null +++ b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/OneOfRequestAndResponseRequestTest.scala @@ -0,0 +1,40 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelTest.mustache +package sample.cask.model +import java.time.LocalDate +import java.time.OffsetDateTime +import java.util.UUID +import scala.math.BigDecimal + +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import scala.util.* + +class OneOfRequestAndResponseRequestTest extends AnyWordSpec with Matchers { + + + "OneOfRequestAndResponseRequest.fromJson" should { + """not parse invalid json""" in { + val Failure(err) = Try(OneOfRequestAndResponseRequestData.fromJsonString("invalid jason")) + err.getMessage should startWith ("Error parsing json 'invalid jason'") + } + """parse """ ignore { + val d8a = OneOfRequestAndResponseRequestData.fromJsonString("""""") + val Failure(err : ValidationErrors) = OneOfRequestAndResponseRequestData.validated(d8a, true) + + sys.error("TODO") + } + } +} diff --git a/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/SomeResponse1Test.scala b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/SomeResponse1Test.scala new file mode 100644 index 000000000000..63c26cff6b68 --- /dev/null +++ b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/SomeResponse1Test.scala @@ -0,0 +1,36 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelTest.mustache +package sample.cask.model + +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import scala.util.* + +class SomeResponse1Test extends AnyWordSpec with Matchers { + + + "SomeResponse1.fromJson" should { + """not parse invalid json""" in { + val Failure(err) = Try(SomeResponse1Data.fromJsonString("invalid jason")) + err.getMessage should startWith ("Error parsing json 'invalid jason'") + } + """parse """ ignore { + val d8a = SomeResponse1Data.fromJsonString("""""") + val Failure(err : ValidationErrors) = SomeResponse1Data.validated(d8a, true) + + sys.error("TODO") + } + } +} diff --git a/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/SomeResponse2DetailsTest.scala b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/SomeResponse2DetailsTest.scala new file mode 100644 index 000000000000..8a71e91dbf55 --- /dev/null +++ b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/SomeResponse2DetailsTest.scala @@ -0,0 +1,36 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelTest.mustache +package sample.cask.model + +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import scala.util.* + +class SomeResponse2DetailsTest extends AnyWordSpec with Matchers { + + + "SomeResponse2Details.fromJson" should { + """not parse invalid json""" in { + val Failure(err) = Try(SomeResponse2DetailsData.fromJsonString("invalid jason")) + err.getMessage should startWith ("Error parsing json 'invalid jason'") + } + """parse """ ignore { + val d8a = SomeResponse2DetailsData.fromJsonString("""""") + val Failure(err : ValidationErrors) = SomeResponse2DetailsData.validated(d8a, true) + + sys.error("TODO") + } + } +} diff --git a/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/SomeResponse2Test.scala b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/SomeResponse2Test.scala new file mode 100644 index 000000000000..c63dddd0c764 --- /dev/null +++ b/samples/server/petstore/scala-cask/jvm/src/test/scala/sample/cask/model/SomeResponse2Test.scala @@ -0,0 +1,37 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelTest.mustache +package sample.cask.model +import java.time.OffsetDateTime + +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import scala.util.* + +class SomeResponse2Test extends AnyWordSpec with Matchers { + + + "SomeResponse2.fromJson" should { + """not parse invalid json""" in { + val Failure(err) = Try(SomeResponse2Data.fromJsonString("invalid jason")) + err.getMessage should startWith ("Error parsing json 'invalid jason'") + } + """parse """ ignore { + val d8a = SomeResponse2Data.fromJsonString("""""") + val Failure(err : ValidationErrors) = SomeResponse2Data.validated(d8a, true) + + sys.error("TODO") + } + } +} diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/api/ComplexRouteService.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/api/ComplexRouteService.scala new file mode 100644 index 000000000000..08b99029f598 --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/api/ComplexRouteService.scala @@ -0,0 +1,118 @@ +//> using scala "3.3.1" +//> using lib "com.lihaoyi::cask:0.8.3" +//> using lib "com.lihaoyi::scalatags:0.12.0" +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + + +// generated from apiService.mustache +package sample.cask.api + +import _root_.sample.cask.model.OneOfRequestAndResponse200Response +import _root_.sample.cask.model.OneOfRequestAndResponseRequest +import scala.util.Failure +import scala.util.Try +import _root_.sample.cask.model.* + +/** + * The ComplexRouteService companion object. + * + * Use the ComplexRouteService() companion object to create an instance which returns a 'not implemented' error + * for each operation. + * + */ +object ComplexRouteService { + + /** + * The 'Handler' is an implementation of ComplexRouteService convenient for delegating or overriding individual functions + */ + case class Handler[F[_]]( + oneOfRequestAndResponseHandler : (oneOfRequestAndResponseRequest : OneOfRequestAndResponseRequest) => F[OneOfRequestAndResponse200Response] + ) extends ComplexRouteService[F] { + + override def oneOfRequestAndResponse(oneOfRequestAndResponseRequest : OneOfRequestAndResponseRequest) : F[OneOfRequestAndResponse200Response] = { + oneOfRequestAndResponseHandler(oneOfRequestAndResponseRequest) + } + } + + def apply() : ComplexRouteService[Try] = ComplexRouteService.Handler[Try]( + (_) => notImplemented("oneOfRequestAndResponse") + ) + + private def notImplemented(name : String) = Failure(new Exception(s"TODO: $name not implemented")) +} + +/** + * The ComplexRoute business-logic + * + * + * The 'asHandler' will return an implementation which allows for easily overriding individual operations. + * + * equally there are "on<Function>" helper methods for easily overriding individual functions + * + * @tparam F the effect type (Future, Try, IO, ID, etc) of the operations + */ +trait ComplexRouteService[F[_]] { + /** Example route with 'oneOf' content and response + * + * @return OneOfRequestAndResponse200Response + */ + def oneOfRequestAndResponse(oneOfRequestAndResponseRequest : OneOfRequestAndResponseRequest) : F[OneOfRequestAndResponse200Response] + + /** + * override oneOfRequestAndResponse with the given handler + * @return a new implementation of ComplexRouteService[F] with oneOfRequestAndResponse overridden using the given handler + */ + final def onOneOfRequestAndResponse(handler : (oneOfRequestAndResponseRequest : OneOfRequestAndResponseRequest) => F[OneOfRequestAndResponse200Response]) : ComplexRouteService[F] = { + asHandler.copy(oneOfRequestAndResponseHandler = handler) + } + + /** + * @return a Handler implementation of this service + */ + final def asHandler : ComplexRouteService.Handler[F] = this match { + case h : ComplexRouteService.Handler[F] => h + case _ => + ComplexRouteService.Handler[F]( + (oneOfRequestAndResponseRequest) => oneOfRequestAndResponse(oneOfRequestAndResponseRequest) + ) + } + + /** + * This function will change the effect type of this service. + * + * It's not unlike a typical map operation from A => B, except we're not mapping + * a type from A to B, but rather from F[A] => G[A] using the 'changeEffect' function. + * + * For, this could turn an asynchronous service (one which returns Future[_] types) into + * a synchronous one (one which returns Try[_] types) by awaiting on the Future. + * + * It could change an IO type (like cats effect or ZIO) into an ID[A] which is just: + * ``` + * type ID[A] => A + * ``` + * + * @tparam G the new "polymorphic" effect type + * @param changeEffect the "natural transformation" which can change one effect type into another + * @return a new ComplexRouteService service implementation with effect type [G] + */ + final def mapEffect[G[_]](changeEffect : [A] => F[A] => G[A]) : ComplexRouteService[G] = { + val self = this + + new ComplexRouteService[G] { + override def oneOfRequestAndResponse(oneOfRequestAndResponseRequest : OneOfRequestAndResponseRequest) : G[OneOfRequestAndResponse200Response] = changeEffect { + self.oneOfRequestAndResponse(oneOfRequestAndResponseRequest) + } + } + } +} diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequest.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequest.scala new file mode 100644 index 000000000000..df67709ee245 --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequest.scala @@ -0,0 +1,58 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using model.mustache +package sample.cask.model + +import scala.util.control.NonFatal + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + +case class AllOfRequest( + name: String, + age: Int, + details: Option[AllOfRequestDetails] = None + + +) { + +def asJsonString: String = asData.asJsonString +def asJson: ujson.Value = asData.asJson + +def asData : AllOfRequestData = { +AllOfRequestData( + name = name /* 2 */, + age = age /* 2 */, + details = details.map(_.asData).getOrElse(null) /* 4 */ + +) +} +} + +object AllOfRequest { +given RW[AllOfRequest] = summon[RW[ujson.Value]].bimap[AllOfRequest](_.asJson, json => read[AllOfRequestData](json).asModel) + +enum Fields(val fieldName : String) extends Field(fieldName) { + case name extends Fields("name") + case age extends Fields("age") + case details extends Fields("details") +} + + +} + + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequestData.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequestData.scala new file mode 100644 index 000000000000..7f43eb9792c1 --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequestData.scala @@ -0,0 +1,149 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelData.mustache +package sample.cask.model +import scala.util.control.NonFatal +import scala.util.* + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + /** AllOfRequestData a data transfer object, primarily for simple json serialisation. + * It has no validation - there may be nulls, values out of range, etc + */ +case class AllOfRequestData( + name: String, + age: Int, + details: AllOfRequestDetailsData = null + + +) 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 _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]() + // ================== name validation ================== + + + + + + // ================== age validation ================== + + + + + + // ================== details validation ================== + + + + + // validating details + if (_allValidationErrors.isEmpty || !failFast) { + if details != null then _allValidationErrors ++= details.validationErrors(path :+ AllOfRequest.Fields.details, failFast) + } + + _allValidationErrors.toSeq + } + + /** + * @return the validated model within a Try (if successful) + */ + def validated(failFast : Boolean = false) : scala.util.Try[AllOfRequest] = { + validationErrors(Vector(), failFast) match { + case Seq() => Success(asModel) + case first +: theRest => Failure(ValidationErrors(first, theRest)) + } + } + + /** use 'validated' to check validation */ + def asModel : AllOfRequest = { + AllOfRequest( + name = name /* 2 */, + age = age /* 2 */, + details = Option(details).map(_.asModel) /* 4 */ + + ) + } +} + +object AllOfRequestData { + + def validated(d8a : AllOfRequestData, failFast : Boolean) : scala.util.Try[AllOfRequest] = d8a.validated(failFast) + + def fromJson(jason : ujson.Value) : AllOfRequestData = try { + val data = read[AllOfRequestData](jason) + data + } catch { + case NonFatal(e) => sys.error(s"Error creating AllOfRequestData from json '$jason': $e") + } + + def fromJsonString(jason : String) : AllOfRequestData = { + 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[AllOfRequestData] = try { + read[List[AllOfRequestData]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as list: $e") + } + + def manyFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Seq[AllOfRequest]] = { + Try(manyFromJsonString(jason)).flatMap { list => + list.zipWithIndex.foldLeft(Try(Vector[AllOfRequest]())) { + case (Success(list), (next, i)) => + next.validated(failFast) match { + case Success(ok) => Success(list :+ ok) + case Failure(err) => Failure(new Exception(s"Validation error on element $i: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } + + def mapFromJsonString(jason : String) : Map[String, AllOfRequestData] = try { + read[Map[String, AllOfRequestData]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as map: $e") + } + + + def mapFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Map[String, AllOfRequest]] = { + Try(mapFromJsonString(jason)).flatMap { map => + map.foldLeft(Try(Map[String, AllOfRequest]())) { + case (Success(map), (key, next)) => + next.validated(failFast) match { + case Success(ok) => Success(map.updated(key, ok)) + case Failure(err) => Failure(new Exception(s"Validation error on element $key: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } +} + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequestDetails.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequestDetails.scala new file mode 100644 index 000000000000..28d1774320f9 --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequestDetails.scala @@ -0,0 +1,55 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using model.mustache +package sample.cask.model + +import scala.util.control.NonFatal + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + +case class AllOfRequestDetails( + address: Option[String] = None , + contactNumber: Option[String] = None + + +) { + +def asJsonString: String = asData.asJsonString +def asJson: ujson.Value = asData.asJson + +def asData : AllOfRequestDetailsData = { +AllOfRequestDetailsData( + address = address.getOrElse("") /* 1 */, + contactNumber = contactNumber.getOrElse("") /* 1 */ + +) +} +} + +object AllOfRequestDetails { +given RW[AllOfRequestDetails] = summon[RW[ujson.Value]].bimap[AllOfRequestDetails](_.asJson, json => read[AllOfRequestDetailsData](json).asModel) + +enum Fields(val fieldName : String) extends Field(fieldName) { + case address extends Fields("address") + case contactNumber extends Fields("contactNumber") +} + + +} + + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequestDetailsData.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequestDetailsData.scala new file mode 100644 index 000000000000..c8a5775e6734 --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/AllOfRequestDetailsData.scala @@ -0,0 +1,137 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelData.mustache +package sample.cask.model +import scala.util.control.NonFatal +import scala.util.* + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + /** AllOfRequestDetailsData a data transfer object, primarily for simple json serialisation. + * It has no validation - there may be nulls, values out of range, etc + */ +case class AllOfRequestDetailsData( + address: String = "" , + contactNumber: String = "" + + +) 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 _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]() + // ================== address validation ================== + + + + + + // ================== contactNumber validation ================== + + + + + + _allValidationErrors.toSeq + } + + /** + * @return the validated model within a Try (if successful) + */ + def validated(failFast : Boolean = false) : scala.util.Try[AllOfRequestDetails] = { + validationErrors(Vector(), failFast) match { + case Seq() => Success(asModel) + case first +: theRest => Failure(ValidationErrors(first, theRest)) + } + } + + /** use 'validated' to check validation */ + def asModel : AllOfRequestDetails = { + AllOfRequestDetails( + address = Option(address) /* 1 */, + contactNumber = Option(contactNumber) /* 1 */ + + ) + } +} + +object AllOfRequestDetailsData { + + def validated(d8a : AllOfRequestDetailsData, failFast : Boolean) : scala.util.Try[AllOfRequestDetails] = d8a.validated(failFast) + + def fromJson(jason : ujson.Value) : AllOfRequestDetailsData = try { + val data = read[AllOfRequestDetailsData](jason) + data + } catch { + case NonFatal(e) => sys.error(s"Error creating AllOfRequestDetailsData from json '$jason': $e") + } + + def fromJsonString(jason : String) : AllOfRequestDetailsData = { + 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[AllOfRequestDetailsData] = try { + read[List[AllOfRequestDetailsData]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as list: $e") + } + + def manyFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Seq[AllOfRequestDetails]] = { + Try(manyFromJsonString(jason)).flatMap { list => + list.zipWithIndex.foldLeft(Try(Vector[AllOfRequestDetails]())) { + case (Success(list), (next, i)) => + next.validated(failFast) match { + case Success(ok) => Success(list :+ ok) + case Failure(err) => Failure(new Exception(s"Validation error on element $i: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } + + def mapFromJsonString(jason : String) : Map[String, AllOfRequestDetailsData] = try { + read[Map[String, AllOfRequestDetailsData]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as map: $e") + } + + + def mapFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Map[String, AllOfRequestDetails]] = { + Try(mapFromJsonString(jason)).flatMap { map => + map.foldLeft(Try(Map[String, AllOfRequestDetails]())) { + case (Success(map), (key, next)) => + next.validated(failFast) match { + case Success(ok) => Success(map.updated(key, ok)) + case Failure(err) => Failure(new Exception(s"Validation error on element $key: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } +} + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequest.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequest.scala new file mode 100644 index 000000000000..1aa43f6ac932 --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequest.scala @@ -0,0 +1,127 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using model.mustache +package sample.cask.model +import java.time.LocalDate +import java.time.OffsetDateTime +import java.util.UUID +import scala.math.BigDecimal +import ujson.Value + +import scala.util.control.NonFatal + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + +case class ComplexRequest( + `type`: ComplexRequest.TypeEnum, + /* A required string field */ + requiredField: String, + /* String with length constraints */ + stringField: Option[String] = None , + /* Integer with a range from 1 to 100 */ + integerField: Option[Int] = None , + /* Number with exclusive minimum and maximum constraints */ + numberField: Option[BigDecimal] = None , + /* A boolean field */ + booleanField: Option[Boolean] = None , + /* A valid email address */ + emailField: Option[String] = None , + /* A valid UUID */ + uuidField: Option[UUID] = None , + /* A date field (YYYY-MM-DD) */ + dateField: Option[LocalDate] = None , + /* A date-time field (RFC 3339 format) */ + dateTimeField: Option[OffsetDateTime] = None , + /* An array of strings with min and max items constraints */ + arrayField: Seq[String] = Nil , + /* A string field with enum validation */ + enumField: Option[ComplexRequest.EnumFieldEnum] = None , + nestedObject: Option[ComplexRequestNestedObject] = None , + arrayOfObjectsField: Seq[ComplexRequestArrayOfObjectsFieldInner] = Nil , + /* A string field with regex pattern validation */ + patternField: Option[String] = None , + /* A string field that allows null values */ + nullableField: Option[String] = None + +, additionalProperties : ujson.Value = ujson.Null +) { + +def asJsonString: String = asData.asJsonString +def asJson: ujson.Value = asData.asJson + +def asData : ComplexRequestData = { +ComplexRequestData( + `type` = `type` /* 2 */, + requiredField = requiredField /* 2 */, + stringField = stringField.getOrElse("") /* 1 */, + integerField = integerField.getOrElse(0) /* 1 */, + numberField = numberField.getOrElse(0) /* 1 */, + booleanField = booleanField.getOrElse(false) /* 1 */, + emailField = emailField.getOrElse("") /* 1 */, + uuidField = uuidField.getOrElse(java.util.UUID.randomUUID()) /* 1 */, + dateField = dateField.getOrElse(null) /* 1 */, + dateTimeField = dateTimeField.getOrElse(null) /* 1 */, + arrayField = arrayField /* 2 */, + enumField = enumField.getOrElse(null) /* 1 */, + nestedObject = nestedObject.map(_.asData).getOrElse(null) /* 4 */, + arrayOfObjectsField = arrayOfObjectsField.map(_.asData) /* 6 */, + patternField = patternField.getOrElse("") /* 1 */, + nullableField = nullableField.getOrElse("") /* 1 */ +, additionalProperties +) +} +} + +object ComplexRequest { +given RW[ComplexRequest] = summon[RW[ujson.Value]].bimap[ComplexRequest](_.asJson, json => read[ComplexRequestData](json).asModel) + +enum Fields(val fieldName : String) extends Field(fieldName) { + case `type` extends Fields("`type`") + case requiredField extends Fields("requiredField") + case stringField extends Fields("stringField") + case integerField extends Fields("integerField") + case numberField extends Fields("numberField") + case booleanField extends Fields("booleanField") + case emailField extends Fields("emailField") + case uuidField extends Fields("uuidField") + case dateField extends Fields("dateField") + case dateTimeField extends Fields("dateTimeField") + case arrayField extends Fields("arrayField") + case enumField extends Fields("enumField") + case nestedObject extends Fields("nestedObject") + case arrayOfObjectsField extends Fields("arrayOfObjectsField") + case patternField extends Fields("patternField") + case nullableField extends Fields("nullableField") +} + + // baseName=type + // nameInCamelCase = `type` + enum TypeEnum derives ReadWriter { + case someEnum + } + // baseName=enumField + // nameInCamelCase = enumField + enum EnumFieldEnum derives ReadWriter { + case value1 + case value2 + case value3 + } + +} + + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestArrayOfObjectsFieldInner.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestArrayOfObjectsFieldInner.scala new file mode 100644 index 000000000000..c7e221cc2db1 --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestArrayOfObjectsFieldInner.scala @@ -0,0 +1,55 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using model.mustache +package sample.cask.model + +import scala.util.control.NonFatal + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + +case class ComplexRequestArrayOfObjectsFieldInner( + objectField1: Option[String] = None , + objectField2: Option[Int] = None + + +) { + +def asJsonString: String = asData.asJsonString +def asJson: ujson.Value = asData.asJson + +def asData : ComplexRequestArrayOfObjectsFieldInnerData = { +ComplexRequestArrayOfObjectsFieldInnerData( + objectField1 = objectField1.getOrElse("") /* 1 */, + objectField2 = objectField2.getOrElse(0) /* 1 */ + +) +} +} + +object ComplexRequestArrayOfObjectsFieldInner { +given RW[ComplexRequestArrayOfObjectsFieldInner] = summon[RW[ujson.Value]].bimap[ComplexRequestArrayOfObjectsFieldInner](_.asJson, json => read[ComplexRequestArrayOfObjectsFieldInnerData](json).asModel) + +enum Fields(val fieldName : String) extends Field(fieldName) { + case objectField1 extends Fields("objectField1") + case objectField2 extends Fields("objectField2") +} + + +} + + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestArrayOfObjectsFieldInnerData.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestArrayOfObjectsFieldInnerData.scala new file mode 100644 index 000000000000..07f08a9d5e90 --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestArrayOfObjectsFieldInnerData.scala @@ -0,0 +1,137 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelData.mustache +package sample.cask.model +import scala.util.control.NonFatal +import scala.util.* + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + /** ComplexRequestArrayOfObjectsFieldInnerData a data transfer object, primarily for simple json serialisation. + * It has no validation - there may be nulls, values out of range, etc + */ +case class ComplexRequestArrayOfObjectsFieldInnerData( + objectField1: String = "" , + objectField2: Int = 0 + + +) 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 _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]() + // ================== objectField1 validation ================== + + + + + + // ================== objectField2 validation ================== + + + + + + _allValidationErrors.toSeq + } + + /** + * @return the validated model within a Try (if successful) + */ + def validated(failFast : Boolean = false) : scala.util.Try[ComplexRequestArrayOfObjectsFieldInner] = { + validationErrors(Vector(), failFast) match { + case Seq() => Success(asModel) + case first +: theRest => Failure(ValidationErrors(first, theRest)) + } + } + + /** use 'validated' to check validation */ + def asModel : ComplexRequestArrayOfObjectsFieldInner = { + ComplexRequestArrayOfObjectsFieldInner( + objectField1 = Option(objectField1) /* 1 */, + objectField2 = Option(objectField2) /* 1 */ + + ) + } +} + +object ComplexRequestArrayOfObjectsFieldInnerData { + + def validated(d8a : ComplexRequestArrayOfObjectsFieldInnerData, failFast : Boolean) : scala.util.Try[ComplexRequestArrayOfObjectsFieldInner] = d8a.validated(failFast) + + def fromJson(jason : ujson.Value) : ComplexRequestArrayOfObjectsFieldInnerData = try { + val data = read[ComplexRequestArrayOfObjectsFieldInnerData](jason) + data + } catch { + case NonFatal(e) => sys.error(s"Error creating ComplexRequestArrayOfObjectsFieldInnerData from json '$jason': $e") + } + + def fromJsonString(jason : String) : ComplexRequestArrayOfObjectsFieldInnerData = { + 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[ComplexRequestArrayOfObjectsFieldInnerData] = try { + read[List[ComplexRequestArrayOfObjectsFieldInnerData]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as list: $e") + } + + def manyFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Seq[ComplexRequestArrayOfObjectsFieldInner]] = { + Try(manyFromJsonString(jason)).flatMap { list => + list.zipWithIndex.foldLeft(Try(Vector[ComplexRequestArrayOfObjectsFieldInner]())) { + case (Success(list), (next, i)) => + next.validated(failFast) match { + case Success(ok) => Success(list :+ ok) + case Failure(err) => Failure(new Exception(s"Validation error on element $i: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } + + def mapFromJsonString(jason : String) : Map[String, ComplexRequestArrayOfObjectsFieldInnerData] = try { + read[Map[String, ComplexRequestArrayOfObjectsFieldInnerData]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as map: $e") + } + + + def mapFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Map[String, ComplexRequestArrayOfObjectsFieldInner]] = { + Try(mapFromJsonString(jason)).flatMap { map => + map.foldLeft(Try(Map[String, ComplexRequestArrayOfObjectsFieldInner]())) { + case (Success(map), (key, next)) => + next.validated(failFast) match { + case Success(ok) => Success(map.updated(key, ok)) + case Failure(err) => Failure(new Exception(s"Validation error on element $key: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } +} + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestData.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestData.scala new file mode 100644 index 000000000000..eb5b7f407e25 --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestData.scala @@ -0,0 +1,338 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelData.mustache +package sample.cask.model +import java.time.LocalDate +import java.time.OffsetDateTime +import java.util.UUID +import scala.math.BigDecimal +import ujson.Value +import scala.util.control.NonFatal +import scala.util.* + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + /** ComplexRequestData a data transfer object, primarily for simple json serialisation. + * It has no validation - there may be nulls, values out of range, etc + */ +case class ComplexRequestData( + `type`: ComplexRequest.TypeEnum, +/* A required string field */ + requiredField: String, +/* String with length constraints */ + stringField: String = "" , +/* Integer with a range from 1 to 100 */ + integerField: Int = 0 , +/* Number with exclusive minimum and maximum constraints */ + numberField: BigDecimal = 0 , +/* A boolean field */ + booleanField: Boolean = false , +/* A valid email address */ + emailField: String = "" , +/* A valid UUID */ + uuidField: UUID = java.util.UUID.randomUUID() , +/* A date field (YYYY-MM-DD) */ + dateField: LocalDate = null , +/* A date-time field (RFC 3339 format) */ + dateTimeField: OffsetDateTime = null , +/* An array of strings with min and max items constraints */ + arrayField: Seq[String] = Nil , +/* A string field with enum validation */ + enumField: ComplexRequest.EnumFieldEnum = null , + nestedObject: ComplexRequestNestedObjectData = null , + arrayOfObjectsField: Seq[ComplexRequestArrayOfObjectsFieldInnerData] = Nil , +/* A string field with regex pattern validation */ + patternField: String = "" , +/* A string field that allows null values */ + nullableField: String = "" + , additionalProperties : ujson.Value = ujson.Null + +) derives RW { + + def asJsonString: String = asJson.toString() + + def asJson : ujson.Value = { + val jason = writeJs(this) + jason.obj.remove("additionalProperties") + jason.mergeWith(additionalProperties) + } + + def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = { + val _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]() + // ================== `type` validation ================== + + + + + + // ================== requiredField validation ================== + + + + + + // ================== stringField validation ================== + // validate min length 5 + if (_allValidationErrors.isEmpty || !failFast) { + val len = if stringField == null then 0 else stringField.length + if (len < 5) { + _allValidationErrors += ValidationError(path :+ ComplexRequest.Fields.stringField, s"length $len is shorter than the min length 5") + } + } + // validate max length 100 + if (_allValidationErrors.isEmpty || !failFast) { + val len = if stringField == null then 0 else stringField.length + if (len < 100) { + _allValidationErrors += ValidationError(path :+ ComplexRequest.Fields.stringField, s"length $len is longer than the max length 100") + } + } + + + + + + // ================== integerField validation ================== + // validate against minimum 1 + if (_allValidationErrors.isEmpty || !failFast) { + if !(integerField >= 1) then + _allValidationErrors += ValidationError(path :+ ComplexRequest.Fields.integerField, s"value '$integerField' is not greater than the minimum value 1") + } + // validate against maximum 100 + if (_allValidationErrors.isEmpty || !failFast) { + if !(integerField <= 100) then + _allValidationErrors += ValidationError(path :+ ComplexRequest.Fields.integerField, s"value '$integerField' is not greater than the maximum value 100") + } + + + + + + // ================== numberField validation ================== + + + + + + // ================== booleanField validation ================== + + + + + + // ================== emailField validation ================== + // validate emailField is a valid email address + if (_allValidationErrors.isEmpty || !failFast) { + val emailRegex = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$""" + // validate emailField is email + if (emailField == null || !emailRegex.r.matches(emailField)) { + _allValidationErrors += ValidationError(path :+ ComplexRequest.Fields.emailField, s"$emailField is not a valid email address according to the pattern $emailRegex") + } + } + + + + + + // ================== uuidField validation ================== + + + + + + // ================== dateField validation ================== + + + + + + // ================== dateTimeField validation ================== + + + + + + // ================== arrayField validation ================== + + // validate min items 1 + if (_allValidationErrors.isEmpty || !failFast) { + val len = if arrayField == null then 0 else arrayField.size + if (len < 1) { + _allValidationErrors += ValidationError(path :+ ComplexRequest.Fields.arrayField, s"arrayField has $len, which is less than the min items 1") + } + } + // validate min items 10 + if (_allValidationErrors.isEmpty || !failFast) { + val len = if arrayField == null then 0 else arrayField.size + if (len > 10) { + _allValidationErrors += ValidationError(path :+ ComplexRequest.Fields.arrayField, s"arrayField has $len, which is greater than the max items 10") + } + } + + + + + // ================== enumField validation ================== + + + + + + // ================== nestedObject validation ================== + + + + + // validating nestedObject + if (_allValidationErrors.isEmpty || !failFast) { + if nestedObject != null then _allValidationErrors ++= nestedObject.validationErrors(path :+ ComplexRequest.Fields.nestedObject, failFast) + } + + // ================== arrayOfObjectsField validation ================== + + + + + if (_allValidationErrors.isEmpty || !failFast) { + if (arrayOfObjectsField != null) { + arrayOfObjectsField.zipWithIndex.foreach { + case (value, i) if _allValidationErrors.isEmpty || !failFast => + _allValidationErrors ++= value.validationErrors( + path :+ ComplexRequest.Fields.arrayOfObjectsField :+ Field(i.toString), + failFast) + case (value, i) => + } + } + } + + + // ================== patternField validation ================== + // validate against pattern '^[a-zA-Z0-9]{3,10}$' + if (_allValidationErrors.isEmpty || !failFast) { + val regex = """^[a-zA-Z0-9]{3,10}$""" + if patternField == null || !regex.r.matches(patternField) then + _allValidationErrors += ValidationError(path :+ ComplexRequest.Fields.patternField, s"value '$patternField' doesn't match pattern $regex") + } + + + + + + // ================== nullableField validation ================== + + + + + + _allValidationErrors.toSeq + } + + /** + * @return the validated model within a Try (if successful) + */ + def validated(failFast : Boolean = false) : scala.util.Try[ComplexRequest] = { + validationErrors(Vector(), failFast) match { + case Seq() => Success(asModel) + case first +: theRest => Failure(ValidationErrors(first, theRest)) + } + } + + /** use 'validated' to check validation */ + def asModel : ComplexRequest = { + ComplexRequest( + `type` = `type` /* 2 */, + requiredField = requiredField /* 2 */, + stringField = Option(stringField) /* 1 */, + integerField = Option(integerField) /* 1 */, + numberField = Option(numberField) /* 1 */, + booleanField = Option(booleanField) /* 1 */, + emailField = Option(emailField) /* 1 */, + uuidField = Option(uuidField) /* 1 */, + dateField = Option(dateField) /* 1 */, + dateTimeField = Option(dateTimeField) /* 1 */, + arrayField = arrayField /* 2 */, + enumField = Option(enumField) /* 1 */, + nestedObject = Option(nestedObject).map(_.asModel) /* 4 */, + arrayOfObjectsField = arrayOfObjectsField.map(_.asModel) /* 5 */, + patternField = Option(patternField) /* 1 */, + nullableField = Option(nullableField) /* 1 */ + , additionalProperties + ) + } +} + +object ComplexRequestData { + + def validated(d8a : ComplexRequestData, failFast : Boolean) : scala.util.Try[ComplexRequest] = d8a.validated(failFast) + + def fromJson(jason : ujson.Value) : ComplexRequestData = try { + val data = read[ComplexRequestData](jason) + val obj = jason.obj + ComplexRequest.Fields.values.foreach(v => obj.value.subtractOne(v.fieldName)) + data.copy(additionalProperties = obj) + } catch { + case NonFatal(e) => sys.error(s"Error creating ComplexRequestData from json '$jason': $e") + } + + def fromJsonString(jason : String) : ComplexRequestData = { + 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[ComplexRequestData] = try { + read[List[ComplexRequestData]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as list: $e") + } + + def manyFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Seq[ComplexRequest]] = { + Try(manyFromJsonString(jason)).flatMap { list => + list.zipWithIndex.foldLeft(Try(Vector[ComplexRequest]())) { + case (Success(list), (next, i)) => + next.validated(failFast) match { + case Success(ok) => Success(list :+ ok) + case Failure(err) => Failure(new Exception(s"Validation error on element $i: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } + + def mapFromJsonString(jason : String) : Map[String, ComplexRequestData] = try { + read[Map[String, ComplexRequestData]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as map: $e") + } + + + def mapFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Map[String, ComplexRequest]] = { + Try(mapFromJsonString(jason)).flatMap { map => + map.foldLeft(Try(Map[String, ComplexRequest]())) { + case (Success(map), (key, next)) => + next.validated(failFast) match { + case Success(ok) => Success(map.updated(key, ok)) + case Failure(err) => Failure(new Exception(s"Validation error on element $key: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } +} + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestNestedObject.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestNestedObject.scala new file mode 100644 index 000000000000..c3ea749dff2d --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestNestedObject.scala @@ -0,0 +1,57 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using model.mustache +package sample.cask.model + +import scala.util.control.NonFatal + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + +case class ComplexRequestNestedObject( + /* A field within a nested object */ + nestedField1: Option[String] = None , + /* Another field in a nested object */ + nestedField2: Option[Int] = None + + +) { + +def asJsonString: String = asData.asJsonString +def asJson: ujson.Value = asData.asJson + +def asData : ComplexRequestNestedObjectData = { +ComplexRequestNestedObjectData( + nestedField1 = nestedField1.getOrElse("") /* 1 */, + nestedField2 = nestedField2.getOrElse(0) /* 1 */ + +) +} +} + +object ComplexRequestNestedObject { +given RW[ComplexRequestNestedObject] = summon[RW[ujson.Value]].bimap[ComplexRequestNestedObject](_.asJson, json => read[ComplexRequestNestedObjectData](json).asModel) + +enum Fields(val fieldName : String) extends Field(fieldName) { + case nestedField1 extends Fields("nestedField1") + case nestedField2 extends Fields("nestedField2") +} + + +} + + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestNestedObjectData.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestNestedObjectData.scala new file mode 100644 index 000000000000..b7d204891f44 --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/ComplexRequestNestedObjectData.scala @@ -0,0 +1,139 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelData.mustache +package sample.cask.model +import scala.util.control.NonFatal +import scala.util.* + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + /** ComplexRequestNestedObjectData a data transfer object, primarily for simple json serialisation. + * It has no validation - there may be nulls, values out of range, etc + */ +case class ComplexRequestNestedObjectData( +/* A field within a nested object */ + nestedField1: String = "" , +/* Another field in a nested object */ + nestedField2: Int = 0 + + +) 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 _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]() + // ================== nestedField1 validation ================== + + + + + + // ================== nestedField2 validation ================== + + + + + + _allValidationErrors.toSeq + } + + /** + * @return the validated model within a Try (if successful) + */ + def validated(failFast : Boolean = false) : scala.util.Try[ComplexRequestNestedObject] = { + validationErrors(Vector(), failFast) match { + case Seq() => Success(asModel) + case first +: theRest => Failure(ValidationErrors(first, theRest)) + } + } + + /** use 'validated' to check validation */ + def asModel : ComplexRequestNestedObject = { + ComplexRequestNestedObject( + nestedField1 = Option(nestedField1) /* 1 */, + nestedField2 = Option(nestedField2) /* 1 */ + + ) + } +} + +object ComplexRequestNestedObjectData { + + def validated(d8a : ComplexRequestNestedObjectData, failFast : Boolean) : scala.util.Try[ComplexRequestNestedObject] = d8a.validated(failFast) + + def fromJson(jason : ujson.Value) : ComplexRequestNestedObjectData = try { + val data = read[ComplexRequestNestedObjectData](jason) + data + } catch { + case NonFatal(e) => sys.error(s"Error creating ComplexRequestNestedObjectData from json '$jason': $e") + } + + def fromJsonString(jason : String) : ComplexRequestNestedObjectData = { + 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[ComplexRequestNestedObjectData] = try { + read[List[ComplexRequestNestedObjectData]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as list: $e") + } + + def manyFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Seq[ComplexRequestNestedObject]] = { + Try(manyFromJsonString(jason)).flatMap { list => + list.zipWithIndex.foldLeft(Try(Vector[ComplexRequestNestedObject]())) { + case (Success(list), (next, i)) => + next.validated(failFast) match { + case Success(ok) => Success(list :+ ok) + case Failure(err) => Failure(new Exception(s"Validation error on element $i: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } + + def mapFromJsonString(jason : String) : Map[String, ComplexRequestNestedObjectData] = try { + read[Map[String, ComplexRequestNestedObjectData]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as map: $e") + } + + + def mapFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Map[String, ComplexRequestNestedObject]] = { + Try(mapFromJsonString(jason)).flatMap { map => + map.foldLeft(Try(Map[String, ComplexRequestNestedObject]())) { + case (Success(map), (key, next)) => + next.validated(failFast) match { + case Success(ok) => Success(map.updated(key, ok)) + case Failure(err) => Failure(new Exception(s"Validation error on element $key: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } +} + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponse200Response.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponse200Response.scala new file mode 100644 index 000000000000..12dc5f4a1d83 --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponse200Response.scala @@ -0,0 +1,33 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using model.mustache +package sample.cask.model +import java.time.OffsetDateTime + +import scala.util.control.NonFatal + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + +type OneOfRequestAndResponse200Response = SomeResponse1 | SomeResponse2 +object OneOfRequestAndResponse200Response { + + given RW[OneOfRequestAndResponse200Response] = RW.merge(summon[RW[SomeResponse1]], summon[RW[SomeResponse2]]) +} + + + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponse200ResponseData.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponse200ResponseData.scala new file mode 100644 index 000000000000..dd4e1051c407 --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponse200ResponseData.scala @@ -0,0 +1,48 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelData.mustache +package sample.cask.model +import java.time.OffsetDateTime +import scala.util.control.NonFatal +import scala.util.* + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + +type OneOfRequestAndResponse200ResponseData = SomeResponse1Data | SomeResponse2Data + +object OneOfRequestAndResponse200ResponseData { + + def validated(d8a : OneOfRequestAndResponse200ResponseData, failFast: Boolean) : Try[OneOfRequestAndResponse200Response] = { + d8a match { + case value : SomeResponse1Data => value.validated(failFast) + case value : SomeResponse2Data => value.validated(failFast) + } + } + + def fromJsonString(jason : String) = fromJson { + try { + read[ujson.Value](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason': $e") + } + } + + def fromJson(jason : ujson.Value) : OneOfRequestAndResponse200ResponseData = { + val attempt = Try(SomeResponse1Data.fromJson(jason)) .orElse( Try(SomeResponse2Data.fromJson(jason)) ) /* not first */ + attempt.get + } +} diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponseRequest.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponseRequest.scala new file mode 100644 index 000000000000..af69e0107e7a --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponseRequest.scala @@ -0,0 +1,36 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using model.mustache +package sample.cask.model +import java.time.LocalDate +import java.time.OffsetDateTime +import java.util.UUID +import scala.math.BigDecimal + +import scala.util.control.NonFatal + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + +type OneOfRequestAndResponseRequest = AllOfRequest | ComplexRequest +object OneOfRequestAndResponseRequest { + + given RW[OneOfRequestAndResponseRequest] = RW.merge(summon[RW[AllOfRequest]], summon[RW[ComplexRequest]]) +} + + + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponseRequestData.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponseRequestData.scala new file mode 100644 index 000000000000..f9c7ef40b0d8 --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/OneOfRequestAndResponseRequestData.scala @@ -0,0 +1,51 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelData.mustache +package sample.cask.model +import java.time.LocalDate +import java.time.OffsetDateTime +import java.util.UUID +import scala.math.BigDecimal +import scala.util.control.NonFatal +import scala.util.* + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + +type OneOfRequestAndResponseRequestData = AllOfRequestData | ComplexRequestData + +object OneOfRequestAndResponseRequestData { + + def validated(d8a : OneOfRequestAndResponseRequestData, failFast: Boolean) : Try[OneOfRequestAndResponseRequest] = { + d8a match { + case value : AllOfRequestData => value.validated(failFast) + case value : ComplexRequestData => value.validated(failFast) + } + } + + def fromJsonString(jason : String) = fromJson { + try { + read[ujson.Value](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason': $e") + } + } + + def fromJson(jason : ujson.Value) : OneOfRequestAndResponseRequestData = { + val attempt = Try(AllOfRequestData.fromJson(jason)) .orElse( Try(ComplexRequestData.fromJson(jason)) ) /* not first */ + attempt.get + } +} diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse1.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse1.scala new file mode 100644 index 000000000000..4ba6da63550b --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse1.scala @@ -0,0 +1,55 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using model.mustache +package sample.cask.model + +import scala.util.control.NonFatal + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + +case class SomeResponse1( + status: String, + message: String + + +) { + +def asJsonString: String = asData.asJsonString +def asJson: ujson.Value = asData.asJson + +def asData : SomeResponse1Data = { +SomeResponse1Data( + status = status /* 2 */, + message = message /* 2 */ + +) +} +} + +object SomeResponse1 { +given RW[SomeResponse1] = summon[RW[ujson.Value]].bimap[SomeResponse1](_.asJson, json => read[SomeResponse1Data](json).asModel) + +enum Fields(val fieldName : String) extends Field(fieldName) { + case status extends Fields("status") + case message extends Fields("message") +} + + +} + + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse1Data.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse1Data.scala new file mode 100644 index 000000000000..1437eafe712f --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse1Data.scala @@ -0,0 +1,137 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelData.mustache +package sample.cask.model +import scala.util.control.NonFatal +import scala.util.* + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + /** SomeResponse1Data a data transfer object, primarily for simple json serialisation. + * It has no validation - there may be nulls, values out of range, etc + */ +case class SomeResponse1Data( + status: String, + message: String + + +) 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 _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]() + // ================== status validation ================== + + + + + + // ================== message validation ================== + + + + + + _allValidationErrors.toSeq + } + + /** + * @return the validated model within a Try (if successful) + */ + def validated(failFast : Boolean = false) : scala.util.Try[SomeResponse1] = { + validationErrors(Vector(), failFast) match { + case Seq() => Success(asModel) + case first +: theRest => Failure(ValidationErrors(first, theRest)) + } + } + + /** use 'validated' to check validation */ + def asModel : SomeResponse1 = { + SomeResponse1( + status = status /* 2 */, + message = message /* 2 */ + + ) + } +} + +object SomeResponse1Data { + + def validated(d8a : SomeResponse1Data, failFast : Boolean) : scala.util.Try[SomeResponse1] = d8a.validated(failFast) + + def fromJson(jason : ujson.Value) : SomeResponse1Data = try { + val data = read[SomeResponse1Data](jason) + data + } catch { + case NonFatal(e) => sys.error(s"Error creating SomeResponse1Data from json '$jason': $e") + } + + def fromJsonString(jason : String) : SomeResponse1Data = { + 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[SomeResponse1Data] = try { + read[List[SomeResponse1Data]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as list: $e") + } + + def manyFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Seq[SomeResponse1]] = { + Try(manyFromJsonString(jason)).flatMap { list => + list.zipWithIndex.foldLeft(Try(Vector[SomeResponse1]())) { + case (Success(list), (next, i)) => + next.validated(failFast) match { + case Success(ok) => Success(list :+ ok) + case Failure(err) => Failure(new Exception(s"Validation error on element $i: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } + + def mapFromJsonString(jason : String) : Map[String, SomeResponse1Data] = try { + read[Map[String, SomeResponse1Data]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as map: $e") + } + + + def mapFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Map[String, SomeResponse1]] = { + Try(mapFromJsonString(jason)).flatMap { map => + map.foldLeft(Try(Map[String, SomeResponse1]())) { + case (Success(map), (key, next)) => + next.validated(failFast) match { + case Success(ok) => Success(map.updated(key, ok)) + case Failure(err) => Failure(new Exception(s"Validation error on element $key: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } +} + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2.scala new file mode 100644 index 000000000000..122706e73575 --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2.scala @@ -0,0 +1,59 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using model.mustache +package sample.cask.model +import java.time.OffsetDateTime + +import scala.util.control.NonFatal + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + +case class SomeResponse2( + id: String, + createdAt: OffsetDateTime, + details: Option[SomeResponse2Details] = None + + +) { + +def asJsonString: String = asData.asJsonString +def asJson: ujson.Value = asData.asJson + +def asData : SomeResponse2Data = { +SomeResponse2Data( + id = id /* 2 */, + createdAt = createdAt /* 2 */, + details = details.map(_.asData).getOrElse(null) /* 4 */ + +) +} +} + +object SomeResponse2 { +given RW[SomeResponse2] = summon[RW[ujson.Value]].bimap[SomeResponse2](_.asJson, json => read[SomeResponse2Data](json).asModel) + +enum Fields(val fieldName : String) extends Field(fieldName) { + case id extends Fields("id") + case createdAt extends Fields("createdAt") + case details extends Fields("details") +} + + +} + + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2Data.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2Data.scala new file mode 100644 index 000000000000..fad33a53186c --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2Data.scala @@ -0,0 +1,156 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelData.mustache +package sample.cask.model +import java.time.OffsetDateTime +import scala.util.control.NonFatal +import scala.util.* + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + /** SomeResponse2Data a data transfer object, primarily for simple json serialisation. + * It has no validation - there may be nulls, values out of range, etc + */ +case class SomeResponse2Data( + id: String, + createdAt: OffsetDateTime, + details: SomeResponse2DetailsData = null + + +) 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 _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]() + // ================== id validation ================== + + + + + + // ================== createdAt validation ================== + + if (_allValidationErrors.isEmpty || !failFast) { + if (createdAt == null) { + _allValidationErrors += ValidationError(path :+ SomeResponse2.Fields.createdAt, "createdAt is a required field and cannot be null") + } + } + + + + + + // ================== details validation ================== + + + + + // validating details + if (_allValidationErrors.isEmpty || !failFast) { + if details != null then _allValidationErrors ++= details.validationErrors(path :+ SomeResponse2.Fields.details, failFast) + } + + _allValidationErrors.toSeq + } + + /** + * @return the validated model within a Try (if successful) + */ + def validated(failFast : Boolean = false) : scala.util.Try[SomeResponse2] = { + validationErrors(Vector(), failFast) match { + case Seq() => Success(asModel) + case first +: theRest => Failure(ValidationErrors(first, theRest)) + } + } + + /** use 'validated' to check validation */ + def asModel : SomeResponse2 = { + SomeResponse2( + id = id /* 2 */, + createdAt = createdAt /* 2 */, + details = Option(details).map(_.asModel) /* 4 */ + + ) + } +} + +object SomeResponse2Data { + + def validated(d8a : SomeResponse2Data, failFast : Boolean) : scala.util.Try[SomeResponse2] = d8a.validated(failFast) + + def fromJson(jason : ujson.Value) : SomeResponse2Data = try { + val data = read[SomeResponse2Data](jason) + data + } catch { + case NonFatal(e) => sys.error(s"Error creating SomeResponse2Data from json '$jason': $e") + } + + def fromJsonString(jason : String) : SomeResponse2Data = { + 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[SomeResponse2Data] = try { + read[List[SomeResponse2Data]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as list: $e") + } + + def manyFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Seq[SomeResponse2]] = { + Try(manyFromJsonString(jason)).flatMap { list => + list.zipWithIndex.foldLeft(Try(Vector[SomeResponse2]())) { + case (Success(list), (next, i)) => + next.validated(failFast) match { + case Success(ok) => Success(list :+ ok) + case Failure(err) => Failure(new Exception(s"Validation error on element $i: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } + + def mapFromJsonString(jason : String) : Map[String, SomeResponse2Data] = try { + read[Map[String, SomeResponse2Data]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as map: $e") + } + + + def mapFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Map[String, SomeResponse2]] = { + Try(mapFromJsonString(jason)).flatMap { map => + map.foldLeft(Try(Map[String, SomeResponse2]())) { + case (Success(map), (key, next)) => + next.validated(failFast) match { + case Success(ok) => Success(map.updated(key, ok)) + case Failure(err) => Failure(new Exception(s"Validation error on element $key: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } +} + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2Details.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2Details.scala new file mode 100644 index 000000000000..e3b2eaf26e71 --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2Details.scala @@ -0,0 +1,55 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using model.mustache +package sample.cask.model + +import scala.util.control.NonFatal + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + +case class SomeResponse2Details( + description: Option[String] = None , + status: Option[String] = None + + +) { + +def asJsonString: String = asData.asJsonString +def asJson: ujson.Value = asData.asJson + +def asData : SomeResponse2DetailsData = { +SomeResponse2DetailsData( + description = description.getOrElse("") /* 1 */, + status = status.getOrElse("") /* 1 */ + +) +} +} + +object SomeResponse2Details { +given RW[SomeResponse2Details] = summon[RW[ujson.Value]].bimap[SomeResponse2Details](_.asJson, json => read[SomeResponse2DetailsData](json).asModel) + +enum Fields(val fieldName : String) extends Field(fieldName) { + case description extends Fields("description") + case status extends Fields("status") +} + + +} + + diff --git a/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2DetailsData.scala b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2DetailsData.scala new file mode 100644 index 000000000000..371693b4c83f --- /dev/null +++ b/samples/server/petstore/scala-cask/shared/src/main/scala/sample/cask/model/SomeResponse2DetailsData.scala @@ -0,0 +1,137 @@ +/** + * 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. + * + * OpenAPI spec version: 1.0.0 + * + * Contact: team@openapitools.org + * + * NOTE: This class is auto generated by OpenAPI Generator. + * + * https://openapi-generator.tech + */ + +// this model was generated using modelData.mustache +package sample.cask.model +import scala.util.control.NonFatal +import scala.util.* + +// see https://com-lihaoyi.github.io/upickle/ +import upickle.default.{ReadWriter => RW, macroRW} +import upickle.default.* + + + /** SomeResponse2DetailsData a data transfer object, primarily for simple json serialisation. + * It has no validation - there may be nulls, values out of range, etc + */ +case class SomeResponse2DetailsData( + description: String = "" , + status: String = "" + + +) 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 _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]() + // ================== description validation ================== + + + + + + // ================== status validation ================== + + + + + + _allValidationErrors.toSeq + } + + /** + * @return the validated model within a Try (if successful) + */ + def validated(failFast : Boolean = false) : scala.util.Try[SomeResponse2Details] = { + validationErrors(Vector(), failFast) match { + case Seq() => Success(asModel) + case first +: theRest => Failure(ValidationErrors(first, theRest)) + } + } + + /** use 'validated' to check validation */ + def asModel : SomeResponse2Details = { + SomeResponse2Details( + description = Option(description) /* 1 */, + status = Option(status) /* 1 */ + + ) + } +} + +object SomeResponse2DetailsData { + + def validated(d8a : SomeResponse2DetailsData, failFast : Boolean) : scala.util.Try[SomeResponse2Details] = d8a.validated(failFast) + + def fromJson(jason : ujson.Value) : SomeResponse2DetailsData = try { + val data = read[SomeResponse2DetailsData](jason) + data + } catch { + case NonFatal(e) => sys.error(s"Error creating SomeResponse2DetailsData from json '$jason': $e") + } + + def fromJsonString(jason : String) : SomeResponse2DetailsData = { + 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[SomeResponse2DetailsData] = try { + read[List[SomeResponse2DetailsData]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as list: $e") + } + + def manyFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Seq[SomeResponse2Details]] = { + Try(manyFromJsonString(jason)).flatMap { list => + list.zipWithIndex.foldLeft(Try(Vector[SomeResponse2Details]())) { + case (Success(list), (next, i)) => + next.validated(failFast) match { + case Success(ok) => Success(list :+ ok) + case Failure(err) => Failure(new Exception(s"Validation error on element $i: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } + + def mapFromJsonString(jason : String) : Map[String, SomeResponse2DetailsData] = try { + read[Map[String, SomeResponse2DetailsData]](jason) + } catch { + case NonFatal(e) => sys.error(s"Error parsing json '$jason' as map: $e") + } + + + def mapFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Map[String, SomeResponse2Details]] = { + Try(mapFromJsonString(jason)).flatMap { map => + map.foldLeft(Try(Map[String, SomeResponse2Details]())) { + case (Success(map), (key, next)) => + next.validated(failFast) match { + case Success(ok) => Success(map.updated(key, ok)) + case Failure(err) => Failure(new Exception(s"Validation error on element $key: ${err.getMessage}", err)) + } + case (fail, _) => fail + } + } + } +} +