mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2025-06-25 05:41:35 +00:00
Add validation to api
This commit is contained in:
@ -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;
|
||||||
|
@ -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: [] });
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user