Skip to content

Commit

Permalink
Feat: Support Wildcard and * contract filters (#567)
Browse files Browse the repository at this point in the history
added the * filter for contracts.
A valid contract is defined as 

```
function validateContractId(accountId) {
  accountId = accountId.trim();
  
  const isLengthValid = accountId.length >= 2 && accountId.length <= 64;
  if (!isLengthValid) return false; 

  //test if the string starts with a '*.' and remove it if it does
  const isWildCard = WILD_CARD_REGEX.test(accountId);
  isWildCard ? accountId = accountId.slice(2) : null;

  //test if rest of string is valid accounting for/not isWildCard
  const isRegexValid = CONTRACT_NAME_REGEX.test(accountId);
  return isRegexValid;
}

note: early termination for performance
```

The function take in account all **invalid** contract names on
[nomicon](https://nomicon.io/DataStructures/Account)
Please view test for more details.

Regex Broken down Below:

CONTRACT_NAME_REGEX:


`/^(([a-z\d]+[-_])*[a-z\d]+(\.([a-z\d]+[-_])*[a-z\d]+)*\.([a-z\d]+)|([a-z\d]+))$/`

1. **^**: Asserts the start of the string.

2. **(**: Begins a capturing group.
3. **([a-z\d]+[-_])***: Matches a sequence of lowercase letters or
digits, possibly followed by hyphens or underscores. The sequence may
repeat zero or more times.
4. **[a-z\d]+**: Matches a sequence of one or more lowercase letters or
digits.
5. **(\.([a-z\d]+[-_])*[a-z\d]+)***: Allows for zero or more occurrences
of a dot . followed by another sequence.
6. **\.([a-z\d]+)**: Matches a dot followed by a sequence of lowercase
letters or digits.
7. **|**: OR operator.
8. **([a-z\d]+)**: Matches a standalone sequence of lowercase letters or
digits.
9. **)$**: Asserts the end of the string.
WILD_CARD_REGEX:

`/\*\./`
1. **\***: Matches a literal asterisk.
2. **\.**: Matches a literal dot.


EDIT: Added more test please review
Added jest to support function testing.

It seems there's a problem getting Babel and the VM to work smoothly
together. I'm going to dig deeper into it. Right now, I'm adjusting the
test file to recreate the functions instead of using the usual
require/import method, which seems to cause trouble with the VM code. It
does mean there's some duplicated code in the testing files, but the
test work.
  • Loading branch information
Kevin101Zhang authored Feb 26, 2024
1 parent 9079a57 commit 761a9a9
Show file tree
Hide file tree
Showing 7 changed files with 2,951 additions and 53 deletions.
15 changes: 15 additions & 0 deletions frontend/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// jest.config.js
module.exports = {
testEnvironment: 'node',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
extensionsToTreatAsEsm: ['.jsx'],
transform: {
'^.+\\.[jt]sx?$': 'babel-jest',
},
testPathIgnorePatterns: [
'/formatters\\.test\\.js',
'/Editor\\.test\\.js',
],
};
9 changes: 9 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"scripts": {
"dev": "next dev",
"test": "jest --watch --config jest.config.js",
"serve:widgets": "bos-loader dataplatform.near --path widgets/src -r replacement.mainnet.json",
"serve:widgets:local": "bos-loader dataplatform.near --path widgets/src -r replacement.local.json",
"serve:widgets:dev": "bos-loader dev-queryapi.dataplatform.near --path widgets/src -r replacement.dev.json",
Expand All @@ -25,6 +26,7 @@
"buffer": "^6.0.3",
"graphiql": "3.0.6",
"graphql": "^16.8.1",
"graphql-ws": "^5.15.0",
"gridjs": "6.0.6",
"monaco-editor": "^0.45.0",
"near-api-js": "1.1.0",
Expand All @@ -44,11 +46,18 @@
"use-debounce": "^10.0.0"
},
"devDependencies": {
"@babel/core": "^7.23.9",
"@babel/plugin-transform-modules-commonjs": "^7.23.3",
"@babel/preset-env": "^7.23.9",
"@babel/preset-react": "^7.23.3",
"@babel/register": "^7.23.7",
"autoprefixer": "^10.4.17",
"babel-jest": "^29.7.0",
"eslint": "8.50.0",
"eslint-config-next": "13.5.3",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.7.0",
"postcss": "^8.4.33",
"tailwindcss": "^3.4.1",
"typescript": "4.9.5"
Expand Down
243 changes: 243 additions & 0 deletions frontend/src/components/Editor/__tests__/validator.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
const CONTRACT_NAME_REGEX = RegExp(/^(([a-z\d]+[-_])*[a-z\d]+(\.([a-z\d]+[-_])*[a-z\d]+)*\.([a-z\d]+)|([a-z\d]+))$/);
const WILD_CARD_REGEX = RegExp(/^\*\./);
const WILD_CARD = '*';
// const INVALID_ACCOUNT = 'system';

function validateContractId(accountId) {
accountId = accountId.trim();
if(accountId === WILD_CARD) return true;

const isLengthValid = accountId.length >= 2 && accountId.length <= 64;
if (!isLengthValid) return false;

//test if the string starts with a '*.' and remove it if it does
const isWildCard = WILD_CARD_REGEX.test(accountId);
accountId = isWildCard ? accountId.slice(2) : accountId;

const isRegexValid = CONTRACT_NAME_REGEX.test(accountId);
return isRegexValid;
}

function validateContractIds(accountIds) {
const ids = accountIds.split(',').map(id => id.trim());
return ids.every(accountId => validateContractId(accountId));
}

describe('validateContractId', () => {
test('it should return true for valid contract ID', () => {
const validId = 'contract1.near';
expect(validateContractId(validId)).toBe(true);
});

test('it should return true for wildcard contract ID', () => {
const wildcardId = '*.near';
expect(validateContractId(wildcardId)).toBe(true);
});

test('it should return false for empty string', () => {
const emptyString = '';
expect(validateContractId(emptyString)).toBe(false);
});

test('it should return false for invalid contract ID', () => {
const invalidId = 'invalid$contract';
expect(validateContractId(invalidId)).toBe(false);
});

test('it should return false for too short contract ID', () => {
const shortId = 'c';
expect(validateContractId(shortId)).toBe(false);
});

test('it should return false for too long contract ID', () => {
const longId = 'a'.repeat(65);
expect(validateContractId(longId)).toBe(false);
});

test('it should return true for contract ID with leading or trailing spaces', () => {
const spacedId = '*.kaching ';
expect(validateContractId(spacedId)).toBe(true);
});

test('it should return false for contract ID with consecutive dots', () => {
const dotId = 'contract..near';
expect(validateContractId(dotId)).toBe(false);
});

test('it should return false for contract ID with star in the middle characters', () => {
const invalidAsteriskOrder = 'contract.*.near';
expect(validateContractId(invalidAsteriskOrder)).toBe(false);
});

test('it should return false for contract ID with asterisk in center of string characters', () => {
const invalidAsteriskPosition = 'contract*2.near';
expect(validateContractId(invalidAsteriskPosition)).toBe(false);
});

test('it should return false for double asterisk in string', () => {
const multipleAsteriskOrder = '**.near';
expect(validateContractId(multipleAsteriskOrder)).toBe(false);
});

test('it should return false for double . in string', () => {
const invalidDotPosition = '*..near';
expect(validateContractId(invalidDotPosition)).toBe(false);
});

test('it should return false for contract ID starting with a dot', () => {
const dotStartId = '.near';
expect(validateContractId(dotStartId)).toBe(false);
});

test('it should return false for contract ID ending with a dot', () => {
const dotEndId = 'contract.near.';
expect(validateContractId(dotEndId)).toBe(false);
});

test('it should return false for contract ID ending with underscore or hyphen', () => {
const underscoreEndId = 'contract.near_';
const hyphenEndId = 'contract.near-';
expect(validateContractId(underscoreEndId)).toBe(false);
expect(validateContractId(hyphenEndId)).toBe(false);
});

//test on nomicon - https://nomicon.io/DataStructures/Account
test('it should return false for string with whitespace characters', () => {
const invalidWhitespace = 'not ok';
expect(validateContractId(invalidWhitespace)).toBe(false);
});

test('it should return false for string that is too short', () => {
const tooShort = 'a';
expect(validateContractId(tooShort)).toBe(false);
});

test('it should return false for string with suffix separator', () => {
const suffixSeparator = '100-';
expect(validateContractId(suffixSeparator)).toBe(false);
});

test('it should return false for string with consecutive separators', () => {
const consecutiveSeparators = 'bo__wen';
expect(validateContractId(consecutiveSeparators)).toBe(false);
});

test('it should return false for string with prefix separator', () => {
const prefixSeparator = '_illia';
expect(validateContractId(prefixSeparator)).toBe(false);
});

test('it should return false for string with prefix dot separator', () => {
const prefixDotSeparator = '.near';
expect(validateContractId(prefixDotSeparator)).toBe(false);
});

test('it should return false for string with suffix dot separator', () => {
const suffixDotSeparator = 'near.';
expect(validateContractId(suffixDotSeparator)).toBe(false);
});

test('it should return false for string with two dot separators in a row', () => {
const twoDotSeparators = 'a..near';
expect(validateContractId(twoDotSeparators)).toBe(false);
});

test('it should return false for string with non-alphanumeric characters', () => {
const nonAlphanumeric = '$$$';
expect(validateContractId(nonAlphanumeric)).toBe(false);
});

test('it should return false for string with non-lowercase characters', () => {
const nonLowercase = 'WAT';
expect(validateContractId(nonLowercase)).toBe(false);
});

test('it should return false for string with @ character', () => {
const invalidAtCharacter = '[email protected]';
expect(validateContractId(invalidAtCharacter)).toBe(false);
});

// not sure if this is valid
// test('it should return false for system account', () => {
// const systemAccount = 'system';
// expect(validateContractId(systemAccount)).toBe(false);
// });

test('it should return false for string that is too long', () => {
const tooLong = 'abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz';
expect(validateContractId(tooLong)).toBe(false);
});

test('it should fail abc*.near', () => {
const validId = 'abc*.near';
expect(validateContractId(validId)).toBe(false);
});

test('it should succeed for *.a.b.c.near', () => {
const validId = '*.a.b.c.near';
expect(validateContractId(validId)).toBe(true);
});

test('it should succeed for *', () => {
const validId = '*';
expect(validateContractId(validId)).toBe(true);
});

});

describe('validateContractIds', () => {
test('it should return true for valid contract IDs', () => {
const validIds = 'contract1.near, contract2.near, contract3.near';
expect(validateContractIds(validIds)).toBe(true);
});

test('it should return true for wildcard contract ID in a list', () => {
const mixedIds = 'contract1.near, *.kaching, contract3.near';
expect(validateContractIds(mixedIds)).toBe(true);
});

test('it should return false for an empty string', () => {
const emptyString = '';
expect(validateContractIds(emptyString)).toBe(false);
});

test('it should return false for invalid contract IDs', () => {
const invalidIds = 'invalid$contract, 123, contract with space';
expect(validateContractIds(invalidIds)).toBe(false);
});

test('it should return false for a single invalid contract ID in the list', () => {
const mixedIds = 'contract1, invalid$contract, contract3';
expect(validateContractIds(mixedIds)).toBe(false);
});

test('it should return false for a mix of valid and invalid contract IDs', () => {
const mixedIds = 'contract1.near, invalid$contract, contract3.near';
expect(validateContractIds(mixedIds)).toBe(false);
});

test('it should return false for a mix of valid and invalid contract IDs with spaces', () => {
const spacedIds = 'contract1.near, invalid$contract, contract3.near ';
expect(validateContractIds(spacedIds)).toBe(false);
});

test('it should return true for a mix of valid and wildcard contract IDs', () => {
const mixedIds = 'contract1.near, *.near, contract3.near';
expect(validateContractIds(mixedIds)).toBe(true);
});

test('it should return false for an invalid wildcard contract ID where the wildcard is in the string', () => {
const invalidWildcard = '*invalid.near';
expect(validateContractIds(invalidWildcard)).toBe(false);
});

test('it should return false for an invalid wildcard contract ID followed by valid contractIDs', () => {
const invalidWildcardWithOthers = '*invalid.near, contract1.near, *.near';
expect(validateContractIds(invalidWildcardWithOthers)).toBe(false);
});

test('it should return false for an valid wildcard contract ID followed by invalid contractIDs', () => {
const validWildCardwithInvalid = '*.invalid.near, *contract1.near, *.near';
expect(validateContractIds(validWildCardwithInvalid)).toBe(false);
});
});
4 changes: 3 additions & 1 deletion frontend/src/constants/RegexExp.js
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const CONTRACT_NAME_REGEX = RegExp(/^(\*|([a-z\d]+[-_])*[a-z\d]+)(\.*(\*|([a-z\d]+[-_])*[a-z\d]+))*\.(\w+)$/);
export const CONTRACT_NAME_REGEX = RegExp(/^(([a-z\d]+[-_])*[a-z\d]+(\.([a-z\d]+[-_])*[a-z\d]+)*\.([a-z\d]+)|([a-z\d]+))$/);
export const WILD_CARD_REGEX = RegExp(/^\*\./);
export const WILD_CARD = '*';
22 changes: 22 additions & 0 deletions frontend/src/pages/middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NextResponse } from "next/server";
console.log('middleware.js', NextResponse);
export function middleware() {
// retrieve the current response
const res = NextResponse.next()

// add the CORS headers to the response
res.headers.append('Access-Control-Allow-Credentials', "true")
res.headers.append('Access-Control-Allow-Origin', '*') // replace this your actual origin
res.headers.append('Access-Control-Allow-Methods', 'GET,DELETE,PATCH,POST,PUT')
res.headers.append(
'Access-Control-Allow-Headers',
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
)

return res
}

// specify the path regex to apply the middleware to
export const config = {
matcher: '/api/:path*',
}
22 changes: 15 additions & 7 deletions frontend/src/utils/validators.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { defaultSchema, formatIndexingCode, formatSQL } from "./formatters";
import { PgSchemaTypeGen } from "./pgSchemaTypeGen";
import { CONTRACT_NAME_REGEX } from '../constants/RegexExp';
import { CONTRACT_NAME_REGEX, WILD_CARD_REGEX, WILD_CARD } from '../constants/RegexExp';
import { ValidationError } from '../classes/ValidationError';
import { FORMATTING_ERROR_TYPE, TYPE_GENERATION_ERROR_TYPE } from "@/constants/Strings";

export function validateContractId(accountId) {
return (
accountId.length >= 2 &&
accountId.length <= 64 &&
CONTRACT_NAME_REGEX.test(accountId)
);
function validateContractId(accountId) {
accountId = accountId.trim();
if (accountId === WILD_CARD) return true;

const isLengthValid = accountId.length >= 2 && accountId.length <= 64;
if (!isLengthValid) return false;

//test if the string starts with a '*.' and remove it if it does
const isWildCard = WILD_CARD_REGEX.test(accountId);
accountId = isWildCard ? accountId.slice(2) : accountId;

//test if rest of string is valid accounting for/not isWildCard
const isRegexValid = CONTRACT_NAME_REGEX.test(accountId);
return isRegexValid;
}

export function validateContractIds(accountIds) {
Expand Down
Loading

0 comments on commit 761a9a9

Please sign in to comment.