diff --git a/src/main/resources/io/viash/platforms/nextflow/arguments/_checkArgumentType.nf b/src/main/resources/io/viash/platforms/nextflow/arguments/_checkArgumentType.nf index 8fe22f384..ae5086086 100644 --- a/src/main/resources/io/viash/platforms/nextflow/arguments/_checkArgumentType.nf +++ b/src/main/resources/io/viash/platforms/nextflow/arguments/_checkArgumentType.nf @@ -1,6 +1,38 @@ +class UnexpectedArgumentTypeException extends Exception { + String key + String id + String stage + String plainName + String expectedClass + String foundClass + + UnexpectedArgumentTypeException(String key, String id, String stage, String plainName, String expectedClass, String foundClass) { + super("Error${key ? " module '$key'" : ""}${id ? " id '$id'" : ""}:${stage ? " $stage" : "" } argument '${plainName}' has the wrong type. " + + "Expected type: ${expectedClass}. Found type: ${foundClass}") + this.key = key + this.id = id + this.stage = stage + this.plainName = plainName + this.expectedClass = expectedClass + this.foundClass = foundClass + } +} + +/** + * Checks if the given value is of the expected type. If not, an exception is thrown. + * + * @param stage The stage of the argument (input or output) + * @param par The parameter definition + * @param value The value to check + * @param id The id of tuple where the data comes from. Can be null if unknown. + * @param key The key of the module + * @return The value, if it is of the expected type + * @throws UnexpectedArgumentTypeException If the value is not of the expected type +*/ def _checkArgumentType(String stage, Map par, Object value, String id, String key) { // expectedClass will only be != null if value is not of the expected type def expectedClass = null + def foundClass = null if (!par.required && value == null) { expectedClass = null @@ -8,15 +40,16 @@ def _checkArgumentType(String stage, Map par, Object value, String id, String ke if (par.type == "file" && par.direction == "input" && value instanceof String) { value = file(value, hidden: true) } - if (value !instanceof List) { + if (value !instanceof Collection) { value = [value] } try { value = value.collect { listVal -> _checkArgumentType(stage, par + [multiple: false], listVal, id, key) } - } catch (Exception e) { - expectedClass = "List[${par.type}]" + } catch (UnexpectedArgumentTypeException e) { + expectedClass = "List[${e.expectedClass}]" + foundClass = "List[${e.foundClass}]" } } else if (par.type == "string") { if (value instanceof GString) { @@ -24,13 +57,26 @@ def _checkArgumentType(String stage, Map par, Object value, String id, String ke } expectedClass = value instanceof String ? null : "String" } else if (par.type == "integer") { + // TODO: allow this? + if (value instanceof String && value.isInteger()) { + value = value.toInteger() + } + if (value instanceof java.math.BigInteger) { + value = value.intValue() + } expectedClass = value instanceof Integer ? null : "Integer" } else if (par.type == "long") { + if (value instanceof String && value.isLong()) { + value = value.toLong() + } if (value instanceof Integer) { value = value.toLong() } expectedClass = value instanceof Long ? null : "Long" } else if (par.type == "double") { + if (value instanceof String && value.isDouble()) { + value = value.toDouble() + } if (value instanceof java.math.BigDecimal) { value = value.doubleValue() } @@ -39,15 +85,23 @@ def _checkArgumentType(String stage, Map par, Object value, String id, String ke } expectedClass = value instanceof Double ? null : "Double" } else if (par.type == "boolean" | par.type == "boolean_true" | par.type == "boolean_false") { + if (value instanceof String) { + value = value.toLowerCase() + if (value == "true") { + value = true + } else if (value == "false") { + value = false + } + } expectedClass = value instanceof Boolean ? null : "Boolean" } else if (par.type == "file") { if (stage == "output" || par.direction == "input") { - if (value instanceof File) { - value = value.toPath() - } if (value instanceof String) { value = file(value, hidden: true) } + if (value instanceof File) { + value = value.toPath() + } expectedClass = value instanceof Path ? null : "Path" } else { // stage == "input" && par.direction == "output" if (value instanceof GString) { @@ -60,8 +114,10 @@ def _checkArgumentType(String stage, Map par, Object value, String id, String ke } if (expectedClass != null) { - error "Error in module '${key}' id '${id}': ${stage} argument '${par.plainName}' has the wrong type. " + - "Expected type: ${expectedClass}. Found type: ${value.getClass()}" + if (foundClass == null) { + foundClass = value.getClass().getName() + } + throw new UnexpectedArgumentTypeException(key, id, stage, par.plainName, expectedClass, foundClass) } return value diff --git a/src/main/resources/io/viash/platforms/nextflow/channel/applyConfigToOneParameterSet.nf b/src/main/resources/io/viash/platforms/nextflow/channel/applyConfigToOneParameterSet.nf index 25c20f0ac..bb5da1fe0 100644 --- a/src/main/resources/io/viash/platforms/nextflow/channel/applyConfigToOneParameterSet.nf +++ b/src/main/resources/io/viash/platforms/nextflow/channel/applyConfigToOneParameterSet.nf @@ -1,63 +1,3 @@ -/** - * Cast parameters to the correct type as defined in the Viash config - * - * @param parValues A Map of input arguments. - * - * @return The input arguments that have been cast to the type from the viash config. - */ -private Map _castParamTypes(Map parValues, Map config) { - // Cast the input to the correct type according to viash config - def castParValues = parValues.collectEntries({ parName, parValue -> - def paramSettings = config.functionality.allArguments.find({it.plainName == parName}) - // dont parse parameters like publish_dir ( in which case paramSettings = null) - def parType = paramSettings ? paramSettings.get("type", null) : null - - // turn parValue into a list, if it isn't one already - if (parValue !instanceof Collection) { - parValue = [parValue] - } - - if (parType == "file" && paramSettings.get("direction", "input") == "input") { - parValue = parValue.collectMany{ parValueItem -> - if (parValueItem instanceof String) { - parValueItem = file(parValueItem) - } - if (parValueItem !instanceof Collection) { - parValueItem = [parValueItem] - } - parValueItem - } - // cast Paths to File? Or vice versa? - // parValue = parValue.collect{ - // if (path instanceof Path) { - // [path.toFile()] - // } else { - // [path] - // } - // } - } else if (parType == "integer") { - parValue = parValue.collect{it as Integer} - } else if (parType == "double") { - parValue = parValue.collect{it as Double} - } else if (parType == "boolean" || - parType == "boolean_true" || - parType == "boolean_false") { - parValue = parValue.collect{it as Boolean} - } - - // simplify list to value if need be - if (paramSettings && !paramSettings.multiple) { - assert parValue.size() == 1 : - "Error: argument ${parName} has too many values.\n" + - " Expected amount: 1. Found: ${parValue.size()}" - parValue = parValue[0] - } - - [parName, parValue] - }) - return castParValues -} - /** * Apply the argument settings specified in a Viash config to a single parameter set. * - Split the parameter values according to their seperator if @@ -74,7 +14,19 @@ private Map _castParamTypes(Map parValues, Map c */ Map applyConfigToOneParameterSet(Map paramValues, Map config){ def splitParamValues = _splitParams(paramValues, config) - def castParamValues = _castParamTypes(splitParamValues, config) + def castParamValues = paramValues.collectEntries({ parName, parValue -> + def paramSettings = config.functionality.allArguments.find({it.plainName == parName}) + // dont parse parameters like publish_dir ( in which case paramSettings = null) + + if (paramSettings) { + // check if the parameter is a list + def tupleId = null + def componentKey = null + parValue = _checkArgumentType("input", paramSettings, parValue, tupleId, componentKey) + } + + [parName, parValue] + }) // Check if any unexpected arguments were passed def knownParams = config.functionality.allArguments.collect({it.plainName}) + ["publishDir", "publish_dir"]