Scala cask api effects (#19936)

* Scala-cask improvements:

 * fixe for grouped methods which have routes containing dashes.

Previously our OperationGroup work-around would potentially
Create methods like ‘foo-bar’, which isn’t a valid function name

 * Fix to not import some.package.Array[Byte] when binary format is specified

 * Fix for grouped operations which contain duplicate query parameters

 * Fix for binary response fields. This can come up with the following example

        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "format" : "binary",
                  "type" : "string"
                }
              }
            },
            "description" : "data"
          },

 * Fix for enum model classes
Extracted complex logic for ‘asData’ and ‘asModel’ transformations for properties

 * Introduced a generic effect F[_] for services

This was done to support composable services
(Service A calls Service B) by using monadic
Effect types (ones which can flatMap)

 * Fixed unique union types for responses, asModel and asData fixes for non-model types

* scala-cask: regenerated samples

* Fix for reserved-word properties in the API

* Fix for null imports and reserved-word enum types

* Fixes for api methods with backticked params

* Fix for duplicate (by name) grouped params

* small syntax fix

* logging response type

* Regenerated samples

* String.format fix
This commit is contained in:
Aaron Pritzlaff 2024-11-06 08:14:31 +00:00 committed by GitHub
parent cded99c3fc
commit b51b18e3ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 1725 additions and 821 deletions

View File

@ -31,6 +31,8 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -125,7 +127,7 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
// mapped to String as a workaround
typeMapping.put("binary", "String");
typeMapping.put("object", "Value");
typeMapping.put("object", AdditionalPropertiesType);
cliOptions.add(new CliOption(CodegenConstants.GROUP_ID, CodegenConstants.GROUP_ID_DESC));
cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_ID, CodegenConstants.ARTIFACT_ID_DESC));
@ -139,7 +141,7 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
public String toDefaultValue(Schema p) {
if (ModelUtils.isMapSchema(p)) {
String inner = getSchemaType(ModelUtils.getAdditionalProperties(p));
return "Map[String, " + inner + "]() ";
return "Map[String, " + inner + "]()";
} else if (ModelUtils.isFreeFormObject(p, openAPI)) {
// We're opinionated in this template to use ujson
return "ujson.Null";
@ -150,8 +152,12 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
@Override
public String getSchemaType(Schema p) {
if (ModelUtils.isFreeFormObject(p, openAPI)) {
if (ModelUtils.isMapSchema(p)) {
final String inner = getSchemaType(ModelUtils.getAdditionalProperties(p));
return "Map[String, " + inner + "]";
}
// We're opinionated in this template to use ujson
return "Value";
return AdditionalPropertiesType;
}
return super.getSchemaType(p);
}
@ -246,7 +252,6 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
importMapping.put("LocalDate", "java.time.LocalDate");
importMapping.put("OffsetDateTime", "java.time.OffsetDateTime");
importMapping.put("LocalTime", "java.time.LocalTime");
importMapping.put("Value", "ujson.Value");
importMapping.put(AdditionalPropertiesType, "ujson.Value");
}
@ -269,19 +274,6 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
}
}
static String formatMap(Map<?, ?> map) {
StringBuilder mapAsString = new StringBuilder("{");
for (Object key : map.keySet().stream().sorted().collect(Collectors.toList())) {
mapAsString.append(key + " -- " + map.get(key) + ",\n");
}
if (mapAsString.length() > 1) {
mapAsString.delete(mapAsString.length() - 2, mapAsString.length());
}
mapAsString.append("}");
return mapAsString.toString();
}
@Override
public String toApiName(String name) {
if (name.isEmpty()) {
@ -316,8 +308,7 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
@Override
public String apiFileFolder() {
final String folder = outputFolder + "/jvm/" + sourceFolder + "/" + apiPackage().replace('.', File.separatorChar);
return folder;
return outputFolder + "/jvm/" + sourceFolder + "/" + apiPackage().replace('.', File.separatorChar);
}
@Override
@ -329,6 +320,22 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
return outputFolder + "/shared/" + sourceFolder + "/" + apiPackage().replace('.', File.separatorChar);
}
static String asMethod(final String input) {
// Remove all non-alphanumeric characters using regex
String alphanumeric = input.replaceAll("[^a-zA-Z0-9]", "");
// Ensure the method name doesn't start with a digit
if (alphanumeric.isEmpty()) {
throw new IllegalArgumentException("Input string does not contain any valid alphanumeric characters");
}
if (Character.isDigit(alphanumeric.charAt(0))) {
alphanumeric = "_" + alphanumeric;
}
return alphanumeric;
}
static String capitalise(String p) {
if (p.length() < 2) {
return p.toUpperCase(Locale.ROOT);
@ -470,14 +477,24 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
*
* @return the CodegenParameters
*/
public List<CodegenParameter> getGroupQueryParams() {
List<CodegenParameter> list = operations.stream().flatMap(op -> op.queryParams.stream()).map(p -> {
final CodegenParameter copy = p.copy();
copy.vendorExtensions.put("x-default-value", defaultValue(p));
copy.required = false; // all our query params are optional for our work-around as it's a super-set of a few different routes
copy.dataType = asScalaDataType(copy, false, true, true);
copy.defaultValue = defaultValue(copy);
return copy;
public Collection<CodegenParameter> getGroupQueryParams() {
// wow is this a pain to do in Java ... I just wanted to have distinct items of a list
// based on their name.
Set<String> alreadySeen = new HashSet<>();
List<CodegenParameter> list = operations.stream().flatMap(op -> op.queryParams.stream()).flatMap(p -> {
if (alreadySeen.add(p.paramName)) {
final CodegenParameter copy = p.copy();
copy.vendorExtensions.put("x-default-value", defaultValue(p));
copy.required = false; // all our query params are optional for our work-around as it's a super-set of a few different routes
copy.dataType = asScalaDataType(copy, false, true, true);
copy.defaultValue = defaultValue(copy);
return Arrays.asList(copy).stream();
} else {
final List<CodegenParameter> empty = Collections.emptyList();
return empty.stream();
}
}
).collect(Collectors.toList());
@ -498,7 +515,7 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
List<String> stripped = Arrays.stream(pathPrefix.split("/", -1))
.map(ScalaCaskServerCodegen::capitalise).collect(Collectors.toList());
methodName = "routeWorkAroundFor" + capitalise(httpMethod) + String.join("", stripped);
methodName = asMethod("routeWorkAroundFor" + capitalise(httpMethod) + String.join("", stripped));
}
public void add(CodegenOperation op) {
@ -552,6 +569,7 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
String prefix = nonParamPathPrefix(op);
String key = op.httpMethod + " " + prefix;
if (!op.pathParams.isEmpty()) {
final ScalaCaskServerCodegen.OperationGroup group = groupedByPrefix.getOrDefault(key, new ScalaCaskServerCodegen.OperationGroup(op.httpMethod, prefix));
group.add(op);
groupedByPrefix.put(key, group);
@ -603,6 +621,25 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
final Map<String, Object> operations = (Map<String, Object>) objs.get("operations");
final List<CodegenOperation> operationList = (List<CodegenOperation>) operations.get("operation");
/**
* In this case, there is a import set to 'null':
*
* {{{
* ...
* responses:
* "200":
* content:
* application/json:
* schema:
* format: byte
* type: string
* }}}
*/
List<Map<String, String>> imports = (List<Map<String, String>>) objs.get("imports");
var filtered = imports.stream().filter(entry -> entry.get("import") != null).collect(Collectors.toList());
objs.put("imports", filtered);
objs.put("route-groups", createRouteGroups(operationList));
operationList.forEach(ScalaCaskServerCodegen::postProcessOperation);
@ -611,6 +648,7 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
@Override
public ModelsMap postProcessModels(ModelsMap objs) {
super.postProcessModels(objs);
objs.getModels().stream().map(ModelMap::getModel).forEach(this::postProcessModel);
return objs;
}
@ -632,8 +670,8 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
model.getAllVars().forEach(this::setDefaultValueForCodegenProperty);
model.getVars().forEach(this::setDefaultValueForCodegenProperty);
model.getVars().forEach(ScalaCaskServerCodegen::postProcessProperty);
model.getAllVars().forEach(ScalaCaskServerCodegen::postProcessProperty);
model.getVars().forEach(this::postProcessProperty);
model.getAllVars().forEach(this::postProcessProperty);
}
private static void postProcessOperation(CodegenOperation op) {
@ -643,6 +681,7 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
/* Put in 'x-consumes-json' and 'x-consumes-xml' */
op.vendorExtensions.put("x-consumes-json", consumesMimetype(op, "application/json"));
op.vendorExtensions.put("x-consumes-xml", consumesMimetype(op, "application/xml"));
op.vendorExtensions.put("x-handlerName", "on" + capitalise(op.operationId));
op.bodyParams.stream().filter((b) -> b.isBodyParam).forEach((p) -> {
p.vendorExtensions.put("x-consumes-json", consumesMimetype(op, "application/json"));
@ -666,27 +705,216 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
op.vendorExtensions.put("x-cask-path-typed", routeArgs(op));
op.vendorExtensions.put("x-query-args", queryArgs(op));
List<String> responses = op.responses.stream().map(r -> r.dataType).filter(Objects::nonNull).collect(Collectors.toList());
op.vendorExtensions.put("x-response-type", responses.isEmpty() ? "Unit" : String.join(" | ", responses));
LinkedHashSet<String> responses = op.responses.stream().map(r -> r.dataType)
.filter(Objects::nonNull)
.collect(Collectors.toCollection(LinkedHashSet::new));
var responseType = responses.isEmpty() ? "Unit" : String.join(" | ", responses);
op.vendorExtensions.put("x-response-type", responseType);
}
private static void postProcessProperty(CodegenProperty p) {
/**
* primitive or enum types don't have Data representations
* @param p the property
* @return if this property need to have '.asModel' or '.asData' called on it?
*/
private static boolean doesNotNeedMapping(final CodegenProperty p, final Set<String> typesWhichDoNotNeedMapping) {
// ermph. Apparently 'isPrimitive' can be false while 'isNumeric' is true.
/*
* if dataType == Value then it doesn't need mapping -- this can happen with properties like ths:
* {{{
* example:
* items: {}
* type: array
* }}}
*/
// we can't use !p.isModel, since 'isModel' is false (apparently) for models within arrays
return p.isPrimitiveType || p.isEnum || p.isEnumRef || p.isNumeric || isByteArray(p) || typesWhichDoNotNeedMapping.contains(p.dataType);
}
/**
* There's a weird edge-case where fields can be declared like this:
*
* {{{
* someField:
* format: byte
* type: string
* }}
*/
private static boolean isByteArray(final CodegenProperty p) {
return "byte".equalsIgnoreCase(p.dataFormat); // &&
}
/**
* this parameter is used to create the function:
* {{{
* class <Thing> {
* ...
* def asData = <Thing>Data(
* someProp = ... <-- how do we turn this property into a model property?
* )
* }
* }}}
*
* and then back again
*/
private static String asDataCode(final CodegenProperty p, final Set<String> typesWhichDoNotNeedMapping) {
final var wrapInOptional = !p.required && !p.isArray && !p.isMap;
String code = "";
String dv = defaultValueNonOption(p, p.defaultValue);
if (doesNotNeedMapping(p, typesWhichDoNotNeedMapping)) {
if (wrapInOptional) {
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 (isByteArray(p)) {
code = String.format(Locale.ROOT, "%s.getOrElse(%s) /* 3 */", p.name, dv);
} else {
code = String.format(Locale.ROOT, "%s.map(_.asData).getOrElse(%s) /* 4 */", p.name, dv);
}
} else if (p.isArray) {
if (isByteArray(p)) {
code = String.format(Locale.ROOT, "%s /* 5 */", p.name);
} else {
code = String.format(Locale.ROOT, "%s.map(_.asData) /* 6 */", p.name);
}
} else {
code = String.format(Locale.ROOT, "%s.asData /* 7 */", p.name);
}
}
return code;
}
/**
*
* {{{
* class <Thing>Data {
* ...
* def asModel = <Thing>(
* someProp = ... <-- how do we turn this property into a model property?
* )
* }
* }}}
*
* @param p
* @return
*/
private static String asModelCode(final CodegenProperty p, final Set<String> typesWhichDoNotNeedMapping) {
final var wrapInOptional = !p.required && !p.isArray && !p.isMap;
String code = "";
if (doesNotNeedMapping(p, typesWhichDoNotNeedMapping)) {
if (wrapInOptional) {
code = String.format(Locale.ROOT, "Option(%s) /* 1 */", p.name);
} else {
code = String.format(Locale.ROOT, "%s /* 2 */", p.name);
}
} else {
if (wrapInOptional) {
if (isByteArray(p)) {
code = String.format(Locale.ROOT, "Option(%s) /* 3 */", p.name);
} else {
code = String.format(Locale.ROOT, "Option(%s).map(_.asModel) /* 4 */", p.name);
}
} else if (p.isArray) {
code = String.format(Locale.ROOT, "%s.map(_.asModel) /* 5 */", p.name);
} else {
code = String.format(Locale.ROOT, "%s.asModel /* 6 */", p.name);
}
}
return code;
}
private static String fixBackTicks(String text) {
// Create a regular expression pattern to find text between backticks
Pattern pattern = Pattern.compile("`([^`]+)`");
Matcher matcher = pattern.matcher(text);
// Use a StringBuffer to construct the result
StringBuffer result = new StringBuffer();
// Loop through all matches
while (matcher.find()) {
// Extract the text between backticks
String extractedText = matcher.group(1);
// Replace it with the capitalized version
matcher.appendReplacement(result, capitalise(extractedText));
}
// Append the remaining part of the string
matcher.appendTail(result);
return result.toString();
}
private String ensureNonKeyword(String text) {
if (isReservedWord(text)) {
return "`" + text + "`";
}
return text;
}
private void postProcessProperty(final CodegenProperty p) {
p.vendorExtensions.put("x-datatype-model", asScalaDataType(p, p.required, false));
p.vendorExtensions.put("x-defaultValue-model", defaultValue(p, p.required, p.defaultValue));
String dataTypeData = asScalaDataType(p, p.required, true);
final String dataTypeData = asScalaDataType(p, p.required, true);
p.vendorExtensions.put("x-datatype-data", dataTypeData);
p.vendorExtensions.put("x-containertype-data", containerType(dataTypeData));
p.vendorExtensions.put("x-defaultValue-data", defaultValueNonOption(p, p.defaultValue));
/*
* Fix enum values which may be reserved words
*/
if (p._enum != null) {
p._enum = p._enum.stream().map(this::ensureNonKeyword).collect(Collectors.toList());
}
/**
* This is a fix for the enum property "type" declared like this:
* {{{
* type:
* enum:
* - foo
* type: string
* }}}
*/
if (p.datatypeWithEnum != null && p.datatypeWithEnum.matches(".*[^a-zA-Z0-9_\\]\\[].*")) {
p.datatypeWithEnum = fixBackTicks(p.datatypeWithEnum);
}
// We have two data models: a "data transfer" model: A "<Foo>Data" model for unvalidated data and a "<Foo>" model
// which has passed validation.
//
// The <model> has a '.asData' method, and the data model has a '.asModel' function which we can use to map
// between the two.
//
// annoying, we can't just answer the question "p.isModel" to see if it's one of our modes we need to map.
// instead it seems we have to figure out the answer by determining if it is NOT a type which has to be mapped.
var typesWhichShouldNotBeMapped = importMapping.keySet().stream()
.flatMap(key -> Stream.of(
key,
String.format(Locale.ROOT, "List[%s]", key),
String.format(Locale.ROOT, "Seq[%s]", key),
String.format(Locale.ROOT, "Set[%s]", key)
)).collect(Collectors.toSet());
typesWhichShouldNotBeMapped.add("byte");
// the 'asModel' logic for modelData.mustache
//
// if it's optional (not required), then wrap the value in Option()
// ... unless it's a map or array, in which case it can just be empty
//
p.vendorExtensions.put("x-wrap-in-optional", !p.required && !p.isArray && !p.isMap);
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
@ -782,6 +1010,21 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
* @return true if the property is numeric
*/
private static boolean isNumeric(IJsonSchemaValidationProperties p) {
/**
* This is nice. I've seen an example of a property defined as such:
* round:
* maximum: 4294967295
* minimum: 0
* type: long
*
* which returns false for isLong and isNumeric, but dataType is set to "Long"
*/
if ("Long".equalsIgnoreCase(p.getDataType())) {
return true;
}
if (p instanceof CodegenParameter) {
return ((CodegenParameter)p).isNumeric;
} else if (p instanceof CodegenProperty) {
@ -829,8 +1072,8 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
// Customize type for freeform objects
if (ModelUtils.isFreeFormObject(schema, openAPI)) {
property.dataType = "Value";
property.baseType = "Value";
property.dataType = AdditionalPropertiesType;
property.baseType = AdditionalPropertiesType;
}
return property;
@ -839,7 +1082,11 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
@Override
public String getTypeDeclaration(Schema schema) {
if (ModelUtils.isFreeFormObject(schema, openAPI)) {
return "Value";
if (ModelUtils.isMapSchema(schema)) {
String inner = getSchemaType(ModelUtils.getAdditionalProperties(schema));
return "Map[String, " + inner + "]";
}
return AdditionalPropertiesType;
}
return super.getTypeDeclaration(schema);
}
@ -850,6 +1097,11 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
if (importMapping.containsKey(name)) {
result = importMapping.get(name);
}
// we seem to get a weird 'import foo.bar.Array[Byte]' for fields which are declared as strings w/ format 'byte'
// this test is to "fix" imports which may be e.g. "import foo.bar.Array[Byte]" by removing them
if (name.contains("[")) {
result = null;
}
return result;
}
@ -887,6 +1139,21 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code
}
private static String asScalaDataType(final IJsonSchemaValidationProperties param, boolean required, boolean useJason, boolean allowOptional) {
/*
* BUG: 'getIsModel()' is returning false (GAH!!!) for a nested, in-line property such as this:
* {{{
* objectValue:
* type: object
* additionalProperties: true
* properties:
* nestedProperty:
* type: string
* example: "Nested example"
* }}}
* Not sure how to go about fixing that ... there doesn't seem to be any obvious properties set on the param
* which would indicate it's actually a model type
*/
String dataType = (param.getIsModel() && useJason) ? param.getDataType() + "Data" : param.getDataType();
final String dataSuffix = useJason && param.getItems() != null && param.getItems().getIsModel() ? "Data" : "";

View File

@ -13,6 +13,16 @@ import scala.reflect.ClassTag
import scala.util.*
import upickle.default.*
extension (f: java.io.File) {
def bytes: Array[Byte] = java.nio.file.Files.readAllBytes(f.toPath)
def toBase64: String = java.util.Base64.getEncoder.encodeToString(bytes)
}
given Writer[java.io.File] = new Writer[java.io.File] {
def write0[V](out: upickle.core.Visitor[?, V], v: java.io.File) = out.visitString(v.toBase64, -1)
}
// needed for BigDecimal params
given cask.endpoints.QueryParamReader.SimpleParam[BigDecimal](BigDecimal.apply)

View File

@ -10,11 +10,12 @@ import {{modelPackage}}.*
import upickle.default.{ReadWriter => RW, macroRW}
import upickle.default.*
import scala.util.Try
{{#imports}}import {{import}}
{{/imports}}
class {{classname}}Routes(service : {{classname}}Service) extends cask.Routes {
class {{classname}}Routes(service : {{classname}}Service[Try]) extends cask.Routes {
{{#route-groups}}
// route group for {{methodName}}
@ -35,7 +36,7 @@ class {{classname}}Routes(service : {{classname}}Service) extends cask.Routes {
* {{description}}
*/
{{vendorExtensions.x-annotation}}("{{vendorExtensions.x-cask-path}}")
def {{operationId}}({{vendorExtensions.x-cask-path-typed}}) = {
def {{operationId}}({{{vendorExtensions.x-cask-path-typed}}}) = {
{{#authMethods}}
// auth method {{name}} : {{type}}, keyParamName: {{keyParamName}}
{{/authMethods}}

View File

@ -8,30 +8,123 @@ package {{apiPackage}}
{{#imports}}import _root_.{{import}}
{{/imports}}
import scala.util.Failure
import scala.util.Try
import _root_.{{modelPackage}}.*
/**
* The {{classname}}Service companion object.
*
* Use the {{classname}}Service() companion object to create an instance which returns a 'not implemented' error
* for each operation.
*
*/
object {{classname}}Service {
def apply() : {{classname}}Service = new {{classname}}Service {
/**
* The 'Handler' is an implementation of {{classname}}Service convenient for delegating or overriding individual functions
*/
case class Handler[F[_]](
{{#operations}}
{{#operation}}
override def {{operationId}}({{vendorExtensions.x-param-list-typed}}) : {{vendorExtensions.x-response-type}} = ???
{{operationId}}Handler : ({{{vendorExtensions.x-param-list-typed}}}) => F[{{{vendorExtensions.x-response-type}}}]{{^-last}}, {{/-last}}
{{/operation}}
{{/operations}}
) extends {{classname}}Service[F] {
{{#operations}}
{{#operation}}
override def {{operationId}}({{{vendorExtensions.x-param-list-typed}}}) : F[{{{vendorExtensions.x-response-type}}}] = {
{{operationId}}Handler({{{vendorExtensions.x-param-list}}})
}
{{/operation}}
{{/operations}}
}
def apply() : {{classname}}Service[Try] = {{classname}}Service.Handler[Try](
{{#operations}}
{{#operation}}
({{#allParams}}_{{^-last}}, {{/-last}}{{/allParams}}) => notImplemented("{{operationId}}"){{^-last}}, {{/-last}}
{{/operation}}
{{/operations}}
)
private def notImplemented(name : String) = Failure(new Exception(s"TODO: $name not implemented"))
}
/**
* The {{classname}} business-logic
*
*
* The 'asHandler' will return an implementation which allows for easily overriding individual operations.
*
* equally there are "on&lt;Function&gt;" helper methods for easily overriding individual functions
*
* @tparam F the effect type (Future, Try, IO, ID, etc) of the operations
*/
trait {{classname}}Service {
trait {{classname}}Service[F[_]] {
{{#operations}}
{{#operation}}
/** {{{summary}}}
* {{{description}}}
* @return {{returnType}}
*/
def {{operationId}}({{vendorExtensions.x-param-list-typed}}) : {{vendorExtensions.x-response-type}}
def {{operationId}}({{{vendorExtensions.x-param-list-typed}}}) : F[{{{vendorExtensions.x-response-type}}}]
/**
* override {{operationId}} with the given handler
* @return a new implementation of {{classname}}Service[F] with {{operationId}} overridden using the given handler
*/
final def {{vendorExtensions.x-handlerName}}(handler : ({{{vendorExtensions.x-param-list-typed}}}) => F[{{{vendorExtensions.x-response-type}}}]) : {{classname}}Service[F] = {
asHandler.copy({{operationId}}Handler = handler)
}
{{/operation}}
{{/operations}}
/**
* @return a Handler implementation of this service
*/
final def asHandler : {{classname}}Service.Handler[F] = this match {
case h : {{classname}}Service.Handler[F] => h
case _ =>
{{classname}}Service.Handler[F](
{{#operations}}
{{#operation}}
({{{vendorExtensions.x-param-list}}}) => {{operationId}}({{{vendorExtensions.x-param-list}}}){{^-last}}, {{/-last}}
{{/operation}}
{{/operations}}
)
}
/**
* 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 {{classname}}Service service implementation with effect type [G]
*/
final def mapEffect[G[_]](changeEffect : [A] => F[A] => G[A]) : {{classname}}Service[G] = {
val self = this
new {{classname}}Service[G] {
{{#operations}}
{{#operation}}
override def {{operationId}}({{{vendorExtensions.x-param-list-typed}}}) : G[{{{vendorExtensions.x-response-type}}}] = changeEffect {
self.{{operationId}}({{{vendorExtensions.x-param-list}}})
}
{{/operation}}
{{/operations}}
}
}
}

View File

@ -2,10 +2,10 @@
//> using lib "com.lihaoyi::cask:0.9.2"
//> using lib "com.lihaoyi::scalatags:0.8.2"
{{>licenseInfo}}
// this file was generated from app.mustache
package {{packageName}}
import scala.util.Try
{{#imports}}import {{import}}
{{/imports}}
import _root_.{{modelPackage}}.*
@ -20,16 +20,17 @@ import _root_.{{apiPackage}}.*
* If you wanted fine-grained control over the routes and services, you could
* extend the cask.MainRoutes and mix in this trait by using this:
*
* \{\{\{
* ```
* override def allRoutes = appRoutes
* \}\}\}
* ```
*
* More typically, however, you would extend the 'BaseApp' class
*/
trait AppRoutes {
{{#operations}}
def app{{classname}}Service : {{classname}}Service = {{classname}}Service()
def app{{classname}}Service : {{classname}}Service[Try] = {{classname}}Service()
def routeFor{{classname}} : {{classname}}Routes = {{classname}}Routes(app{{classname}}Service)
{{/operations}}
def appRoutes = Seq(

View File

@ -6,6 +6,7 @@
// this file was generated from app.mustache
package {{packageName}}
import scala.util.Try
{{#imports}}import {{import}}
{{/imports}}
import _root_.{{modelPackage}}.*
@ -16,7 +17,7 @@ import _root_.{{apiPackage}}.*
* passing in the custom business logic services
*/
class BaseApp({{#operations}}
override val app{{classname}}Service : {{classname}}Service = {{classname}}Service(),
override val app{{classname}}Service : {{classname}}Service[Try] = {{classname}}Service(),
{{/operations}}
override val port : Int = sys.env.get("PORT").map(_.toInt).getOrElse(8080)) extends cask.MainRoutes with AppRoutes {

View File

@ -3,6 +3,7 @@
package {{modelPackage}}
{{#imports}}import {{import}}
{{/imports}}
import scala.util.control.NonFatal
// see https://com-lihaoyi.github.io/upickle/
@ -11,52 +12,13 @@ import upickle.default.*
{{#models}}
{{#model}}
case class {{classname}}(
{{#vars}}
{{#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}}
{{/vars}}
{{#isAdditionalPropertiesTrue}}, additionalProperties : ujson.Value = ujson.Null{{/isAdditionalPropertiesTrue}}
) {
def asJsonString: String = asData.asJsonString
def asJson: ujson.Value = asData.asJson
def asData : {{classname}}Data = {
{{classname}}Data(
{{#vars}}
{{name}} = {{name}}{{#vendorExtensions.x-map-asModel}}.map(_.asData){{/vendorExtensions.x-map-asModel}}{{#vendorExtensions.x-wrap-in-optional}}.getOrElse({{{defaultValue}}}){{/vendorExtensions.x-wrap-in-optional}}{{^-last}},{{/-last}}
{{/vars}}
{{#isAdditionalPropertiesTrue}}, additionalProperties{{/isAdditionalPropertiesTrue}}
)
}
}
object {{classname}} {
given RW[{{classname}}] = summon[RW[ujson.Value]].bimap[{{classname}}](_.asJson, json => read[{{classname}}Data](json).asModel)
enum Fields(val fieldName : String) extends Field(fieldName) {
{{#vars}}
case {{name}} extends Fields("{{name}}")
{{/vars}}
}
{{#vars}}
{{#isEnum}}
// baseName={{{baseName}}}
// nameInCamelCase = {{{nameInCamelCase}}}
enum {{datatypeWithEnum}} derives ReadWriter {
{{#_enum}}
case {{.}}
{{/_enum}}
}
{{/isEnum}}
{{/vars}}
}
{{#isEnum}}
{{>modelEnum}}
{{/isEnum}}
{{^isEnum}}
{{>modelClass}}
{{/isEnum}}
{{/model}}
{{/models}}

View File

@ -0,0 +1,47 @@
case class {{classname}}(
{{#vars}}
{{#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}}
{{/vars}}
{{#isAdditionalPropertiesTrue}}, additionalProperties : ujson.Value = ujson.Null{{/isAdditionalPropertiesTrue}}
) {
def asJsonString: String = asData.asJsonString
def asJson: ujson.Value = asData.asJson
def asData : {{classname}}Data = {
{{classname}}Data(
{{#vars}}
{{name}} = {{{vendorExtensions.x-asData}}}{{^-last}},{{/-last}}
{{/vars}}
{{#isAdditionalPropertiesTrue}}, additionalProperties{{/isAdditionalPropertiesTrue}}
)
}
}
object {{classname}} {
given RW[{{classname}}] = summon[RW[ujson.Value]].bimap[{{classname}}](_.asJson, json => read[{{classname}}Data](json).asModel)
enum Fields(val fieldName : String) extends Field(fieldName) {
{{#vars}}
case {{name}} extends Fields("{{name}}")
{{/vars}}
}
{{#vars}}
{{#isEnum}}
// baseName={{{baseName}}}
// nameInCamelCase = {{{nameInCamelCase}}}
enum {{datatypeWithEnum}} derives ReadWriter {
{{#_enum}}
case {{.}}
{{/_enum}}
}
{{/isEnum}}
{{/vars}}
}

View File

@ -12,250 +12,12 @@ import upickle.default.*
{{#models}}
{{#model}}
/** {{classname}}Data a data transfer object, primarily for simple json serialisation.
* It has no validation - there may be nulls, values out of range, etc
*/
case class {{classname}}Data(
{{#vars}}
{{#description}}
/* {{{description}}} */
{{/description}}
{{name}}: {{#isEnum}}{{classname}}.{{datatypeWithEnum}}{{/isEnum}}{{^isEnum}}{{{vendorExtensions.x-datatype-data}}}{{/isEnum}}{{^required}} = {{{vendorExtensions.x-defaultValue-data}}} {{/required}}{{^-last}},{{/-last}}
{{/vars}}
{{#isAdditionalPropertiesTrue}}, additionalProperties : ujson.Value = ujson.Null{{/isAdditionalPropertiesTrue}}
) derives RW {
def asJsonString: String = asJson.toString()
def asJson : ujson.Value = {
val jason = writeJs(this)
{{#isAdditionalPropertiesTrue}}
jason.obj.remove("additionalProperties")
jason.mergeWith(additionalProperties)
{{/isAdditionalPropertiesTrue}}
{{^isAdditionalPropertiesTrue}}
jason
{{/isAdditionalPropertiesTrue}}
}
def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = {
val errors = scala.collection.mutable.ListBuffer[ValidationError]()
{{#vars}}
// ================== {{name}} validation ==================
{{#pattern}}
// validate against pattern '{{{pattern}}}'
if (errors.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")
}
{{/pattern}}
{{#minimum}}
// validate against {{#exclusiveMinimum}}exclusive {{/exclusiveMinimum}}minimum {{minimum}}
if (errors.isEmpty || !failFast) {
if !({{name}} >{{^exclusiveMinimum}}={{/exclusiveMinimum}} {{minimum}}) then
errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"value '${{name}}' is not greater than the {{#exclusiveMinimum}}exclusive {{/exclusiveMinimum}}minimum value {{minimum}}")
}
{{/minimum}}
{{#maximum}}
// validate against {{#exclusiveMaximum}}exclusive {{/exclusiveMaximum}}maximum {{maximum}}
if (errors.isEmpty || !failFast) {
if !({{name}} <{{^exclusiveMaximum}}={{/exclusiveMaximum}} {{maximum}}) then
errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"value '${{name}}' is not greater than the {{#exclusiveMaximum}}exclusive {{/exclusiveMaximum}}maximum value {{maximum}}")
}
{{/maximum}}
{{#minLength}}
// validate min length {{minLength}}
if (errors.isEmpty || !failFast) {
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}}")
}
}
{{/minLength}}
{{#maxLength}}
// validate max length {{maxLength}}
if (errors.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}}")
}
}
{{/maxLength}}
{{#isEmail}}
// validate {{name}} is a valid email address
if (errors.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")
}
}
{{/isEmail}}
{{#required}}{{^isPrimitiveType}}
if (errors.isEmpty || !failFast) {
if ({{name}} == null) {
errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, "{{name}} is a required field and cannot be null")
}
}
{{/isPrimitiveType}}{{/required}}
{{#uniqueItems}}
// validate {{name}} has unique items
if (errors.isEmpty || !failFast) {
if ({{name}} != null) {
{{name}}.foldLeft(Set[{{{vendorExtensions.x-containertype-data}}}]()) {
case (set, next) if set.contains(next) =>
errors += ValidationError(
path :+ {{classname}}.Fields.{{name}},
s"duplicate value: $next"
)
set + next
case (set, next) => set + next
}
}
}
{{/uniqueItems}}
{{#multipleOf}}
if (errors.isEmpty || !failFast) {
// validate {{name}} multiple of {{multipleOf}}
if ({{name}} % {{multipleOf}} != 0) {
errors += ValidationError(
path :+ {{classname}}.Fields.{{name}},
s"${{name}} is not a multiple of {{multipleOf}}"
)
}
}
{{/multipleOf}}
{{#minItems}}
// validate min items {{minItems}}
if (errors.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}}")
}
}
{{/minItems}}
{{#maxItems}}
// validate min items {{maxItems}}
if (errors.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}}")
}
}
{{/maxItems}}
{{#minProperties}} TODO - minProperties {{/minProperties}}
{{#maxProperties}} TODO - maxProperties {{/maxProperties}}
{{#items}}{{#isModel}}
if (errors.isEmpty || !failFast) {
if ({{name}} != null) {
{{name}}.zipWithIndex.foreach {
case (value, i) if errors.isEmpty || !failFast =>
errors ++= value.validationErrors(
path :+ {{classname}}.Fields.{{name}} :+ Field(i.toString),
failFast)
case (value, i) =>
}
}
}
{{/isModel}}{{/items}}
{{#isModel}}
// validating {{name}}
if (errors.isEmpty || !failFast) {
if {{name}} != null then errors ++= {{name}}.validationErrors(path :+ {{classname}}.Fields.{{name}}, failFast)
}
{{/isModel}}
{{/vars}}
errors.toSeq
}
def validated(failFast : Boolean = false) : scala.util.Try[{{classname}}] = {
validationErrors(Vector(), failFast) match {
case Seq() => Success(asModel)
case first +: theRest => Failure(ValidationErrors(first, theRest))
}
}
/** use 'validated' to check validation */
def asModel : {{classname}} = {
{{classname}}(
{{#vars}}
{{name}} = {{#vendorExtensions.x-wrap-in-optional}}Option({{/vendorExtensions.x-wrap-in-optional}}
{{name}}
{{#vendorExtensions.x-wrap-in-optional}}){{/vendorExtensions.x-wrap-in-optional}}
{{#vendorExtensions.x-map-asModel}}.map(_.asModel){{/vendorExtensions.x-map-asModel}}{{^-last}},{{/-last}}
{{/vars}}
{{#isAdditionalPropertiesTrue}}, additionalProperties{{/isAdditionalPropertiesTrue}}
)
}
}
object {{classname}}Data {
def fromJson(jason : ujson.Value) : {{classname}}Data = try {
val data = read[{{classname}}Data](jason)
{{^isAdditionalPropertiesTrue}}
data
{{/isAdditionalPropertiesTrue}}
{{#isAdditionalPropertiesTrue}}
val obj = jason.obj
{{classname}}.Fields.values.foreach(v => obj.value.subtractOne(v.fieldName))
data.copy(additionalProperties = obj)
{{/isAdditionalPropertiesTrue}}
} catch {
case NonFatal(e) => sys.error(s"Error creating {{classname}}Data from json '$jason': $e")
}
def fromJsonString(jason : String) : {{classname}}Data = {
val parsed = try {
read[ujson.Value](jason)
} catch {
case NonFatal(e) => sys.error(s"Error parsing json '$jason': $e")
}
fromJson(parsed)
}
def manyFromJsonString(jason : String) : Seq[{{classname}}Data] = try {
read[List[{{classname}}Data]](jason)
} catch {
case NonFatal(e) => sys.error(s"Error parsing json '$jason' as list: $e")
}
def manyFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Seq[{{classname}}]] = {
Try(manyFromJsonString(jason)).flatMap { list =>
list.zipWithIndex.foldLeft(Try(Vector[{{classname}}]())) {
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, {{classname}}Data] = try {
read[Map[String, {{classname}}Data]](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, {{classname}}]] = {
Try(mapFromJsonString(jason)).flatMap { map =>
map.foldLeft(Try(Map[String, {{classname}}]())) {
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
}
}
}
}
{{#isEnum}}
{{>modelDataEnum}}
{{/isEnum}}
{{^isEnum}}
{{>modelDataClass}}
{{/isEnum}}
{{/model}}
{{/models}}

View File

@ -0,0 +1,244 @@
/** {{classname}}Data a data transfer object, primarily for simple json serialisation.
* It has no validation - there may be nulls, values out of range, etc
*/
case class {{classname}}Data(
{{#vars}}
{{#description}}
/* {{{description}}} */
{{/description}}
{{name}}: {{#isEnum}}{{classname}}.{{datatypeWithEnum}}{{/isEnum}}{{^isEnum}}{{{vendorExtensions.x-datatype-data}}}{{/isEnum}}{{^required}} = {{{vendorExtensions.x-defaultValue-data}}} {{/required}}{{^-last}},{{/-last}}
{{/vars}}
{{#isAdditionalPropertiesTrue}}, additionalProperties : ujson.Value = ujson.Null{{/isAdditionalPropertiesTrue}}
) derives RW {
def asJsonString: String = asJson.toString()
def asJson : ujson.Value = {
val jason = writeJs(this)
{{#isAdditionalPropertiesTrue}}
jason.obj.remove("additionalProperties")
jason.mergeWith(additionalProperties)
{{/isAdditionalPropertiesTrue}}
{{^isAdditionalPropertiesTrue}}
jason
{{/isAdditionalPropertiesTrue}}
}
def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = {
val errors = scala.collection.mutable.ListBuffer[ValidationError]()
{{#vars}}
// ================== {{name}} validation ==================
{{#pattern}}
// validate against pattern '{{{pattern}}}'
if (errors.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")
}
{{/pattern}}
{{#minimum}}
// validate against {{#exclusiveMinimum}}exclusive {{/exclusiveMinimum}}minimum {{minimum}}
if (errors.isEmpty || !failFast) {
if !({{name}} >{{^exclusiveMinimum}}={{/exclusiveMinimum}} {{minimum}}) then
errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"value '${{name}}' is not greater than the {{#exclusiveMinimum}}exclusive {{/exclusiveMinimum}}minimum value {{minimum}}")
}
{{/minimum}}
{{#maximum}}
// validate against {{#exclusiveMaximum}}exclusive {{/exclusiveMaximum}}maximum {{maximum}}
if (errors.isEmpty || !failFast) {
if !({{name}} <{{^exclusiveMaximum}}={{/exclusiveMaximum}} {{maximum}}) then
errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"value '${{name}}' is not greater than the {{#exclusiveMaximum}}exclusive {{/exclusiveMaximum}}maximum value {{maximum}}")
}
{{/maximum}}
{{#minLength}}
// validate min length {{minLength}}
if (errors.isEmpty || !failFast) {
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}}")
}
}
{{/minLength}}
{{#maxLength}}
// validate max length {{maxLength}}
if (errors.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}}")
}
}
{{/maxLength}}
{{#isEmail}}
// validate {{name}} is a valid email address
if (errors.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")
}
}
{{/isEmail}}
{{#required}}{{^isPrimitiveType}}
if (errors.isEmpty || !failFast) {
if ({{name}} == null) {
errors += ValidationError(path :+ {{classname}}.Fields.{{name}}, "{{name}} is a required field and cannot be null")
}
}
{{/isPrimitiveType}}{{/required}}
{{#uniqueItems}}
// validate {{name}} has unique items
if (errors.isEmpty || !failFast) {
if ({{name}} != null) {
{{name}}.foldLeft(Set[{{{vendorExtensions.x-containertype-data}}}]()) {
case (set, next) if set.contains(next) =>
errors += ValidationError(
path :+ {{classname}}.Fields.{{name}},
s"duplicate value: $next"
)
set + next
case (set, next) => set + next
}
}
}
{{/uniqueItems}}
{{#multipleOf}}
if (errors.isEmpty || !failFast) {
// validate {{name}} multiple of {{multipleOf}}
if ({{name}} % {{multipleOf}} != 0) {
errors += ValidationError(
path :+ {{classname}}.Fields.{{name}},
s"${{name}} is not a multiple of {{multipleOf}}"
)
}
}
{{/multipleOf}}
{{#minItems}}
// validate min items {{minItems}}
if (errors.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}}")
}
}
{{/minItems}}
{{#maxItems}}
// validate min items {{maxItems}}
if (errors.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}}")
}
}
{{/maxItems}}
{{#minProperties}} TODO - minProperties {{/minProperties}}
{{#maxProperties}} TODO - maxProperties {{/maxProperties}}
{{#items}}{{#isModel}}
if (errors.isEmpty || !failFast) {
if ({{name}} != null) {
{{name}}.zipWithIndex.foreach {
case (value, i) if errors.isEmpty || !failFast =>
errors ++= value.validationErrors(
path :+ {{classname}}.Fields.{{name}} :+ Field(i.toString),
failFast)
case (value, i) =>
}
}
}
{{/isModel}}{{/items}}
{{#isModel}}
// validating {{name}}
if (errors.isEmpty || !failFast) {
if {{name}} != null then errors ++= {{name}}.validationErrors(path :+ {{classname}}.Fields.{{name}}, failFast)
}
{{/isModel}}
{{/vars}}
errors.toSeq
}
/**
* @return the validated model within a Try (if successful)
*/
def validated(failFast : Boolean = false) : scala.util.Try[{{classname}}] = {
validationErrors(Vector(), failFast) match {
case Seq() => Success(asModel)
case first +: theRest => Failure(ValidationErrors(first, theRest))
}
}
/** use 'validated' to check validation */
def asModel : {{classname}} = {
{{classname}}(
{{#vars}}
{{name}} = {{{vendorExtensions.x-asModel}}}{{^-last}},{{/-last}}
{{/vars}}
{{#isAdditionalPropertiesTrue}}, additionalProperties{{/isAdditionalPropertiesTrue}}
)
}
}
object {{classname}}Data {
def fromJson(jason : ujson.Value) : {{classname}}Data = try {
val data = read[{{classname}}Data](jason)
{{^isAdditionalPropertiesTrue}}
data
{{/isAdditionalPropertiesTrue}}
{{#isAdditionalPropertiesTrue}}
val obj = jason.obj
{{classname}}.Fields.values.foreach(v => obj.value.subtractOne(v.fieldName))
data.copy(additionalProperties = obj)
{{/isAdditionalPropertiesTrue}}
} catch {
case NonFatal(e) => sys.error(s"Error creating {{classname}}Data from json '$jason': $e")
}
def fromJsonString(jason : String) : {{classname}}Data = {
val parsed = try {
read[ujson.Value](jason)
} catch {
case NonFatal(e) => sys.error(s"Error parsing json '$jason': $e")
}
fromJson(parsed)
}
def manyFromJsonString(jason : String) : Seq[{{classname}}Data] = try {
read[List[{{classname}}Data]](jason)
} catch {
case NonFatal(e) => sys.error(s"Error parsing json '$jason' as list: $e")
}
def manyFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Seq[{{classname}}]] = {
Try(manyFromJsonString(jason)).flatMap { list =>
list.zipWithIndex.foldLeft(Try(Vector[{{classname}}]())) {
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, {{classname}}Data] = try {
read[Map[String, {{classname}}Data]](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, {{classname}}]] = {
Try(mapFromJsonString(jason)).flatMap { map =>
map.foldLeft(Try(Map[String, {{classname}}]())) {
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
}
}
}
}

View File

@ -0,0 +1 @@
type {{classname}}Data = {{classname}}

View File

@ -0,0 +1,6 @@
enum {{classname}} derives RW :
{{#allowableValues}}
{{#values}}
case {{.}}
{{/values}}
{{/allowableValues}}

View File

@ -20,6 +20,8 @@ class {{classname}}Test extends AnyWordSpec with Matchers {
value= {{value}}
{{/examples}}
{{/operations}}
{{^isEnum}}
"{{classname}}.fromJson" should {
"""not parse invalid json""" in {
val Failure(err) = Try({{classname}}Data.fromJsonString("invalid jason"))
@ -31,7 +33,7 @@ class {{classname}}Test extends AnyWordSpec with Matchers {
sys.error("TODO")
}
}
{{/isEnum}}
}
{{/model}}
{{/models}}

View File

@ -53,5 +53,6 @@
{{/vendorExtensions.x-consumes-xml}}
{{/vendorExtensions.x-consumes-json}}
{{/bodyParams}}
result <- Parsed.eval(service.{{operationId}}({{vendorExtensions.x-param-list}}))
resultTry <- Parsed.eval(service.{{operationId}}({{{vendorExtensions.x-param-list}}}))
result <- Parsed.fromTry(resultTry)
} yield result

View File

@ -14,10 +14,10 @@
* https://openapi-generator.tech
*/
// this file was generated from app.mustache
package cask.groupId.server
import scala.util.Try
import _root_.sample.cask.model.*
import _root_.sample.cask.api.*
@ -30,20 +30,23 @@ import _root_.sample.cask.api.*
* If you wanted fine-grained control over the routes and services, you could
* extend the cask.MainRoutes and mix in this trait by using this:
*
* \{\{\{
* ```
* override def allRoutes = appRoutes
* \}\}\}
* ```
*
* More typically, however, you would extend the 'BaseApp' class
*/
trait AppRoutes {
def appPetService : PetService = PetService()
def appPetService : PetService[Try] = PetService()
def routeForPet : PetRoutes = PetRoutes(appPetService)
def appStoreService : StoreService = StoreService()
def appStoreService : StoreService[Try] = StoreService()
def routeForStore : StoreRoutes = StoreRoutes(appStoreService)
def appUserService : UserService = UserService()
def appUserService : UserService[Try] = UserService()
def routeForUser : UserRoutes = UserRoutes(appUserService)
def appRoutes = Seq(
routeForPet ,
routeForStore ,

View File

@ -18,6 +18,7 @@
// this file was generated from app.mustache
package cask.groupId.server
import scala.util.Try
import _root_.sample.cask.model.*
import _root_.sample.cask.api.*
@ -26,11 +27,11 @@ import _root_.sample.cask.api.*
* passing in the custom business logic services
*/
class BaseApp(
override val appPetService : PetService = PetService(),
override val appPetService : PetService[Try] = PetService(),
override val appStoreService : StoreService = StoreService(),
override val appStoreService : StoreService[Try] = StoreService(),
override val appUserService : UserService = UserService(),
override val appUserService : UserService[Try] = UserService(),
override val port : Int = sys.env.get("PORT").map(_.toInt).getOrElse(8080)) extends cask.MainRoutes with AppRoutes {
/** routes for the UI

View File

@ -22,12 +22,13 @@ import sample.cask.model.*
import upickle.default.{ReadWriter => RW, macroRW}
import upickle.default.*
import scala.util.Try
import sample.cask.model.ApiResponse
import java.io.File
import sample.cask.model.Pet
class PetRoutes(service : PetService) extends cask.Routes {
class PetRoutes(service : PetService[Try]) extends cask.Routes {
// route group for routeWorkAroundForPOSTPet
@cask.post("/pet", true)
@ -63,7 +64,8 @@ class PetRoutes(service : PetService) extends cask.Routes {
petJson <- Parsed.fromTry(request.bodyAsJson)
petData <- Parsed.eval(PetData.fromJson(petJson)) /* not array or map */
pet <- Parsed.fromTry(petData.validated(failFast))
result <- Parsed.eval(service.addPet(pet))
resultTry <- Parsed.eval(service.addPet(pet))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -84,7 +86,8 @@ class PetRoutes(service : PetService) extends cask.Routes {
val result = for {
petId <- Parsed(petId)
apiKey <- request.headerSingleValueOptional("apiKey")
result <- Parsed.eval(service.deletePet(petId, apiKey))
resultTry <- Parsed.eval(service.deletePet(petId, apiKey))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -102,7 +105,8 @@ class PetRoutes(service : PetService) extends cask.Routes {
def failFast = request.queryParams.keySet.contains("failFast")
val result = for {
result <- Parsed.eval(service.findPetsByStatus(status))
resultTry <- Parsed.eval(service.findPetsByStatus(status))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -121,7 +125,8 @@ class PetRoutes(service : PetService) extends cask.Routes {
def failFast = request.queryParams.keySet.contains("failFast")
val result = for {
result <- Parsed.eval(service.findPetsByTags(tags))
resultTry <- Parsed.eval(service.findPetsByTags(tags))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -141,7 +146,8 @@ class PetRoutes(service : PetService) extends cask.Routes {
val result = for {
petId <- Parsed(petId)
result <- Parsed.eval(service.getPetById(petId))
resultTry <- Parsed.eval(service.getPetById(petId))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -163,7 +169,8 @@ class PetRoutes(service : PetService) extends cask.Routes {
petJson <- Parsed.fromTry(request.bodyAsJson)
petData <- Parsed.eval(PetData.fromJson(petJson)) /* not array or map */
pet <- Parsed.fromTry(petData.validated(failFast))
result <- Parsed.eval(service.updatePet(pet))
resultTry <- Parsed.eval(service.updatePet(pet))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -185,7 +192,8 @@ class PetRoutes(service : PetService) extends cask.Routes {
petId <- Parsed(petId)
name <- request.formSingleValueOptional("name")
status <- request.formSingleValueOptional("status")
result <- Parsed.eval(service.updatePetWithForm(petId, name, status))
resultTry <- Parsed.eval(service.updatePetWithForm(petId, name, status))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -206,7 +214,8 @@ class PetRoutes(service : PetService) extends cask.Routes {
petId <- Parsed(petId)
additionalMetadata <- request.formSingleValueOptional("additionalMetadata")
file <- request.formValueAsFileOptional("file")
result <- Parsed.eval(service.uploadFile(petId, additionalMetadata, file))
resultTry <- Parsed.eval(service.uploadFile(petId, additionalMetadata, file))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {

View File

@ -22,10 +22,11 @@ import sample.cask.model.*
import upickle.default.{ReadWriter => RW, macroRW}
import upickle.default.*
import scala.util.Try
import sample.cask.model.Order
class StoreRoutes(service : StoreService) extends cask.Routes {
class StoreRoutes(service : StoreService[Try]) extends cask.Routes {
/** Delete purchase order by ID
@ -38,7 +39,8 @@ class StoreRoutes(service : StoreService) extends cask.Routes {
val result = for {
orderId <- Parsed(orderId)
result <- Parsed.eval(service.deleteOrder(orderId))
resultTry <- Parsed.eval(service.deleteOrder(orderId))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -56,7 +58,8 @@ class StoreRoutes(service : StoreService) extends cask.Routes {
def failFast = request.queryParams.keySet.contains("failFast")
val result = for {
result <- Parsed.eval(service.getInventory())
resultTry <- Parsed.eval(service.getInventory())
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -75,7 +78,8 @@ class StoreRoutes(service : StoreService) extends cask.Routes {
val result = for {
orderId <- Parsed(orderId)
result <- Parsed.eval(service.getOrderById(orderId))
resultTry <- Parsed.eval(service.getOrderById(orderId))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -96,7 +100,8 @@ class StoreRoutes(service : StoreService) extends cask.Routes {
orderJson <- Parsed.fromTry(request.bodyAsJson)
orderData <- Parsed.eval(OrderData.fromJson(orderJson)) /* not array or map */
order <- Parsed.fromTry(orderData.validated(failFast))
result <- Parsed.eval(service.placeOrder(order))
resultTry <- Parsed.eval(service.placeOrder(order))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {

View File

@ -22,11 +22,12 @@ import sample.cask.model.*
import upickle.default.{ReadWriter => RW, macroRW}
import upickle.default.*
import scala.util.Try
import java.time.OffsetDateTime
import sample.cask.model.User
class UserRoutes(service : UserService) extends cask.Routes {
class UserRoutes(service : UserService[Try]) extends cask.Routes {
// route group for routeWorkAroundForGETUser
@cask.get("/user", true)
@ -52,7 +53,8 @@ class UserRoutes(service : UserService) extends cask.Routes {
userJson <- Parsed.fromTry(request.bodyAsJson)
userData <- Parsed.eval(UserData.fromJson(userJson)) /* not array or map */
user <- Parsed.fromTry(userData.validated(failFast))
result <- Parsed.eval(service.createUser(user))
resultTry <- Parsed.eval(service.createUser(user))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -71,7 +73,8 @@ class UserRoutes(service : UserService) extends cask.Routes {
val result = for {
user <- Parsed.fromTry(UserData.manyFromJsonStringValidated(request.bodyAsString)).mapError(e => s"Error parsing json as an array of User from >${request.bodyAsString}< : ${e}") /* array */
result <- Parsed.eval(service.createUsersWithArrayInput(user))
resultTry <- Parsed.eval(service.createUsersWithArrayInput(user))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -90,7 +93,8 @@ class UserRoutes(service : UserService) extends cask.Routes {
val result = for {
user <- Parsed.fromTry(UserData.manyFromJsonStringValidated(request.bodyAsString)).mapError(e => s"Error parsing json as an array of User from >${request.bodyAsString}< : ${e}") /* array */
result <- Parsed.eval(service.createUsersWithListInput(user))
resultTry <- Parsed.eval(service.createUsersWithListInput(user))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -109,7 +113,8 @@ class UserRoutes(service : UserService) extends cask.Routes {
val result = for {
username <- Parsed(username)
result <- Parsed.eval(service.deleteUser(username))
resultTry <- Parsed.eval(service.deleteUser(username))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -127,7 +132,8 @@ class UserRoutes(service : UserService) extends cask.Routes {
val result = for {
username <- Parsed(username)
result <- Parsed.eval(service.getUserByName(username))
resultTry <- Parsed.eval(service.getUserByName(username))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -145,7 +151,8 @@ class UserRoutes(service : UserService) extends cask.Routes {
def failFast = request.queryParams.keySet.contains("failFast")
val result = for {
result <- Parsed.eval(service.loginUser(username, password))
resultTry <- Parsed.eval(service.loginUser(username, password))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -164,7 +171,8 @@ class UserRoutes(service : UserService) extends cask.Routes {
def failFast = request.queryParams.keySet.contains("failFast")
val result = for {
result <- Parsed.eval(service.logoutUser())
resultTry <- Parsed.eval(service.logoutUser())
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {
@ -186,7 +194,8 @@ class UserRoutes(service : UserService) extends cask.Routes {
userJson <- Parsed.fromTry(request.bodyAsJson)
userData <- Parsed.eval(UserData.fromJson(userJson)) /* not array or map */
user <- Parsed.fromTry(userData.validated(failFast))
result <- Parsed.eval(service.updateUser(username, user))
resultTry <- Parsed.eval(service.updateUser(username, user))
result <- Parsed.fromTry(resultTry)
} yield result
(result : @unchecked) match {

View File

@ -25,6 +25,16 @@ import scala.reflect.ClassTag
import scala.util.*
import upickle.default.*
extension (f: java.io.File) {
def bytes: Array[Byte] = java.nio.file.Files.readAllBytes(f.toPath)
def toBase64: String = java.util.Base64.getEncoder.encodeToString(bytes)
}
given Writer[java.io.File] = new Writer[java.io.File] {
def write0[V](out: upickle.core.Visitor[?, V], v: java.io.File) = out.visitString(v.toBase64, -1)
}
// needed for BigDecimal params
given cask.endpoints.QueryParamReader.SimpleParam[BigDecimal](BigDecimal.apply)

View File

@ -1,15 +1,14 @@
/**
* 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
*/
/** 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
@ -20,16 +19,16 @@ import scala.util.*
class ApiResponseTest extends AnyWordSpec with Matchers {
"ApiResponse.fromJson" should {
"""not parse invalid json""" in {
val Failure(err) = Try(ApiResponseData.fromJsonString("invalid jason"))
err.getMessage should startWith ("Error parsing json 'invalid jason'")
}
"""parse """ ignore {
val Failure(err : ValidationErrors) = ApiResponseData.fromJsonString("""""").validated()
sys.error("TODO")
}
"ApiResponse.fromJson" should {
"""not parse invalid json""" in {
val Failure(err) = Try(ApiResponseData.fromJsonString("invalid jason"))
err.getMessage should startWith("Error parsing json 'invalid jason'")
}
"""parse """ ignore {
val Failure(err: ValidationErrors) = ApiResponseData.fromJsonString("""""").validated()
sys.error("TODO")
}
}
}

View File

@ -1,15 +1,14 @@
/**
* 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
*/
/** 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
@ -20,16 +19,16 @@ import scala.util.*
class CategoryTest extends AnyWordSpec with Matchers {
"Category.fromJson" should {
"""not parse invalid json""" in {
val Failure(err) = Try(CategoryData.fromJsonString("invalid jason"))
err.getMessage should startWith ("Error parsing json 'invalid jason'")
}
"""parse """ ignore {
val Failure(err : ValidationErrors) = CategoryData.fromJsonString("""""").validated()
sys.error("TODO")
}
"Category.fromJson" should {
"""not parse invalid json""" in {
val Failure(err) = Try(CategoryData.fromJsonString("invalid jason"))
err.getMessage should startWith("Error parsing json 'invalid jason'")
}
"""parse """ ignore {
val Failure(err: ValidationErrors) = CategoryData.fromJsonString("""""").validated()
sys.error("TODO")
}
}
}

View File

@ -1,15 +1,14 @@
/**
* 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
*/
/** 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
@ -21,16 +20,16 @@ import scala.util.*
class OrderTest extends AnyWordSpec with Matchers {
"Order.fromJson" should {
"""not parse invalid json""" in {
val Failure(err) = Try(OrderData.fromJsonString("invalid jason"))
err.getMessage should startWith ("Error parsing json 'invalid jason'")
}
"""parse """ ignore {
val Failure(err : ValidationErrors) = OrderData.fromJsonString("""""").validated()
sys.error("TODO")
}
"Order.fromJson" should {
"""not parse invalid json""" in {
val Failure(err) = Try(OrderData.fromJsonString("invalid jason"))
err.getMessage should startWith("Error parsing json 'invalid jason'")
}
"""parse """ ignore {
val Failure(err: ValidationErrors) = OrderData.fromJsonString("""""").validated()
sys.error("TODO")
}
}
}

View File

@ -1,20 +1,17 @@
/**
* 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
*/
/** 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 sample.cask.model.Category
import sample.cask.model.Tag
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
@ -22,16 +19,16 @@ import scala.util.*
class PetTest extends AnyWordSpec with Matchers {
"Pet.fromJson" should {
"""not parse invalid json""" in {
val Failure(err) = Try(PetData.fromJsonString("invalid jason"))
err.getMessage should startWith ("Error parsing json 'invalid jason'")
}
"""parse """ ignore {
val Failure(err : ValidationErrors) = PetData.fromJsonString("""""").validated()
sys.error("TODO")
}
"Pet.fromJson" should {
"""not parse invalid json""" in {
val Failure(err) = Try(PetData.fromJsonString("invalid jason"))
err.getMessage should startWith("Error parsing json 'invalid jason'")
}
"""parse """ ignore {
val Failure(err: ValidationErrors) = PetData.fromJsonString("""""").validated()
sys.error("TODO")
}
}
}

View File

@ -1,15 +1,14 @@
/**
* 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
*/
/** 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
@ -20,16 +19,16 @@ import scala.util.*
class TagTest extends AnyWordSpec with Matchers {
"Tag.fromJson" should {
"""not parse invalid json""" in {
val Failure(err) = Try(TagData.fromJsonString("invalid jason"))
err.getMessage should startWith ("Error parsing json 'invalid jason'")
}
"""parse """ ignore {
val Failure(err : ValidationErrors) = TagData.fromJsonString("""""").validated()
sys.error("TODO")
}
"Tag.fromJson" should {
"""not parse invalid json""" in {
val Failure(err) = Try(TagData.fromJsonString("invalid jason"))
err.getMessage should startWith("Error parsing json 'invalid jason'")
}
"""parse """ ignore {
val Failure(err: ValidationErrors) = TagData.fromJsonString("""""").validated()
sys.error("TODO")
}
}
}

View File

@ -1,15 +1,14 @@
/**
* 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
*/
/** 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
@ -20,16 +19,16 @@ import scala.util.*
class UserTest extends AnyWordSpec with Matchers {
"User.fromJson" should {
"""not parse invalid json""" in {
val Failure(err) = Try(UserData.fromJsonString("invalid jason"))
err.getMessage should startWith ("Error parsing json 'invalid jason'")
}
"""parse """ ignore {
val Failure(err : ValidationErrors) = UserData.fromJsonString("""""").validated()
sys.error("TODO")
}
"User.fromJson" should {
"""not parse invalid json""" in {
val Failure(err) = Try(UserData.fromJsonString("invalid jason"))
err.getMessage should startWith("Error parsing json 'invalid jason'")
}
"""parse """ ignore {
val Failure(err: ValidationErrors) = UserData.fromJsonString("""""").validated()
sys.error("TODO")
}
}
}

View File

@ -21,64 +21,260 @@ package sample.cask.api
import _root_.sample.cask.model.ApiResponse
import _root_.java.io.File
import _root_.sample.cask.model.Pet
import scala.util.Failure
import scala.util.Try
import _root_.sample.cask.model.*
/**
* The PetService companion object.
*
* Use the PetService() companion object to create an instance which returns a 'not implemented' error
* for each operation.
*
*/
object PetService {
def apply() : PetService = new PetService {
override def addPet(pet : Pet) : Pet = ???
override def deletePet(petId : Long, apiKey : Option[String]) : Unit = ???
override def findPetsByStatus(status : Seq[String]) : List[Pet] = ???
override def findPetsByTags(tags : Seq[String]) : List[Pet] = ???
override def getPetById(petId : Long) : Pet = ???
override def updatePet(pet : Pet) : Pet = ???
override def updatePetWithForm(petId : Long, name : Option[String], status : Option[String]) : Unit = ???
override def uploadFile(petId : Long, additionalMetadata : Option[String], file : Option[File]) : ApiResponse = ???
/**
* The 'Handler' is an implementation of PetService convenient for delegating or overriding individual functions
*/
case class Handler[F[_]](
addPetHandler : (pet : Pet) => F[Pet],
deletePetHandler : (petId : Long, apiKey : Option[String]) => F[Unit],
findPetsByStatusHandler : (status : Seq[String]) => F[List[Pet]],
findPetsByTagsHandler : (tags : Seq[String]) => F[List[Pet]],
getPetByIdHandler : (petId : Long) => F[Pet],
updatePetHandler : (pet : Pet) => F[Pet],
updatePetWithFormHandler : (petId : Long, name : Option[String], status : Option[String]) => F[Unit],
uploadFileHandler : (petId : Long, additionalMetadata : Option[String], file : Option[File]) => F[ApiResponse]
) extends PetService[F] {
override def addPet(pet : Pet) : F[Pet] = {
addPetHandler(pet)
}
override def deletePet(petId : Long, apiKey : Option[String]) : F[Unit] = {
deletePetHandler(petId, apiKey)
}
override def findPetsByStatus(status : Seq[String]) : F[List[Pet]] = {
findPetsByStatusHandler(status)
}
override def findPetsByTags(tags : Seq[String]) : F[List[Pet]] = {
findPetsByTagsHandler(tags)
}
override def getPetById(petId : Long) : F[Pet] = {
getPetByIdHandler(petId)
}
override def updatePet(pet : Pet) : F[Pet] = {
updatePetHandler(pet)
}
override def updatePetWithForm(petId : Long, name : Option[String], status : Option[String]) : F[Unit] = {
updatePetWithFormHandler(petId, name, status)
}
override def uploadFile(petId : Long, additionalMetadata : Option[String], file : Option[File]) : F[ApiResponse] = {
uploadFileHandler(petId, additionalMetadata, file)
}
}
def apply() : PetService[Try] = PetService.Handler[Try](
(_) => notImplemented("addPet"),
(_, _) => notImplemented("deletePet"),
(_) => notImplemented("findPetsByStatus"),
(_) => notImplemented("findPetsByTags"),
(_) => notImplemented("getPetById"),
(_) => notImplemented("updatePet"),
(_, _, _) => notImplemented("updatePetWithForm"),
(_, _, _) => notImplemented("uploadFile")
)
private def notImplemented(name : String) = Failure(new Exception(s"TODO: $name not implemented"))
}
/**
* The Pet business-logic
*
*
* The 'asHandler' will return an implementation which allows for easily overriding individual operations.
*
* equally there are "on&lt;Function&gt;" helper methods for easily overriding individual functions
*
* @tparam F the effect type (Future, Try, IO, ID, etc) of the operations
*/
trait PetService {
trait PetService[F[_]] {
/** Add a new pet to the store
*
* @return Pet
*/
def addPet(pet : Pet) : Pet
def addPet(pet : Pet) : F[Pet]
/**
* override addPet with the given handler
* @return a new implementation of PetService[F] with addPet overridden using the given handler
*/
final def onAddPet(handler : (pet : Pet) => F[Pet]) : PetService[F] = {
asHandler.copy(addPetHandler = handler)
}
/** Deletes a pet
*
* @return
*/
def deletePet(petId : Long, apiKey : Option[String]) : Unit
def deletePet(petId : Long, apiKey : Option[String]) : F[Unit]
/**
* override deletePet with the given handler
* @return a new implementation of PetService[F] with deletePet overridden using the given handler
*/
final def onDeletePet(handler : (petId : Long, apiKey : Option[String]) => F[Unit]) : PetService[F] = {
asHandler.copy(deletePetHandler = handler)
}
/** Finds Pets by status
*
* @return List[Pet]
*/
def findPetsByStatus(status : Seq[String]) : List[Pet]
def findPetsByStatus(status : Seq[String]) : F[List[Pet]]
/**
* override findPetsByStatus with the given handler
* @return a new implementation of PetService[F] with findPetsByStatus overridden using the given handler
*/
final def onFindPetsByStatus(handler : (status : Seq[String]) => F[List[Pet]]) : PetService[F] = {
asHandler.copy(findPetsByStatusHandler = handler)
}
/** Finds Pets by tags
*
* @return List[Pet]
*/
def findPetsByTags(tags : Seq[String]) : List[Pet]
def findPetsByTags(tags : Seq[String]) : F[List[Pet]]
/**
* override findPetsByTags with the given handler
* @return a new implementation of PetService[F] with findPetsByTags overridden using the given handler
*/
final def onFindPetsByTags(handler : (tags : Seq[String]) => F[List[Pet]]) : PetService[F] = {
asHandler.copy(findPetsByTagsHandler = handler)
}
/** Find pet by ID
*
* @return Pet
*/
def getPetById(petId : Long) : Pet
def getPetById(petId : Long) : F[Pet]
/**
* override getPetById with the given handler
* @return a new implementation of PetService[F] with getPetById overridden using the given handler
*/
final def onGetPetById(handler : (petId : Long) => F[Pet]) : PetService[F] = {
asHandler.copy(getPetByIdHandler = handler)
}
/** Update an existing pet
*
* @return Pet
*/
def updatePet(pet : Pet) : Pet
def updatePet(pet : Pet) : F[Pet]
/**
* override updatePet with the given handler
* @return a new implementation of PetService[F] with updatePet overridden using the given handler
*/
final def onUpdatePet(handler : (pet : Pet) => F[Pet]) : PetService[F] = {
asHandler.copy(updatePetHandler = handler)
}
/** Updates a pet in the store with form data
*
* @return
*/
def updatePetWithForm(petId : Long, name : Option[String], status : Option[String]) : Unit
def updatePetWithForm(petId : Long, name : Option[String], status : Option[String]) : F[Unit]
/**
* override updatePetWithForm with the given handler
* @return a new implementation of PetService[F] with updatePetWithForm overridden using the given handler
*/
final def onUpdatePetWithForm(handler : (petId : Long, name : Option[String], status : Option[String]) => F[Unit]) : PetService[F] = {
asHandler.copy(updatePetWithFormHandler = handler)
}
/** uploads an image
*
* @return ApiResponse
*/
def uploadFile(petId : Long, additionalMetadata : Option[String], file : Option[File]) : ApiResponse
def uploadFile(petId : Long, additionalMetadata : Option[String], file : Option[File]) : F[ApiResponse]
/**
* override uploadFile with the given handler
* @return a new implementation of PetService[F] with uploadFile overridden using the given handler
*/
final def onUploadFile(handler : (petId : Long, additionalMetadata : Option[String], file : Option[File]) => F[ApiResponse]) : PetService[F] = {
asHandler.copy(uploadFileHandler = handler)
}
/**
* @return a Handler implementation of this service
*/
final def asHandler : PetService.Handler[F] = this match {
case h : PetService.Handler[F] => h
case _ =>
PetService.Handler[F](
(pet) => addPet(pet),
(petId, apiKey) => deletePet(petId, apiKey),
(status) => findPetsByStatus(status),
(tags) => findPetsByTags(tags),
(petId) => getPetById(petId),
(pet) => updatePet(pet),
(petId, name, status) => updatePetWithForm(petId, name, status),
(petId, additionalMetadata, file) => uploadFile(petId, additionalMetadata, file)
)
}
/**
* 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 PetService service implementation with effect type [G]
*/
final def mapEffect[G[_]](changeEffect : [A] => F[A] => G[A]) : PetService[G] = {
val self = this
new PetService[G] {
override def addPet(pet : Pet) : G[Pet] = changeEffect {
self.addPet(pet)
}
override def deletePet(petId : Long, apiKey : Option[String]) : G[Unit] = changeEffect {
self.deletePet(petId, apiKey)
}
override def findPetsByStatus(status : Seq[String]) : G[List[Pet]] = changeEffect {
self.findPetsByStatus(status)
}
override def findPetsByTags(tags : Seq[String]) : G[List[Pet]] = changeEffect {
self.findPetsByTags(tags)
}
override def getPetById(petId : Long) : G[Pet] = changeEffect {
self.getPetById(petId)
}
override def updatePet(pet : Pet) : G[Pet] = changeEffect {
self.updatePet(pet)
}
override def updatePetWithForm(petId : Long, name : Option[String], status : Option[String]) : G[Unit] = changeEffect {
self.updatePetWithForm(petId, name, status)
}
override def uploadFile(petId : Long, additionalMetadata : Option[String], file : Option[File]) : G[ApiResponse] = changeEffect {
self.uploadFile(petId, additionalMetadata, file)
}
}
}
}

View File

@ -19,40 +19,168 @@
package sample.cask.api
import _root_.sample.cask.model.Order
import scala.util.Failure
import scala.util.Try
import _root_.sample.cask.model.*
/**
* The StoreService companion object.
*
* Use the StoreService() companion object to create an instance which returns a 'not implemented' error
* for each operation.
*
*/
object StoreService {
def apply() : StoreService = new StoreService {
override def deleteOrder(orderId : String) : Unit = ???
override def getInventory() : Map[String, Int] = ???
override def getOrderById(orderId : Long) : Order = ???
override def placeOrder(order : Order) : Order = ???
/**
* The 'Handler' is an implementation of StoreService convenient for delegating or overriding individual functions
*/
case class Handler[F[_]](
deleteOrderHandler : (orderId : String) => F[Unit],
getInventoryHandler : () => F[Map[String, Int]],
getOrderByIdHandler : (orderId : Long) => F[Order],
placeOrderHandler : (order : Order) => F[Order]
) extends StoreService[F] {
override def deleteOrder(orderId : String) : F[Unit] = {
deleteOrderHandler(orderId)
}
override def getInventory() : F[Map[String, Int]] = {
getInventoryHandler()
}
override def getOrderById(orderId : Long) : F[Order] = {
getOrderByIdHandler(orderId)
}
override def placeOrder(order : Order) : F[Order] = {
placeOrderHandler(order)
}
}
def apply() : StoreService[Try] = StoreService.Handler[Try](
(_) => notImplemented("deleteOrder"),
() => notImplemented("getInventory"),
(_) => notImplemented("getOrderById"),
(_) => notImplemented("placeOrder")
)
private def notImplemented(name : String) = Failure(new Exception(s"TODO: $name not implemented"))
}
/**
* The Store business-logic
*
*
* The 'asHandler' will return an implementation which allows for easily overriding individual operations.
*
* equally there are "on&lt;Function&gt;" helper methods for easily overriding individual functions
*
* @tparam F the effect type (Future, Try, IO, ID, etc) of the operations
*/
trait StoreService {
trait StoreService[F[_]] {
/** Delete purchase order by ID
*
* @return
*/
def deleteOrder(orderId : String) : Unit
def deleteOrder(orderId : String) : F[Unit]
/**
* override deleteOrder with the given handler
* @return a new implementation of StoreService[F] with deleteOrder overridden using the given handler
*/
final def onDeleteOrder(handler : (orderId : String) => F[Unit]) : StoreService[F] = {
asHandler.copy(deleteOrderHandler = handler)
}
/** Returns pet inventories by status
*
* @return Map[String, Int]
*/
def getInventory() : Map[String, Int]
def getInventory() : F[Map[String, Int]]
/**
* override getInventory with the given handler
* @return a new implementation of StoreService[F] with getInventory overridden using the given handler
*/
final def onGetInventory(handler : () => F[Map[String, Int]]) : StoreService[F] = {
asHandler.copy(getInventoryHandler = handler)
}
/** Find purchase order by ID
*
* @return Order
*/
def getOrderById(orderId : Long) : Order
def getOrderById(orderId : Long) : F[Order]
/**
* override getOrderById with the given handler
* @return a new implementation of StoreService[F] with getOrderById overridden using the given handler
*/
final def onGetOrderById(handler : (orderId : Long) => F[Order]) : StoreService[F] = {
asHandler.copy(getOrderByIdHandler = handler)
}
/** Place an order for a pet
*
* @return Order
*/
def placeOrder(order : Order) : Order
def placeOrder(order : Order) : F[Order]
/**
* override placeOrder with the given handler
* @return a new implementation of StoreService[F] with placeOrder overridden using the given handler
*/
final def onPlaceOrder(handler : (order : Order) => F[Order]) : StoreService[F] = {
asHandler.copy(placeOrderHandler = handler)
}
/**
* @return a Handler implementation of this service
*/
final def asHandler : StoreService.Handler[F] = this match {
case h : StoreService.Handler[F] => h
case _ =>
StoreService.Handler[F](
(orderId) => deleteOrder(orderId),
() => getInventory(),
(orderId) => getOrderById(orderId),
(order) => placeOrder(order)
)
}
/**
* 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 StoreService service implementation with effect type [G]
*/
final def mapEffect[G[_]](changeEffect : [A] => F[A] => G[A]) : StoreService[G] = {
val self = this
new StoreService[G] {
override def deleteOrder(orderId : String) : G[Unit] = changeEffect {
self.deleteOrder(orderId)
}
override def getInventory() : G[Map[String, Int]] = changeEffect {
self.getInventory()
}
override def getOrderById(orderId : Long) : G[Order] = changeEffect {
self.getOrderById(orderId)
}
override def placeOrder(order : Order) : G[Order] = changeEffect {
self.placeOrder(order)
}
}
}
}

View File

@ -20,64 +20,260 @@ package sample.cask.api
import _root_.java.time.OffsetDateTime
import _root_.sample.cask.model.User
import scala.util.Failure
import scala.util.Try
import _root_.sample.cask.model.*
/**
* The UserService companion object.
*
* Use the UserService() companion object to create an instance which returns a 'not implemented' error
* for each operation.
*
*/
object UserService {
def apply() : UserService = new UserService {
override def createUser(user : User) : Unit = ???
override def createUsersWithArrayInput(user : Seq[User]) : Unit = ???
override def createUsersWithListInput(user : Seq[User]) : Unit = ???
override def deleteUser(username : String) : Unit = ???
override def getUserByName(username : String) : User = ???
override def loginUser(username : String, password : String) : String = ???
override def logoutUser() : Unit = ???
override def updateUser(username : String, user : User) : Unit = ???
/**
* The 'Handler' is an implementation of UserService convenient for delegating or overriding individual functions
*/
case class Handler[F[_]](
createUserHandler : (user : User) => F[Unit],
createUsersWithArrayInputHandler : (user : Seq[User]) => F[Unit],
createUsersWithListInputHandler : (user : Seq[User]) => F[Unit],
deleteUserHandler : (username : String) => F[Unit],
getUserByNameHandler : (username : String) => F[User],
loginUserHandler : (username : String, password : String) => F[String],
logoutUserHandler : () => F[Unit],
updateUserHandler : (username : String, user : User) => F[Unit]
) extends UserService[F] {
override def createUser(user : User) : F[Unit] = {
createUserHandler(user)
}
override def createUsersWithArrayInput(user : Seq[User]) : F[Unit] = {
createUsersWithArrayInputHandler(user)
}
override def createUsersWithListInput(user : Seq[User]) : F[Unit] = {
createUsersWithListInputHandler(user)
}
override def deleteUser(username : String) : F[Unit] = {
deleteUserHandler(username)
}
override def getUserByName(username : String) : F[User] = {
getUserByNameHandler(username)
}
override def loginUser(username : String, password : String) : F[String] = {
loginUserHandler(username, password)
}
override def logoutUser() : F[Unit] = {
logoutUserHandler()
}
override def updateUser(username : String, user : User) : F[Unit] = {
updateUserHandler(username, user)
}
}
def apply() : UserService[Try] = UserService.Handler[Try](
(_) => notImplemented("createUser"),
(_) => notImplemented("createUsersWithArrayInput"),
(_) => notImplemented("createUsersWithListInput"),
(_) => notImplemented("deleteUser"),
(_) => notImplemented("getUserByName"),
(_, _) => notImplemented("loginUser"),
() => notImplemented("logoutUser"),
(_, _) => notImplemented("updateUser")
)
private def notImplemented(name : String) = Failure(new Exception(s"TODO: $name not implemented"))
}
/**
* The User business-logic
*
*
* The 'asHandler' will return an implementation which allows for easily overriding individual operations.
*
* equally there are "on&lt;Function&gt;" helper methods for easily overriding individual functions
*
* @tparam F the effect type (Future, Try, IO, ID, etc) of the operations
*/
trait UserService {
trait UserService[F[_]] {
/** Create user
*
* @return
*/
def createUser(user : User) : Unit
def createUser(user : User) : F[Unit]
/**
* override createUser with the given handler
* @return a new implementation of UserService[F] with createUser overridden using the given handler
*/
final def onCreateUser(handler : (user : User) => F[Unit]) : UserService[F] = {
asHandler.copy(createUserHandler = handler)
}
/** Creates list of users with given input array
*
* @return
*/
def createUsersWithArrayInput(user : Seq[User]) : Unit
def createUsersWithArrayInput(user : Seq[User]) : F[Unit]
/**
* override createUsersWithArrayInput with the given handler
* @return a new implementation of UserService[F] with createUsersWithArrayInput overridden using the given handler
*/
final def onCreateUsersWithArrayInput(handler : (user : Seq[User]) => F[Unit]) : UserService[F] = {
asHandler.copy(createUsersWithArrayInputHandler = handler)
}
/** Creates list of users with given input array
*
* @return
*/
def createUsersWithListInput(user : Seq[User]) : Unit
def createUsersWithListInput(user : Seq[User]) : F[Unit]
/**
* override createUsersWithListInput with the given handler
* @return a new implementation of UserService[F] with createUsersWithListInput overridden using the given handler
*/
final def onCreateUsersWithListInput(handler : (user : Seq[User]) => F[Unit]) : UserService[F] = {
asHandler.copy(createUsersWithListInputHandler = handler)
}
/** Delete user
*
* @return
*/
def deleteUser(username : String) : Unit
def deleteUser(username : String) : F[Unit]
/**
* override deleteUser with the given handler
* @return a new implementation of UserService[F] with deleteUser overridden using the given handler
*/
final def onDeleteUser(handler : (username : String) => F[Unit]) : UserService[F] = {
asHandler.copy(deleteUserHandler = handler)
}
/** Get user by user name
*
* @return User
*/
def getUserByName(username : String) : User
def getUserByName(username : String) : F[User]
/**
* override getUserByName with the given handler
* @return a new implementation of UserService[F] with getUserByName overridden using the given handler
*/
final def onGetUserByName(handler : (username : String) => F[User]) : UserService[F] = {
asHandler.copy(getUserByNameHandler = handler)
}
/** Logs user into the system
*
* @return String
*/
def loginUser(username : String, password : String) : String
def loginUser(username : String, password : String) : F[String]
/**
* override loginUser with the given handler
* @return a new implementation of UserService[F] with loginUser overridden using the given handler
*/
final def onLoginUser(handler : (username : String, password : String) => F[String]) : UserService[F] = {
asHandler.copy(loginUserHandler = handler)
}
/** Logs out current logged in user session
*
* @return
*/
def logoutUser() : Unit
def logoutUser() : F[Unit]
/**
* override logoutUser with the given handler
* @return a new implementation of UserService[F] with logoutUser overridden using the given handler
*/
final def onLogoutUser(handler : () => F[Unit]) : UserService[F] = {
asHandler.copy(logoutUserHandler = handler)
}
/** Updated user
*
* @return
*/
def updateUser(username : String, user : User) : Unit
def updateUser(username : String, user : User) : F[Unit]
/**
* override updateUser with the given handler
* @return a new implementation of UserService[F] with updateUser overridden using the given handler
*/
final def onUpdateUser(handler : (username : String, user : User) => F[Unit]) : UserService[F] = {
asHandler.copy(updateUserHandler = handler)
}
/**
* @return a Handler implementation of this service
*/
final def asHandler : UserService.Handler[F] = this match {
case h : UserService.Handler[F] => h
case _ =>
UserService.Handler[F](
(user) => createUser(user),
(user) => createUsersWithArrayInput(user),
(user) => createUsersWithListInput(user),
(username) => deleteUser(username),
(username) => getUserByName(username),
(username, password) => loginUser(username, password),
() => logoutUser(),
(username, user) => updateUser(username, user)
)
}
/**
* 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 UserService service implementation with effect type [G]
*/
final def mapEffect[G[_]](changeEffect : [A] => F[A] => G[A]) : UserService[G] = {
val self = this
new UserService[G] {
override def createUser(user : User) : G[Unit] = changeEffect {
self.createUser(user)
}
override def createUsersWithArrayInput(user : Seq[User]) : G[Unit] = changeEffect {
self.createUsersWithArrayInput(user)
}
override def createUsersWithListInput(user : Seq[User]) : G[Unit] = changeEffect {
self.createUsersWithListInput(user)
}
override def deleteUser(username : String) : G[Unit] = changeEffect {
self.deleteUser(username)
}
override def getUserByName(username : String) : G[User] = changeEffect {
self.getUserByName(username)
}
override def loginUser(username : String, password : String) : G[String] = changeEffect {
self.loginUser(username, password)
}
override def logoutUser() : G[Unit] = changeEffect {
self.logoutUser()
}
override def updateUser(username : String, user : User) : G[Unit] = changeEffect {
self.updateUser(username, user)
}
}
}
}

View File

@ -13,41 +13,44 @@
// 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 ApiResponse(
code: Option[Int] = None ,
`type`: Option[String] = None ,
message: Option[String] = None
code: Option[Int] = None ,
`type`: Option[String] = None ,
message: Option[String] = None
) {
def asJsonString: String = asData.asJsonString
def asJson: ujson.Value = asData.asJson
def asJsonString: String = asData.asJsonString
def asJson: ujson.Value = asData.asJson
def asData : ApiResponseData = {
ApiResponseData(
code = code.getOrElse(0),
`type` = `type`.getOrElse(""),
message = message.getOrElse("")
def asData : ApiResponseData = {
ApiResponseData(
code = code.getOrElse(0) /* 1 */,
`type` = `type`.getOrElse("") /* 1 */,
message = message.getOrElse("") /* 1 */
)
}
)
}
}
object ApiResponse {
given RW[ApiResponse] = summon[RW[ujson.Value]].bimap[ApiResponse](_.asJson, json => read[ApiResponseData](json).asModel)
given RW[ApiResponse] = summon[RW[ujson.Value]].bimap[ApiResponse](_.asJson, json => read[ApiResponseData](json).asModel)
enum Fields(val fieldName : String) extends Field(fieldName) {
case code extends Fields("code")
case `type` extends Fields("`type`")
case message extends Fields("message")
}
enum Fields(val fieldName : String) extends Field(fieldName) {
case code extends Fields("code")
case `type` extends Fields("`type`")
case message extends Fields("message")
}
}

View File

@ -20,7 +20,8 @@ import scala.util.*
import upickle.default.{ReadWriter => RW, macroRW}
import upickle.default.*
/** ApiResponseData a data transfer object, primarily for simple json serialisation.
/** ApiResponseData a data transfer object, primarily for simple json serialisation.
* It has no validation - there may be nulls, values out of range, etc
*/
case class ApiResponseData(
@ -61,6 +62,9 @@ case class ApiResponseData(
errors.toSeq
}
/**
* @return the validated model within a Try (if successful)
*/
def validated(failFast : Boolean = false) : scala.util.Try[ApiResponse] = {
validationErrors(Vector(), failFast) match {
case Seq() => Success(asModel)
@ -71,18 +75,9 @@ case class ApiResponseData(
/** use 'validated' to check validation */
def asModel : ApiResponse = {
ApiResponse(
code = Option(
code
)
,
`type` = Option(
`type`
)
,
message = Option(
message
)
code = Option(code) /* 1 */,
`type` = Option(`type`) /* 1 */,
message = Option(message) /* 1 */
)
}

View File

@ -13,38 +13,41 @@
// 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 Category(
id: Option[Long] = None ,
name: Option[String] = None
id: Option[Long] = None ,
name: Option[String] = None
) {
def asJsonString: String = asData.asJsonString
def asJson: ujson.Value = asData.asJson
def asJsonString: String = asData.asJsonString
def asJson: ujson.Value = asData.asJson
def asData : CategoryData = {
CategoryData(
id = id.getOrElse(0),
name = name.getOrElse("")
def asData : CategoryData = {
CategoryData(
id = id.getOrElse(0) /* 1 */,
name = name.getOrElse("") /* 1 */
)
}
)
}
}
object Category {
given RW[Category] = summon[RW[ujson.Value]].bimap[Category](_.asJson, json => read[CategoryData](json).asModel)
given RW[Category] = summon[RW[ujson.Value]].bimap[Category](_.asJson, json => read[CategoryData](json).asModel)
enum Fields(val fieldName : String) extends Field(fieldName) {
case id extends Fields("id")
case name extends Fields("name")
}
enum Fields(val fieldName : String) extends Field(fieldName) {
case id extends Fields("id")
case name extends Fields("name")
}
}

View File

@ -20,7 +20,8 @@ import scala.util.*
import upickle.default.{ReadWriter => RW, macroRW}
import upickle.default.*
/** CategoryData a data transfer object, primarily for simple json serialisation.
/** CategoryData a data transfer object, primarily for simple json serialisation.
* It has no validation - there may be nulls, values out of range, etc
*/
case class CategoryData(
@ -60,6 +61,9 @@ case class CategoryData(
errors.toSeq
}
/**
* @return the validated model within a Try (if successful)
*/
def validated(failFast : Boolean = false) : scala.util.Try[Category] = {
validationErrors(Vector(), failFast) match {
case Seq() => Success(asModel)
@ -70,14 +74,8 @@ case class CategoryData(
/** use 'validated' to check validation */
def asModel : Category = {
Category(
id = Option(
id
)
,
name = Option(
name
)
id = Option(id) /* 1 */,
name = Option(name) /* 1 */
)
}

View File

@ -14,51 +14,54 @@
// 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 Order(
id: Option[Long] = None ,
petId: Option[Long] = None ,
quantity: Option[Int] = None ,
shipDate: Option[OffsetDateTime] = None ,
/* Order Status */
status: Option[Order.StatusEnum] = None ,
complete: Option[Boolean] = None
id: Option[Long] = None ,
petId: Option[Long] = None ,
quantity: Option[Int] = None ,
shipDate: Option[OffsetDateTime] = None ,
/* Order Status */
status: Option[Order.StatusEnum] = None ,
complete: Option[Boolean] = None
) {
def asJsonString: String = asData.asJsonString
def asJson: ujson.Value = asData.asJson
def asJsonString: String = asData.asJsonString
def asJson: ujson.Value = asData.asJson
def asData : OrderData = {
OrderData(
id = id.getOrElse(0),
petId = petId.getOrElse(0),
quantity = quantity.getOrElse(0),
shipDate = shipDate.getOrElse(null),
status = status.getOrElse(null),
complete = complete.getOrElse(false)
def asData : OrderData = {
OrderData(
id = id.getOrElse(0) /* 1 */,
petId = petId.getOrElse(0) /* 1 */,
quantity = quantity.getOrElse(0) /* 1 */,
shipDate = shipDate.getOrElse(null) /* 1 */,
status = status.getOrElse(null) /* 1 */,
complete = complete.getOrElse(false) /* 1 */
)
}
)
}
}
object Order {
given RW[Order] = summon[RW[ujson.Value]].bimap[Order](_.asJson, json => read[OrderData](json).asModel)
given RW[Order] = summon[RW[ujson.Value]].bimap[Order](_.asJson, json => read[OrderData](json).asModel)
enum Fields(val fieldName : String) extends Field(fieldName) {
case id extends Fields("id")
case petId extends Fields("petId")
case quantity extends Fields("quantity")
case shipDate extends Fields("shipDate")
case status extends Fields("status")
case complete extends Fields("complete")
}
enum Fields(val fieldName : String) extends Field(fieldName) {
case id extends Fields("id")
case petId extends Fields("petId")
case quantity extends Fields("quantity")
case shipDate extends Fields("shipDate")
case status extends Fields("status")
case complete extends Fields("complete")
}
// baseName=status
// nameInCamelCase = status

View File

@ -21,7 +21,8 @@ import scala.util.*
import upickle.default.{ReadWriter => RW, macroRW}
import upickle.default.*
/** OrderData a data transfer object, primarily for simple json serialisation.
/** OrderData a data transfer object, primarily for simple json serialisation.
* It has no validation - there may be nulls, values out of range, etc
*/
case class OrderData(
@ -84,6 +85,9 @@ case class OrderData(
errors.toSeq
}
/**
* @return the validated model within a Try (if successful)
*/
def validated(failFast : Boolean = false) : scala.util.Try[Order] = {
validationErrors(Vector(), failFast) match {
case Seq() => Success(asModel)
@ -94,30 +98,12 @@ case class OrderData(
/** use 'validated' to check validation */
def asModel : Order = {
Order(
id = Option(
id
)
,
petId = Option(
petId
)
,
quantity = Option(
quantity
)
,
shipDate = Option(
shipDate
)
,
status = Option(
status
)
,
complete = Option(
complete
)
id = Option(id) /* 1 */,
petId = Option(petId) /* 1 */,
quantity = Option(quantity) /* 1 */,
shipDate = Option(shipDate) /* 1 */,
status = Option(status) /* 1 */,
complete = Option(complete) /* 1 */
)
}

View File

@ -13,53 +13,54 @@
// this model was generated using model.mustache
package sample.cask.model
import sample.cask.model.Category
import sample.cask.model.Tag
import scala.util.control.NonFatal
// see https://com-lihaoyi.github.io/upickle/
import upickle.default.{ReadWriter => RW, macroRW}
import upickle.default.*
case class Pet(
id: Option[Long] = None ,
category: Option[Category] = None ,
name: String,
photoUrls: Seq[String],
tags: Seq[Tag] = Nil ,
/* pet status in the store */
status: Option[Pet.StatusEnum] = None
id: Option[Long] = None ,
category: Option[Category] = None ,
name: String,
photoUrls: Seq[String],
tags: Seq[Tag] = Nil ,
/* pet status in the store */
status: Option[Pet.StatusEnum] = None
) {
def asJsonString: String = asData.asJsonString
def asJson: ujson.Value = asData.asJson
def asJsonString: String = asData.asJsonString
def asJson: ujson.Value = asData.asJson
def asData : PetData = {
PetData(
id = id.getOrElse(0),
category = category.map(_.asData).getOrElse(null),
name = name,
photoUrls = photoUrls,
tags = tags.map(_.asData),
status = status.getOrElse(null)
def asData : PetData = {
PetData(
id = id.getOrElse(0) /* 1 */,
category = category.map(_.asData).getOrElse(null) /* 4 */,
name = name /* 2 */,
photoUrls = photoUrls /* 2 */,
tags = tags.map(_.asData) /* 6 */,
status = status.getOrElse(null) /* 1 */
)
}
)
}
}
object Pet {
given RW[Pet] = summon[RW[ujson.Value]].bimap[Pet](_.asJson, json => read[PetData](json).asModel)
given RW[Pet] = summon[RW[ujson.Value]].bimap[Pet](_.asJson, json => read[PetData](json).asModel)
enum Fields(val fieldName : String) extends Field(fieldName) {
case id extends Fields("id")
case category extends Fields("category")
case name extends Fields("name")
case photoUrls extends Fields("photoUrls")
case tags extends Fields("tags")
case status extends Fields("status")
}
enum Fields(val fieldName : String) extends Field(fieldName) {
case id extends Fields("id")
case category extends Fields("category")
case name extends Fields("name")
case photoUrls extends Fields("photoUrls")
case tags extends Fields("tags")
case status extends Fields("status")
}
// baseName=status
// nameInCamelCase = status

View File

@ -13,8 +13,6 @@
// this model was generated using modelData.mustache
package sample.cask.model
import sample.cask.model.Category
import sample.cask.model.Tag
import scala.util.control.NonFatal
import scala.util.*
@ -22,7 +20,8 @@ import scala.util.*
import upickle.default.{ReadWriter => RW, macroRW}
import upickle.default.*
/** PetData a data transfer object, primarily for simple json serialisation.
/** PetData a data transfer object, primarily for simple json serialisation.
* It has no validation - there may be nulls, values out of range, etc
*/
case class PetData(
@ -101,6 +100,9 @@ case class PetData(
errors.toSeq
}
/**
* @return the validated model within a Try (if successful)
*/
def validated(failFast : Boolean = false) : scala.util.Try[Pet] = {
validationErrors(Vector(), failFast) match {
case Seq() => Success(asModel)
@ -111,30 +113,12 @@ case class PetData(
/** use 'validated' to check validation */
def asModel : Pet = {
Pet(
id = Option(
id
)
,
category = Option(
category
)
.map(_.asModel),
name =
name
,
photoUrls =
photoUrls
,
tags =
tags
.map(_.asModel),
status = Option(
status
)
id = Option(id) /* 1 */,
category = Option(category).map(_.asModel) /* 4 */,
name = name /* 2 */,
photoUrls = photoUrls /* 2 */,
tags = tags.map(_.asModel) /* 5 */,
status = Option(status) /* 1 */
)
}

View File

@ -13,38 +13,41 @@
// 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 Tag(
id: Option[Long] = None ,
name: Option[String] = None
id: Option[Long] = None ,
name: Option[String] = None
) {
def asJsonString: String = asData.asJsonString
def asJson: ujson.Value = asData.asJson
def asJsonString: String = asData.asJsonString
def asJson: ujson.Value = asData.asJson
def asData : TagData = {
TagData(
id = id.getOrElse(0),
name = name.getOrElse("")
def asData : TagData = {
TagData(
id = id.getOrElse(0) /* 1 */,
name = name.getOrElse("") /* 1 */
)
}
)
}
}
object Tag {
given RW[Tag] = summon[RW[ujson.Value]].bimap[Tag](_.asJson, json => read[TagData](json).asModel)
given RW[Tag] = summon[RW[ujson.Value]].bimap[Tag](_.asJson, json => read[TagData](json).asModel)
enum Fields(val fieldName : String) extends Field(fieldName) {
case id extends Fields("id")
case name extends Fields("name")
}
enum Fields(val fieldName : String) extends Field(fieldName) {
case id extends Fields("id")
case name extends Fields("name")
}
}

View File

@ -20,7 +20,8 @@ import scala.util.*
import upickle.default.{ReadWriter => RW, macroRW}
import upickle.default.*
/** TagData a data transfer object, primarily for simple json serialisation.
/** TagData a data transfer object, primarily for simple json serialisation.
* It has no validation - there may be nulls, values out of range, etc
*/
case class TagData(
@ -54,6 +55,9 @@ case class TagData(
errors.toSeq
}
/**
* @return the validated model within a Try (if successful)
*/
def validated(failFast : Boolean = false) : scala.util.Try[Tag] = {
validationErrors(Vector(), failFast) match {
case Seq() => Success(asModel)
@ -64,14 +68,8 @@ case class TagData(
/** use 'validated' to check validation */
def asModel : Tag = {
Tag(
id = Option(
id
)
,
name = Option(
name
)
id = Option(id) /* 1 */,
name = Option(name) /* 1 */
)
}

View File

@ -13,57 +13,60 @@
// 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 User(
id: Option[Long] = None ,
username: Option[String] = None ,
firstName: Option[String] = None ,
lastName: Option[String] = None ,
email: Option[String] = None ,
password: Option[String] = None ,
phone: Option[String] = None ,
/* User Status */
userStatus: Option[Int] = None
id: Option[Long] = None ,
username: Option[String] = None ,
firstName: Option[String] = None ,
lastName: Option[String] = None ,
email: Option[String] = None ,
password: Option[String] = None ,
phone: Option[String] = None ,
/* User Status */
userStatus: Option[Int] = None
) {
def asJsonString: String = asData.asJsonString
def asJson: ujson.Value = asData.asJson
def asJsonString: String = asData.asJsonString
def asJson: ujson.Value = asData.asJson
def asData : UserData = {
UserData(
id = id.getOrElse(0),
username = username.getOrElse(""),
firstName = firstName.getOrElse(""),
lastName = lastName.getOrElse(""),
email = email.getOrElse(""),
password = password.getOrElse(""),
phone = phone.getOrElse(""),
userStatus = userStatus.getOrElse(0)
def asData : UserData = {
UserData(
id = id.getOrElse(0) /* 1 */,
username = username.getOrElse("") /* 1 */,
firstName = firstName.getOrElse("") /* 1 */,
lastName = lastName.getOrElse("") /* 1 */,
email = email.getOrElse("") /* 1 */,
password = password.getOrElse("") /* 1 */,
phone = phone.getOrElse("") /* 1 */,
userStatus = userStatus.getOrElse(0) /* 1 */
)
}
)
}
}
object User {
given RW[User] = summon[RW[ujson.Value]].bimap[User](_.asJson, json => read[UserData](json).asModel)
given RW[User] = summon[RW[ujson.Value]].bimap[User](_.asJson, json => read[UserData](json).asModel)
enum Fields(val fieldName : String) extends Field(fieldName) {
case id extends Fields("id")
case username extends Fields("username")
case firstName extends Fields("firstName")
case lastName extends Fields("lastName")
case email extends Fields("email")
case password extends Fields("password")
case phone extends Fields("phone")
case userStatus extends Fields("userStatus")
}
enum Fields(val fieldName : String) extends Field(fieldName) {
case id extends Fields("id")
case username extends Fields("username")
case firstName extends Fields("firstName")
case lastName extends Fields("lastName")
case email extends Fields("email")
case password extends Fields("password")
case phone extends Fields("phone")
case userStatus extends Fields("userStatus")
}
}

View File

@ -20,7 +20,8 @@ import scala.util.*
import upickle.default.{ReadWriter => RW, macroRW}
import upickle.default.*
/** UserData a data transfer object, primarily for simple json serialisation.
/** UserData a data transfer object, primarily for simple json serialisation.
* It has no validation - there may be nulls, values out of range, etc
*/
case class UserData(
@ -97,6 +98,9 @@ case class UserData(
errors.toSeq
}
/**
* @return the validated model within a Try (if successful)
*/
def validated(failFast : Boolean = false) : scala.util.Try[User] = {
validationErrors(Vector(), failFast) match {
case Seq() => Success(asModel)
@ -107,38 +111,14 @@ case class UserData(
/** use 'validated' to check validation */
def asModel : User = {
User(
id = Option(
id
)
,
username = Option(
username
)
,
firstName = Option(
firstName
)
,
lastName = Option(
lastName
)
,
email = Option(
email
)
,
password = Option(
password
)
,
phone = Option(
phone
)
,
userStatus = Option(
userStatus
)
id = Option(id) /* 1 */,
username = Option(username) /* 1 */,
firstName = Option(firstName) /* 1 */,
lastName = Option(lastName) /* 1 */,
email = Option(email) /* 1 */,
password = Option(password) /* 1 */,
phone = Option(phone) /* 1 */,
userStatus = Option(userStatus) /* 1 */
)
}