Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/ant-util-tests/functions/objectLibFunction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* @fileoverview Object lib function for testing purposes.
*/

const { Observable } = require('rxjs');

module.exports = () => {
return Observable.create(
subscriber => {
subscriber.next({ foo: 'bar' });
subscriber.complete();
return () => {};
}
);
};
4 changes: 2 additions & 2 deletions packages/ant/lib/config/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ provider "${providerName}"`
}
return Object.keys(functions).map(name => {
const func = functions[name];
const { bin, handler, runtime } = func;
const { bin, handler, runtime, args } = func;
try {
if (bin) {
return new BinFunction(runtimeController.ant, name, bin);
Expand All @@ -828,7 +828,7 @@ provider "${providerName}"`
} else {
runtimeInstance = runtimeController.defaultRuntime;
}
return new LibFunction(runtimeController.ant, name, handler, runtimeInstance);
return new LibFunction(runtimeController.ant, name, handler, runtimeInstance, args);
}
throw new AntError(`Function type unknown: ${JSON.stringify(func)}`);
} catch (e) {
Expand Down
28 changes: 25 additions & 3 deletions packages/ant/lib/functions/LibFunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class LibFunction extends AntFunction {
* @throws {AssertionError} If "ant", "name", "handler" or "runtime" params
* are not valid.
*/
constructor(ant, name, handler, runtime) {
constructor(ant, name, handler, runtime, args) {
super(ant, name);

assert(
Expand All @@ -51,6 +51,13 @@ class LibFunction extends AntFunction {
* @private
*/
this._runtime = runtime;

/**
* Contains the fixed arguments that will be used when running the function.
* @type {Array<String>}
* @private
*/
this._args = args || [];
}

/**
Expand All @@ -71,6 +78,15 @@ class LibFunction extends AntFunction {
return this._runtime;
}

/**
* Contains the function fixed execution argumnets.
* @type {Array<String>}
* @readonly
*/
get args() {
return this._args;
}

/**
* Runs the function. It can receive different arguments depending on the
* function instance.
Expand All @@ -80,16 +96,22 @@ class LibFunction extends AntFunction {
run() {
logger.log(`Running lib function ${this.name}...`);

const args = JSON.stringify(
this._args.concat(Array.from(arguments))
);
try {
return this._runtime.run([
this._handler,
JSON.stringify(Array.from(arguments))
args
]).pipe(map(data => {
// JSON fails to parse 'undefined', but not '"undefined"'
try {
return JSON.parse(data);
} catch (e) {
return undefined;
if (typeof data === 'string' && data.trim() === 'undefined') {
return undefined;
}
return data;
}
}));
} catch (e) {
Expand Down
4 changes: 3 additions & 1 deletion packages/ant/spec/lib/config/Config.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,8 @@ Template category value is not an object!'
},
Foo: {
handler: '/foo/bar',
runtime: 'python'
runtime: 'python',
args: ['--foo', '--bar']
}
};
const runtimeController = {
Expand All @@ -1158,6 +1159,7 @@ Template category value is not an object!'
expect(func).toBeInstanceOf(LibFunction);
expect(func.name).toBe('Foo');
expect(func.handler).toBe(functions.Foo.handler);
expect(func.args).toBe(functions.Foo.args);

expect(runtimeController.getRuntime.mock.calls.length).toBe(2);
expect(runtimeController.getRuntime.mock.calls[0][0]).toBe(functions.MyLib.runtime);
Expand Down
23 changes: 22 additions & 1 deletion packages/ant/spec/lib/functions/LibFunction.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

const path = require('path');
const { Observable } = require('rxjs');
const { Observable, of } = require('rxjs');
const { toArray } = require('rxjs/operators');
const Ant = require('../../../lib/Ant');
const LibFunction = require('../../../lib/functions/LibFunction');
Expand Down Expand Up @@ -96,5 +96,26 @@ describe('lib/functions/LibFunction.js', () => {
)).run();
}).toThrowError('Could not run lib function fooLibFunction');
});

test('should run and handle multiple types result', async () => {
// Mocks the runtime.run function
const mockRuntime = new Runtime(ant, 'mockRuntime', 'bin', [], undefined, '1');
const values = [
1,
'a',
true,
{ foo: {
bar: false
}},
['lorem', 'ipsum'],
undefined,
null
];
mockRuntime.run = jest.fn(() => of(...values));
const typeslibAntFunction = new LibFunction(ant, 'foo', 'bar', mockRuntime);
const runReturn = typeslibAntFunction.run();
expect(await runReturn.pipe(toArray()).toPromise())
.toEqual(values);
});
});
});
63 changes: 63 additions & 0 deletions plugins/ant-core/functions/javaRuntime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env node

/**
* @fileoverview Java runtime for Ant Framework.
*/
const { spawn, spawnSync } = require('child_process');
const { parse } = require('path');
const { argv, stdout, stderr } = process;

/**
* Flushes the stdout and stderr and exits the process.
*
* @param {Number} code The status code
*/
const exit = code => {
// On "close", waits stdout and stderr to flush
// and then exits. If it is already done,
// the cb is called anyway
stdout.end(() => {
stderr.end(() => {
process.exit(code);
});
});
};

let javaFile = argv[2];
const { dirname, name: fileName, ext } = parse(javaFile);

let args = [];
const options = {};
if (ext === '.jar') {
args.push('-cp');
} else if (ext === '.java') {
const { error, status, stderr: javacStderr, stdout: javacStdout } = spawnSync('javac', [javaFile]);
if (error) {
stdout.write(javacStdout);
stderr.write(javacStderr);
exit(status);
}
options.cwd = dirname;
javaFile = fileName;
}
args.push(javaFile);

// Filters null or undefined arguments and non serializable items
args = args.concat(JSON.parse(argv[3]))
.filter(arg => {
if (arg === undefined || arg === null) {
return false;
}
try {
JSON.stringify(arg);
return true;
} catch (err) {
return false;
}
})
.map(arg => typeof arg === 'string' ? arg : JSON.stringify(arg));

const javaProgram = spawn('java', args, options);
javaProgram.stdout.on('data', data => stdout.write(data.toString()));
javaProgram.stderr.on('data', data => stderr.write(data.toString()));
javaProgram.on('close', exit);
8 changes: 8 additions & 0 deletions plugins/ant-core/lib/Core.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ class Core extends Plugin {
['js'],
path.resolve(__dirname, '../templates/function/node.js.mustache'),
'10'
),
new Runtime(
this._ant,
'Java',
path.resolve(__dirname, '../functions/javaRuntime.js'),
['jar', 'java'],
path.resolve(__dirname, '../templates/function/java.java.mustache'),
'8'
)
];
}
Expand Down
19 changes: 17 additions & 2 deletions plugins/ant-core/spec/lib/Core.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1836,9 +1836,9 @@ describe('lib/Core.js', () => {
];
antCli._ant.runtimeController._runtimes = new Map();
antCli._ant.runtimeController.loadRuntimes(runtimes);
antCli._yargs.parse('runtime ls');

process.exit = jest.fn(code => {
expect(code).toEqual(1);
expect(code).toBe(0);
expect(console.log.mock.calls.length).toBe(5);
expect(console.log.mock.calls[0][0]).toBe('Listing all runtimes available \
([default] <name> <version> <bin> [extensions] [template]):');
Expand All @@ -1848,6 +1848,21 @@ describe('lib/Core.js', () => {
expect(console.log.mock.calls[4][0]).toBe('lorem 2 /ipsum');
done();
});
antCli._yargs.parse('runtime ls');
});

test('should not show "runtime ls" friendly error when error is unknown', () => {
const handleErrorMessage = jest.spyOn(yargsHelper, 'handleErrorMessage');
const error = new Error('Mocked error');
const antCli = new AntCli();
antCli._ant.pluginController.getPlugin('Core').listRuntimes = jest.fn(async () => {
throw error;
});
process.exit = jest.fn(code => {
expect(code).toBe(1);
expect(handleErrorMessage).toHaveBeenCalledWith(error.message, error, 'runtime ls');
});
antCli._yargs.parse('runtime ls');
});
});
});
Expand Down
69 changes: 69 additions & 0 deletions plugins/ant-core/templates/function/java.java.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import java.lang.StringBuffer;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* An example class which invokes a RESTful API and returns its content
* to the Ant Framework GraphQL API.
*/
public class {{name}} {
/**
* The args[0] represents the field parameters from the GraphQL query.
*/
public static void main(String[] args) throws IOException {
/**
* The parameters are stored in a stringified JSON object.
* So, in order to make use of them, we need to parse it.
*/
String cityName = parseJson(args[0]);
try {
// Opening connection to the RESTful API
String url = "http://api.openweathermap.org/data/2.5/weather?q="
+ URLEncoder.encode(cityName, "UTF-8")
+ "&appid=464ddf23d2c714ee8ecbc5b39f1f7eae";
HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
con.setRequestMethod("GET");

// Retrieving the response content
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();

while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();

// Prints the response to the stdout, which Ant is watching.
System.out.print(response.toString());
} catch (Exception e) {
// If any exception occurs, we should also let Ant be aware of it.
System.out.print(e.toString());
}
}

/**
* Since this file is only for testing purposes and we aren't importing any
* external libs, we parse the JSON using a regex.
*
* In this example, we are expecting the "city" parameter. So
* all we need to do here is retrieve it from the JSON and return
* its value, which is a String.
*/
public static String parseJson(jsonAsString) {
String cityJsonRegex = "(?:\\\"city\\\":\\\")(.*?)(?:\\\")";
Pattern pattern = Pattern.compile(cityJsonRegex);
Matcher matcher = pattern.matcher(jsonAsString);
if (matcher.find()) {
String cityName = matcher.group().split(":")[1].replaceAll("\\\"", "");
return cityName;
}
return null;
}
}
5 changes: 3 additions & 2 deletions plugins/ant-graphql/lib/GraphQL.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const Map = require('yaml/map').default;
const Pair = require('yaml/pair').default;
const Scalar = require('yaml/scalar').default;

const { stdout, stderr } = process;
const defaultServerPath = path.dirname(
require.resolve('@back4app/ant-graphql-express')
);
Expand Down Expand Up @@ -340,7 +341,7 @@ directory "${cwd}"`
this._serverProcess.stdout.on('data', (data) => {
data = data.toString();

console.log(`Server => ${data}`);
stdout.write(`Server => ${data}`);

const successMessage = 'GraphQL API server listening for requests on ';

Expand All @@ -360,7 +361,7 @@ directory "${cwd}"`
});

this._serverProcess.stderr.on('data', (data) => {
console.error(`Server => ${data}`);
stderr.write(`Server => ${data}`);
});

const promise = new Promise((resolve, reject) => {
Expand Down
Loading