Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 67 additions & 36 deletions src/api/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,15 @@ import {
setEntityEndpointsUpdater,
setEventEndpointsUpdater,
setRelationshipEndpointsUpdater,
UnauthorisedError,
} from '../runtime/defs.js';
import { evaluate } from '../runtime/interpreter.js';
import { Config } from '../runtime/state.js';
import {
getErrorCode,
httpStatusFromError,
logEntityRouteError,
resolveEntityErrorMessage,
} from '../runtime/errors/http-error.js';
import {
findFileByFilename,
createFileRecord,
Expand Down Expand Up @@ -434,22 +439,34 @@ function ok(res: Response) {
}

function statusFromErrorType(err: any): number {
if (err instanceof UnauthorisedError) {
return 401;
} else if (err instanceof BadRequestError) {
return 400;
} else {
return 500;
}
return httpStatusFromError(err);
}

function internalError(res: Response) {
function entityRouteError(res: Response, moduleName: string, entryName: string) {
return (reason: any) => {
logger.error(reason);
res.status(statusFromErrorType(reason)).send(reason.message);
const code = getErrorCode(reason);
const defaultMsg = reason instanceof Error ? reason.message : String(reason ?? 'Unknown error');
const message = resolveEntityErrorMessage(moduleName, entryName, code, defaultMsg);
logEntityRouteError(reason, code);
res.status(httpStatusFromError(reason)).json({ code, message });
};
}

function sendAuthRequired(res: Response, moduleName: string, entryName: string) {
const code = 'AL_HTTP_AUTH_REQUIRED';
const defaultMsg = 'Authorization required';
const message = resolveEntityErrorMessage(moduleName, entryName, code, defaultMsg);
res.status(401).json({ code, message });
}

function sendEntityCatch(res: Response, moduleName: string, entryName: string, err: any) {
const code = getErrorCode(err);
const defaultMsg = err instanceof Error ? err.message : String(err);
const message = resolveEntityErrorMessage(moduleName, entryName, code, defaultMsg);
logEntityRouteError(err, code);
res.status(httpStatusFromError(err)).json({ code, message });
}

function formatAttrValue(v: any, n: string): string {
let av = isString(v) ? `"${v}"` : v;
if (av instanceof Object) {
Expand Down Expand Up @@ -565,18 +582,19 @@ async function handleEventPost(
try {
const sessionInfo = await verifyAuth(moduleName, eventName, req.headers.authorization);
if (isNoSession(sessionInfo)) {
res.status(401).send('Authorization required');
sendAuthRequired(res, moduleName, eventName);
return;
}
const inst: Instance = makeInstance(
moduleName,
eventName,
objectAsInstanceAttributes(req.body)
).setAuthContext(sessionInfo);
evaluate(inst).then(ok(res)).catch(internalError(res));
evaluate(inst)
.then(ok(res))
.catch(entityRouteError(res, moduleName, eventName));
} catch (err: any) {
logger.error(err);
res.status(500).send(err.toString());
sendEntityCatch(res, moduleName, eventName, err);
}
}

Expand All @@ -586,10 +604,12 @@ async function handleEntityPost(
req: Request,
res: Response
): Promise<void> {
const routeModule = moduleName;
const routeEntry = entityName;
try {
const sessionInfo = await verifyAuth(moduleName, entityName, req.headers.authorization);
if (isNoSession(sessionInfo)) {
res.status(401).send('Authorization required');
sendAuthRequired(res, routeModule, routeEntry);
return;
}
const entityFqName = makeFqName(moduleName, entityName);
Expand All @@ -601,10 +621,11 @@ async function handleEntityPost(
objectAsInstanceAttributes(req.body),
entityFqName
);
parseAndEvaluateStatement(pattern, sessionInfo.userId).then(ok(res)).catch(internalError(res));
parseAndEvaluateStatement(pattern, sessionInfo.userId)
.then(ok(res))
.catch(entityRouteError(res, routeModule, routeEntry));
} catch (err: any) {
logger.error(err);
res.status(500).send(err.toString());
sendEntityCatch(res, routeModule, routeEntry, err);
}
}

Expand All @@ -614,11 +635,13 @@ async function handleEntityGet(
req: Request,
res: Response
): Promise<void> {
const routeModule = moduleName;
const routeEntry = entityName;
try {
const path = pathFromRequest(moduleName, entityName, req);
const sessionInfo = await verifyAuth(moduleName, entityName, req.headers.authorization);
if (isNoSession(sessionInfo)) {
res.status(401).send('Authorization required');
sendAuthRequired(res, routeModule, routeEntry);
return;
}
let pattern = '';
Expand All @@ -627,10 +650,11 @@ async function handleEntityGet(
} else {
pattern = queryPatternFromPath(path, req);
}
parseAndEvaluateStatement(pattern, sessionInfo.userId).then(ok(res)).catch(internalError(res));
parseAndEvaluateStatement(pattern, sessionInfo.userId)
.then(ok(res))
.catch(entityRouteError(res, routeModule, routeEntry));
} catch (err: any) {
logger.error(err);
res.status(500).send(err.toString());
sendEntityCatch(res, routeModule, routeEntry, err);
}
}

Expand Down Expand Up @@ -740,11 +764,13 @@ async function handleEntityPut(
req: Request,
res: Response
): Promise<void> {
const routeModule = moduleName;
const routeEntry = entityName;
try {
const path = pathFromRequest(moduleName, entityName, req);
const sessionInfo = await verifyAuth(moduleName, entityName, req.headers.authorization);
if (isNoSession(sessionInfo)) {
res.status(401).send('Authorization required');
sendAuthRequired(res, routeModule, routeEntry);
return;
}
const attrs = objectAsInstanceAttributes(req.body);
Expand All @@ -753,10 +779,11 @@ async function handleEntityPut(
moduleName = r[0];
entityName = r[1];
const pattern = patternFromAttributes(moduleName, entityName, attrs);
parseAndEvaluateStatement(pattern, sessionInfo.userId).then(ok(res)).catch(internalError(res));
parseAndEvaluateStatement(pattern, sessionInfo.userId)
.then(ok(res))
.catch(entityRouteError(res, routeModule, routeEntry));
} catch (err: any) {
logger.error(err);
res.status(500).send(err.toString());
sendEntityCatch(res, routeModule, routeEntry, err);
}
}

Expand All @@ -766,19 +793,22 @@ async function handleEntityDelete(
req: Request,
res: Response
): Promise<void> {
const routeModule = moduleName;
const routeEntry = entityName;
try {
const path = pathFromRequest(moduleName, entityName, req);
const sessionInfo = await verifyAuth(moduleName, entityName, req.headers.authorization);
if (isNoSession(sessionInfo)) {
res.status(401).send('Authorization required');
sendAuthRequired(res, routeModule, routeEntry);
return;
}
const cmd = req.query.purge == 'true' ? 'purge' : 'delete';
const pattern = `${cmd} ${queryPatternFromPath(path, req)}`;
parseAndEvaluateStatement(pattern, sessionInfo.userId).then(ok(res)).catch(internalError(res));
parseAndEvaluateStatement(pattern, sessionInfo.userId)
.then(ok(res))
.catch(entityRouteError(res, routeModule, routeEntry));
} catch (err: any) {
logger.error(err);
res.status(500).send(err.toString());
sendEntityCatch(res, routeModule, routeEntry, err);
}
}

Expand All @@ -792,7 +822,7 @@ async function handleBetweenRelationshipLinking(
try {
const sessionInfo = await verifyAuth(moduleName, betweenRelName, req.headers.authorization);
if (isNoSession(sessionInfo)) {
res.status(401).send('Authorization required');
sendAuthRequired(res, moduleName, betweenRelName);
return;
}
const path = await linkInstancesEvent(moduleName, betweenRelName, unlink);
Expand All @@ -801,10 +831,11 @@ async function handleBetweenRelationshipLinking(
path.getEntryName(),
objectAsInstanceAttributes(req.body)
);
parseAndEvaluateStatement(pattern, sessionInfo.userId).then(ok(res)).catch(internalError(res));
parseAndEvaluateStatement(pattern, sessionInfo.userId)
.then(ok(res))
.catch(entityRouteError(res, moduleName, betweenRelName));
} catch (err: any) {
logger.error(err);
res.status(500).send(err.toString());
sendEntityCatch(res, moduleName, betweenRelName, err);
}
}

Expand Down Expand Up @@ -858,7 +889,7 @@ function createChildPattern(moduleName: string, entityName: string, req: Request
);
return `{${parentFqname} {${PathAttributeNameQuery} "${parentPath}"}, ${relName} ${cp}}`;
} catch (err: any) {
throw new BadRequestError(err.message);
throw new BadRequestError(err.message, { agentlangCode: 'AL_HTTP_CHILD_PATTERN_INVALID' });
}
}

Expand Down
19 changes: 15 additions & 4 deletions src/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { registerOpenApiModule } from '../runtime/openapi.js';
import { initDatabase, resetDefaultDatabase } from '../runtime/resolvers/sqldb/database.js';
import { runInitFunctions } from '../runtime/util.js';
import { startServer } from '../api/http.js';
import { initErrorMessageOverrides } from '../runtime/errors/http-error.js';
import { enableExecutionGraph } from '../runtime/exec-graph.js';
import { importModule } from '../runtime/jsmodules.js';
import {
Expand Down Expand Up @@ -142,8 +143,18 @@ export const parseAndValidate = async (fileName: string): Promise<void> => {
};

let LastActiveConfig: Config | undefined;

export async function runPostInitTasks(appSpec?: ApplicationSpec, config?: Config) {
let LastConfigDir: string | undefined;

export async function runPostInitTasks(
appSpec?: ApplicationSpec,
config?: Config,
configDir?: string
) {
if (configDir !== undefined) {
LastConfigDir = configDir;
}
const effectiveConfigDir = configDir ?? LastConfigDir;
await initErrorMessageOverrides(effectiveConfigDir, config?.customErrorMessages);
await initDatabase(config?.store);
await importModule('../runtime/api.js', 'agentlang');
await runInitFunctions();
Expand All @@ -164,7 +175,7 @@ export async function runPostInitTasks(appSpec?: ApplicationSpec, config?: Confi
}

export async function runPostInitTasksWithLastActiveConfig() {
await runPostInitTasks(undefined, LastActiveConfig);
await runPostInitTasks(undefined, LastActiveConfig, LastConfigDir);
}

let execGraphEnabled = false;
Expand Down Expand Up @@ -256,7 +267,7 @@ export const runModule = async (fileName: string, releaseDb: boolean = false): P
}
try {
await load(fileName, undefined, async (appSpec?: ApplicationSpec) => {
await runPostInitTasks(appSpec, config);
await runPostInitTasks(appSpec, config, configDir);
});
} catch (err: any) {
if (isNodeEnv && chalk) {
Expand Down
Loading
Loading