Add more control on http trigger (#21216)
add "new Response" utils to define response code or content type of http route triggered logic function responses follow up of https://github.com/twentyhq/twenty/pull/21214 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
export const LOGIC_FUNCTION_HTTP_RESPONSE_MARKER = '__twentyHttpResponse';
|
||||
|
||||
export type LogicFunctionHttpResponse = {
|
||||
__twentyHttpResponse: true;
|
||||
body: unknown;
|
||||
status?: number;
|
||||
headers?: Record<string, string>;
|
||||
};
|
||||
|
||||
const isValidHttpStatusCode = (value: unknown): value is number =>
|
||||
typeof value === 'number' &&
|
||||
Number.isInteger(value) &&
|
||||
value >= 100 &&
|
||||
value <= 599;
|
||||
|
||||
const isStringRecord = (value: unknown): value is Record<string, string> =>
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
!Array.isArray(value) &&
|
||||
Object.values(value).every((entry) => typeof entry === 'string');
|
||||
|
||||
export const isLogicFunctionHttpResponse = (
|
||||
value: unknown,
|
||||
): value is LogicFunctionHttpResponse => {
|
||||
if (typeof value !== 'object' || value === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const candidate = value as Record<string, unknown>;
|
||||
|
||||
return (
|
||||
candidate[LOGIC_FUNCTION_HTTP_RESPONSE_MARKER] === true &&
|
||||
(candidate.status === undefined ||
|
||||
isValidHttpStatusCode(candidate.status)) &&
|
||||
(candidate.headers === undefined || isStringRecord(candidate.headers))
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
isLogicFunctionHttpResponse,
|
||||
LOGIC_FUNCTION_HTTP_RESPONSE_MARKER,
|
||||
} from '../LogicFunctionResponse';
|
||||
|
||||
describe('isLogicFunctionHttpResponse', () => {
|
||||
it('returns true when the marker is present and true', () => {
|
||||
expect(
|
||||
isLogicFunctionHttpResponse({
|
||||
[LOGIC_FUNCTION_HTTP_RESPONSE_MARKER]: true,
|
||||
body: { ok: true },
|
||||
status: 201,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for a plain object with body/status keys but no marker', () => {
|
||||
expect(isLogicFunctionHttpResponse({ body: 'x', status: 200 })).toBe(false);
|
||||
});
|
||||
|
||||
it.each([null, undefined, 'string', 42, true])(
|
||||
'returns false for non-object value %p',
|
||||
(value) => {
|
||||
expect(isLogicFunctionHttpResponse(value)).toBe(false);
|
||||
},
|
||||
);
|
||||
|
||||
it('returns false when the marker is not strictly true', () => {
|
||||
expect(
|
||||
isLogicFunctionHttpResponse({
|
||||
[LOGIC_FUNCTION_HTTP_RESPONSE_MARKER]: 'yes',
|
||||
body: null,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true when status and headers are absent', () => {
|
||||
expect(
|
||||
isLogicFunctionHttpResponse({
|
||||
[LOGIC_FUNCTION_HTTP_RESPONSE_MARKER]: true,
|
||||
body: { ok: true },
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for valid status and headers', () => {
|
||||
expect(
|
||||
isLogicFunctionHttpResponse({
|
||||
[LOGIC_FUNCTION_HTTP_RESPONSE_MARKER]: true,
|
||||
body: 'ok',
|
||||
status: 204,
|
||||
headers: { 'Content-Type': 'text/plain', 'X-Custom': 'foo' },
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['a string', 'oops'],
|
||||
['NaN', Number.NaN],
|
||||
['Infinity', Number.POSITIVE_INFINITY],
|
||||
['a non-integer', 200.5],
|
||||
['below the http range', 99],
|
||||
['above the http range', 600],
|
||||
])('returns false when status is %s', (_label, status) => {
|
||||
expect(
|
||||
isLogicFunctionHttpResponse({
|
||||
[LOGIC_FUNCTION_HTTP_RESPONSE_MARKER]: true,
|
||||
body: null,
|
||||
status,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['a string', 'oops'],
|
||||
['an array', ['a', 'b']],
|
||||
['null', null],
|
||||
['a record with a non-string value', { 'Content-Length': 42 }],
|
||||
])('returns false when headers is %s', (_label, headers) => {
|
||||
expect(
|
||||
isLogicFunctionHttpResponse({
|
||||
[LOGIC_FUNCTION_HTTP_RESPONSE_MARKER]: true,
|
||||
body: null,
|
||||
headers,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -135,6 +135,11 @@ export type { IsGreaterOrEqual } from './IsGreaterOrEqual.type';
|
||||
export type { IsNever } from './IsNever.type';
|
||||
export type { IsSerializedRelation } from './IsSerializedRelation.type';
|
||||
export type { LogicFunctionEvent } from './LogicFunctionEvent';
|
||||
export type { LogicFunctionHttpResponse } from './LogicFunctionResponse';
|
||||
export {
|
||||
LOGIC_FUNCTION_HTTP_RESPONSE_MARKER,
|
||||
isLogicFunctionHttpResponse,
|
||||
} from './LogicFunctionResponse';
|
||||
export { MessageChannelContactAutoCreationPolicy } from './MessageChannelContactAutoCreationPolicy';
|
||||
export { MessageChannelPendingGroupEmailsAction } from './MessageChannelPendingGroupEmailsAction';
|
||||
export { MessageChannelSyncStage } from './MessageChannelSyncStage';
|
||||
|
||||
Reference in New Issue
Block a user