Skip to content

Commit 99886a0

Browse files
committed
Restore Workflow files
1 parent be6d697 commit 99886a0

File tree

9 files changed

+1184
-0
lines changed

9 files changed

+1184
-0
lines changed

.fernignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,11 @@ src/main/java/com/pipedream/api/resources/proxy/AsyncProxyClient.java
2424
src/main/java/com/pipedream/api/resources/proxy/ProxyClient.java
2525

2626
# Custom Workflow files
27+
src/main/java/com/pipedream/api/resources/workflows/AsyncWorkflowsClient.java
28+
src/main/java/com/pipedream/api/resources/workflows/requests/InvokeWorkflowOpts.java
29+
src/main/java/com/pipedream/api/resources/workflows/requests/InvokeWorkflowForExternalUserOpts.java
30+
src/main/java/com/pipedream/api/resources/workflows/AsyncRawWorkflowsClient.java
31+
src/main/java/com/pipedream/api/resources/workflows/WorkflowsClient.java
32+
src/main/java/com/pipedream/api/resources/workflows/RawWorkflowsClient.java
33+
src/main/java/com/pipedream/api/resources/workflows/package-info.java
34+
src/main/java/com/pipedream/api/types/HTTPAuthType.java
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
/**
2+
* This file was manually created to add workflow invocation support.
3+
*/
4+
package com.pipedream.api.resources.workflows;
5+
6+
import com.pipedream.api.core.BaseClientApiException;
7+
import com.pipedream.api.core.BaseClientException;
8+
import com.pipedream.api.core.BaseClientHttpResponse;
9+
import com.pipedream.api.core.ClientOptions;
10+
import com.pipedream.api.core.MediaTypes;
11+
import com.pipedream.api.core.ObjectMappers;
12+
import com.pipedream.api.core.RequestOptions;
13+
import com.pipedream.api.resources.workflows.requests.InvokeWorkflowForExternalUserOpts;
14+
import com.pipedream.api.resources.workflows.requests.InvokeWorkflowOpts;
15+
import com.pipedream.api.types.HTTPAuthType;
16+
import java.io.IOException;
17+
import java.net.MalformedURLException;
18+
import java.net.URL;
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
import java.util.concurrent.CompletableFuture;
22+
import okhttp3.Call;
23+
import okhttp3.Callback;
24+
import okhttp3.Headers;
25+
import okhttp3.HttpUrl;
26+
import okhttp3.OkHttpClient;
27+
import okhttp3.Request;
28+
import okhttp3.RequestBody;
29+
import okhttp3.Response;
30+
import okhttp3.ResponseBody;
31+
32+
public class AsyncRawWorkflowsClient {
33+
protected final ClientOptions clientOptions;
34+
private final String workflowDomain;
35+
private final String urlProtocol;
36+
37+
public AsyncRawWorkflowsClient(ClientOptions clientOptions) {
38+
this.clientOptions = clientOptions;
39+
this.workflowDomain = getDefaultWorkflowDomain();
40+
this.urlProtocol = getUrlProtocol();
41+
}
42+
43+
public CompletableFuture<BaseClientHttpResponse<Object>> invoke(InvokeWorkflowOpts request) {
44+
return invoke(request, null);
45+
}
46+
47+
public CompletableFuture<BaseClientHttpResponse<Object>> invoke(
48+
InvokeWorkflowOpts request, RequestOptions requestOptions) {
49+
50+
// Build the workflow URL
51+
String urlString = buildWorkflowUrl(request.getUrlOrEndpoint());
52+
53+
HttpUrl httpUrl;
54+
try {
55+
httpUrl = HttpUrl.parse(urlString);
56+
if (httpUrl == null) {
57+
throw new IllegalArgumentException("Invalid URL: " + urlString);
58+
}
59+
} catch (Exception e) {
60+
CompletableFuture<BaseClientHttpResponse<Object>> future = new CompletableFuture<>();
61+
future.completeExceptionally(new IllegalArgumentException("Invalid URL: " + urlString, e));
62+
return future;
63+
}
64+
65+
// Determine auth type - default to OAuth if not specified
66+
HTTPAuthType authType = request.getAuthType().orElse(HTTPAuthType.OAUTH);
67+
68+
// Prepare headers - start with client options headers (includes OAuth auth if configured)
69+
Map<String, String> allHeaders = new HashMap<>(clientOptions.headers(requestOptions));
70+
71+
// Handle authentication based on type
72+
if (authType == HTTPAuthType.OAUTH) {
73+
// For OAuth, the Authorization header should already be in clientOptions.headers()
74+
// No additional action needed
75+
} else if (authType == HTTPAuthType.STATIC_BEARER) {
76+
// For static_bearer, users must provide the Authorization header in request.getHeaders()
77+
// Their header will override any existing OAuth header when we merge request headers
78+
} else if (authType == HTTPAuthType.NONE) {
79+
// For NONE auth type, set Authorization header to empty string (matches Python SDK)
80+
allHeaders.put("Authorization", "");
81+
}
82+
83+
// Add request-specific headers (can override auth headers for STATIC_BEARER)
84+
if (request.getHeaders().isPresent()) {
85+
allHeaders.putAll(request.getHeaders().get());
86+
}
87+
88+
// Determine HTTP method
89+
String method = request.getMethod().orElse("POST").toUpperCase();
90+
91+
// Prepare request body if needed
92+
RequestBody body = null;
93+
if (request.getBody().isPresent()) {
94+
try {
95+
body = RequestBody.create(
96+
ObjectMappers.JSON_MAPPER.writeValueAsBytes(
97+
request.getBody().get()),
98+
MediaTypes.APPLICATION_JSON);
99+
allHeaders.put("Content-Type", "application/json");
100+
} catch (Exception e) {
101+
CompletableFuture<BaseClientHttpResponse<Object>> future = new CompletableFuture<>();
102+
future.completeExceptionally(new RuntimeException("Failed to serialize request body", e));
103+
return future;
104+
}
105+
} else if (("POST".equals(method) || "PUT".equals(method) || "PATCH".equals(method))) {
106+
// For methods that typically require a body, send an empty body
107+
// to avoid OkHttp's "method POST must have a request body" error
108+
body = RequestBody.create(new byte[0], null);
109+
}
110+
111+
// Build the request
112+
Request.Builder requestBuilder =
113+
new Request.Builder().url(httpUrl).method(method, body).headers(Headers.of(allHeaders));
114+
115+
if (!allHeaders.containsKey("Accept")) {
116+
requestBuilder.addHeader("Accept", "application/json");
117+
}
118+
119+
Request okhttpRequest = requestBuilder.build();
120+
121+
// Execute the request asynchronously
122+
OkHttpClient client = clientOptions.httpClient();
123+
if (requestOptions != null && requestOptions.getTimeout().isPresent()) {
124+
client = clientOptions.httpClientWithTimeout(requestOptions);
125+
}
126+
127+
CompletableFuture<BaseClientHttpResponse<Object>> future = new CompletableFuture<>();
128+
129+
client.newCall(okhttpRequest).enqueue(new Callback() {
130+
@Override
131+
public void onFailure(Call call, IOException e) {
132+
future.completeExceptionally(new BaseClientException("Network error executing HTTP request", e));
133+
}
134+
135+
@Override
136+
public void onResponse(Call call, Response response) throws IOException {
137+
try (ResponseBody responseBody = response.body()) {
138+
if (response.isSuccessful()) {
139+
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
140+
Object parsedResponse;
141+
try {
142+
parsedResponse = ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class);
143+
} catch (Exception e) {
144+
// If JSON parsing fails, return the raw string
145+
parsedResponse = responseBodyString;
146+
}
147+
future.complete(new BaseClientHttpResponse<>(parsedResponse, response));
148+
} else {
149+
String responseBodyString = responseBody != null ? responseBody.string() : "{}";
150+
future.completeExceptionally(new BaseClientApiException(
151+
"Error with status code " + response.code(),
152+
response.code(),
153+
ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class),
154+
response));
155+
}
156+
} catch (Exception e) {
157+
future.completeExceptionally(e);
158+
}
159+
}
160+
});
161+
162+
return future;
163+
}
164+
165+
public CompletableFuture<BaseClientHttpResponse<Object>> invokeForExternalUser(
166+
InvokeWorkflowForExternalUserOpts request) {
167+
return invokeForExternalUser(request, null);
168+
}
169+
170+
public CompletableFuture<BaseClientHttpResponse<Object>> invokeForExternalUser(
171+
InvokeWorkflowForExternalUserOpts request, RequestOptions requestOptions) {
172+
173+
// Validate inputs
174+
if (request.getExternalUserId() == null
175+
|| request.getExternalUserId().trim().isEmpty()) {
176+
CompletableFuture<BaseClientHttpResponse<Object>> future = new CompletableFuture<>();
177+
future.completeExceptionally(new IllegalArgumentException("External user ID is required"));
178+
return future;
179+
}
180+
181+
if (request.getUrl() == null || request.getUrl().trim().isEmpty()) {
182+
CompletableFuture<BaseClientHttpResponse<Object>> future = new CompletableFuture<>();
183+
future.completeExceptionally(new IllegalArgumentException("Workflow URL is required"));
184+
return future;
185+
}
186+
187+
// Prepare headers with external user ID
188+
Map<String, String> headers = new HashMap<>();
189+
if (request.getHeaders().isPresent()) {
190+
headers.putAll(request.getHeaders().get());
191+
}
192+
headers.put("X-PD-External-User-ID", request.getExternalUserId());
193+
194+
// Create a new request with the authentication from the original request and the external user header
195+
InvokeWorkflowOpts invokeRequest = InvokeWorkflowOpts.builder()
196+
.urlOrEndpoint(request.getUrl())
197+
.body(request.getBody())
198+
.headers(headers)
199+
.method(request.getMethod())
200+
.authType(request.getAuthType().orElse(HTTPAuthType.OAUTH))
201+
.build();
202+
203+
return invoke(invokeRequest, requestOptions);
204+
}
205+
206+
/**
207+
* Builds a full workflow URL based on the input.
208+
*
209+
* @param input Either a full URL (with or without protocol) or just an endpoint ID.
210+
* @return The fully constructed URL.
211+
*/
212+
private String buildWorkflowUrl(String input) {
213+
String sanitizedInput = input.trim().toLowerCase();
214+
if (sanitizedInput.isEmpty()) {
215+
throw new IllegalArgumentException("URL or endpoint ID is required");
216+
}
217+
218+
// Check if it's already a full URL
219+
if (sanitizedInput.startsWith("http://") || sanitizedInput.startsWith("https://")) {
220+
try {
221+
URL url = new URL(input);
222+
// Validate the hostname
223+
String workflowDomain = this.workflowDomain;
224+
if (!url.getHost().endsWith(this.workflowDomain)) {
225+
throw new IllegalArgumentException(
226+
"Invalid workflow domain. URL must end with " + this.workflowDomain);
227+
}
228+
return input;
229+
} catch (MalformedURLException e) {
230+
throw new IllegalArgumentException("The provided URL is malformed: " + input, e);
231+
}
232+
}
233+
234+
// Check if it's a URL without protocol
235+
if (sanitizedInput.contains(".")) {
236+
return buildWorkflowUrl("https://" + input);
237+
}
238+
239+
// It's an endpoint ID
240+
if (!sanitizedInput.matches("^e[no][a-z0-9-]+$")) {
241+
throw new IllegalArgumentException(
242+
"Invalid endpoint ID format. Must contain only letters, numbers, and hyphens, "
243+
+ "and start with either 'en' or 'eo'.");
244+
}
245+
246+
return urlProtocol + "://" + sanitizedInput + "." + workflowDomain;
247+
}
248+
249+
private String getDefaultWorkflowDomain() {
250+
String envUrl = clientOptions.environment().getUrl();
251+
// For non-prod environments (dev, staging), use dev domain
252+
if (!envUrl.equals("https://api.pipedream.com") && !envUrl.equals("https://api2.pipedream.com")) {
253+
return "m.d.pipedream.net";
254+
}
255+
// For prod and canary, use standard domain
256+
return "m.pipedream.net";
257+
}
258+
259+
private String getUrlProtocol() {
260+
String envUrl = clientOptions.environment().getUrl();
261+
// For non-prod environments (dev, staging), use http
262+
if (!envUrl.equals("https://api.pipedream.com") && !envUrl.equals("https://api2.pipedream.com")) {
263+
return "http";
264+
}
265+
// For prod and canary, use https
266+
return "https";
267+
}
268+
}

0 commit comments

Comments
 (0)