Skip to content

Commit

Permalink
Merge pull request #684 from IntersectMBO/feat/609-add-metadata-valid…
Browse files Browse the repository at this point in the history
…ation-against-cip-fields-to-metadata-validation-service

[#609] feat: add metadata validation against cip fields
  • Loading branch information
MSzalowski authored Apr 12, 2024
2 parents a0408f8 + 8e11410 commit e33ffbf
Show file tree
Hide file tree
Showing 20 changed files with 205 additions and 47 deletions.
17 changes: 17 additions & 0 deletions govtool/metadata-validation/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const { pathsToModuleNameMapper } = require('ts-jest');
const { compilerOptions } = require('./tsconfig');

module.exports = {
preset: 'ts-jest',
moduleFileExtensions: ['js', 'json', 'ts'],
testRegex: '.*\\.(spec|test)\\.ts$',
transform: {
'^.+\\.(t|j)s$': 'ts-jest',
},
collectCoverageFrom: ['**/*.(t|j)s'],
coverageDirectory: '../coverage',
testEnvironment: 'node',
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths),
modulePaths: ['<rootDir>'],
};
18 changes: 1 addition & 17 deletions govtool/metadata-validation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"blakejs": "^1.2.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"joi": "^17.12.3",
"jsonld": "^8.3.2",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
Expand All @@ -57,22 +58,5 @@
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
39 changes: 19 additions & 20 deletions govtool/metadata-validation/src/app.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Test, TestingModule } from '@nestjs/testing';
import { HttpModule } from '@nestjs/axios';

import { MetadataValidationStatus } from '@enums';

import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MetadataValidationStatus } from './enums/ValidationError';

// TODO: Mock HttpService
describe('AppController', () => {
Expand All @@ -24,28 +25,26 @@ describe('AppController', () => {
appController = app.get<AppController>(AppController);
});

describe('metadata validation', () => {
it('should throw invalid URL', async () => {
const result = await appController.validateMetadata({
hash: 'hash',
url: 'url',
});
expect(result).toEqual({
status: MetadataValidationStatus.URL_NOT_FOUND,
valid: false,
});
it('should throw invalid URL', async () => {
const result = await appController.validateMetadata({
hash: 'hash',
url: 'url',
});
expect(result).toEqual({
status: MetadataValidationStatus.URL_NOT_FOUND,
valid: false,
});
});

it('should throw invalid JSONLD', async () => {
const result = await appController.validateMetadata({
hash: 'hash',
url: 'http://www.schema.org',
});
it('should throw invalid JSONLD', async () => {
const result = await appController.validateMetadata({
hash: 'hash',
url: 'http://www.schema.org',
});

expect(result).toEqual({
status: MetadataValidationStatus.INVALID_JSONLD,
valid: false,
});
expect(result).toEqual({
status: MetadataValidationStatus.INVALID_JSONLD,
valid: false,
});
});
});
6 changes: 4 additions & 2 deletions govtool/metadata-validation/src/app.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Controller, Body, Post } from '@nestjs/common';

import { ValidateMetadataDTO } from '@dto';
import { ValidateMetadataResult } from '@types';

import { AppService } from './app.service';
import { ValidateMetadataDTO } from './dto/validateMetadata.dto';
import { ValidateMetadataResult } from './types/validateMetadata';

@Controller()
export class AppController {
Expand Down
13 changes: 9 additions & 4 deletions govtool/metadata-validation/src/app.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { catchError, firstValueFrom } from 'rxjs';
import { HttpService } from '@nestjs/axios';
import * as blake from 'blakejs';

import { ValidateMetadataDTO } from './dto/validateMetadata.dto';
import { MetadataValidationStatus } from './enums/ValidationError';
import { canonizeJSON } from './utils/canonizeJSON';
import { ValidateMetadataResult } from './types/validateMetadata';
import { ValidateMetadataDTO } from '@dto';
import { MetadataValidationStatus } from '@enums';
import { canonizeJSON, validateMetadataStandard } from '@utils';
import { ValidateMetadataResult } from '@types';

@Injectable()
export class AppService {
Expand All @@ -15,6 +15,7 @@ export class AppService {
async validateMetadata({
hash,
url,
standard,
}: ValidateMetadataDTO): Promise<ValidateMetadataResult> {
let status: MetadataValidationStatus;
try {
Expand All @@ -26,6 +27,10 @@ export class AppService {
),
);

if (standard) {
await validateMetadataStandard(data, standard);
}

let canonizedMetadata;
try {
canonizedMetadata = await canonizeJSON(data);
Expand Down
1 change: 1 addition & 0 deletions govtool/metadata-validation/src/dto/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './validateMetadata.dto';
8 changes: 7 additions & 1 deletion govtool/metadata-validation/src/dto/validateMetadata.dto.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { IsUrl, IsNotEmpty } from 'class-validator';
import { IsUrl, IsNotEmpty, IsEnum, IsOptional } from 'class-validator';

import { MetadataStandard } from '@types';

export class ValidateMetadataDTO {
@IsNotEmpty()
hash: string;

@IsUrl()
url: string;

@IsOptional()
@IsEnum(MetadataStandard)
standard?: MetadataStandard;
}
1 change: 1 addition & 0 deletions govtool/metadata-validation/src/enums/ValidationError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export enum MetadataValidationStatus {
URL_NOT_FOUND = 'URL_NOT_FOUND',
INVALID_JSONLD = 'INVALID_JSONLD',
INVALID_HASH = 'INVALID_HASH',
INCORRECT_FORMAT = 'INCORRECT_FORMAT',
}
1 change: 1 addition & 0 deletions govtool/metadata-validation/src/enums/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ValidationError';
1 change: 1 addition & 0 deletions govtool/metadata-validation/src/health/health.module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';

import { HealthController } from './health.controller';

@Module({
Expand Down
2 changes: 1 addition & 1 deletion govtool/metadata-validation/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

import { AppModule } from './app.module';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
Expand Down
45 changes: 45 additions & 0 deletions govtool/metadata-validation/src/schemas/cipStandardSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as Joi from 'joi';

import { MetadataStandard } from '@types';

type StandardSpecification = Record<MetadataStandard, Joi.ObjectSchema<any>>;

const CIP100_URL =
'https://github.com/cardano-foundation/CIPs/blob/master/CIP-0100/README.md#';
const CIP108_URL =
'https://github.com/cardano-foundation/CIPs/blob/master/CIP-0108/README.md#';

export const cipStandardSchema: StandardSpecification = {
// Source of CIP-108: https://github.com/Ryun1/CIPs/blob/governance-metadata-actions/CIP-0108/README.md
[MetadataStandard.CIP108]: Joi.object({
'@context': Joi.object({
'@language': Joi.string().required(),
CIP100: Joi.string().valid(CIP100_URL).required(),
CIP108: Joi.string().valid(CIP108_URL).required(),
hashAlgorithm: Joi.string().valid('CIP100:hashAlgorithm').required(),
body: Joi.object(),
authors: Joi.object(),
}),
authors: Joi.array(),
hashAlgorithm: Joi.object({
'@value': Joi.string().valid('blake2b-256').required(),
}),
body: Joi.object({
title: Joi.object({ '@value': Joi.string().max(80).required() }),
abstract: Joi.object({ '@value': Joi.string().max(2500).required() }),
motivation: Joi.object({ '@value': Joi.string().required() }),
rationale: Joi.object({ '@value': Joi.string().required() }),
references: Joi.array().items(
Joi.object({
'@type': Joi.string(),
label: Joi.object({ '@value': Joi.string().required() }),
uri: Joi.object({ '@value': Joi.string().uri().required() }),
referenceHash: Joi.object({
hashDigest: Joi.string().required(),
hashAlgorithm: Joi.string().required(),
}),
}).required(),
),
}),
}),
};
1 change: 1 addition & 0 deletions govtool/metadata-validation/src/schemas/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './cipStandardSchema';
1 change: 1 addition & 0 deletions govtool/metadata-validation/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './validateMetadata';
6 changes: 5 additions & 1 deletion govtool/metadata-validation/src/types/validateMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { MetadataValidationStatus } from '../enums/ValidationError';
import { MetadataValidationStatus } from '@enums';

export enum MetadataStandard {
CIP108 = 'CIP108',
}

export type ValidateMetadataResult = {
status?: MetadataValidationStatus;
Expand Down
2 changes: 2 additions & 0 deletions govtool/metadata-validation/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './canonizeJSON';
export * from './validateMetadataStandard';
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { MetadataStandard } from '@types';

import { validateMetadataStandard } from './validateMetadataStandard';

describe('validateMetadataStandard', () => {
it('should throw MetadataValidationStatus.INCORRECT_FORMAT if validation fails', async () => {
try {
await validateMetadataStandard(
{
testValue: 'test',
anotherValue: 'another',
},
MetadataStandard.CIP108,
);
} catch (error) {
expect(error).toBe('INCORRECT_FORMAT');
}
});
});
20 changes: 20 additions & 0 deletions govtool/metadata-validation/src/utils/validateMetadataStandard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { MetadataValidationStatus } from '@enums';
import { cipStandardSchema } from '@schemas';
import { MetadataStandard } from '@types';

/**
* Validates the metadata against a specific standard.
* @param data - The metadata to be validated.
* @param standard - The metadata standard to validate against.
* @throws {MetadataValidationStatus.INCORRECT_FORMAT} - If the metadata does not conform to the specified standard.
*/
export const validateMetadataStandard = async (
data: Record<string, unknown>,
standard: MetadataStandard,
) => {
try {
await cipStandardSchema[standard]?.validateAsync(data);
} catch (error) {
throw MetadataValidationStatus.INCORRECT_FORMAT;
}
};
11 changes: 10 additions & 1 deletion govtool/metadata-validation/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
"noFallthroughCasesInSwitch": false,
"paths": {
"@/*": ["src/*"],
"@dto": ["src/dto"],
"@enums": ["src/enums"],
"@health": ["src/health"],
"@schemas": ["src/schemas"],
"@types": ["src/types"],
"@utils": ["src/utils"]
}
}
}
40 changes: 40 additions & 0 deletions govtool/metadata-validation/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,18 @@
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d"
integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==

"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0":
version "9.3.0"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==

"@hapi/topo@^5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012"
integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==
dependencies:
"@hapi/hoek" "^9.0.0"

"@humanwhocodes/config-array@^0.11.14":
version "0.11.14"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
Expand Down Expand Up @@ -858,6 +870,23 @@
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==

"@sideway/address@^4.1.5":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5"
integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==
dependencies:
"@hapi/hoek" "^9.0.0"

"@sideway/formula@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f"
integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==

"@sideway/pinpoint@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df"
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==

"@sinclair/typebox@^0.27.8":
version "0.27.8"
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
Expand Down Expand Up @@ -3517,6 +3546,17 @@ jest@^29.5.0:
import-local "^3.0.2"
jest-cli "^29.7.0"

joi@^17.12.3:
version "17.12.3"
resolved "https://registry.yarnpkg.com/joi/-/joi-17.12.3.tgz#944646979cd3b460178547b12ba37aca8482f63d"
integrity sha512-2RRziagf555owrm9IRVtdKynOBeITiDpuZqIpgwqXShPncPKNiRQoiGsl/T8SQdq+8ugRzH2LqY67irr2y/d+g==
dependencies:
"@hapi/hoek" "^9.3.0"
"@hapi/topo" "^5.1.0"
"@sideway/address" "^4.1.5"
"@sideway/formula" "^3.0.1"
"@sideway/pinpoint" "^2.0.0"

js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
Expand Down

0 comments on commit e33ffbf

Please sign in to comment.