@@ -29,9 +29,12 @@ static CLOUD_URL: &str = "https://api.axiom.co";
2929pub 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
151164impl 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