-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #39 from XYOracleNetwork/feature/xns-validators
xns name validators and helper class
- Loading branch information
Showing
32 changed files
with
808 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
packages/payloadset/packages/xns/plugins/record/src/estimate/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './lib/index.ts' |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './lib/index.ts' | ||
export * from './estimate/index.ts' | ||
export * from './validation/index.ts' |
2 changes: 2 additions & 0 deletions
2
packages/payloadset/packages/xns/plugins/record/src/validation/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './name/index.ts' | ||
export * from './validation/index.ts' |
100 changes: 100 additions & 0 deletions
100
packages/payloadset/packages/xns/plugins/record/src/validation/name/Name.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import { assertEx } from '@xylabs/assert' | ||
import { isHash } from '@xylabs/hex' | ||
import type { Promisable } from '@xylabs/promise' | ||
import type { Payload } from '@xyo-network/payload-model' | ||
import type { DomainFields, TopLevelDomain } from '@xyo-network/xns-record-payload-plugins' | ||
import { DomainSchema } from '@xyo-network/xns-record-payload-plugins' | ||
|
||
import { MAX_DOMAIN_LENGTH, XnsNameSimpleValidators } from '../validation/index.ts' | ||
import { removeDisallowedCharacters } from './lib/index.ts' | ||
import type { ValidSourceTypes } from './types/index.ts' | ||
|
||
export class XnsNameHelper { | ||
static ValidTLDs = ['.xyo'] as const | ||
|
||
private _xnsName: Payload<DomainFields> | ||
|
||
private constructor(xnsName: Payload<DomainFields>) { | ||
this._xnsName = xnsName | ||
} | ||
|
||
get domain() { | ||
return assertEx(this.xnsName.domain, () => 'domain not found in payload') | ||
} | ||
|
||
get name() { | ||
return `${this.domain}.${this.tld}` | ||
} | ||
|
||
get tld() { | ||
return assertEx(this.xnsName.tld, () => 'tld not found in payload') | ||
} | ||
|
||
get xnsName() { | ||
return assertEx(this._xnsName, () => 'XnsNameHelper xnsName not set') | ||
} | ||
|
||
/** | ||
* Create an XnsNameHelper from a domain payload | ||
* @param {Domain} domain | ||
* @returns Promise<XnsNameHelper> | ||
*/ | ||
static fromPayload(domain: Payload<DomainFields>): Promisable<XnsNameHelper> { | ||
return new XnsNameHelper(domain) | ||
} | ||
|
||
/** | ||
* Create an XnsNameHelper from a string | ||
* @param {string} xnsName | ||
* @returns Promise<XnsNameHelper> | ||
*/ | ||
static fromString(xnsName: string): XnsNameHelper { | ||
const parts = xnsName.split('.') | ||
assertEx(parts.length === 2, () => 'Unable to parse xnsName') | ||
|
||
const domain = parts[0] | ||
const tld = parts[1] as TopLevelDomain | ||
return new XnsNameHelper({ | ||
schema: DomainSchema, domain, tld, | ||
}) | ||
} | ||
|
||
/** | ||
* Determine if a string is a valid XNS name or hash | ||
* @param {string} source? | ||
* @returns ValidSourceTypes | ||
*/ | ||
static isPotentialXnsNameOrHash(source?: string): ValidSourceTypes { | ||
if (isHash(source)) return 'hash' | ||
const xnsName = XnsNameHelper.ValidTLDs.some(tld => source?.endsWith(tld)) ? source : null | ||
return xnsName ? 'xnsName' : null | ||
} | ||
|
||
static isValid(domain: Payload<DomainFields>) { | ||
return XnsNameSimpleValidators.every(validator => validator(domain)) | ||
} | ||
|
||
/** | ||
* Mask a string to be a valid XNS name | ||
* @param {string} str | ||
* @returns string | ||
*/ | ||
static mask(str: string) { | ||
// Check if the domain name is too long | ||
if (str.length > MAX_DOMAIN_LENGTH) { | ||
throw new Error(`Domain name too long: ${str.length} exceeds max length: ${MAX_DOMAIN_LENGTH}`) | ||
} | ||
|
||
// convert to lowercase | ||
const lowercaseXnsName = str.toLowerCase() | ||
|
||
// Remove everything except letters, numbers, and dashes | ||
let formattedXnsName = lowercaseXnsName.replaceAll(/[^\dA-Za-z-]+$/g, '') | ||
|
||
// Remove leading and trailing dashes | ||
formattedXnsName = formattedXnsName.replaceAll(/^-+|-+$/g, '') | ||
|
||
// Filter out disallowed characters. | ||
return removeDisallowedCharacters(formattedXnsName) | ||
} | ||
} |
2 changes: 2 additions & 0 deletions
2
packages/payloadset/packages/xns/plugins/record/src/validation/name/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './Name.ts' | ||
export * from './types/index.ts' |
1 change: 1 addition & 0 deletions
1
packages/payloadset/packages/xns/plugins/record/src/validation/name/lib/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './removeDisallowedCharacters.ts' |
26 changes: 26 additions & 0 deletions
26
...loadset/packages/xns/plugins/record/src/validation/name/lib/removeDisallowedCharacters.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { DisallowedModuleIdentifierCharacters } from '@xyo-network/module-model' | ||
|
||
/** | ||
* A set of all the disallowed characters in module identifiers | ||
*/ | ||
const DISALLOWED_CHARACTERS = new Set(Object.keys(DisallowedModuleIdentifierCharacters)) | ||
|
||
/** | ||
* Iterates over a string removing disallowed characters | ||
* @param xnsName The XNS name to remove disallowed characters from | ||
* @returns The XNS name with disallowed characters removed | ||
*/ | ||
export const removeDisallowedCharacters = (xnsName: string): string => { | ||
// Create the initial result | ||
let result = '' | ||
// Iterate over each character in the XNS name | ||
for (const char of xnsName) { | ||
// If the character is not a disallowed character | ||
if (!DISALLOWED_CHARACTERS.has(char)) { | ||
// add it to the result | ||
result += char | ||
} | ||
} | ||
// Return the result which contains only allowed characters | ||
return result | ||
} |
125 changes: 125 additions & 0 deletions
125
packages/payloadset/packages/xns/plugins/record/src/validation/name/spec/Name.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import type { Domain } from '@xyo-network/xns-record-payload-plugins' | ||
import { DomainSchema } from '@xyo-network/xns-record-payload-plugins' | ||
|
||
import { MAX_DOMAIN_LENGTH } from '../../validation/index.ts' | ||
import { XnsNameHelper } from '../Name.ts' | ||
|
||
describe('XnsNameHelper', () => { | ||
const validDomain: Domain = { | ||
schema: DomainSchema, domain: 'example', tld: 'xyo', | ||
} | ||
describe('domain getter', () => { | ||
it('should return the domain if set', async () => { | ||
const helper = await XnsNameHelper.fromPayload(validDomain) | ||
expect(helper.domain).toBe('example') | ||
}) | ||
|
||
it('should throw an error if domain is not set', async () => { | ||
const domain: Domain = { | ||
schema: DomainSchema, domain: '', tld: 'xyo', | ||
} | ||
const helper = await XnsNameHelper.fromPayload(domain) | ||
expect(() => helper.domain).toThrow('domain not found in payload') | ||
}) | ||
}) | ||
|
||
describe('tld getter', () => { | ||
it('should return the tld if set', async () => { | ||
const helper = await XnsNameHelper.fromPayload(validDomain) | ||
expect(helper.tld).toBe('xyo') | ||
}) | ||
|
||
it('should throw an error if tld is not set', async () => { | ||
const domain: Domain = { | ||
schema: DomainSchema, domain: 'example', tld: '' as 'xyo', | ||
} | ||
const helper = await XnsNameHelper.fromPayload(domain) | ||
expect(() => helper.tld).toThrow('tld not found in payload') | ||
}) | ||
}) | ||
|
||
describe('xnsName getter', () => { | ||
it('should return the xnsName if set', async () => { | ||
const helper = await XnsNameHelper.fromPayload(validDomain) | ||
expect(helper.xnsName).toEqual(validDomain) | ||
}) | ||
|
||
it('should throw an error if xnsName is not set', async () => { | ||
const helper = await XnsNameHelper.fromPayload(undefined as unknown as Domain) | ||
expect(() => helper.xnsName).toThrow('XnsNameHelper xnsName not set') | ||
}) | ||
}) | ||
|
||
describe('fromString', () => { | ||
it('should create an instance from a valid xnsName string', () => { | ||
const { domain, tld } = validDomain | ||
const helper = XnsNameHelper.fromString(`${domain}.${tld}`) | ||
expect(helper.domain).toBe(domain) | ||
expect(helper.xnsName.domain).toBe(domain) | ||
expect(helper.tld).toBe(tld) | ||
expect(helper.xnsName.tld).toBe(tld) | ||
}) | ||
|
||
it('should throw an error if xnsName string is invalid', () => { | ||
expect(() => XnsNameHelper.fromString('invalid')).toThrow('Unable to parse xnsName') | ||
}) | ||
}) | ||
|
||
describe('isXnsNameOrHash', () => { | ||
it('should return "xnsName" if the source ends with a valid TLD', () => { | ||
expect(XnsNameHelper.isPotentialXnsNameOrHash('example.xyo')).toBe('xnsName') | ||
}) | ||
|
||
it('should return "hash" if the source is a valid hash', () => { | ||
expect(XnsNameHelper.isPotentialXnsNameOrHash('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2')).toBe('hash') | ||
}) | ||
|
||
it('should return null if the source is neither a valid xnsName nor a hash', () => { | ||
expect(XnsNameHelper.isPotentialXnsNameOrHash('invalid')).toBe(null) | ||
}) | ||
}) | ||
|
||
describe('isValid', () => { | ||
it('should return true for valid xns names', () => { | ||
expect(XnsNameHelper.isValid(validDomain)).toBe(true) | ||
}) | ||
|
||
it('should return false for invalid xns names', () => { | ||
const domain: Domain = { | ||
schema: DomainSchema, domain: 'example-', tld: 'xyo', | ||
} | ||
expect(XnsNameHelper.isValid(domain)).toBe(false) | ||
}) | ||
}) | ||
|
||
describe('mask', () => { | ||
const cases = [ | ||
['Example$123', 'example123'], | ||
['Example/123', 'example123'], | ||
['Example.123', 'example123'], | ||
['Example-123', 'example-123'], | ||
['Example 123', 'example123'], | ||
['Example_123', 'example123'], | ||
['-Example_123-', 'example123'], | ||
['-Example_123', 'example123'], | ||
['Example_123-', 'example123'], | ||
['--Example_123', 'example123'], | ||
['Example_123--', 'example123'], | ||
['--Example_123--', 'example123'], | ||
['- Example_123 -', 'example123'], | ||
] | ||
|
||
describe.each(cases)('mask(%s)', (input, expected) => { | ||
it(`should return ${expected}`, () => { | ||
expect(XnsNameHelper.mask(input)).toBe(expected) | ||
}) | ||
}) | ||
|
||
describe('With invalid input', () => { | ||
it('should throw an error', () => { | ||
expect(() => XnsNameHelper.mask('a'.repeat(MAX_DOMAIN_LENGTH + 1))) | ||
.toThrow('Domain name too long: 129 exceeds max length: 128') | ||
}) | ||
}) | ||
}) | ||
}) |
13 changes: 13 additions & 0 deletions
13
packages/payloadset/packages/xns/plugins/record/src/validation/name/spec/tsconfig.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"compilerOptions": { | ||
"emitDecoratorMetadata": true, | ||
"experimentalDecorators": true, | ||
"esModuleInterop": true, | ||
"allowSyntheticDefaultImports": true, | ||
"moduleResolution": "NodeNext", | ||
"module": "NodeNext", | ||
"sourceMap": true, | ||
"inlineSources": true | ||
}, | ||
"extends": "@xylabs/tsconfig-jest" | ||
} |
1 change: 1 addition & 0 deletions
1
packages/payloadset/packages/xns/plugins/record/src/validation/name/types/ValidSources.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export type ValidSourceTypes = 'xnsName' | 'hash' | null |
1 change: 1 addition & 0 deletions
1
packages/payloadset/packages/xns/plugins/record/src/validation/name/types/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './ValidSources.ts' |
2 changes: 2 additions & 0 deletions
2
packages/payloadset/packages/xns/plugins/record/src/validation/validation/Constants.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export const MIN_DOMAIN_LENGTH = 3 | ||
export const MAX_DOMAIN_LENGTH = 128 |
1 change: 1 addition & 0 deletions
1
packages/payloadset/packages/xns/plugins/record/src/validation/validation/factory/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './validators.ts' |
13 changes: 13 additions & 0 deletions
13
...yloadset/packages/xns/plugins/record/src/validation/validation/factory/spec/tsconfig.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"compilerOptions": { | ||
"emitDecoratorMetadata": true, | ||
"experimentalDecorators": true, | ||
"esModuleInterop": true, | ||
"allowSyntheticDefaultImports": true, | ||
"moduleResolution": "NodeNext", | ||
"module": "NodeNext", | ||
"sourceMap": true, | ||
"inlineSources": true | ||
}, | ||
"extends": "@xylabs/tsconfig-jest" | ||
} |
Oops, something went wrong.