distri_types/http_request.rs
1//! Typed request/response structs for the HTTP request proxy.
2//!
3//! Used by the `POST /request` server endpoint and the client-side
4//! `http_request` tool handler. Serializes cleanly across the wire.
5
6use std::collections::HashMap;
7
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10
11/// HTTP method.
12#[derive(Debug, Clone, Serialize, Deserialize, Default)]
13#[serde(rename_all = "UPPERCASE")]
14pub enum HttpMethod {
15 #[default]
16 #[serde(alias = "get")]
17 GET,
18 #[serde(alias = "post")]
19 POST,
20 #[serde(alias = "put")]
21 PUT,
22 #[serde(alias = "patch")]
23 PATCH,
24 #[serde(alias = "delete")]
25 DELETE,
26}
27
28impl std::fmt::Display for HttpMethod {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 Self::GET => write!(f, "GET"),
32 Self::POST => write!(f, "POST"),
33 Self::PUT => write!(f, "PUT"),
34 Self::PATCH => write!(f, "PATCH"),
35 Self::DELETE => write!(f, "DELETE"),
36 }
37 }
38}
39
40/// Input for an HTTP request — matches the tool parameter schema.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct HttpRequestInput {
43 /// Absolute URL. May contain `$VAR_NAME` for variable substitution.
44 pub url: String,
45 /// HTTP method.
46 #[serde(default)]
47 pub method: HttpMethod,
48 /// Request headers. May contain `$VAR_NAME` for variable substitution.
49 /// Set `x-connection-id` to inject an OAuth Bearer token.
50 #[serde(default)]
51 pub headers: HashMap<String, String>,
52 /// Request body. Sent as JSON if no Content-Type is set.
53 /// May contain `$VAR_NAME` for variable substitution.
54 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub body: Option<serde_json::Value>,
56}
57
58/// Configuration for an HTTP request factory (type = "http").
59#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
60pub struct HttpFactoryConfig {
61 /// Base URL for all requests. May contain $VAR_NAME.
62 pub base_url: String,
63 /// Default headers merged into every request. May contain $VAR_NAME.
64 #[serde(default)]
65 pub headers: HashMap<String, String>,
66}
67
68/// Input for a factory-created HTTP tool.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct HttpFactoryToolInput {
71 /// Request path (appended to base_url). Used for platform API calls.
72 #[serde(default)]
73 pub path: Option<String>,
74 /// Absolute URL. When set, `path` is ignored and `base_url` is NOT prepended.
75 /// Use this for external API calls (e.g., googleapis.com, slack.com).
76 /// Set `x-connection-id` header to auto-inject OAuth Bearer token.
77 #[serde(default)]
78 pub url: Option<String>,
79 /// HTTP method. Defaults to GET.
80 #[serde(default)]
81 pub method: HttpMethod,
82 /// Additional headers (merged with factory defaults, per-call wins).
83 #[serde(default)]
84 pub headers: HashMap<String, String>,
85 /// Request body.
86 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub body: Option<serde_json::Value>,
88}
89
90impl HttpFactoryConfig {
91 /// Build an HttpRequestInput from factory defaults + per-call input.
92 ///
93 /// If `url` is set, use it as-is (external API call — base_url is NOT prepended).
94 /// If `path` is set, prepend base_url (platform API call).
95 /// Factory default headers are merged, but per-call headers win on conflict.
96 pub fn build_request(&self, input: &HttpFactoryToolInput) -> HttpRequestInput {
97 let url = if let Some(ref absolute_url) = input.url {
98 // External API call — use absolute URL directly, skip base_url
99 absolute_url.clone()
100 } else if let Some(ref path) = input.path {
101 // Platform API call — prepend base_url
102 format!("{}{}", self.base_url.trim_end_matches('/'), path)
103 } else {
104 // Fallback: just use base_url (shouldn't normally happen)
105 self.base_url.clone()
106 };
107
108 // For external URLs (url field), don't inject factory default headers
109 // (they contain platform auth like x-api-key which shouldn't leak to external APIs)
110 let headers = if input.url.is_some() {
111 input.headers.clone()
112 } else {
113 let mut headers = self.headers.clone();
114 headers.extend(input.headers.clone());
115 headers
116 };
117
118 HttpRequestInput {
119 url,
120 method: input.method.clone(),
121 headers,
122 body: input.body.clone(),
123 }
124 }
125}
126
127/// Response from an HTTP request.
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct HttpRequestResponse {
130 /// HTTP status code.
131 pub status: u16,
132 /// Whether the status is 2xx.
133 pub ok: bool,
134 /// Filtered response headers (content-type, location, ratelimit, etc.)
135 pub headers: HashMap<String, String>,
136 /// Response body — parsed as JSON if content-type is application/json,
137 /// otherwise a string.
138 pub body: serde_json::Value,
139}