Skip to content

Commit

Permalink
Merge pull request #55 from Cardano-Forge/feat/validator-asset-name
Browse files Browse the repository at this point in the history
Duplicate Asset Name
  • Loading branch information
JFKFred authored Sep 11, 2024
2 parents 0e6fdbc + f477e83 commit 7a922d4
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 12 deletions.
2 changes: 2 additions & 0 deletions client/src/server/validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ export async function validateMetadata(
mainValidator.Execute(asset.assetName, asset.metadata, metadata);
}

mainValidator.ExecuteOnce(metadata);

const result = mainValidator.GetResults();

console.timeEnd(`timeToValidate`);
Expand Down
17 changes: 15 additions & 2 deletions core/cli/__tests__/cetardio.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"assetName": "CETARDIOwhitelist4",
"metadata": {
"head": "CETARDIO whitelist",
"name": "CETARDIO WL",
"name": "CETARDIO WL 00",
"image": "ipfs://QmNZb1qc7dY8m1zRgwfqTEztUxdF9eL9h666tbK95cAwP3",
"Discord": "https://discord.gg/MyProject",
"Twitter": "https://twitter.com/MyProject",
Expand All @@ -16,7 +16,20 @@
"assetName": "CETARDIOwhitelist5",
"metadata": {
"head": "CETARDIO whitelist",
"name": "CETARDIO WL",
"name": "CETARDIO WL 01",
"image": "ipfs://QmNZb1qc7dY8m1zRgwfqTEztUxdF9eL9h666tbK95cAwP3",
"Discord": "https://discord.gg/MyProject",
"Twitter": "https://twitter.com/MyProject",
"Website": "https://myproject.com",
"mediaType": "image/png",
"description": "My cool NFT project description"
}
},
{
"assetName": "CETARDIOwhitelist4",
"metadata": {
"head": "CETARDIO whitelist",
"name": "CETARDIO WL 02",
"image": "ipfs://QmNZb1qc7dY8m1zRgwfqTEztUxdF9eL9h666tbK95cAwP3",
"Discord": "https://discord.gg/MyProject",
"Twitter": "https://twitter.com/MyProject",
Expand Down
1 change: 1 addition & 0 deletions core/cli/__tests__/template.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ duplicateKeysValidator
duplicateImage
duplicateName
duplicateNameAndImage
duplicateAssetName

#
# mandatory
Expand Down
5 changes: 4 additions & 1 deletion core/cli/src/load-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import fs from "node:fs";
// Load templates from a text file.
export function loadTemplates(filepath: string): string[] {
const rules: string[] = [];
const data: string[] = fs.readFileSync(filepath, "utf8").trim().split("\n");
const data: string[] = fs
.readFileSync(filepath, "utf8")
.trim()
.split(/\r?\n/);

for (const line of data) {
if (line.startsWith("#") || !line.trim()) continue;
Expand Down
12 changes: 6 additions & 6 deletions core/cli/src/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { DataRead, Metadata } from "./types.ts";
export async function validate(
metadataPath: string,
templatePath: string,
outputPath: string,
outputPath: string
) {
console.log(`Validating metadata at: ${metadataPath}`);
console.log(`Using template at: ${templatePath}`);
Expand All @@ -27,15 +27,15 @@ export async function validate(
const main = new Validator("Main");
for (const validator of rules) {
main.Enable(
new mapping[validator.split(DIVIDER)[0] as keyof typeof mapping](
extractOptions(validator),
),
new mapping[validator.split(DIVIDER)[0].trim() as keyof typeof mapping](
extractOptions(validator)
)
);
}

// 3. Load metadata (CSV or JSON)
const reader = ReaderFactory.createReader(
metadataPath.substring(metadataPath.lastIndexOf(".") + 1).toLowerCase(),
metadataPath.substring(metadataPath.lastIndexOf(".") + 1).toLowerCase()
);
reader.Load(metadataPath);
const metadatas: DataRead[] | null = reader.Read();
Expand All @@ -50,7 +50,7 @@ export async function validate(
main.Execute(
(metadata as Metadata).assetName, // extract asset_name
(metadata as Metadata).metadata, // extract payload
metadatas,
metadatas
);
}
console.timeEnd("timeAll");
Expand Down
77 changes: 77 additions & 0 deletions core/validator/__tests__/test.duplicate-asset-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { assertEquals } from "@std/assert";

import { Validator } from "../src/core.ts";

import { DuplicateName } from "../src/rules/duplicate-name.ts";
import { DuplicateAssetName } from "../src/rules/duplicate-asset-name.ts";

Deno.test("DuplicateAssetName - withError", () => {
const metadata = [
{
assetName: "asset_0000",
metadata: {
name: "asset_0000",
image: "adibou.png",
},
},
{
assetName: "asset_0001",
metadata: {
name: "asset_0001",
image: "roller-coaster-tycoon.png",
},
},
{
assetName: "asset_0000",
metadata: {
name: "asset_0002",
image: "adibou.png",
},
},
{
assetName: "asset_0003",
metadata: {
name: "asset_0003",
image: "roller-coaster-tycoon.png",
},
},
{
assetName: "asset_0000",
metadata: {
name: "asset_0004",
image: "roller-coaster-tycoon.png",
},
},
];

const mainValidator = new Validator("Main");
mainValidator.Enable(new DuplicateAssetName());

mainValidator.ExecuteOnce(metadata);

const result = mainValidator.GetResults();

assertEquals(result, {
asset_0000: {
status: "error",
warnings: [],
errors: [
{
validatorId: "duplicate-asset-name",
message:
"AssetName: asset_0000 has been detected as a duplicate. (metadata.name = asset_0000)",
},
{
validatorId: "duplicate-asset-name",
message:
"AssetName: asset_0000 has been detected as a duplicate. (metadata.name = asset_0002)",
},
{
validatorId: "duplicate-asset-name",
message:
"AssetName: asset_0000 has been detected as a duplicate. (metadata.name = asset_0004)",
},
],
},
});
});
2 changes: 1 addition & 1 deletion core/validator/__tests__/test.duplicate-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Validator } from "../src/core.ts";

import { DuplicateName } from "../src/rules/duplicate-name.ts";

Deno.test("DuplicateName - withWarning", () => {
Deno.test("DuplicateName - withError", () => {
const metadata = [
{
assetName: "asset_0000",
Expand Down
2 changes: 2 additions & 0 deletions core/validator/src/mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { DuplicateImage } from "./rules/duplicate-image.ts";
import { DuplicateName } from "./rules/duplicate-name.ts";
import { DuplicateNameAndImage } from "./rules/duplicate-name-and-image.ts";
import { KeyAnvilCasing } from "./rules/key-anvil-casing.ts";
import { DuplicateAssetName } from "./rules/duplicate-asset-name.ts";
export { KeyWhiteSpace } from "./rules/key-white-space.ts";
export { DuplicateImage } from "./rules/duplicate-image.ts";
export { DuplicateName } from "./rules/duplicate-name.ts";
Expand Down Expand Up @@ -57,4 +58,5 @@ export const mapping = {
duplicateName: DuplicateName,
duplicateNameAndImage: DuplicateNameAndImage,
keyAnvilCasing: KeyAnvilCasing,
duplicateAssetName: DuplicateAssetName,
};
1 change: 1 addition & 0 deletions core/validator/src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ export { KeyWhiteSpace } from "./rules/key-white-space.ts";
export { DuplicateImage } from "./rules/duplicate-image.ts";
export { DuplicateName } from "./rules/duplicate-name.ts";
export { DuplicateNameAndImage } from "./rules/duplicate-name-and-image.ts";
export { DuplicateAssetName } from "./rules/duplicate-asset-name.ts";

export { mapping } from "./mapping.ts";
85 changes: 85 additions & 0 deletions core/validator/src/rules/duplicate-asset-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { BaseValidator } from "../core.ts";

import type { Metadata, StateOutput } from "../utils/types.ts";
import { logger } from "../utils/logger.ts";

/**
* A validator that checks if there are any duplicate asset names in the provided metadatas.
*
* This validator counts the occurrences of each asset name and identifies duplicates based on the count. It assumes that the asset name is the top-level key in each metadata object.
*
* @class DuplicateAssetName
* @module Rules
* @extends BaseValidator
*/
export class DuplicateAssetName extends BaseValidator {
/**
* Constructs a new instance of the `DuplicateAssetName` validator with an optional configuration object.
*
* @param {object} [options] - The options for the validator (not used in this validator).
*/
constructor(options?: object) {
const id = "duplicate-asset-name";
super(id, options, "once");
}

/**
* Executes the validation logic for a given asset and metadatas.
*
* @param {object[]} metadatas - An array of all metadatas being validated.
* @param {Record<string, StateOutput>} validations - An object of all validations made.
* @returns {Record<string, StateOutput>} An array containing the validation results.
*/
ExecuteOnce(
metadatas: object[],
validations: Record<string, StateOutput>
): Record<string, StateOutput> {
logger(`Executing ${this.id} with: `, metadatas.length);
return this.Logic(metadatas as Metadata[], validations);
}

/**
* Logic method to check for duplicate asset names.
*
* @param {Metadata[]} metadatas - An array of all metadatas being validated.
* @param {Record<string, StateOutput>} validations - An object of all validations made.
* @returns {Record<string, StateOutput>} - Returns an array containing validation results.
*/
Logic(
metadatas: Metadata[],
validations: Record<string, StateOutput>
): Record<string, StateOutput> {
const errorsMetadata = new Set<Metadata>();

for (const entry of metadatas) {
const founds = metadatas.filter(
(meta) => meta.assetName === entry.assetName
);

if (founds.length > 1) {
founds.forEach((meta) => {
errorsMetadata.add(meta);
});
}
}

// ERRORS
errorsMetadata.forEach(({ assetName, metadata }) => {
if (!validations[assetName]) {
validations[assetName] = {
status: "error",
warnings: [],
errors: [],
};
}

validations[assetName].status = "error";
validations[assetName].errors.push({
validatorId: this.id,
message: `AssetName: ${assetName} has been detected as a duplicate. (metadata.name = ${metadata.name})`,
});
});

return validations;
}
}
4 changes: 2 additions & 2 deletions core/validator/src/rules/duplicate-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import type { Metadata, StateOutput } from "../utils/types.ts";
import { logger } from "../utils/logger.ts";

/**
* A validator that checks if there are any duplicate asset names in the provided metadatas.
* A validator that checks if there are any duplicate names in the provided metadatas.
*
* This validator counts the occurrences of each asset name and identifies duplicates based on the count. It assumes that the asset name is the top-level key in each metadata object.
* This validator counts the occurrences of each name and identifies duplicates based on the count. It assumes that the asset name is the top-level key in each metadata object.
*
* @class DuplicateName
* @module Rules
Expand Down

0 comments on commit 7a922d4

Please sign in to comment.