(input: {
readonly ctx: PluginCtx<OpenapiStore>;
readonly integration: IntegrationRecord;
readonly credential: ToolInvocationCredential;
readonly spec?: HealthCheckSpec;
readonly httpClientLayer: Layer.Layer<HttpClient.HttpClient, never, never>;
})
| 910 | * reasons: a rejected credential is a `HealthCheckResult` with |
| 911 | * `status: "expired"`, not an error. */ |
| 912 | export const checkHealthOpenApi = (input: { |
| 913 | readonly ctx: PluginCtx<OpenapiStore>; |
| 914 | readonly integration: IntegrationRecord; |
| 915 | readonly credential: ToolInvocationCredential; |
| 916 | readonly spec?: HealthCheckSpec; |
| 917 | readonly httpClientLayer: Layer.Layer<HttpClient.HttpClient, never, never>; |
| 918 | }): Effect.Effect<HealthCheckResult, StorageFailure> => |
| 919 | Effect.gen(function* () { |
| 920 | const checkedAt = Date.now(); |
| 921 | const config = decodeOpenApiIntegrationConfig(input.integration.config); |
| 922 | const spec = input.spec; |
| 923 | if (!spec) { |
| 924 | return { |
| 925 | status: "unknown", |
| 926 | checkedAt, |
| 927 | detail: "No health check configured.", |
| 928 | } satisfies HealthCheckResult; |
| 929 | } |
| 930 | |
| 931 | const integration = String(input.integration.slug); |
| 932 | const binding = yield* resolveHealthCheckBinding( |
| 933 | input.ctx, |
| 934 | integration, |
| 935 | spec.operation, |
| 936 | config, |
| 937 | ); |
| 938 | if (!binding) { |
| 939 | return { |
| 940 | status: "unknown", |
| 941 | checkedAt, |
| 942 | detail: `Health check operation "${spec.operation}" not found on "${integration}".`, |
| 943 | } satisfies HealthCheckResult; |
| 944 | } |
| 945 | |
| 946 | // HTTP RPC reads can use POST; the editor warns users before enabling them. |
| 947 | if ( |
| 948 | REQUIRE_APPROVAL.has(binding.method.toLowerCase()) && |
| 949 | binding.method.toLowerCase() !== "post" |
| 950 | ) { |
| 951 | return { |
| 952 | status: "unknown", |
| 953 | checkedAt, |
| 954 | detail: `Health check operation "${spec.operation}" uses ${binding.method.toUpperCase()} and is not supported for health checks. Pick a read-only operation.`, |
| 955 | } satisfies HealthCheckResult; |
| 956 | } |
| 957 | |
| 958 | const headers: Record<string, string> = { ...(config?.headers ?? {}) }; |
| 959 | const queryParams: Record<string, string> = { ...(config?.queryParams ?? {}) }; |
| 960 | |
| 961 | const template = (config?.authenticationTemplate ?? []).find( |
| 962 | (entry) => String(entry.slug) === String(input.credential.template), |
| 963 | ); |
| 964 | if (template) { |
| 965 | const missing = requiredTemplateVariables(template).filter((name) => { |
| 966 | const value = input.credential.values[name]; |
| 967 | return value == null || value === ""; |
| 968 | }); |
| 969 | if (missing.length > 0) { |
no test coverage detected