Skip to content

Commit

Permalink
Add generics to Controller
Browse files Browse the repository at this point in the history
  • Loading branch information
ComLock committed Oct 18, 2024
1 parent 7c2bf7f commit e24726d
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 158 deletions.
25 changes: 14 additions & 11 deletions modules/lib/core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,17 +371,20 @@ export interface WebSocketEvent<T> {

type WebSocketEventHandler<T = Record<string, unknown>> = (event: WebSocketEvent<T>) => void;

export interface Controller {
all?: RequestHandler;
connect?: RequestHandler;
delete?: RequestHandler;
get?: RequestHandler;
head?: RequestHandler;
options?: RequestHandler;
patch?: RequestHandler;
post?: RequestHandler;
put?: RequestHandler;
trace?: RequestHandler;
export interface Controller<
Request extends RequestInterface = DefaultRequest,
Response extends ResponseInterface = DefaultResponse,
> {
all?: RequestHandler<Request, Response>;
connect?: RequestHandler<Request, Response>;
delete?: RequestHandler<Request, Response>;
get?: RequestHandler<Request, Response>;
head?: RequestHandler<Request, Response>;
options?: RequestHandler<Request, Response>;
patch?: RequestHandler<Request, Response>;
post?: RequestHandler<Request, Response>;
put?: RequestHandler<Request, Response>;
trace?: RequestHandler<Request, Response>;
webSocketEvent?: WebSocketEventHandler;
}

Expand Down
148 changes: 1 addition & 147 deletions modules/lib/test/Controller.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import type {
Controller,
Request,
RequestBranch,
RequestMethod,
RequestMode,
RequestScheme,
Response,
WebSocketEventHandler,
} from '../core/index';
import {
expectAssignable,
expectNotAssignable,
} from 'tsd';
import {expectAssignable} from 'tsd';

const log = {
info: (message?: string, ...optionalParams: string[]) => { /* no-op */ },
Expand Down Expand Up @@ -63,140 +54,3 @@ const myController = {
};

expectAssignable<Controller>(myController);

type PageRequest = Omit<Request<{
// Only allow literal string
branch: RequestBranch
method: RequestMethod
mode: RequestMode
scheme: RequestScheme

// Make some optional properties required
repositoryId: string
webSocket: boolean
}>,
// Omit/Disallow some optional properties
'contextPath' | 'validTicket'
>;
type PageRequestHandler = (request: PageRequest) => Response;

interface PageController {
all?: PageRequestHandler
connect?: PageRequestHandler
delete?: PageRequestHandler
get?: PageRequestHandler
head?: PageRequestHandler
options?: PageRequestHandler
patch?: PageRequestHandler
post?: PageRequestHandler
put?: PageRequestHandler
trace?: PageRequestHandler
webSocketEvent?: WebSocketEventHandler
}

const pageRequest /* : PageRequest */ = {
method: 'GET' as PageRequest['method'], // Avoid flattening down to string
scheme: 'http' as PageRequest['scheme'], // Avoid flattening down to string
mode: 'preview' as PageRequest['mode'], // Avoid flattening down to string
branch: 'draft' as PageRequest['branch'], // Avoid flattening down to string
host: 'localhost',
port: 8080,
path: '/admin/site/preview/my-project/draft/my-site',
rawPath: '/admin/site/preview/my-project/draft/my-site',
url: 'http://localhost:8080/admin/site/preview/my-project/draft/my-site',
remoteAddress: '127.0.0.1',
webSocket: false,
repositoryId: 'com.enonic.cms.my-project',
params: {},
headers: {
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Accept-Language': 'en-GB,en-US;q=0.9,en;q=0.8,no;q=0.7',
Connection: 'keep-alive',
Cookie: 'app.browse.RecentItemsList=portal%3Asite; JSESSIONID=19g9dxfzwnyqo1i6ufom5ksuhg0',
Host: 'localhost:8080',
Referer: 'http://localhost:8080/admin/tool/com.enonic.app.contentstudio/main',
'sec-ch-ua': 'Chromium;v=128, Not;A=Brand;v=24, Google Chrome;v=128',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': 'macOS',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-origin',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36',
} as Record<string, string>,
getHeader: (header: string): string => {
return pageRequest.headers[header];
},
cookies: {
'app.browse.RecentItemsList': 'portal%3Asite',
JSESSIONID: '19g9dxfzwnyqo1i6ufom5ksuhg0',
},
};

expectAssignable<PageRequest>(pageRequest);

expectNotAssignable<PageRequest>({
...pageRequest,
contextPath: '/contextPath',
});

expectNotAssignable<PageRequest>({
...pageRequest,
validTicket: true,
});

expectNotAssignable<PageRequest>({
...pageRequest,
branch: 'stringButNotMatchingLiterals',
});

expectNotAssignable<PageRequest>({
...pageRequest,
method: 'stringButNotMatchingLiterals',
});

expectNotAssignable<PageRequest>({
...pageRequest,
mode: 'stringButNotMatchingLiterals',
});

expectNotAssignable<PageRequest>({
...pageRequest,
scheme: 'stringButNotMatchingLiterals',
});

expectNotAssignable<PageRequest>({
...pageRequest,
cookies: undefined,
});

expectNotAssignable<PageRequest>({
...pageRequest,
headers: undefined,
});

expectNotAssignable<PageRequest>({
...pageRequest,
params: undefined,
});

expectNotAssignable<PageRequest>({
...pageRequest,
rawPath: undefined,
});

expectNotAssignable<PageRequest>({
...pageRequest,
remoteAddress: undefined,
});

expectNotAssignable<PageRequest>({
...pageRequest,
repositoryId: undefined,
});

expectNotAssignable<PageRequest>({
...pageRequest,
webSocket: undefined,
});
168 changes: 168 additions & 0 deletions modules/lib/test/PageController.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import type {
Controller,
Request,
RequestBranch,
RequestHeaders,
RequestMethod,
RequestMode,
RequestScheme,
} from '../core/index';

import {
expectAssignable,
expectNotAssignable,
} from 'tsd';


type PageRequest = Omit<Request<{
// Only allow literal string
branch: RequestBranch
method: RequestMethod
mode: RequestMode
scheme: RequestScheme

// Make some optional properties required
repositoryId: string
webSocket: boolean
}>,
// Omit/Disallow some optional properties
'contextPath' | 'validTicket'
>;

const pageRequest = {
method: 'GET',
scheme: 'http',
mode: 'preview',
branch: 'draft',

host: 'localhost',
port: 8080,
path: '/admin/site/preview/my-project/draft/my-site',
rawPath: '/admin/site/preview/my-project/draft/my-site',
url: 'http://localhost:8080/admin/site/preview/my-project/draft/my-site',
remoteAddress: '127.0.0.1',
webSocket: false,
repositoryId: 'com.enonic.cms.my-project',
params: {},
headers: {
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Accept-Language': 'en-GB,en-US;q=0.9,en;q=0.8,no;q=0.7',
Connection: 'keep-alive',
Cookie: 'app.browse.RecentItemsList=portal%3Asite; JSESSIONID=19g9dxfzwnyqo1i6ufom5ksuhg0',
Host: 'localhost:8080',
Referer: 'http://localhost:8080/admin/tool/com.enonic.app.contentstudio/main',
'sec-ch-ua': 'Chromium;v=128, Not;A=Brand;v=24, Google Chrome;v=128',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': 'macOS',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-origin',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36',
},
getHeader: (header: string): string | null => {
// When you just create an object, without declaring it's type, the type of the object is "autogenerated".
// The autogenerated type has named properties rather than indexed properties.
// If you later try to access a property using an index, it causes type errors, since "named" properties are not "indexed".
// You can fix this either by casting to a type with an index signature, either upon creation, or on each usage.
// Here the latter is done:
const value = (pageRequest.headers as RequestHeaders)[header];
if (value === undefined) {
return null;
}
return value;
},
cookies: {
'app.browse.RecentItemsList': 'portal%3Asite',
JSESSIONID: '19g9dxfzwnyqo1i6ufom5ksuhg0',
},
} as
// When you just create an object, without declaring it's type, the type of the object is "autogenerated".
// For properties that have literal string values, they are simply flattened down to string.
// Since the value of some of the properties of PageRequest are limitied to literal string,
// that causes a mismatch when comparing the autogenerated type with the PageRequest type.
// Just adding as const to the whole object avoids the flattening down to string.
const;

expectAssignable<PageRequest>(pageRequest);

expectNotAssignable<PageRequest>({
...pageRequest,
contextPath: '/contextPath',
});

expectNotAssignable<PageRequest>({
...pageRequest,
validTicket: true,
});

expectNotAssignable<PageRequest>({
...pageRequest,
branch: 'stringButNotMatchingLiterals',
});

expectNotAssignable<PageRequest>({
...pageRequest,
method: 'stringButNotMatchingLiterals',
});

expectNotAssignable<PageRequest>({
...pageRequest,
mode: 'stringButNotMatchingLiterals',
});

expectNotAssignable<PageRequest>({
...pageRequest,
scheme: 'stringButNotMatchingLiterals',
});

expectNotAssignable<PageRequest>({
...pageRequest,
cookies: undefined,
});

expectNotAssignable<PageRequest>({
...pageRequest,
headers: undefined,
});

expectNotAssignable<PageRequest>({
...pageRequest,
params: undefined,
});

expectNotAssignable<PageRequest>({
...pageRequest,
rawPath: undefined,
});

expectNotAssignable<PageRequest>({
...pageRequest,
remoteAddress: undefined,
});

expectNotAssignable<PageRequest>({
...pageRequest,
repositoryId: undefined,
});

expectNotAssignable<PageRequest>({
...pageRequest,
webSocket: undefined,
});

type PageController = Controller<PageRequest>;

const log = {
info: (message?: string, ...optionalParams: string[]) => { /* no-op */ },
};

expectAssignable<PageController>({
get: (request: PageRequest) => {
log.info('get request:%s', JSON.stringify(request, null, 4));
return {
status: 200,
};
},
});

0 comments on commit e24726d

Please sign in to comment.