mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2025-06-14 00:21:20 +00:00
feat(js-client)!: Segregation of responsibility between js-client packages [fixes DXJ-525] (#378)
Schema validation in js-client
This commit is contained in:
@ -14,74 +14,144 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import assert from "assert";
|
||||
import { Buffer } from "buffer";
|
||||
|
||||
import { JSONValue } from "@fluencelabs/interfaces";
|
||||
import bs58 from "bs58";
|
||||
import { sha256 } from "multiformats/hashes/sha2";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
CallServiceData,
|
||||
CallServiceResult,
|
||||
CallServiceResultType,
|
||||
GenericCallServiceHandler,
|
||||
ResultCodes,
|
||||
} from "../jsServiceHost/interfaces.js";
|
||||
import { getErrorMessage, isString, jsonify } from "../util/utils.js";
|
||||
import { getErrorMessage, jsonify } from "../util/utils.js";
|
||||
|
||||
const success = (
|
||||
// TODO: Remove unknown after adding validation to builtin inputs
|
||||
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
||||
result: CallServiceResultType | unknown,
|
||||
): CallServiceResult => {
|
||||
const success = (result: CallServiceResultType): CallServiceResult => {
|
||||
return {
|
||||
// TODO: Remove type assertion after adding validation to builtin inputs
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
result: result as CallServiceResultType,
|
||||
result,
|
||||
retCode: ResultCodes.success,
|
||||
};
|
||||
};
|
||||
|
||||
const error = (
|
||||
// TODO: Remove unknown after adding validation to builtin inputs
|
||||
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
||||
error: CallServiceResultType | unknown,
|
||||
): CallServiceResult => {
|
||||
const error = (error: CallServiceResultType): CallServiceResult => {
|
||||
return {
|
||||
// TODO: Remove type assertion after adding validation to builtin inputs
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
result: error as CallServiceResultType,
|
||||
result: error,
|
||||
retCode: ResultCodes.error,
|
||||
};
|
||||
};
|
||||
|
||||
const chunk = <T>(arr: T[]): T[][] => {
|
||||
const res: T[][] = [];
|
||||
const chunkSize = 2;
|
||||
|
||||
for (let i = 0; i < arr.length; i += chunkSize) {
|
||||
const chunk = arr.slice(i, i + chunkSize);
|
||||
res.push(chunk);
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
const errorNotImpl = (methodName: string) => {
|
||||
return error(
|
||||
`The JS implementation of Peer does not support "${methodName}"`,
|
||||
);
|
||||
};
|
||||
|
||||
const makeJsonImpl = (args: [Record<string, JSONValue>, ...JSONValue[]]) => {
|
||||
const [obj, ...kvs] = args;
|
||||
const parseWithSchema = <T extends z.ZodTypeAny>(
|
||||
schema: T,
|
||||
req: CallServiceData,
|
||||
): [z.infer<T>, null] | [null, string] => {
|
||||
const result = schema.safeParse(req.args, {
|
||||
errorMap: (issue, ctx) => {
|
||||
if (
|
||||
issue.code === z.ZodIssueCode.invalid_type &&
|
||||
issue.path.length === 1 &&
|
||||
typeof issue.path[0] === "number"
|
||||
) {
|
||||
const [arg] = issue.path;
|
||||
return {
|
||||
message: `Argument ${arg} expected to be of type ${issue.expected}, Got ${issue.received}`,
|
||||
};
|
||||
}
|
||||
|
||||
const toMerge: Record<string, JSONValue> = {};
|
||||
if (issue.code === z.ZodIssueCode.too_big) {
|
||||
return {
|
||||
message: `Expected ${
|
||||
issue.maximum
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
} argument(s). Got ${ctx.data.length}`,
|
||||
};
|
||||
}
|
||||
|
||||
for (let i = 0; i < kvs.length / 2; i++) {
|
||||
const k = kvs[i * 2];
|
||||
if (issue.code === z.ZodIssueCode.too_small) {
|
||||
return {
|
||||
message: `Expected ${
|
||||
issue.minimum
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
} argument(s). Got ${ctx.data.length}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!isString(k)) {
|
||||
return error(`Argument ${i * 2 + 1} is expected to be string`);
|
||||
}
|
||||
if (issue.code === z.ZodIssueCode.invalid_union) {
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
message: `Expected argument(s). Got ${ctx.data.length}`,
|
||||
};
|
||||
}
|
||||
|
||||
const v = kvs[i * 2 + 1];
|
||||
toMerge[k] = v;
|
||||
return { message: ctx.defaultError };
|
||||
},
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
return [result.data, null];
|
||||
} else {
|
||||
return [null, result.error.errors[0].message];
|
||||
}
|
||||
|
||||
const res = { ...obj, ...toMerge };
|
||||
return success(res);
|
||||
};
|
||||
|
||||
// TODO: These assert made for silencing more stricter ts rules. Will be fixed in DXJ-493
|
||||
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
||||
type Literal = z.infer<typeof literalSchema>;
|
||||
type Json = Literal | { [key: string]: Json } | Json[];
|
||||
|
||||
const jsonSchema: z.ZodType<Json> = z.lazy(() => {
|
||||
return z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]);
|
||||
});
|
||||
|
||||
const jsonImplSchema = z
|
||||
.tuple([z.record(jsonSchema)])
|
||||
.rest(z.tuple([z.string(), jsonSchema]));
|
||||
|
||||
const makeJsonImpl = (args: z.infer<typeof jsonImplSchema>) => {
|
||||
const [obj, ...kvs] = args;
|
||||
return success({ ...obj, ...Object.fromEntries(kvs) });
|
||||
};
|
||||
|
||||
type withSchema = <T extends z.ZodTypeAny>(
|
||||
arg: T,
|
||||
) => (
|
||||
arg1: (value: z.infer<T>) => CallServiceResult | Promise<CallServiceResult>,
|
||||
) => (req: CallServiceData) => CallServiceResult | Promise<CallServiceResult>;
|
||||
|
||||
const withSchema: withSchema = <T extends z.ZodTypeAny>(schema: T) => {
|
||||
return (bound) => {
|
||||
return (req) => {
|
||||
const [value, message] = parseWithSchema(schema, req);
|
||||
|
||||
if (message != null) {
|
||||
return error(message);
|
||||
}
|
||||
|
||||
return bound(value);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export const builtInServices: Record<
|
||||
string,
|
||||
Record<string, GenericCallServiceHandler>
|
||||
@ -116,29 +186,16 @@ export const builtInServices: Record<
|
||||
return errorNotImpl("peer.get_contact");
|
||||
},
|
||||
|
||||
timeout: (req) => {
|
||||
if (req.args.length !== 2) {
|
||||
return error(
|
||||
"timeout accepts exactly two arguments: timeout duration in ms and a message string",
|
||||
);
|
||||
}
|
||||
|
||||
const durationMs = req.args[0];
|
||||
const message = req.args[1];
|
||||
|
||||
if (typeof durationMs !== "number" || typeof message !== "string") {
|
||||
return error(
|
||||
"timeout accepts exactly two arguments: timeout duration in ms and a message string",
|
||||
);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const res = success(message);
|
||||
resolve(res);
|
||||
}, durationMs);
|
||||
});
|
||||
},
|
||||
timeout: withSchema(z.tuple([z.number(), z.string()]))(
|
||||
([durationMs, msg]) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const res = success(msg);
|
||||
resolve(res);
|
||||
}, durationMs);
|
||||
});
|
||||
},
|
||||
),
|
||||
},
|
||||
|
||||
kad: {
|
||||
@ -246,120 +303,48 @@ export const builtInServices: Record<
|
||||
return success(req.args);
|
||||
},
|
||||
|
||||
array_length: (req) => {
|
||||
if (req.args.length !== 1) {
|
||||
return error(
|
||||
"array_length accepts exactly one argument, found: " +
|
||||
req.args.length,
|
||||
);
|
||||
} else {
|
||||
assert(Array.isArray(req.args[0]));
|
||||
return success(req.args[0].length);
|
||||
}
|
||||
},
|
||||
array_length: withSchema(z.tuple([z.array(z.unknown())]))(([arr]) => {
|
||||
return success(arr.length);
|
||||
}),
|
||||
|
||||
identity: (req) => {
|
||||
if (req.args.length > 1) {
|
||||
return error(
|
||||
`identity accepts up to 1 arguments, received ${req.args.length} arguments`,
|
||||
);
|
||||
} else {
|
||||
return success(req.args.length === 0 ? {} : req.args[0]);
|
||||
}
|
||||
},
|
||||
identity: withSchema(z.array(jsonSchema).max(1))((args) => {
|
||||
return success(args.length === 0 ? {} : args[0]);
|
||||
}),
|
||||
|
||||
concat: (req) => {
|
||||
const incorrectArgIndices = req.args //
|
||||
.map((x, i): [boolean, number] => {
|
||||
return [Array.isArray(x), i];
|
||||
})
|
||||
.filter(([isArray]) => {
|
||||
return !isArray;
|
||||
})
|
||||
.map(([, index]) => {
|
||||
return index;
|
||||
});
|
||||
|
||||
if (incorrectArgIndices.length > 0) {
|
||||
const str = incorrectArgIndices.join(", ");
|
||||
return error(
|
||||
`All arguments of 'concat' must be arrays: arguments ${str} are not`,
|
||||
);
|
||||
} else {
|
||||
// TODO: remove after adding validation
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return success([].concat(...(req.args as never[][])));
|
||||
}
|
||||
},
|
||||
|
||||
string_to_b58: (req) => {
|
||||
if (req.args.length !== 1) {
|
||||
return error("string_to_b58 accepts only one string argument");
|
||||
} else {
|
||||
const [input] = req.args;
|
||||
// TODO: remove after adding validation
|
||||
assert(typeof input === "string");
|
||||
return success(bs58.encode(new TextEncoder().encode(input)));
|
||||
}
|
||||
},
|
||||
|
||||
string_from_b58: (req) => {
|
||||
if (req.args.length !== 1) {
|
||||
return error("string_from_b58 accepts only one string argument");
|
||||
} else {
|
||||
const [input] = req.args;
|
||||
// TODO: remove after adding validation
|
||||
assert(typeof input === "string");
|
||||
return success(new TextDecoder().decode(bs58.decode(input)));
|
||||
}
|
||||
},
|
||||
|
||||
bytes_to_b58: (req) => {
|
||||
if (req.args.length !== 1 || !Array.isArray(req.args[0])) {
|
||||
return error(
|
||||
"bytes_to_b58 accepts only single argument: array of numbers",
|
||||
);
|
||||
} else {
|
||||
// TODO: remove after adding validation
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const argumentArray = req.args[0] as number[];
|
||||
return success(bs58.encode(new Uint8Array(argumentArray)));
|
||||
}
|
||||
},
|
||||
|
||||
bytes_from_b58: (req) => {
|
||||
if (req.args.length !== 1) {
|
||||
return error("bytes_from_b58 accepts only one string argument");
|
||||
} else {
|
||||
const [input] = req.args;
|
||||
// TODO: remove after adding validation
|
||||
assert(typeof input === "string");
|
||||
return success(Array.from(bs58.decode(input)));
|
||||
}
|
||||
},
|
||||
|
||||
sha256_string: async (req) => {
|
||||
if (req.args.length !== 1) {
|
||||
return error(
|
||||
`sha256_string accepts 1 argument, found: ${req.args.length}`,
|
||||
);
|
||||
} else {
|
||||
const [input] = req.args;
|
||||
// TODO: remove after adding validation
|
||||
assert(typeof input === "string");
|
||||
const inBuffer = Buffer.from(input);
|
||||
const multihash = await sha256.digest(inBuffer);
|
||||
|
||||
return success(bs58.encode(multihash.bytes));
|
||||
}
|
||||
},
|
||||
|
||||
concat_strings: (req) => {
|
||||
// TODO: remove after adding validation
|
||||
concat: withSchema(z.array(z.array(z.unknown())))((args) => {
|
||||
// Schema is used with unknown type to prevent useless runtime check
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const res = "".concat(...(req.args as string[]));
|
||||
return success(res);
|
||||
},
|
||||
const arr = args as never[][];
|
||||
|
||||
return success(arr.flat());
|
||||
}),
|
||||
|
||||
string_to_b58: withSchema(z.tuple([z.string()]))(([input]) => {
|
||||
return success(bs58.encode(new TextEncoder().encode(input)));
|
||||
}),
|
||||
|
||||
string_from_b58: withSchema(z.tuple([z.string()]))(([input]) => {
|
||||
return success(new TextDecoder().decode(bs58.decode(input)));
|
||||
}),
|
||||
|
||||
bytes_to_b58: withSchema(z.tuple([z.array(z.number())]))(([input]) => {
|
||||
return success(bs58.encode(new Uint8Array(input)));
|
||||
}),
|
||||
|
||||
bytes_from_b58: withSchema(z.tuple([z.string()]))(([input]) => {
|
||||
return success(Array.from(bs58.decode(input)));
|
||||
}),
|
||||
|
||||
sha256_string: withSchema(z.tuple([z.string()]))(async ([input]) => {
|
||||
const inBuffer = Buffer.from(input);
|
||||
const multihash = await sha256.digest(inBuffer);
|
||||
|
||||
return success(bs58.encode(multihash.bytes));
|
||||
}),
|
||||
|
||||
concat_strings: withSchema(z.array(z.string()))((args) => {
|
||||
return success(args.join(""));
|
||||
}),
|
||||
},
|
||||
|
||||
debug: {
|
||||
@ -379,365 +364,187 @@ export const builtInServices: Record<
|
||||
},
|
||||
|
||||
math: {
|
||||
add: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [x, y] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(typeof x === "number" && typeof y === "number");
|
||||
add: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||
return success(x + y);
|
||||
},
|
||||
}),
|
||||
|
||||
sub: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [x, y] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(typeof x === "number" && typeof y === "number");
|
||||
sub: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||
return success(x - y);
|
||||
},
|
||||
}),
|
||||
|
||||
mul: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [x, y] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(typeof x === "number" && typeof y === "number");
|
||||
mul: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||
return success(x * y);
|
||||
},
|
||||
}),
|
||||
|
||||
fmul: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [x, y] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(typeof x === "number" && typeof y === "number");
|
||||
fmul: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||
return success(Math.floor(x * y));
|
||||
},
|
||||
}),
|
||||
|
||||
div: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [x, y] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(typeof x === "number" && typeof y === "number");
|
||||
div: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||
return success(Math.floor(x / y));
|
||||
},
|
||||
}),
|
||||
|
||||
rem: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [x, y] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(typeof x === "number" && typeof y === "number");
|
||||
rem: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||
return success(x % y);
|
||||
},
|
||||
}),
|
||||
|
||||
pow: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [x, y] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(typeof x === "number" && typeof y === "number");
|
||||
pow: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||
return success(Math.pow(x, y));
|
||||
},
|
||||
}),
|
||||
|
||||
log: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [x, y] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(typeof x === "number" && typeof y === "number");
|
||||
log: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||
return success(Math.log(y) / Math.log(x));
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
cmp: {
|
||||
gt: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [x, y] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(typeof x === "number" && typeof y === "number");
|
||||
gt: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||
return success(x > y);
|
||||
},
|
||||
}),
|
||||
|
||||
gte: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [x, y] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(typeof x === "number" && typeof y === "number");
|
||||
gte: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||
return success(x >= y);
|
||||
},
|
||||
}),
|
||||
|
||||
lt: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [x, y] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(typeof x === "number" && typeof y === "number");
|
||||
lt: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||
return success(x < y);
|
||||
},
|
||||
}),
|
||||
|
||||
lte: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [x, y] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(typeof x === "number" && typeof y === "number");
|
||||
lte: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||
return success(x <= y);
|
||||
},
|
||||
}),
|
||||
|
||||
cmp: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [x, y] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(typeof x === "number" && typeof y === "number");
|
||||
cmp: withSchema(z.tuple([z.number(), z.number()]))(([x, y]) => {
|
||||
return success(x === y ? 0 : x > y ? 1 : -1);
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
array: {
|
||||
sum: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 1)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// TODO: Remove after adding validation
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const [xs] = req.args as [number[]];
|
||||
sum: withSchema(z.tuple([z.array(z.number())]))(([xs]) => {
|
||||
return success(
|
||||
xs.reduce((agg, cur) => {
|
||||
return agg + cur;
|
||||
}, 0),
|
||||
);
|
||||
},
|
||||
}),
|
||||
|
||||
dedup: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 1)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [xs] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(Array.isArray(xs));
|
||||
dedup: withSchema(z.tuple([z.array(z.any())]))(([xs]) => {
|
||||
const set = new Set(xs);
|
||||
return success(Array.from(set));
|
||||
},
|
||||
}),
|
||||
|
||||
intersect: (req) => {
|
||||
let err;
|
||||
intersect: withSchema(z.tuple([z.array(z.any()), z.array(z.any())]))(
|
||||
([xs, ys]) => {
|
||||
const intersection = xs.filter((x) => {
|
||||
return ys.includes(x);
|
||||
});
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
return success(intersection);
|
||||
},
|
||||
),
|
||||
|
||||
const [xs, ys] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(Array.isArray(xs) && Array.isArray(ys));
|
||||
diff: withSchema(z.tuple([z.array(z.any()), z.array(z.any())]))(
|
||||
([xs, ys]) => {
|
||||
const diff = xs.filter((x) => {
|
||||
return !ys.includes(x);
|
||||
});
|
||||
|
||||
const intersection = xs.filter((x) => {
|
||||
return ys.includes(x);
|
||||
});
|
||||
return success(diff);
|
||||
},
|
||||
),
|
||||
|
||||
return success(intersection);
|
||||
},
|
||||
sdiff: withSchema(z.tuple([z.array(z.any()), z.array(z.any())]))(
|
||||
([xs, ys]) => {
|
||||
const sdiff = [
|
||||
xs.filter((y) => {
|
||||
return !ys.includes(y);
|
||||
}),
|
||||
ys.filter((x) => {
|
||||
return !xs.includes(x);
|
||||
}),
|
||||
].flat();
|
||||
|
||||
diff: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [xs, ys] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(Array.isArray(xs) && Array.isArray(ys));
|
||||
|
||||
const diff = xs.filter((x) => {
|
||||
return !ys.includes(x);
|
||||
});
|
||||
|
||||
return success(diff);
|
||||
},
|
||||
|
||||
sdiff: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 2)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [xs, ys] = req.args;
|
||||
// TODO: Remove after adding validation
|
||||
assert(Array.isArray(xs) && Array.isArray(ys));
|
||||
|
||||
const sdiff = [
|
||||
// force new line
|
||||
...xs.filter((y) => {
|
||||
return !ys.includes(y);
|
||||
}),
|
||||
...ys.filter((x) => {
|
||||
return !xs.includes(x);
|
||||
}),
|
||||
];
|
||||
|
||||
return success(sdiff);
|
||||
},
|
||||
return success(sdiff);
|
||||
},
|
||||
),
|
||||
},
|
||||
|
||||
json: {
|
||||
obj: (req) => {
|
||||
let err;
|
||||
obj: withSchema(
|
||||
z
|
||||
.array(z.unknown())
|
||||
.refine(
|
||||
(arr) => {
|
||||
return arr.length % 2 === 0;
|
||||
},
|
||||
(arr) => {
|
||||
return {
|
||||
message: "Expected even number of argument(s). Got " + arr.length,
|
||||
};
|
||||
},
|
||||
)
|
||||
.transform((args) => {
|
||||
return chunk(args);
|
||||
})
|
||||
.pipe(z.array(z.tuple([z.string(), jsonSchema]))),
|
||||
)((args) => {
|
||||
return makeJsonImpl([{}, ...args]);
|
||||
}),
|
||||
|
||||
if ((err = checkForArgumentsCountEven(req)) != null) {
|
||||
return err;
|
||||
}
|
||||
put: withSchema(
|
||||
z
|
||||
.tuple([z.record(jsonSchema), z.string(), jsonSchema])
|
||||
.transform(
|
||||
([obj, name, value]): [{ [key: string]: Json }, [string, Json]] => {
|
||||
return [obj, [name, value]];
|
||||
},
|
||||
),
|
||||
)(makeJsonImpl),
|
||||
|
||||
// TODO: remove after adding validation
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return makeJsonImpl([{}, ...req.args] as [
|
||||
Record<string, JSONValue>,
|
||||
...JSONValue[],
|
||||
]);
|
||||
},
|
||||
puts: withSchema(
|
||||
z
|
||||
.array(z.unknown())
|
||||
.refine(
|
||||
(arr) => {
|
||||
return arr.length >= 3;
|
||||
},
|
||||
(value) => {
|
||||
return {
|
||||
message: `Expected more than 3 argument(s). Got ${value.length}`,
|
||||
};
|
||||
},
|
||||
)
|
||||
.refine(
|
||||
(arr) => {
|
||||
return arr.length % 2 === 1;
|
||||
},
|
||||
{
|
||||
message: "Argument count must be odd.",
|
||||
},
|
||||
)
|
||||
.transform((args) => {
|
||||
return [args[0], ...chunk(args.slice(1))];
|
||||
})
|
||||
.pipe(jsonImplSchema),
|
||||
)(makeJsonImpl),
|
||||
|
||||
put: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 3)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((err = checkForArgumentType(req, 0, "object")) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return makeJsonImpl(
|
||||
// TODO: remove after adding validation
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
req.args as [Record<string, JSONValue>, ...JSONValue[]],
|
||||
);
|
||||
},
|
||||
|
||||
puts: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCountOdd(req)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((err = checkForArgumentsCountMoreThan(req, 3)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((err = checkForArgumentType(req, 0, "object")) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return makeJsonImpl(
|
||||
// TODO: remove after adding validation
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
req.args as [Record<string, JSONValue>, ...JSONValue[]],
|
||||
);
|
||||
},
|
||||
|
||||
stringify: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 1)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((err = checkForArgumentType(req, 0, "object")) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [json] = req.args;
|
||||
const res = JSON.stringify(json);
|
||||
return success(res);
|
||||
},
|
||||
|
||||
parse: (req) => {
|
||||
let err;
|
||||
|
||||
if ((err = checkForArgumentsCount(req, 1)) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((err = checkForArgumentType(req, 0, "string")) != null) {
|
||||
return err;
|
||||
}
|
||||
|
||||
const [raw] = req.args;
|
||||
stringify: withSchema(z.tuple([z.record(z.string(), jsonSchema)]))(
|
||||
([json]) => {
|
||||
const res = JSON.stringify(json);
|
||||
return success(res);
|
||||
},
|
||||
),
|
||||
|
||||
parse: withSchema(z.tuple([z.string()]))(([raw]) => {
|
||||
try {
|
||||
// TODO: Remove after adding validation
|
||||
assert(typeof raw === "string");
|
||||
const json = JSON.parse(raw);
|
||||
// Parsing any argument here yields JSONValue
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const json = JSON.parse(raw) as JSONValue;
|
||||
return success(json);
|
||||
} catch (err: unknown) {
|
||||
return error(getErrorMessage(err));
|
||||
}
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
"run-console": {
|
||||
@ -749,59 +556,3 @@ export const builtInServices: Record<
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const checkForArgumentsCount = (
|
||||
req: { args: Array<unknown> },
|
||||
count: number,
|
||||
) => {
|
||||
if (req.args.length !== count) {
|
||||
return error(`Expected ${count} argument(s). Got ${req.args.length}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const checkForArgumentsCountMoreThan = (
|
||||
req: { args: Array<unknown> },
|
||||
count: number,
|
||||
) => {
|
||||
if (req.args.length < count) {
|
||||
return error(
|
||||
`Expected more than ${count} argument(s). Got ${req.args.length}`,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const checkForArgumentsCountEven = (req: { args: Array<unknown> }) => {
|
||||
if (req.args.length % 2 === 1) {
|
||||
return error(`Expected even number of argument(s). Got ${req.args.length}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const checkForArgumentsCountOdd = (req: { args: Array<unknown> }) => {
|
||||
if (req.args.length % 2 === 0) {
|
||||
return error(`Expected odd number of argument(s). Got ${req.args.length}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const checkForArgumentType = (
|
||||
req: { args: Array<unknown> },
|
||||
index: number,
|
||||
type: string,
|
||||
) => {
|
||||
const actual = typeof req.args[index];
|
||||
|
||||
if (actual !== type) {
|
||||
return error(
|
||||
`Argument ${index} expected to be of type ${type}, Got ${actual}`,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
Reference in New Issue
Block a user