@codama/validators
Version:
Validator visitors for the Codama framework
260 lines (257 loc) • 9.14 kB
JavaScript
// src/getValidationItemsVisitor.ts
import { camelCase, getAllInstructionArguments, isNode } from "@codama/nodes";
import {
extendVisitor,
getResolvedInstructionInputsVisitor,
LinkableDictionary,
mergeVisitor,
NodeStack,
pipe,
recordLinkablesOnFirstVisitVisitor,
recordNodeStackVisitor,
visit
} from "@codama/visitors-core";
// src/ValidationItem.ts
var LOG_LEVELS = ["debug", "trace", "info", "warn", "error"];
function validationItem(level, message, node, path) {
return {
level,
message,
node,
path: Array.isArray(path) ? path : path.getPath()
};
}
var getLevelIndex = (level) => LOG_LEVELS.indexOf(level);
// src/getValidationItemsVisitor.ts
function getValidationItemsVisitor() {
const linkables = new LinkableDictionary();
const stack = new NodeStack();
return pipe(
mergeVisitor(
() => [],
(_, items) => items.flat()
),
(v) => recordLinkablesOnFirstVisitVisitor(v, linkables),
(v) => recordNodeStackVisitor(v, stack),
(v) => extendVisitor(v, {
visitAccount(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Account has no name.", node, stack));
}
return [...items, ...next(node)];
},
visitDefinedType(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Defined type has no name.", node, stack));
}
return [...items, ...next(node)];
},
visitDefinedTypeLink(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Pointing to a defined type with no name.", node, stack));
} else if (!linkables.has(stack.getPath(node.kind))) {
items.push(
validationItem(
"error",
`Pointing to a missing defined type named "${node.name}"`,
node,
stack
)
);
}
return [...items, ...next(node)];
},
visitEnumEmptyVariantType(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Enum variant has no name.", node, stack));
}
return [...items, ...next(node)];
},
visitEnumStructVariantType(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Enum variant has no name.", node, stack));
}
return [...items, ...next(node)];
},
visitEnumTupleVariantType(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Enum variant has no name.", node, stack));
}
return [...items, ...next(node)];
},
visitEnumType(node, { next }) {
const items = [];
if (node.variants.length === 0) {
items.push(validationItem("warn", "Enum has no variants.", node, stack));
}
node.variants.forEach((variant) => {
if (!variant.name) {
items.push(validationItem("error", "Enum variant has no name.", node, stack));
}
});
return [...items, ...next(node)];
},
visitError(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Error has no name.", node, stack));
}
if (typeof node.code !== "number") {
items.push(validationItem("error", "Error has no code.", node, stack));
}
if (!node.message) {
items.push(validationItem("warn", "Error has no message.", node, stack));
}
return [...items, ...next(node)];
},
visitInstruction(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Instruction has no name.", node, stack));
}
const accountNameHistogram = /* @__PURE__ */ new Map();
node.accounts.forEach((account) => {
if (!account.name) {
items.push(validationItem("error", "Instruction account has no name.", node, stack));
return;
}
const count = (accountNameHistogram.get(account.name) ?? 0) + 1;
accountNameHistogram.set(account.name, count);
if (count === 2) {
items.push(
validationItem(
"error",
`Account name "${account.name}" is not unique in instruction "${node.name}".`,
node,
stack
)
);
}
});
const cyclicCheckVisitor = getResolvedInstructionInputsVisitor();
try {
visit(node, cyclicCheckVisitor);
} catch (error) {
items.push(validationItem("error", error.message, node, stack));
}
const names = getAllInstructionArguments(node).map(({ name }) => camelCase(name));
const duplicates = names.filter((e, i, a) => a.indexOf(e) !== i);
const uniqueDuplicates = [...new Set(duplicates)];
const hasConflictingNames = uniqueDuplicates.length > 0;
if (hasConflictingNames) {
items.push(
validationItem(
"error",
`The names of the following instruction arguments are conflicting: [${uniqueDuplicates.join(", ")}].`,
node,
stack
)
);
}
getAllInstructionArguments(node).forEach((argument) => {
const { defaultValue } = argument;
if (isNode(defaultValue, "accountBumpValueNode")) {
const defaultAccount = node.accounts.find((account) => account.name === defaultValue.name);
if (defaultAccount && defaultAccount.isSigner !== false) {
items.push(
validationItem(
"error",
`Argument ${argument.name} cannot default to the bump attribute of the [${defaultValue.name}] account as it may be a Signer.`,
node,
stack
)
);
}
}
});
return [...items, ...next(node)];
},
visitProgram(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Program has no name.", node, stack));
}
if (!node.publicKey) {
items.push(validationItem("error", "Program has no public key.", node, stack));
}
if (!node.version) {
items.push(validationItem("warn", "Program has no version.", node, stack));
}
if (!node.origin) {
items.push(validationItem("info", "Program has no origin.", node, stack));
}
return [...items, ...next(node)];
},
visitStructFieldType(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Struct field has no name.", node, stack));
}
return [...items, ...next(node)];
},
visitStructType(node, { next }) {
const items = [];
const fieldNameHistogram = /* @__PURE__ */ new Map();
node.fields.forEach((field) => {
if (!field.name) return;
const count = (fieldNameHistogram.get(field.name) ?? 0) + 1;
fieldNameHistogram.set(field.name, count);
if (count === 2) {
items.push(
validationItem(
"error",
`Struct field name "${field.name}" is not unique.`,
field,
stack
)
);
}
});
return [...items, ...next(node)];
},
visitTupleType(node, { next }) {
const items = [];
if (node.items.length === 0) {
items.push(validationItem("warn", "Tuple has no items.", node, stack));
}
return [...items, ...next(node)];
}
})
);
}
// src/throwValidatorItemsVisitor.ts
import { CODAMA_ERROR__VISITORS__FAILED_TO_VALIDATE_NODE, CodamaError } from "@codama/errors";
import { mapVisitor } from "@codama/visitors-core";
function throwValidatorItemsVisitor(visitor, throwLevel = "error") {
return mapVisitor(visitor, (validationItems) => {
const levelHistogram = [...validationItems].sort((a, b) => getLevelIndex(b.level) - getLevelIndex(a.level)).reduce(
(acc, item) => {
acc[item.level] = (acc[item.level] ?? 0) + 1;
return acc;
},
{}
);
const maxLevel = Object.keys(levelHistogram).map((level) => getLevelIndex(level)).sort((a, b) => b - a)[0];
if (maxLevel >= getLevelIndex(throwLevel)) {
const formattedHistogram = Object.keys(levelHistogram).map((level) => `${level}s: ${levelHistogram[level]}`).join(", ");
throw new CodamaError(CODAMA_ERROR__VISITORS__FAILED_TO_VALIDATE_NODE, {
formattedHistogram,
validationItems
});
}
});
}
export {
LOG_LEVELS,
getLevelIndex,
getValidationItemsVisitor,
throwValidatorItemsVisitor,
validationItem
};
//# sourceMappingURL=index.react-native.mjs.map