Skip to content

Commit 9bf1a13

Browse files
committed
feat(axiom sink): Add regional edge support with smart URL handling
Introduces optional `region` field for regional edge domains and enhances `url` field with intelligent path detection. URLs with custom paths are used as-is, while URLs without paths maintain backwards compatibility by appending the legacy path format. Priority: url > region > default cloud endpoint Fully backwards compatible with existing configurations.
1 parent 3146eca commit 9bf1a13

File tree

2 files changed

+169
-15
lines changed

2 files changed

+169
-15
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The `axiom` sink now supports regional edges for data locality. A new optional `region` configuration field allows you to specify the regional edge domain (e.g., `eu-central-1.aws.edge.axiom.co`). When configured, data is sent to `https://{region}/v1/ingest/{dataset}`. The `url` field now intelligently handles paths: URLs with custom paths are used as-is, while URLs without paths maintain backwards compatibility by appending `/v1/datasets/{dataset}/ingest`.
2+
3+
authors: toppercodes

src/sinks/axiom.rs

Lines changed: 166 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ static CLOUD_URL: &str = "https://api.axiom.co";
2929
pub struct AxiomConfig {
3030
/// URI of the Axiom endpoint to send data to.
3131
///
32-
/// Only required if not using Axiom Cloud.
32+
/// If a path is provided, the URL is used as-is.
33+
/// If no path (or only `/`) is provided, `/v1/datasets/{dataset}/ingest` is appended for backwards compatibility.
34+
/// This takes precedence over `region` if both are set.
3335
#[configurable(validation(format = "uri"))]
34-
#[configurable(metadata(docs::examples = "https://axiom.my-domain.com"))]
36+
#[configurable(metadata(docs::examples = "https://api.eu.axiom.co"))]
37+
#[configurable(metadata(docs::examples = "http://localhost:3400/ingest"))]
3538
#[configurable(metadata(docs::examples = "${AXIOM_URL}"))]
3639
url: Option<String>,
3740

@@ -52,6 +55,16 @@ pub struct AxiomConfig {
5255
#[configurable(metadata(docs::examples = "vector_rocks"))]
5356
dataset: String,
5457

58+
/// The Axiom regional edge domain to use for ingestion.
59+
///
60+
/// Specify the domain name only (no scheme, no path).
61+
/// When set, data will be sent to `https://{region}/v1/ingest/{dataset}`.
62+
/// If `url` is also set, `url` takes precedence.
63+
#[configurable(metadata(docs::examples = "${AXIOM_REGION}"))]
64+
#[configurable(metadata(docs::examples = "mumbai.axiom.co"))]
65+
#[configurable(metadata(docs::examples = "eu-central-1.aws.edge.axiom.co"))]
66+
region: Option<String>,
67+
5568
#[configurable(derived)]
5669
#[serde(default)]
5770
request: RequestConfig,
@@ -150,17 +163,35 @@ impl SinkConfig for AxiomConfig {
150163

151164
impl AxiomConfig {
152165
fn build_endpoint(&self) -> String {
153-
let url = if let Some(url) = self.url.as_ref() {
154-
url.clone()
155-
} else {
156-
CLOUD_URL.to_string()
157-
};
166+
// Priority: url > region > default cloud endpoint
167+
168+
// If url is set, check if it has a path
169+
if let Some(url) = &self.url {
170+
let url = url.trim_end_matches('/');
171+
172+
// Parse URL to check if path is provided
173+
// If path is empty or just "/", append the legacy format for backwards compatibility
174+
// Otherwise, use the URL as-is
175+
if let Ok(parsed) = url::Url::parse(url) {
176+
let path = parsed.path();
177+
if path.is_empty() || path == "/" {
178+
// Backwards compatibility: append legacy path format
179+
return format!("{}/v1/datasets/{}/ingest", url, self.dataset);
180+
}
181+
}
182+
183+
// URL has a custom path, use as-is
184+
return url.to_string();
185+
}
158186

159-
// NOTE trim any trailing slashes to avoid redundant rewriting or 301 redirects from intermediate proxies
160-
// NOTE Most axiom users will not need to configure a url, this is for the other 1%
161-
let url = url.trim_end_matches('/');
187+
// If region is set, build the regional edge endpoint
188+
if let Some(region) = &self.region {
189+
let region = region.trim_end_matches('/');
190+
return format!("https://{}/v1/ingest/{}", region, self.dataset);
191+
}
162192

163-
format!("{}/v1/datasets/{}/ingest", url, self.dataset)
193+
// Default: use cloud endpoint with legacy path format
194+
format!("{}/v1/datasets/{}/ingest", CLOUD_URL, self.dataset)
164195
}
165196
}
166197

@@ -169,17 +200,137 @@ mod test {
169200
#[test]
170201
fn generate_config() {
171202
crate::test_util::test_generate_config::<super::AxiomConfig>();
203+
}
204+
205+
#[test]
206+
fn test_region_domain_only() {
207+
// region: mumbai.axiomdomain.co → https://mumbai.axiomdomain.co/v1/ingest/test-3
208+
let config = super::AxiomConfig {
209+
region: Some("mumbai.axiomdomain.co".to_string()),
210+
dataset: "test-3".to_string(),
211+
..Default::default()
212+
};
213+
let endpoint = config.build_endpoint();
214+
assert_eq!(
215+
endpoint,
216+
"https://mumbai.axiomdomain.co/v1/ingest/test-3"
217+
);
218+
}
172219

220+
#[test]
221+
fn test_default_no_config() {
222+
// No url, no region → https://api.axiom.co/v1/datasets/foo/ingest
223+
let config = super::AxiomConfig {
224+
dataset: "foo".to_string(),
225+
..Default::default()
226+
};
227+
let endpoint = config.build_endpoint();
228+
assert_eq!(
229+
endpoint,
230+
"https://api.axiom.co/v1/datasets/foo/ingest"
231+
);
232+
}
233+
234+
#[test]
235+
fn test_url_with_custom_path() {
236+
// url: http://localhost:3400/ingest → http://localhost:3400/ingest (as-is)
237+
let config = super::AxiomConfig {
238+
url: Some("http://localhost:3400/ingest".to_string()),
239+
dataset: "meh".to_string(),
240+
..Default::default()
241+
};
242+
let endpoint = config.build_endpoint();
243+
assert_eq!(
244+
endpoint,
245+
"http://localhost:3400/ingest"
246+
);
247+
}
248+
249+
#[test]
250+
fn test_url_without_path_backwards_compat() {
251+
// url: https://api.eu.axiom.co/ → https://api.eu.axiom.co/v1/datasets/qoo/ingest
252+
let config = super::AxiomConfig {
253+
url: Some("https://api.eu.axiom.co".to_string()),
254+
dataset: "qoo".to_string(),
255+
..Default::default()
256+
};
257+
let endpoint = config.build_endpoint();
258+
assert_eq!(
259+
endpoint,
260+
"https://api.eu.axiom.co/v1/datasets/qoo/ingest"
261+
);
262+
263+
// Also test with trailing slash
264+
let config = super::AxiomConfig {
265+
url: Some("https://api.eu.axiom.co/".to_string()),
266+
dataset: "qoo".to_string(),
267+
..Default::default()
268+
};
269+
let endpoint = config.build_endpoint();
270+
assert_eq!(
271+
endpoint,
272+
"https://api.eu.axiom.co/v1/datasets/qoo/ingest"
273+
);
274+
}
275+
276+
#[test]
277+
fn test_url_takes_precedence_over_region() {
278+
// When both url and region are set, url takes precedence
279+
let config = super::AxiomConfig {
280+
url: Some("http://localhost:3400/ingest".to_string()),
281+
region: Some("mumbai.axiomdomain.co".to_string()),
282+
dataset: "test".to_string(),
283+
..Default::default()
284+
};
285+
let endpoint = config.build_endpoint();
286+
assert_eq!(
287+
endpoint,
288+
"http://localhost:3400/ingest"
289+
);
290+
}
291+
292+
#[test]
293+
fn test_production_regional_edges() {
294+
// Production AWS edge
295+
let config = super::AxiomConfig {
296+
region: Some("eu-central-1.aws.edge.axiom.co".to_string()),
297+
dataset: "my-dataset".to_string(),
298+
..Default::default()
299+
};
300+
let endpoint = config.build_endpoint();
301+
assert_eq!(
302+
endpoint,
303+
"https://eu-central-1.aws.edge.axiom.co/v1/ingest/my-dataset"
304+
);
305+
}
306+
307+
#[test]
308+
fn test_staging_environment_edges() {
309+
// Staging environment edge
310+
let config = super::AxiomConfig {
311+
region: Some("us-east-1.edge.staging.axiomdomain.co".to_string()),
312+
dataset: "test-dataset".to_string(),
313+
..Default::default()
314+
};
315+
let endpoint = config.build_endpoint();
316+
assert_eq!(
317+
endpoint,
318+
"https://us-east-1.edge.staging.axiomdomain.co/v1/ingest/test-dataset"
319+
);
320+
}
321+
322+
#[test]
323+
fn test_dev_environment_edges() {
324+
// Dev environment edge
173325
let config = super::AxiomConfig {
174-
url: Some("https://axiom.my-domain.com///".to_string()),
175-
org_id: None,
176-
dataset: "vector_rocks".to_string(),
326+
region: Some("eu-west-1.edge.dev.axiomdomain.co".to_string()),
327+
dataset: "dev-dataset".to_string(),
177328
..Default::default()
178329
};
179330
let endpoint = config.build_endpoint();
180331
assert_eq!(
181332
endpoint,
182-
"https://axiom.my-domain.com/v1/datasets/vector_rocks/ingest"
333+
"https://eu-west-1.edge.dev.axiomdomain.co/v1/ingest/dev-dataset"
183334
);
184335
}
185336
}

0 commit comments

Comments
 (0)