1use clap::ValueEnum;
10use eyre::ensure;
11use std::path::PathBuf;
12use url::Url;
13
14#[cfg(feature = "otlp")]
15mod file;
16#[cfg(feature = "otlp")]
17mod otlp;
18#[cfg(feature = "otlp")]
19pub use otlp::*;
20
21const HTTP_TRACE_ENDPOINT: &str = "/v1/traces";
24const HTTP_LOGS_ENDPOINT: &str = "/v1/logs";
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
28pub enum OtlpProtocol {
29 Http,
31 Grpc,
33}
34
35impl OtlpProtocol {
36 pub fn validate_endpoint(&self, url: &mut Url) -> eyre::Result<()> {
43 if url.scheme() == "file" {
44 trace_file_path(url)?;
45 return Ok(());
46 }
47 self.validate_endpoint_with_path(url, HTTP_TRACE_ENDPOINT)
48 }
49
50 pub fn validate_logs_endpoint(&self, url: &mut Url) -> eyre::Result<()> {
55 ensure!(url.scheme() != "file", "OTLP logs do not support file endpoints");
56 self.validate_endpoint_with_path(url, HTTP_LOGS_ENDPOINT)
57 }
58
59 fn validate_endpoint_with_path(&self, url: &mut Url, http_path: &str) -> eyre::Result<()> {
60 match self {
61 Self::Http => {
62 if !url.path().ends_with(http_path) {
63 let path = url.path().trim_end_matches('/');
64 url.set_path(&format!("{}{}", path, http_path));
65 }
66 }
67 Self::Grpc => {
68 ensure!(
69 !url.path().ends_with(http_path),
70 "OTLP gRPC endpoint should not include {} path, got: {}",
71 http_path,
72 url
73 );
74 }
75 }
76 Ok(())
77 }
78}
79
80fn trace_file_path(url: &Url) -> eyre::Result<PathBuf> {
82 ensure!(url.scheme() == "file", "OTLP trace file endpoint must use the file scheme");
83 ensure!(
84 url.host_str().is_none() && url.query().is_none() && url.fragment().is_none(),
85 "OTLP trace file endpoint must be local and have no query or fragment"
86 );
87 let path = url.to_file_path().map_err(|()| eyre::eyre!("Invalid OTLP trace file path"))?;
88 ensure!(
89 path.is_absolute() && path.file_name().is_some(),
90 "OTLP trace file path must be absolute and name a file"
91 );
92 Ok(path)
93}