Skip to content

Commit

Permalink
feat(core): add SdkKey type (#181)
Browse files Browse the repository at this point in the history
* feat(core): add SdkKey type

* fix(core): use URL-safe base64 for SDK key decoding
  • Loading branch information
rasendubi authored Jan 27, 2025
1 parent 02a310d commit d8a8a7c
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 0 deletions.
2 changes: 2 additions & 0 deletions eppo_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ mod error;
mod event_ingestion;
mod obfuscation;
mod precomputed;
#[allow(unused)] // Will be used with event_ingestion
mod sdk_key;
mod sdk_metadata;
mod str;

Expand Down
96 changes: 96 additions & 0 deletions eppo_core/src/sdk_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use url::Url;

use crate::Str;

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SdkKey(Str);

/// Blank implementation of `Debug`, so we don't accidentally leak SDK key in logs.
impl std::fmt::Debug for SdkKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", "SdkKey{..}")
}
}

impl SdkKey {
#[inline]
pub fn new(key: Str) -> SdkKey {
SdkKey(key)
}

#[inline]
pub(crate) fn as_str(&self) -> &str {
self.0.as_ref()
}

/// Decodes and returns the event ingestion URL from the provided Eppo SDK key.
///
/// If the SDK key doesn't contain the event ingestion hostname, or it's invalid, it returns
/// `None`.
pub(crate) fn event_ingestion_url(&self) -> Option<Url> {
use base64::prelude::*;

let (_, payload) = self.as_str().split_once(".")?;
let payload = BASE64_URL_SAFE_NO_PAD.decode(payload).ok()?;
let (_, hostname) =
url::form_urlencoded::parse(&payload).find(|(key, _value)| key == "eh")?;

let s = {
let mut s = String::new();
if !hostname.starts_with("http://") && !hostname.starts_with("https://") {
// Prefix with a scheme if missing.
s.push_str("https://");
}
s.push_str(&hostname);
if !s.ends_with('/') {
s.push('/');
}
s.push_str("v0/i");
s
};

let url = Url::parse(&s).ok()?;

Some(url)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_return_none_when_no_ingestion_url() {
assert_eq!(
SdkKey::new("zCsQuoHJxVPp895".into()).event_ingestion_url(),
None
);
assert_eq!(
SdkKey::new("zCsQuoHJxVPp895.xxxxxx".into()).event_ingestion_url(),
None
);
}

#[test]
fn test_decode_event_ingestion_url() {
assert_eq!(
SdkKey::new("zCsQuoHJxVPp895.ZWg9MTIzNDU2LmUudGVzdGluZy5lcHBvLmNsb3Vk".into())
.event_ingestion_url(),
Some(Url::parse("https://123456.e.testing.eppo.cloud/v0/i").unwrap())
)
}

#[test]
fn test_decode_non_url_safe_event_ingestion_url() {
use base64::prelude::*;

let payload = BASE64_URL_SAFE_NO_PAD.encode("eh=12%3D3456(lol)%2Bhi/.e+testing.eppo.cloud");
let sdk_key = SdkKey::new(format!("zCsQuoHJxVPp895.{payload}").into());

assert_eq!(
sdk_key.event_ingestion_url(),
// Believe it or not, that's a valid url syntax
Some(Url::parse("https://12=3456(lol)+hi/.e testing.eppo.cloud/v0/i").unwrap())
)
}
}

0 comments on commit d8a8a7c

Please sign in to comment.