Add validation to api

This commit is contained in:
Akim Mamedov
2023-11-16 22:58:59 +07:00
parent 20edd89456
commit 2641379167
2 changed files with 103 additions and 28 deletions

View File

@ -169,14 +169,16 @@ export const v5_registerService = (
); );
return; return;
} else if (args.length === 2) { }
if (args.length === 2) {
if (args[0] instanceof FluencePeer) { if (args[0] instanceof FluencePeer) {
v5_registerService([args[0], getDefaultServiceId(def), args[1]], def); v5_registerService([args[0], getDefaultServiceId(def), args[1]], def);
return; return;
} else {
v5_registerService([getDefaultPeer(), args[0], args[1]], def);
return;
} }
v5_registerService([getDefaultPeer(), args[0], args[1]], def);
return;
} }
const [peer, serviceId, serviceImpl] = args; const [peer, serviceId, serviceImpl] = args;

View File

@ -20,6 +20,7 @@ import {
JSONValue, JSONValue,
LabeledProductType, LabeledProductType,
NonArrowSimpleType, NonArrowSimpleType,
ScalarType,
SimpleTypes, SimpleTypes,
} from "@fluencelabs/interfaces"; } from "@fluencelabs/interfaces";
@ -27,6 +28,71 @@ import { ParticleContext } from "../jsServiceHost/interfaces.js";
import { ServiceImpl } from "./types.js"; import { ServiceImpl } from "./types.js";
class SchemaValidationError extends Error {
constructor(
public path: string[],
schema: NonArrowSimpleType,
expected: string,
provided: JSONValue,
) {
const given =
provided === null
? "null"
: Array.isArray(provided)
? "array"
: typeof provided;
const message = `Type mismatch. Path: ${path.join(
".",
)}; Expected: ${expected}; Given: ${given};\n\nschema: ${JSON.stringify(
schema,
)}`;
super(message);
}
}
interface ValidationContext {
path: string[];
}
const numberTypes = [
"u8",
"u16",
"u32",
"u64",
"i8",
"i16",
"i32",
"i64",
"f32",
"f64",
] as const;
function isScalar(
schema: ScalarType,
arg: JSONValue,
{ path }: ValidationContext,
) {
if (numberTypes.includes(schema.name)) {
if (typeof arg !== "number") {
throw new SchemaValidationError(path, schema, "number", arg);
}
} else if (schema.name === "bool") {
if (typeof arg !== "boolean") {
throw new SchemaValidationError(path, schema, "boolean", arg);
}
} else if (schema.name === "string") {
if (typeof arg !== "string") {
throw new SchemaValidationError(path, schema, "string", arg);
}
} else {
throw new SchemaValidationError(path, schema, "never", arg);
}
return arg;
}
export function aqua2ts( export function aqua2ts(
value: JSONValue, value: JSONValue,
schema: NonArrowSimpleType, schema: NonArrowSimpleType,
@ -82,45 +148,52 @@ export function aqua2ts(
export function ts2aqua( export function ts2aqua(
value: JSONValue, value: JSONValue,
schema: NonArrowSimpleType, schema: NonArrowSimpleType,
{ path }: ValidationContext,
): JSONValue { ): JSONValue {
if (schema.tag === "nil") { if (schema.tag === "nil") {
return null; if (value !== null) {
} else if (schema.tag === "option") { throw new SchemaValidationError(path, schema, "null", value);
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return value == null ? [] : ([ts2aqua(value, schema.type)] as [JSONValue]);
} else if (
schema.tag === "scalar" ||
schema.tag === "bottomType" ||
schema.tag === "topType"
) {
return value;
} else if (schema.tag === "array") {
if (!Array.isArray(value)) {
throw new Error("Value and schema doesn't match");
} }
return value.map((y) => { return value;
return ts2aqua(y, schema.type); } else if (schema.tag === "option") {
}); // option means 'type | null'
} else if (schema.tag === "unlabeledProduct") { return value == null ? [] : [ts2aqua(value, schema.type, { path })];
} else if (schema.tag === "topType") {
// topType equals to 'any'
return value;
} else if (schema.tag === "bottomType") {
// bottomType equals to 'never'
throw new SchemaValidationError(path, schema, "never", value);
} else if (schema.tag === "scalar") {
return isScalar(schema, value, { path });
} else if (schema.tag === "array") {
if (!Array.isArray(value)) { if (!Array.isArray(value)) {
throw new Error("Value and schema doesn't match"); throw new SchemaValidationError(path, schema, "array", value);
} }
return value.map((y, i) => { return value.map((y, i) => {
return ts2aqua(y, schema.items[i]); return ts2aqua(y, schema.type, { path: [...path, `[${i}]`] });
});
} else if (schema.tag === "unlabeledProduct") {
if (!Array.isArray(value)) {
throw new SchemaValidationError(path, schema, "array", value);
}
return value.map((y, i) => {
return ts2aqua(y, schema.items[i], { path: [...path, `[${i}]`] });
}); });
} else if (["labeledProduct", "struct"].includes(schema.tag)) { } else if (["labeledProduct", "struct"].includes(schema.tag)) {
if (typeof value !== "object" || value == null || Array.isArray(value)) { if (typeof value !== "object" || value === null || Array.isArray(value)) {
throw new Error("Value and schema doesn't match"); throw new SchemaValidationError(path, schema, "object", value);
} }
return Object.entries(schema.fields).reduce((agg, [key, type]) => { return Object.entries(schema.fields).reduce((agg, [key, type]) => {
const val = ts2aqua(value[key], type); const val = ts2aqua(value[key], type, { path: [...path, key] });
return { ...agg, [key]: val }; return { ...agg, [key]: val };
}, {}); }, {});
} else { } else {
throw new Error("Unexpected tag: " + JSON.stringify(schema)); throw new SchemaValidationError(path, schema, "never", value);
} }
} }
@ -153,6 +226,6 @@ export const wrapFunction = (
? schema.codomain.items[0] ? schema.codomain.items[0]
: schema.codomain; : schema.codomain;
return ts2aqua(result, resultSchema); return ts2aqua(result, resultSchema, { path: [] });
}; };
}; };