Migrate url tooling to twenty-shared (#10440)
Migrate and unify URL tooling in twenty-shared. We now have: - isValidHostname which follows our own business rules - a zod schema that can be re-used in different context and leverages is isValidHostname - isValidUrl on top of the zod schema - a getAbsoluteURl and getHostname on top of the zod schema I have added a LOT of tests to cover all the cases I've found Also fixes: https://github.com/twentyhq/twenty/issues/10147
This commit is contained in:
@ -1,35 +0,0 @@
|
||||
import { isDomain } from '~/utils/is-domain';
|
||||
|
||||
describe('isDomain', () => {
|
||||
it(`should return false if null`, () => {
|
||||
expect(isDomain(null)).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return false if undefined`, () => {
|
||||
expect(isDomain(undefined)).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return true if string google`, () => {
|
||||
expect(isDomain('google')).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should return true if string google.com`, () => {
|
||||
expect(isDomain('google.com')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if string bbc.co.uk`, () => {
|
||||
expect(isDomain('bbc.co.uk')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if string web.io`, () => {
|
||||
expect(isDomain('web.io')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if string x.com`, () => {
|
||||
expect(isDomain('x.com')).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should return true if string 2.com`, () => {
|
||||
expect(isDomain('2.com')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -1,7 +0,0 @@
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export const isDomain = (url: string | undefined | null) =>
|
||||
isDefined(url) &&
|
||||
/^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/.test(
|
||||
url,
|
||||
);
|
||||
@ -1,25 +0,0 @@
|
||||
import { getUrlHostname } from '~/utils/url/getUrlHostname';
|
||||
|
||||
describe('getUrlHostname', () => {
|
||||
it("returns the URL's hostname", () => {
|
||||
expect(getUrlHostname('https://www.example.com')).toBe('example.com');
|
||||
expect(getUrlHostname('http://subdomain.example.com')).toBe(
|
||||
'subdomain.example.com',
|
||||
);
|
||||
expect(getUrlHostname('https://www.example.com/path')).toBe('example.com');
|
||||
expect(getUrlHostname('https://www.example.com?query=123')).toBe(
|
||||
'example.com',
|
||||
);
|
||||
expect(getUrlHostname('http://localhost:3000')).toBe('localhost');
|
||||
expect(getUrlHostname('example.com')).toBe('example.com');
|
||||
expect(getUrlHostname('www.subdomain.example.com')).toBe(
|
||||
'subdomain.example.com',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns an empty string for invalid URLs', () => {
|
||||
expect(getUrlHostname('?o')).toBe('');
|
||||
expect(getUrlHostname('')).toBe('');
|
||||
expect(getUrlHostname('\\')).toBe('');
|
||||
});
|
||||
});
|
||||
@ -1,25 +0,0 @@
|
||||
import { isValidUrl } from '../isValidUrl';
|
||||
|
||||
describe('isValidUrl', () => {
|
||||
it('test cases', () => {
|
||||
// Truthy
|
||||
expect(isValidUrl('https://www.example.com')).toBe(true);
|
||||
expect(isValidUrl('http://192.168.2.0:3000')).toBe(true);
|
||||
expect(isValidUrl('http://localhost')).toBe(true);
|
||||
expect(isValidUrl('http://localhost:3000')).toBe(true);
|
||||
expect(isValidUrl('http://subdomain.example.com')).toBe(true);
|
||||
expect(isValidUrl('https://www.example.com/path')).toBe(true);
|
||||
expect(isValidUrl('https://www.example.com/path/path2?query=123')).toBe(
|
||||
true,
|
||||
);
|
||||
expect(isValidUrl('http://localhost:3000')).toBe(true);
|
||||
expect(isValidUrl('example.com')).toBe(true);
|
||||
expect(isValidUrl('www.subdomain.example.com')).toBe(true);
|
||||
|
||||
// Falsy
|
||||
expect(isValidUrl('?o')).toBe(false);
|
||||
expect(isValidUrl('')).toBe(false);
|
||||
expect(isValidUrl('\\')).toBe(false);
|
||||
expect(isValidUrl('wwwexamplecom')).toBe(false);
|
||||
});
|
||||
});
|
||||
@ -1,9 +0,0 @@
|
||||
import { absoluteUrlSchema } from '~/utils/validation-schemas/absoluteUrlSchema';
|
||||
|
||||
export const getAbsoluteUrl = (url: string) => {
|
||||
try {
|
||||
return absoluteUrlSchema.parse(url);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
@ -1,13 +0,0 @@
|
||||
import { getAbsoluteUrl } from '~/utils/url/getAbsoluteUrl';
|
||||
|
||||
export const getUrlHostname = (
|
||||
url: string,
|
||||
options?: { keepPath?: boolean },
|
||||
) => {
|
||||
try {
|
||||
const parsedUrl = new URL(getAbsoluteUrl(url));
|
||||
return `${parsedUrl.hostname.replace(/^www\./i, '')}${options?.keepPath && parsedUrl.pathname !== '/' ? parsedUrl.pathname : ''}`;
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
@ -1,8 +0,0 @@
|
||||
export const isValidUrl = (url: string) => {
|
||||
const urlRegex =
|
||||
/^(https?:\/\/)?((([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,})|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(localhost))(:\d+)?(\/[^\s]*)?(\?[^\s]*)?$/;
|
||||
|
||||
const urlPattern = new RegExp(urlRegex, 'i');
|
||||
|
||||
return !!urlPattern.test(url);
|
||||
};
|
||||
@ -1,33 +0,0 @@
|
||||
import { absoluteUrlSchema } from '~/utils/validation-schemas/absoluteUrlSchema';
|
||||
|
||||
describe('absoluteUrlSchema', () => {
|
||||
it('validates an absolute url', () => {
|
||||
expect(absoluteUrlSchema.parse('https://www.example.com')).toBe(
|
||||
'https://www.example.com',
|
||||
);
|
||||
expect(absoluteUrlSchema.parse('http://subdomain.example.com')).toBe(
|
||||
'http://subdomain.example.com',
|
||||
);
|
||||
expect(absoluteUrlSchema.parse('https://www.example.com/path')).toBe(
|
||||
'https://www.example.com/path',
|
||||
);
|
||||
expect(absoluteUrlSchema.parse('https://www.example.com?query=123')).toBe(
|
||||
'https://www.example.com?query=123',
|
||||
);
|
||||
expect(absoluteUrlSchema.parse('http://localhost:3000')).toBe(
|
||||
'http://localhost:3000',
|
||||
);
|
||||
});
|
||||
|
||||
it('transforms a non-absolute URL to an absolute URL', () => {
|
||||
expect(absoluteUrlSchema.parse('example.com')).toBe('https://example.com');
|
||||
expect(absoluteUrlSchema.parse('www.subdomain.example.com')).toBe(
|
||||
'https://www.subdomain.example.com',
|
||||
);
|
||||
});
|
||||
|
||||
it('fails for invalid urls', () => {
|
||||
expect(absoluteUrlSchema.safeParse('?o').success).toBe(false);
|
||||
expect(absoluteUrlSchema.safeParse('\\').success).toBe(false);
|
||||
});
|
||||
});
|
||||
@ -1,23 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const absoluteUrlSchema = z
|
||||
.string()
|
||||
.url()
|
||||
.or(
|
||||
z
|
||||
.string()
|
||||
.transform((value) => {
|
||||
try {
|
||||
const url = `https://${value}`.trim();
|
||||
return isNaN(Number(value.trim())) &&
|
||||
new URL(url) &&
|
||||
/\.[a-z]{2,}$/.test(url)
|
||||
? url
|
||||
: '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
})
|
||||
.pipe(z.string().url()),
|
||||
)
|
||||
.or(z.literal(''));
|
||||
Reference in New Issue
Block a user