mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
172 lines
5.5 KiB
TypeScript
172 lines
5.5 KiB
TypeScript
import {
|
|
artifactFileCancellationCause,
|
|
artifactFilePrimaryFailure,
|
|
bindArtifactFileCleanup,
|
|
} from "../artifacts/ArtifactFileOperation.js";
|
|
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
|
|
import { readStableArtifact } from "../artifacts/readStableArtifact.js";
|
|
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
|
|
import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js";
|
|
import type { PythonPycPort } from "../application/python/PythonPycPort.js";
|
|
import type { ExecutionOptions } from "../application/AnalysisProvider.js";
|
|
import { AnalysisError } from "../domain/analysisErrorBase.js";
|
|
import {
|
|
AnalysisAccessDeniedError,
|
|
AnalysisArtifactChangedError,
|
|
AnalysisCancelledError,
|
|
AnalysisInputError,
|
|
AnalysisResourceConstraintError,
|
|
AnalysisUnsupportedTargetError,
|
|
} from "../domain/analysisErrorCore.js";
|
|
import type { InspectPythonPycInput } from "../domain/python/pythonPyc.js";
|
|
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
|
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
|
|
import { err, ok } from "../domain/result.js";
|
|
import {
|
|
PythonPycFormatFailure,
|
|
PythonPycResourceFailure,
|
|
} from "./PythonPyc.errors.js";
|
|
import { parsePythonPyc } from "./PythonPycParser.js";
|
|
|
|
/** Bundled reader identity. Header python_version is a separate observed mapping. */
|
|
export const PYTHON_PYC_PROVIDER_IDENTITY = {
|
|
id: "rea-python-pyc",
|
|
name: "REA CPython pyc parser",
|
|
version: "1",
|
|
} as const;
|
|
|
|
const OPERATION = "inspect_python_pyc";
|
|
|
|
const failureForPrimary = (cause: unknown, input: InspectPythonPycInput) => {
|
|
if (cause instanceof AnalysisError) return cause;
|
|
if (cause instanceof ArtifactReaderFailure && cause.reason === "integrity")
|
|
return new AnalysisArtifactChangedError(
|
|
OPERATION,
|
|
input.path,
|
|
cause.message,
|
|
{ cause },
|
|
);
|
|
if (cause instanceof ArtifactReaderFailure && cause.reason === "cancelled")
|
|
return new AnalysisCancelledError(OPERATION, {
|
|
cause: artifactFileCancellationCause(cause),
|
|
});
|
|
if (cause instanceof PythonPycResourceFailure)
|
|
return new AnalysisResourceConstraintError(
|
|
OPERATION,
|
|
cause.boundary === "depth" ? "capacity" : "memory",
|
|
cause.message,
|
|
cause.reported_limits,
|
|
{ cause },
|
|
);
|
|
if (cause instanceof PythonPycFormatFailure)
|
|
return cause.kind === "unsupported"
|
|
? new AnalysisUnsupportedTargetError(
|
|
OPERATION,
|
|
input.path,
|
|
cause.message,
|
|
{ cause },
|
|
)
|
|
: new AnalysisInputError(OPERATION, { cause }, [
|
|
{
|
|
path: ["path"],
|
|
reason: "invalid_format",
|
|
message: `Selected pyc ${input.path}: ${cause.message}`,
|
|
},
|
|
]);
|
|
if (cause instanceof ArtifactReaderFailure) {
|
|
if (cause.reason === "limit")
|
|
return new AnalysisResourceConstraintError(
|
|
OPERATION,
|
|
"file-size",
|
|
cause.message,
|
|
{ max_file_bytes: input.max_file_bytes },
|
|
{ cause },
|
|
);
|
|
if (cause.reason === "path" || cause.reason === "format")
|
|
return new AnalysisInputError(OPERATION, { cause }, [
|
|
{
|
|
path: ["path"],
|
|
reason: "invalid_format",
|
|
message: cause.message,
|
|
},
|
|
]);
|
|
}
|
|
if (cause instanceof Error && "code" in cause) {
|
|
if (cause.code === "EACCES" || cause.code === "EPERM")
|
|
return new AnalysisAccessDeniedError(OPERATION, input.path, cause.code, {
|
|
cause,
|
|
});
|
|
if (
|
|
["ENOENT", "ENOTDIR", "ELOOP", "ENAMETOOLONG"].includes(
|
|
String(cause.code),
|
|
)
|
|
)
|
|
return new AnalysisInputError(OPERATION, { cause }, [
|
|
{
|
|
path: ["path"],
|
|
reason: "invalid_value",
|
|
message: `Selected pyc could not be read (${String(cause.code)}): ${input.path}.`,
|
|
},
|
|
]);
|
|
}
|
|
return new ProviderAdapterError(PYTHON_PYC_PROVIDER_IDENTITY.id, OPERATION, {
|
|
cause,
|
|
diagnostics: {
|
|
path: input.path,
|
|
reason: cause instanceof Error ? cause.message : String(cause),
|
|
},
|
|
});
|
|
};
|
|
|
|
const failureFor = (cause: unknown, input: InspectPythonPycInput) => {
|
|
const primary = failureForPrimary(artifactFilePrimaryFailure(cause), input);
|
|
if (!(cause instanceof ArtifactReaderFailure) || cause.cleanup === undefined)
|
|
return primary;
|
|
const cleanup = new ProviderCleanupError(
|
|
PYTHON_PYC_PROVIDER_IDENTITY.id,
|
|
cause.cleanup.resources,
|
|
{ reason: cause.cleanup.reason },
|
|
{ operation: OPERATION, cause },
|
|
);
|
|
const error = analysisErrorWithCleanupFailure(primary, cleanup, OPERATION);
|
|
bindArtifactFileCleanup(cause, error);
|
|
return error;
|
|
};
|
|
|
|
/** Portable parser over one bounded, identity-checked regular file. */
|
|
export class PythonPycProvider implements PythonPycPort {
|
|
readonly identity = PYTHON_PYC_PROVIDER_IDENTITY;
|
|
readonly #resources = new ArtifactResourceScope();
|
|
|
|
close(): Promise<void> {
|
|
return this.#resources.close();
|
|
}
|
|
|
|
async inspect(input: InspectPythonPycInput, options?: ExecutionOptions) {
|
|
try {
|
|
const snapshot = await this.#resources.run(() =>
|
|
readStableArtifact(
|
|
input.path,
|
|
input.max_file_bytes,
|
|
options?.signal,
|
|
undefined,
|
|
this.#resources,
|
|
),
|
|
);
|
|
return ok(
|
|
parsePythonPyc(
|
|
snapshot.bytes,
|
|
{
|
|
path: input.path,
|
|
sha256: snapshot.sha256,
|
|
bytes: snapshot.bytes.length,
|
|
},
|
|
input,
|
|
),
|
|
);
|
|
} catch (cause) {
|
|
return err(failureFor(cause, input));
|
|
}
|
|
}
|
|
}
|