Skip to content
Merged
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
74 changes: 44 additions & 30 deletions packages/plugins/google-discovery/src/sdk/invoke.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Effect, Layer, Option } from "effect";
import { Effect, Layer, Option, Schema } from "effect";
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http";

import type { PluginCtx, StorageFailure } from "@executor-js/sdk/core";
Expand All @@ -13,6 +13,16 @@ import {

const SAFE_METHODS = new Set(["get", "head", "options"]);

const UnknownErrorMessage = Schema.Struct({ message: Schema.String });
const decodeUnknownErrorMessage = Schema.decodeUnknownOption(UnknownErrorMessage);

const errorMessageFromUnknown = (cause: unknown): string => {
const decoded = decodeUnknownErrorMessage(cause);
if (Option.isSome(decoded)) return decoded.value.message;
// oxlint-disable-next-line executor/no-unknown-error-message -- boundary: preserves existing fallback text for HTTP client errors
return String(cause);
};

export const annotationsForOperation = (
method: string,
pathTemplate: string,
Expand Down Expand Up @@ -46,19 +56,27 @@ const replacePathParameters = (input: {
pathTemplate: string;
args: Record<string, unknown>;
parameters: readonly GoogleDiscoveryParameter[];
}): string =>
input.pathTemplate.replaceAll(/\{([^}]+)\}/g, (_, name: string) => {
const parameter = input.parameters.find(
(entry) => entry.location === "path" && entry.name === name,
);
const values = stringValuesFromParameter(input.args[name], false);
if (values.length === 0) {
if (parameter?.required) {
throw new Error(`Missing required path parameter: ${name}`);
}): Effect.Effect<string, GoogleDiscoveryInvocationError> =>
Effect.gen(function* () {
let failure: GoogleDiscoveryInvocationError | undefined;
const resolved = input.pathTemplate.replaceAll(/\{([^}]+)\}/g, (_, name: string) => {
const parameter = input.parameters.find(
(entry) => entry.location === "path" && entry.name === name,
);
const values = stringValuesFromParameter(input.args[name], false);
if (values.length === 0) {
if (parameter?.required) {
failure = new GoogleDiscoveryInvocationError({
message: `Missing required path parameter: ${name}`,
statusCode: Option.none(),
});
}
return "";
}
return "";
}
return encodeURIComponent(values[0]!);
return encodeURIComponent(values[0]!);
});
if (failure) return yield* failure;
return resolved;
});

const resolveBaseUrl = (source: GoogleDiscoveryStoredSourceData): string =>
Expand Down Expand Up @@ -87,7 +105,7 @@ const performRequest = Effect.fn("GoogleDiscovery.invoke")(function* (input: {
}) {
const client = yield* HttpClient.HttpClient;

const resolvedPath = replacePathParameters({
const resolvedPath = yield* replacePathParameters({
pathTemplate: input.pathTemplate,
args: input.args,
parameters: input.parameters,
Expand Down Expand Up @@ -138,7 +156,7 @@ const performRequest = Effect.fn("GoogleDiscovery.invoke")(function* (input: {
Effect.mapError(
(err) =>
new GoogleDiscoveryInvocationError({
message: `HTTP request failed: ${err.message}`,
message: `HTTP request failed: ${errorMessageFromUnknown(err)}`,
statusCode: Option.none(),
cause: err,
}),
Expand All @@ -147,9 +165,9 @@ const performRequest = Effect.fn("GoogleDiscovery.invoke")(function* (input: {

const contentType = response.headers["content-type"] ?? null;
const mapBodyError = Effect.mapError(
(err: { readonly message?: string }) =>
(err: unknown) =>
new GoogleDiscoveryInvocationError({
message: `Failed to read response body: ${err.message ?? String(err)}`,
message: `Failed to read response body: ${errorMessageFromUnknown(err)}`,
statusCode: Option.some(response.status),
cause: err,
}),
Expand Down Expand Up @@ -191,21 +209,17 @@ export const invokeGoogleDiscoveryTool = (input: {
Effect.gen(function* () {
const entry = yield* input.ctx.storage.getBinding(input.toolId, input.toolScope);
if (!entry) {
return yield* Effect.fail(
new GoogleDiscoveryInvocationError({
message: `No Google Discovery operation found for tool "${input.toolId}"`,
statusCode: Option.none(),
}),
);
return yield* new GoogleDiscoveryInvocationError({
message: `No Google Discovery operation found for tool "${input.toolId}"`,
statusCode: Option.none(),
});
}
const stored = yield* input.ctx.storage.getSource(entry.namespace, input.toolScope);
if (!stored) {
return yield* Effect.fail(
new GoogleDiscoveryInvocationError({
message: `No Google Discovery source found for "${entry.namespace}"`,
statusCode: Option.none(),
}),
);
return yield* new GoogleDiscoveryInvocationError({
message: `No Google Discovery source found for "${entry.namespace}"`,
statusCode: Option.none(),
});
}
const source = stored.config;

Expand All @@ -215,7 +229,7 @@ export const invokeGoogleDiscoveryTool = (input: {
Effect.mapError(
(err) =>
new GoogleDiscoveryOAuthError({
message: "message" in err ? (err as { message: string }).message : String(err),
message: errorMessageFromUnknown(err),
}),
),
)}`
Expand Down
Loading