diff --git a/README.md b/README.md index 18f16bb..edb311b 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,9 @@ Requires java (8+): ```bash # install and add to PATH -wget https://github.com/adamkewley/jobson/releases/download/1.0.8/jobson-nix-1.0.8.tar.gz -tar xvf jobson-nix-1.0.8.tar.gz -export PATH=$PATH:jobson-nix-1.0.8/bin +wget https://github.com/adamkewley/jobson/releases/download/1.0.9/jobson-nix-1.0.9.tar.gz +tar xvf jobson-nix-1.0.9.tar.gz +export PATH=$PATH:jobson-nix-1.0.9/bin # create demo workspace jobson new --demo diff --git a/jobson-deb/pom.xml b/jobson-deb/pom.xml index ea1da7f..825a4f0 100644 --- a/jobson-deb/pom.xml +++ b/jobson-deb/pom.xml @@ -7,7 +7,7 @@ com.github.jobson jobson-project - 1.0.8 + 1.0.9 jobson-deb @@ -17,7 +17,7 @@ com.github.jobson jobson-nix - 1.0.8 + 1.0.9 tar.gz diff --git a/jobson-docker/pom.xml b/jobson-docker/pom.xml index ec677a5..6de67c9 100644 --- a/jobson-docker/pom.xml +++ b/jobson-docker/pom.xml @@ -7,7 +7,7 @@ com.github.jobson jobson-project - 1.0.8 + 1.0.9 jobson-docker @@ -17,7 +17,7 @@ com.github.jobson jobson-deb - 1.0.8 + 1.0.9 deb diff --git a/jobson-docs/pom.xml b/jobson-docs/pom.xml index 060dc4b..c6e834b 100644 --- a/jobson-docs/pom.xml +++ b/jobson-docs/pom.xml @@ -7,11 +7,11 @@ com.github.jobson jobson-project - 1.0.8 + 1.0.9 jobson-docs - 1.0.8 + 1.0.9 pom diff --git a/jobson-nix/pom.xml b/jobson-nix/pom.xml index 5b4c24b..c9dc3b5 100644 --- a/jobson-nix/pom.xml +++ b/jobson-nix/pom.xml @@ -7,7 +7,7 @@ com.github.jobson jobson-project - 1.0.8 + 1.0.9 jobson-nix @@ -17,18 +17,18 @@ com.github.jobson jobson - 1.0.8 + 1.0.9 com.github.jobson jobson-docs - 1.0.8 + 1.0.9 tar.gz com.github.jobson jobson-ui - 1.0.8 + 1.0.9 tar.gz diff --git a/jobson-swagger-ui/pom.xml b/jobson-swagger-ui/pom.xml index b5ceec6..68b5f41 100644 --- a/jobson-swagger-ui/pom.xml +++ b/jobson-swagger-ui/pom.xml @@ -7,11 +7,11 @@ com.github.jobson jobson-project - 1.0.8 + 1.0.9 jobson-swagger-ui - 1.0.8 + 1.0.9 pom diff --git a/jobson-swagger/pom.xml b/jobson-swagger/pom.xml index 790a477..0506f4b 100644 --- a/jobson-swagger/pom.xml +++ b/jobson-swagger/pom.xml @@ -7,18 +7,18 @@ com.github.jobson jobson-project - 1.0.8 + 1.0.9 jobson-swagger - 1.0.8 + 1.0.9 pom com.github.jobson jobson - 1.0.8 + 1.0.9 com.fasterxml diff --git a/jobson-ui/pom.xml b/jobson-ui/pom.xml index 123e1a1..c3a5d4e 100644 --- a/jobson-ui/pom.xml +++ b/jobson-ui/pom.xml @@ -7,11 +7,11 @@ com.github.jobson jobson-project - 1.0.8 + 1.0.9 jobson-ui - 1.0.8 + 1.0.9 pom diff --git a/jobson-ui/src/ts/components/inputeditors/FileInputEditor.tsx b/jobson-ui/src/ts/components/inputeditors/FileInputEditor.tsx new file mode 100644 index 0000000..d8fbaac --- /dev/null +++ b/jobson-ui/src/ts/components/inputeditors/FileInputEditor.tsx @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import {Component, ReactElement} from "react"; +import {InputEditorProps} from "./InputEditor"; +import * as React from "react"; +import {InputEditorUpdate} from "./updates/InputEditorUpdate"; + +const enum States { + Showing, + Loading, +} + +interface ShowingState { + type: States.Showing; + error: string | null; +} + +interface LoadingState { + type: States.Loading; +} + +type State = ShowingState | LoadingState; + +function toB64(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = e => { + const contents = e.target.result as ArrayBuffer; + const b64 = btoa(String.fromCharCode.apply(null, new Uint8Array(contents))); + resolve(b64); + }; + reader.onerror = e => { + reject(e); + }; + reader.onabort = e => { + reject(e); + }; + reader.readAsArrayBuffer(file); + }); +} + +export class FileInputEditor extends Component { + + public state: State = { + type: States.Showing, + error: null, + }; + + private fileInput: React.RefObject = React.createRef(); + + public render(): ReactElement { + switch (this.state.type) { + case States.Showing: + return this.renderShowingState(); + case States.Loading: + return this.renderLoadingState(); + } + } + + private renderShowingState(): ReactElement { + return ( +
+ this.onFileInputChanged(e)} + required /> +
+ ); + } + + private renderLoadingState(): ReactElement { + return ( +
+ this.onFileInputChanged(e)} + required /> + Loading... +
+ ); + } + + private onFileInputChanged(e: React.FormEvent): void { + const file: File = this.fileInput.current.files[0]; + const filename = file.name; + + this.setState({ + type: States.Loading, + }, () => { + toB64(file) + .then(b64Str => { + const upd = InputEditorUpdate.value({ + filename: file.name, + data: b64Str, + }); + this.props.onJobInputUpdate(upd); + this.setState({ + type: States.Showing, + error: null, + }); + }) + .catch(err => { + this.setState({ + type: States.Showing, + error: err, + }); + }) + }); + } +} diff --git a/jobson-ui/src/ts/components/inputeditors/InputEditor.tsx b/jobson-ui/src/ts/components/inputeditors/InputEditor.tsx index cf69f71..9144ff9 100644 --- a/jobson-ui/src/ts/components/inputeditors/InputEditor.tsx +++ b/jobson-ui/src/ts/components/inputeditors/InputEditor.tsx @@ -26,6 +26,7 @@ import {UnknownInputTypeInputEditor} from "./UnknownInputTypeInputEditor"; import {IntegerInputEditor} from "./IntegerInputEditor"; import {Constants} from "../../Constants"; import {DecimalInputEditor} from "./DecimalInputEditor"; +import {FileInputEditor} from "./FileInputEditor"; import {APIExpectedInput} from "../../apitypes/APIExpectedInput"; import {InputEditorUpdate} from "./updates/InputEditorUpdate"; import {Component, ReactElement} from "react"; @@ -75,15 +76,19 @@ export class InputEditor extends Component { max: Constants.F64_MAX, typeName: "double" }, props), + "file": props => new FileInputEditor(props), }; + private static unknownInputCtor = props => new UnknownInputTypeInputEditor(props); + public static getSupportedInputEditors(): string[] { return Object.keys(this.expectedInputUiComponentCtors); } public render(): ReactElement { - const unknownCtor = (props: InputEditorProps) => new UnknownInputTypeInputEditor(props); - const inputEditor = InputEditor.expectedInputUiComponentCtors[this.props.expectedInput.type] || unknownCtor; + const inputEditor = + InputEditor.expectedInputUiComponentCtors[this.props.expectedInput.type] || + InputEditor.unknownInputCtor; const expectedInput = this.props.expectedInput; const editorProps = { diff --git a/jobson/pom.xml b/jobson/pom.xml index a4f10af..897d38b 100644 --- a/jobson/pom.xml +++ b/jobson/pom.xml @@ -7,11 +7,11 @@ com.github.jobson jobson-project - 1.0.8 + 1.0.9 jobson - 1.0.8 + 1.0.9 jar diff --git a/jobson/src/main/java/com/github/jobson/jobinputs/JobExpectedInput.java b/jobson/src/main/java/com/github/jobson/jobinputs/JobExpectedInput.java index ee5231e..4980ecf 100644 --- a/jobson/src/main/java/com/github/jobson/jobinputs/JobExpectedInput.java +++ b/jobson/src/main/java/com/github/jobson/jobinputs/JobExpectedInput.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.github.jobson.jobinputs.f32.F32ExpectedInput; import com.github.jobson.jobinputs.f64.F64ExpectedInput; +import com.github.jobson.jobinputs.file.FileExpectedInput; import com.github.jobson.jobinputs.i32.I32ExpectedInput; import com.github.jobson.jobinputs.i64.I64ExpectedInput; import com.github.jobson.jobinputs.select.SelectExpectedInput; @@ -49,6 +50,7 @@ @JsonSubTypes.Type(value = F64ExpectedInput.class, name = "double"), @JsonSubTypes.Type(value = I32ExpectedInput.class, name = "int"), @JsonSubTypes.Type(value = I64ExpectedInput.class, name = "long"), + @JsonSubTypes.Type(value = FileExpectedInput.class, name = "file"), }) public abstract class JobExpectedInput { diff --git a/jobson/src/main/java/com/github/jobson/jobinputs/file/FileExpectedInput.java b/jobson/src/main/java/com/github/jobson/jobinputs/file/FileExpectedInput.java new file mode 100644 index 0000000..86d7202 --- /dev/null +++ b/jobson/src/main/java/com/github/jobson/jobinputs/file/FileExpectedInput.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.github.jobson.jobinputs.file; + +import com.github.jobson.jobinputs.JobExpectedInput; +import com.github.jobson.utils.ValidationError; + +import java.util.List; +import java.util.Optional; + +public final class FileExpectedInput extends JobExpectedInput { + + @Override + public Class getExpectedInputClass() { + return FileInput.class; + } + + @Override + public Optional> validate(FileInput input) { + return Optional.empty(); + } + + @Override + public FileInput generateExampleInput() { + // A file containing "Hello, world!" as a b64-encoded string + return new FileInput("hello-world.txt", "SGVsbG8sIHdvcmxkIQo="); + } +} diff --git a/jobson/src/main/java/com/github/jobson/jobinputs/file/FileInput.java b/jobson/src/main/java/com/github/jobson/jobinputs/file/FileInput.java new file mode 100644 index 0000000..3ee7082 --- /dev/null +++ b/jobson/src/main/java/com/github/jobson/jobinputs/file/FileInput.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.github.jobson.jobinputs.file; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; +import com.github.jobson.jobinputs.JobInput; + +import javax.validation.constraints.NotNull; +import java.util.Arrays; +import java.util.Base64; + +public final class FileInput implements JobInput { + + @NotNull + @JsonProperty + private final String filename; + + @NotNull + @JsonProperty + private final byte[] data; + + @JsonCreator + public FileInput(@JsonProperty(value = "filename") String filename, + @JsonProperty(value = "data", required = true) String b64data) { + this.filename = filename != null ? filename : "unnamed"; + this.data = Base64.getDecoder().decode(b64data); + } + + public FileInput(String filename, + byte[] b64data) { + this.filename = filename != null ? filename : "unnamed"; + this.data = b64data; + } + + public byte[] getData() { + return this.data; + } + + public String getFilename() { + return this.filename; + } +} diff --git a/jobson/src/main/java/com/github/jobson/scripting/TemplateStringEvaluator.java b/jobson/src/main/java/com/github/jobson/scripting/TemplateStringEvaluator.java index 31b72ae..7601385 100644 --- a/jobson/src/main/java/com/github/jobson/scripting/TemplateStringEvaluator.java +++ b/jobson/src/main/java/com/github/jobson/scripting/TemplateStringEvaluator.java @@ -19,6 +19,7 @@ package com.github.jobson.scripting; +import com.github.jobson.jobinputs.file.FileInput; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; @@ -46,7 +47,26 @@ public static String evaluate(String templateString, Map environ final TemplateStringEvaluatorVisitor visitor = new TemplateStringEvaluatorVisitor(environment); - ret.append(parser.expression().accept(visitor).toString()); + final Object evaluationOutput = parser.expression().accept(visitor); + + // The evaluation of a template string element (e.g. ${someExpr}) can either yield + // + // - A "normal" java object (e.g. String, number). Could be the result of a function (e.g. `toString`) + // or literal (e.g. `"str"`). Should be left untouched and dumped into the output + // + // - A `JobInput`. Probably the result of a lookup operation (e.g. `${inputs.fileInput}`). Should be + // coerced to its final output value. + // + // For `JobInput`s specifically, the coercions into a string are as follows: + // + // numeric (f32, f64, i32, i64) -> string (e.g. `1.34 -> "1.34") + // string-like (select, sql, string, stringarray) -> string (e.g. ["a", "b"] -> "a,b") + // file -> path to a temporary file + final String coercedValue = evaluationOutput instanceof FileInput ? + ((FreeFunction)environment.get("toFile")).call(evaluationOutput).toString() : + evaluationOutput.toString(); + + ret.append(coercedValue); } else { ret.append(str); } diff --git a/jobson/src/main/java/com/github/jobson/scripting/TemplateStringEvaluatorVisitor.java b/jobson/src/main/java/com/github/jobson/scripting/TemplateStringEvaluatorVisitor.java index 22594f9..1a2fab5 100644 --- a/jobson/src/main/java/com/github/jobson/scripting/TemplateStringEvaluatorVisitor.java +++ b/jobson/src/main/java/com/github/jobson/scripting/TemplateStringEvaluatorVisitor.java @@ -122,7 +122,19 @@ private List evaluateFunctionArgs(JsLikeExpressionParser.FunctionArgsCon @Override public Object visitIdentifierExpression(JsLikeExpressionParser.IdentifierExpressionContext ctx) { - return environment.get(ctx.getText()); + final String k = ctx.getText(); + final Object maybeElement = environment.get(k); + + if (maybeElement != null) { + return maybeElement; + } + + final String errMsg = String.format( + "%s: not found in template string environment (available: %s)", + k, + String.join(", ", environment.keySet())); + + throw new RuntimeException(errMsg); } @Override diff --git a/jobson/src/main/java/com/github/jobson/scripting/functions/ToFileFunction.java b/jobson/src/main/java/com/github/jobson/scripting/functions/ToFileFunction.java index b547a3d..9c9c51a 100644 --- a/jobson/src/main/java/com/github/jobson/scripting/functions/ToFileFunction.java +++ b/jobson/src/main/java/com/github/jobson/scripting/functions/ToFileFunction.java @@ -19,11 +19,13 @@ package com.github.jobson.scripting.functions; +import com.github.jobson.jobinputs.file.FileInput; import com.github.jobson.scripting.FreeFunction; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import static java.lang.String.format; @@ -41,19 +43,55 @@ public ToFileFunction(Path workingDir) { public Object call(Object... args) { if (args.length != 1) { throw new RuntimeException(format("toFile called with %s args (expects 1)", args.length)); - } else if (!(args[0] instanceof String)) { - throw new RuntimeException(format( - "asFile called with %s, should be called with a string (try using toJSON?)", - args[0].getClass().getSimpleName())); - } else { - try { - final String fileContent = (String)args[0]; - final Path path = Files.createTempFile(workingDir, "request", ""); - Files.write(path, fileContent.getBytes()); - return path.toAbsolutePath().toString(); - } catch (IOException ex) { - throw new RuntimeException("Could not create an input file.", ex); + } + + if (args[0] instanceof String) { + return this.call((String)args[0]); + } + + if (args[0] instanceof FileInput) { + return this.call((FileInput)args[0]); + } + + throw new RuntimeException(format( + "asFile called with %s, should be called with a string (try using toJSON?)", + args[0].getClass().getSimpleName())); + } + + private Object call(String fileContent) { + try { + final Path path = Files.createTempFile(workingDir, "request", ""); + Files.write(path, fileContent.getBytes()); + return path.toAbsolutePath().toString(); + } catch (IOException ex) { + throw new RuntimeException("Could not create an input file.", ex); + } + } + + private Object call(FileInput fi) { + try { + Path p = workingDir.resolve(fi.getFilename()); + if (Files.exists(p)) { + // The "ideal" path (e.g. unmodified in the working dir) already exists. This *could* be because + // the client provided the same-named file multiple times through the client (perfectly allowed, + // because there might be several expectedInputs) + // + // The client *may* be reliant on prefixes/suffixes in the provided filename to do something useful + // (e.g. searching for file starting with DATA_, or ending with .gz). We still want the file to be in + // the working dir (because clients might treat that in a special way w.r.t. cleaning up afterwards etc.) + // so our only option is to create a randomly-named directory and put the file in there. This way, its + // name is unchanged (good) *and* it's in the working directory (also good) + // + // The randomly-generated dirname should try and include the desired filename, so that it's easier for + // a developer to guess what's in the dir. + final Path tmpdir = Files.createTempDirectory(workingDir, fi.getFilename()); + p = tmpdir.resolve(fi.getFilename()); } + + Files.write(p, fi.getData()); + return p.toAbsolutePath().toString(); + } catch (IOException ex) { + throw new RuntimeException("Could not create an input file.", ex); } } } diff --git a/jobson/src/test/java/com/github/jobson/ResolvedPersistedJobRequestTest.java b/jobson/src/test/java/com/github/jobson/ResolvedPersistedJobRequestTest.java index f37ceec..80edbb2 100644 --- a/jobson/src/test/java/com/github/jobson/ResolvedPersistedJobRequestTest.java +++ b/jobson/src/test/java/com/github/jobson/ResolvedPersistedJobRequestTest.java @@ -56,7 +56,6 @@ private static void testSpecReqPair( final Either> ret = ValidJobRequest.tryCreate(validJobSchema, userId, jobRequestWithInvalidSpecifiedOption); - ret.handleBoth(req, validationErrors); } @@ -177,6 +176,15 @@ public void testValidateProducesAValidationErrorWhenSpecContainsIntButALongValue validationErrors -> assertThat(validationErrors.size()).isGreaterThan(0)); } + @Test + public void testValidateProducesNoErrorsWhenSpecContainsFile() { + testSpecReqPair( + "fixtures/specs/16_job-spec-with-file.json", + "fixtures/specs/16_req-with-file.json", + req -> {}, + validationErrors -> assertThat(validationErrors.size()).isEqualTo(0)); + } + @Test public void testValidateReturnsValidationErrorsIfTheRequestContainsTheWrongInputTypesForTheSchema() { final JobSpecId jobSpecId = TestHelpers.generateJobSpecId(); diff --git a/jobson/src/test/java/com/github/jobson/jobinputs/file/FileInputSchemaTest.java b/jobson/src/test/java/com/github/jobson/jobinputs/file/FileInputSchemaTest.java new file mode 100644 index 0000000..555271a --- /dev/null +++ b/jobson/src/test/java/com/github/jobson/jobinputs/file/FileInputSchemaTest.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.github.jobson.jobinputs.file; + +import com.github.jobson.TestHelpers; +import org.junit.Test; + +public final class FileInputSchemaTest { + @Test + public void testCanDeserializeFromJSON() { + // Shouldn't throw + TestHelpers.readJSONFixture( + "fixtures/jobinputs/select/FileInput/correct-schema.json", + FileExpectedInput.class); + } + + @Test + public void testCanDeserializeFromJSONWithDefault() { + // Shouldn't throw + TestHelpers.readJSONFixture( + "fixtures/jobinputs/select/FileInput/correct-schema-with-default.json", + FileExpectedInput.class); + } +} diff --git a/jobson/src/test/java/com/github/jobson/jobinputs/file/FileInputTest.java b/jobson/src/test/java/com/github/jobson/jobinputs/file/FileInputTest.java new file mode 100644 index 0000000..86cdbd1 --- /dev/null +++ b/jobson/src/test/java/com/github/jobson/jobinputs/file/FileInputTest.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.github.jobson.jobinputs.file; + +import com.github.jobson.TestHelpers; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public final class FileInputTest { + + @Test + public void testCanDeserializeFromJSON() { + TestHelpers.readJSONFixture( + "fixtures/jobinputs/select/FileInput/example-input.json", + FileInput.class); + } + + @Test(expected = Exception.class) + public void testThrowsWhenContainsInvalidB64() { + TestHelpers.readJSONFixture( + "fixtures/jobinputs/select/FileInput/invalid-input.json", + FileInput.class); + } + + @Test + public void testDefaultsNameWhenNameIsMissing() { + final FileInput fi = TestHelpers.readJSONFixture( + "fixtures/jobinputs/select/FileInput/valid-but-missing-name.json", + FileInput.class); + + assertThat(fi.getFilename()).isEqualTo("unnamed"); + } +} diff --git a/jobson/src/test/java/com/github/jobson/scripting/TemplateStringEvaluatorTest.java b/jobson/src/test/java/com/github/jobson/scripting/TemplateStringEvaluatorTest.java index 95d6323..a8826c5 100644 --- a/jobson/src/test/java/com/github/jobson/scripting/TemplateStringEvaluatorTest.java +++ b/jobson/src/test/java/com/github/jobson/scripting/TemplateStringEvaluatorTest.java @@ -20,13 +20,16 @@ package com.github.jobson.scripting; import com.github.jobson.TestHelpers; +import com.github.jobson.jobinputs.file.FileInput; import com.github.jobson.scripting.testclasses.ExampleObject; import com.github.jobson.scripting.testclasses.JsonStringifier; import org.junit.Test; +import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import static com.github.jobson.Helpers.generateRandomBase36String; import static com.github.jobson.Helpers.toJSON; @@ -264,4 +267,38 @@ public void testMemberIndexExpressionEvaluatesMapsByGettingAMapByKeyWithSingleQu assertThat(ret).isEqualTo(mapValue); } + + @Test + public void testPassingFileInputCallsToFileOnInput() throws IOException { + final AtomicBoolean called = new AtomicBoolean(false); + final FreeFunction f = new FreeFunction() { + @Override + public Object call(Object... args) { + called.set(true); + return "some-path"; + } + }; + final FileInput fileInput = new FileInput("fname", "SGVsbG8sIHdvcmxkIQo="); + final Map map = new HashMap<>(); + map.put("toFile", f); + map.put("someFile", fileInput); + + final String ret = evaluate("${someFile}", map); + + assertThat(ret).isEqualTo("some-path"); + assertThat(called.get()).isTrue(); + } + + @Test + public void testThrowsUsefulExceptionWhenIdentifierMissing() { + Exception exceptionThrown = null; + try { + evaluate("${missingIdentifier}", new HashMap<>()); + } catch (Exception ex) { + exceptionThrown = ex; + } + + assertThat(exceptionThrown).isNotNull(); + assertThat(exceptionThrown.getMessage()).contains("missingIdentifier"); + } } \ No newline at end of file diff --git a/jobson/src/test/java/com/github/jobson/scripting/functions/ToFileTest.java b/jobson/src/test/java/com/github/jobson/scripting/functions/ToFileTest.java new file mode 100644 index 0000000..66cada8 --- /dev/null +++ b/jobson/src/test/java/com/github/jobson/scripting/functions/ToFileTest.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.github.jobson.scripting.functions; + +import com.github.jobson.TestHelpers; +import com.github.jobson.jobinputs.file.FileInput; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.assertj.core.api.Assertions.assertThat; + +public final class ToFileTest { + @Test + public void testWhenCalledWithStringPutsStringContentIntoTempFileInDirAndReturnsPathToFile() throws IOException { + final Path tmpdir = Files.createTempDirectory("toFileTest"); + final ToFileFunction f = new ToFileFunction(tmpdir); + final String content = TestHelpers.generateAlphanumStr(1024); + + final Object rv = f.call(content); + + assertThat(rv instanceof String).isTrue(); + + final Path p = Paths.get((String)rv); + + assertThat(Files.exists(p)).isTrue(); + assertThat(Files.readAllBytes(p)).isEqualTo(content.getBytes()); + assertThat(p.isAbsolute()).isTrue(); + assertThat(p.startsWith(tmpdir)).isTrue(); + } + + @Test + public void testWhenCalledWithFileInputPutsDataIntoTempFileAndReturnsPathToFile() throws IOException { + final Path tmpdir = Files.createTempDirectory("toFileTest"); + final ToFileFunction f = new ToFileFunction(tmpdir); + final byte[] data = TestHelpers.generateRandomBytes(); + final FileInput fi = new FileInput("not-handled-by-this-test", data); + + final Object rv = f.call(fi); + + assertThat(rv instanceof String).isTrue(); + + final Path p = Paths.get((String)rv); + + assertThat(Files.exists(p)).isTrue(); + assertThat(Files.readAllBytes(p)).isEqualTo(data); + assertThat(p.isAbsolute()).isTrue(); + assertThat(p.startsWith(tmpdir)).isTrue(); + } +} diff --git a/jobson/src/test/resources/fixtures/jobinputs/select/FileInput/correct-schema-with-default.json b/jobson/src/test/resources/fixtures/jobinputs/select/FileInput/correct-schema-with-default.json new file mode 100644 index 0000000..6c4da76 --- /dev/null +++ b/jobson/src/test/resources/fixtures/jobinputs/select/FileInput/correct-schema-with-default.json @@ -0,0 +1,10 @@ +{ + "id": "foo", + "name": "Example File Schema", + "description": "Example Description", + "type": "file", + "default": { + "filename": "hello-world.txt", + "data": "SGVsbG8sIHdvcmxkIQo=" + } +} \ No newline at end of file diff --git a/jobson/src/test/resources/fixtures/jobinputs/select/FileInput/correct-schema.json b/jobson/src/test/resources/fixtures/jobinputs/select/FileInput/correct-schema.json new file mode 100644 index 0000000..fd8759d --- /dev/null +++ b/jobson/src/test/resources/fixtures/jobinputs/select/FileInput/correct-schema.json @@ -0,0 +1,6 @@ +{ + "id": "foo", + "name": "Example File Schema", + "description": "Example Description", + "type": "file" +} \ No newline at end of file diff --git a/jobson/src/test/resources/fixtures/jobinputs/select/FileInput/example-input.json b/jobson/src/test/resources/fixtures/jobinputs/select/FileInput/example-input.json new file mode 100644 index 0000000..e9ff680 --- /dev/null +++ b/jobson/src/test/resources/fixtures/jobinputs/select/FileInput/example-input.json @@ -0,0 +1,4 @@ +{ + "filename": "hello-world.txt", + "data": "SGVsbG8sIHdvcmxkIQo=" +} \ No newline at end of file diff --git a/jobson/src/test/resources/fixtures/jobinputs/select/FileInput/invalid-input.json b/jobson/src/test/resources/fixtures/jobinputs/select/FileInput/invalid-input.json new file mode 100644 index 0000000..a813a71 --- /dev/null +++ b/jobson/src/test/resources/fixtures/jobinputs/select/FileInput/invalid-input.json @@ -0,0 +1,4 @@ +{ + "filename": "hello-world.txt", + "data": ";is-not-b64-chars{}" +} \ No newline at end of file diff --git a/jobson/src/test/resources/fixtures/jobinputs/select/FileInput/valid-but-missing-name.json b/jobson/src/test/resources/fixtures/jobinputs/select/FileInput/valid-but-missing-name.json new file mode 100644 index 0000000..6265f94 --- /dev/null +++ b/jobson/src/test/resources/fixtures/jobinputs/select/FileInput/valid-but-missing-name.json @@ -0,0 +1,3 @@ +{ + "data": "SGVsbG8sIHdvcmxkIQo=" +} \ No newline at end of file diff --git a/jobson/src/test/resources/fixtures/specs/16_job-spec-with-file.json b/jobson/src/test/resources/fixtures/specs/16_job-spec-with-file.json new file mode 100644 index 0000000..052cc2f --- /dev/null +++ b/jobson/src/test/resources/fixtures/specs/16_job-spec-with-file.json @@ -0,0 +1,20 @@ +{ + "id": "job-schema-8", + "name": "Job Schema 8", + "description": "Job Schema 8: file input", + "expectedInputs": [ + { + "id": "file-input", + "name": "File input", + "description": "File input", + "type": "file" + } + ], + "execution": { + "application": "bash", + "arguments": [ + "arg1", + "arg2" + ] + } +} \ No newline at end of file diff --git a/jobson/src/test/resources/fixtures/specs/16_req-with-file.json b/jobson/src/test/resources/fixtures/specs/16_req-with-file.json new file mode 100644 index 0000000..cf46bd0 --- /dev/null +++ b/jobson/src/test/resources/fixtures/specs/16_req-with-file.json @@ -0,0 +1,9 @@ +{ + "spec": "", + "inputs": { + "file-input": { + "filename": "hw.txt", + "data": "SGVsbG8sIHdvcmxkIQo=" + } + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index b1eab38..6aebd80 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.github.jobson jobson-project - 1.0.8 + 1.0.9 pom jobson project @@ -48,8 +48,8 @@ - 1.0.8 - 1.0.8 + 1.0.9 + 1.0.9 1.3.16 UTF-8