Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduced a non-strict mode for SRC44 #37

Merged
merged 1 commit into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions packages/standards/src/src44/DescriptorData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { parseIpfsMedia } from './parseIpfsMedia';
*/
export class DescriptorData {

private constructor(private data: SRC44Descriptor) {
private constructor(private data: SRC44Descriptor, private strict: boolean) {
this.validate();
}

Expand Down Expand Up @@ -82,23 +82,28 @@ export class DescriptorData {
}

/**
* Creates a bare minimum SRC44 descriptor instance.
* Creates a bare minimum, but strict, SRC44 descriptor instance.
* @param name The name
*/
public static create(name?: string) {
return new DescriptorData({
vs: 1,
nm: name
});
}, true);
}

/**
* Creates/Parses a SRC44 compliant descriptor string
* @param jsonString The SRC44 compliant string. See also [[stringify]]
* @param strict If true, the standard check is more strictly
*/
public static parse(jsonString: string) {
public static parse(jsonString: string, strict = true) {
try {
return new DescriptorData(JSON.parse(jsonString));
const json = JSON.parse(jsonString);
if (!strict && !json.vs) {
json.vs = 1;
}
return new DescriptorData(json, strict);
// @ts-ignore
} catch (e: any) {
throw new SRC44ParseException(e.message);
Expand Down Expand Up @@ -141,7 +146,7 @@ export class DescriptorData {
* @throws in case of invalid data.
*/
public validate() {
validateSRC44(this.raw);
validateSRC44(this.raw, this.strict);
}

/**
Expand Down
71 changes: 52 additions & 19 deletions packages/standards/src/src44/__tests__/descriptorData.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import {DescriptorData} from '../DescriptorData';

const TestObject1 = {
const TestObjectNotStrict = {
'tp': 'foo',
'nm': 'Bittrex',
'ds': 'World class exchange at your service',
'av': {'QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR': 'image/gif'},
'bg': {'QmUFc4dyX7TJn5dPxp8CrcDeedoV18owTBUWApYMuF6Koc': 'image/jpeg'},
'hp': 'https://bittrex.com',
'sr': '^[0-9a-fA-F]{24}$',
'al': 'somealias',
'xt': 'QmUFc4dyX7TJn5dPxp8CrcDeedoV18owTBUWApYMuF6Koc',
'sc': ['https://twitter.com/bittrex', 'https://twitter.com/bittrex2']
};

const TestObjectStrict = {
'vs': 1,
'tp': 'cex',
'nm': 'Bittrex',
Expand All @@ -18,7 +31,7 @@ const TestObject1 = {
describe('descriptorData', () => {
describe('get', () => {
it('should return a human friendly object', () => {
const descriptor = DescriptorData.parse(JSON.stringify(TestObject1));
const descriptor = DescriptorData.parse(JSON.stringify(TestObjectStrict));
expect(descriptor.get()).toEqual(
{
'alias': 'somealias',
Expand Down Expand Up @@ -47,11 +60,11 @@ describe('descriptorData', () => {
});
describe('stringify', () => {
it('should stringify as expected', () => {
const descriptor = DescriptorData.parse(JSON.stringify(TestObject1));
const descriptor = DescriptorData.parse(JSON.stringify(TestObjectStrict));
expect(descriptor.stringify()).toEqual('{\"vs\":1,\"tp\":\"cex\",\"nm\":\"Bittrex\",\"ds\":\"World class exchange at your service\",\"av\":{\"QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR\":\"image/gif\"},\"bg\":{\"QmUFc4dyX7TJn5dPxp8CrcDeedoV18owTBUWApYMuF6Koc\":\"image/jpeg\"},\"hp\":\"https://bittrex.com\",\"sr\":\"^[0-9a-fA-F]{24}$\",\"al\":\"somealias\",\"xt\":\"QmUFc4dyX7TJn5dPxp8CrcDeedoV18owTBUWApYMuF6Koc\",\"sc\":[\"https://twitter.com/bittrex\",\"https://twitter.com/bittrex2\"]}');
});
it('should parse as expected with custom properties', () => {
const t = {...TestObject1, tw: 'Twitter account', xCustom: 1111};
const t = {...TestObjectStrict, tw: 'Twitter account', xCustom: 1111};
const descriptor = DescriptorData.parse(JSON.stringify(t));
expect(descriptor.version).toBe(1);
expect(descriptor.type).toBe('cex');
Expand All @@ -76,7 +89,7 @@ describe('descriptorData', () => {

it('should throw exception if string is too long', () => {
const t = {
...TestObject1,
...TestObjectStrict,
ds: 'x'.repeat(500)
};
expect(() => {
Expand All @@ -86,7 +99,7 @@ describe('descriptorData', () => {

it('should throw exception if object is too large', () => {
const t = {
...TestObject1,
...TestObjectStrict,
ds: 'x'.repeat(384),
xc: 'foo'.repeat(500)
};
Expand All @@ -112,7 +125,7 @@ describe('descriptorData', () => {
});
describe('parse', () => {
it('should parse as expected', () => {
const descriptor = DescriptorData.parse(JSON.stringify(TestObject1));
const descriptor = DescriptorData.parse(JSON.stringify(TestObjectStrict));
expect(descriptor.version).toBe(1);
expect(descriptor.type).toBe('cex');
expect(descriptor.name).toBe('Bittrex');
Expand All @@ -131,8 +144,28 @@ describe('descriptorData', () => {
expect(descriptor.extension).toBe('QmUFc4dyX7TJn5dPxp8CrcDeedoV18owTBUWApYMuF6Koc');
expect(descriptor.socialMediaLinks).toEqual(['https://twitter.com/bittrex', 'https://twitter.com/bittrex2']);
});
it('should parse as expected - less strict', () => {
const descriptor = DescriptorData.parse(JSON.stringify(TestObjectNotStrict), false);
expect(descriptor.version).toBe(1);
expect(descriptor.type).toBe('foo');
expect(descriptor.name).toBe('Bittrex');
expect(descriptor.avatar).toEqual({
ipfsCid: 'QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR',
mimeType: 'image/gif'
});
expect(descriptor.background).toEqual({
ipfsCid: 'QmUFc4dyX7TJn5dPxp8CrcDeedoV18owTBUWApYMuF6Koc',
mimeType: 'image/jpeg'
});
expect(descriptor.description).toBe('World class exchange at your service');
expect(descriptor.alias).toBe('somealias');
expect(descriptor.homePage).toBe('https://bittrex.com');
expect(descriptor.sendRule).toEqual(new RegExp('^[0-9a-fA-F]{24}$'));
expect(descriptor.extension).toBe('QmUFc4dyX7TJn5dPxp8CrcDeedoV18owTBUWApYMuF6Koc');
expect(descriptor.socialMediaLinks).toEqual(['https://twitter.com/bittrex', 'https://twitter.com/bittrex2']);
});
it('should parse as expected with custom properties', () => {
const t = {...TestObject1, tw: 'Twitter account', xCustom: 1111};
const t = {...TestObjectStrict, tw: 'Twitter account', xCustom: 1111};
const descriptor = DescriptorData.parse(JSON.stringify(t));
expect(descriptor.version).toBe(1);
expect(descriptor.type).toBe('cex');
Expand All @@ -157,7 +190,7 @@ describe('descriptorData', () => {

it('should throw exception if string is too long', () => {
const t = {
...TestObject1,
...TestObjectStrict,
ds: 'x'.repeat(500)
};
expect(() => {
Expand All @@ -167,7 +200,7 @@ describe('descriptorData', () => {

it('should throw exception if object is too large', () => {
const t = {
...TestObject1,
...TestObjectStrict,
ds: 'x'.repeat(384),
xc: 'foo'.repeat(500)
};
Expand Down Expand Up @@ -203,41 +236,41 @@ describe('descriptorData', () => {
describe('estimateFeePlanck', () => {
it('should calculate correct fee', () => {
expect(DescriptorData.create('Some name').estimateFeePlanck()).toEqual('20000000');
expect(DescriptorData.parse(JSON.stringify(TestObject1)).estimateFeePlanck()).toEqual('60000000');
expect(DescriptorData.parse(JSON.stringify(TestObjectStrict)).estimateFeePlanck()).toEqual('60000000');
expect(DescriptorData.parse(JSON.stringify({
...TestObject1,
...TestObjectStrict,
'custom': 'custom'.repeat(50)
})).estimateFeePlanck()).toEqual('80000000');
expect(DescriptorData.parse(JSON.stringify({
...TestObject1,
...TestObjectStrict,
'custom': 'custom'.repeat(75)
})).estimateFeePlanck()).toEqual('100000000');
expect(DescriptorData.parse(JSON.stringify({
...TestObject1,
...TestObjectStrict,
'custom': 'custom'.repeat(90)
})).estimateFeePlanck()).toEqual('120000000');
});
it('should calculate correct fee - baseFee = 0.02', () => {
const BaseFee = 2000000;
expect(DescriptorData.create('Some name').estimateFeePlanck(BaseFee)).toEqual('2000000');
expect(DescriptorData.parse(JSON.stringify(TestObject1)).estimateFeePlanck(BaseFee)).toEqual('6000000');
expect(DescriptorData.parse(JSON.stringify(TestObjectStrict)).estimateFeePlanck(BaseFee)).toEqual('6000000');
expect(DescriptorData.parse(JSON.stringify({
...TestObject1,
...TestObjectStrict,
'custom': 'custom'.repeat(50)
})).estimateFeePlanck(BaseFee)).toEqual('8000000');
expect(DescriptorData.parse(JSON.stringify({
...TestObject1,
...TestObjectStrict,
'custom': 'custom'.repeat(75)
})).estimateFeePlanck(BaseFee)).toEqual('10000000');
expect(DescriptorData.parse(JSON.stringify({
...TestObject1,
...TestObjectStrict,
'custom': 'custom'.repeat(90)
})).estimateFeePlanck(BaseFee)).toEqual('12000000');
});
it('should throw if 1000 bytes is exceeded', () => {
expect(() => {
DescriptorData.parse(JSON.stringify({
...TestObject1,
...TestObjectStrict,
'custom': 'some custom content'.repeat(100)
})).estimateFeePlanck();
}).toThrow('[SRC44 Validation Error]: Maximum length of 1000 bytes allowed');
Expand Down
28 changes: 25 additions & 3 deletions packages/standards/src/src44/__tests__/validateSRC44.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,28 @@ describe('validateSRC44', () => {
'sc': ['https://twitter.com/bittrex']
});
});

it('should be fine - less strict', () => {

// no vs and different type
validateSRC44({
// @ts-ignore
'tp': 'foo',
'id': 'id',
'ac': '895212263565386113',
'nm': 'Bittrex',
'ds': 'World class exchange at your service',
'av': { 'QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR': 'image/gif' },
'bg': { 'QmUFc4dyX7TJn5dPxp8CrcDeedoV18owTBUWApYMuF6Koc': 'image/jpeg' },
'hp': 'https://bittrex.com',
'sr': '^[0-9a-fA-F]{24}$',
'al': 'somealias',
'xt': 'QmUFc4dyX7TJn5dPxp8CrcDeedoV18owTBUWApYMuF6Koc',
'sc': ['https://twitter.com/bittrex'],
'x-custom': 'bla blubb'
}, false);
});

it('throws error for too large object', () => {
expect(() => {
validateSRC44({
Expand Down Expand Up @@ -154,7 +176,7 @@ describe('validateSRC44', () => {
// @ts-ignore
tp: 'foo'
});
}).toThrow('tp must be one of [hum,smc,biz,cex,dex,oth] - Got foo');
}).toThrow('tp must be one of [hum,smc,biz,cex,dex,oth,tok,bot] - Got foo');
});
});

Expand Down Expand Up @@ -202,7 +224,7 @@ describe('validateSRC44', () => {
nm: 'name',
ac: '12432452'
});
}).toThrow('ac must match /^\\d{18,22}$/ - Got 12432452');
}).toThrow('ac must match /^\\d{10,22}$/ - Got 12432452');
});
it('throws error for beign too large', () => {
expect(() => {
Expand All @@ -211,7 +233,7 @@ describe('validateSRC44', () => {
nm: 'name',
ac: '74'.repeat(30)
});
}).toThrow('ac must match /^\\d{18,22}$/');
}).toThrow('ac must match /^\\d{10,22}$/');
});
});

Expand Down
5 changes: 3 additions & 2 deletions packages/standards/src/src44/parseIpfsMedia.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/**
* Copyright (c) 2022 Signum Network
*/
import { SRC44ParseException } from "./exceptions";
import { SRC44ParseException } from './exceptions';
import {IpfsMediaType} from './typings';

/**
*
Expand All @@ -11,7 +12,7 @@ import { SRC44ParseException } from "./exceptions";
* @param o
* @module standards.SRC44
*/
export function parseIpfsMedia(o: object) {
export function parseIpfsMedia(o: object): IpfsMediaType {
if (!o) {
return undefined;
}
Expand Down
11 changes: 4 additions & 7 deletions packages/standards/src/src44/typings/Descriptor.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
/**
* Copyright (c) 2022 Signum Network
*/
import { SRC44DescriptorType } from './SRC44DescriptorType';

interface MediaType {
[key: string]: string;
}
import {SRC44DescriptorType} from './SRC44DescriptorType';
import {IpfsMediaType} from './IpfsMediaType';

/**
* Human friendly descriptor structure
Expand Down Expand Up @@ -34,11 +31,11 @@ export interface Descriptor {
/**
* IPFS Media Link for the Avatar
*/
avatar?: MediaType;
avatar?: IpfsMediaType;
/**
* IPFS Media Link for the background image
*/
background?: MediaType;
background?: IpfsMediaType;
/**
* Homepage - maximal 128 characters
*/
Expand Down
20 changes: 20 additions & 0 deletions packages/standards/src/src44/typings/IpfsMediaType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2022 Signum Network
*/

/**
* Type for media data used in SRC44
*
* @internal
* @module standards.SRC44
*/
export interface IpfsMediaType {
/**
* IPFS CID
*/
ipfsCid: string;
/**
* Mime Type, e.g. image/png
*/
mimeType: string;
}
1 change: 1 addition & 0 deletions packages/standards/src/src44/typings/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './Descriptor';
export * from './SRC44Descriptor';
export * from './SRC44DescriptorType';
export * from './IpfsMediaType';
13 changes: 7 additions & 6 deletions packages/standards/src/src44/validateSRC44.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,20 @@ import {parseIpfsMedia} from './parseIpfsMedia';
*
* @internal
* @param json
* @param strict
* @module standards.SRC44
*/
export function validateSRC44(json: SRC44Descriptor) {
export function validateSRC44(json: SRC44Descriptor, strict = true) {
const MaxLength = 1000;
const DsLength = 384;
const NmLength = 24;
const IdLength = 48;
const HpLength = 128;
const ScItemLength = 3;
const ScItemUrlLength = 92;
const AllowedTypes = ['hum', 'smc', 'biz', 'cex', 'dex', 'oth'];
const AllowedTypes = ['hum', 'smc', 'biz', 'cex', 'dex', 'oth', 'tok', 'bot'];
try {
if (json.vs !== 1) {
if (strict && json.vs !== 1) {
throw new Error(`vs is required and must be 1 - Got ${json.vs}`);
}

Expand All @@ -51,15 +52,15 @@ export function validateSRC44(json: SRC44Descriptor) {
throw new Error(`al must match /^\\w{1,100}$/ - Got ${json.al}`);
}

if (json.ac && !/^\d{18,22}$/.test(json.ac)) {
throw new Error(`ac must match /^\\d{18,22}$/ - Got ${json.ac}`);
if (json.ac && !/^\d{10,22}$/.test(json.ac)) {
throw new Error(`ac must match /^\\d{10,22}$/ - Got ${json.ac}`);
}


// xt is just a IPFS CID string
// sr is just a regex string

if (json.tp && AllowedTypes.indexOf(json.tp) < 0) {
if (strict && json.tp && AllowedTypes.indexOf(json.tp) < 0) {
throw new Error(`tp must be one of [${AllowedTypes.join(',')}] - Got ${json.tp}`);
}

Expand Down