weirdboi_posthog 0.1.0

PostHog analytics integration for Bevy
Documentation
use bevy_ecs::resource::Resource;
use serde_json::Value;
use std::collections::HashMap;
use std::env;
use std::fmt::{Display, Formatter};
use url::Url;

#[derive(Resource)]
pub struct PosthogConfig {
	enabled: bool,
	api_key: String,
	current_user: u128,
	pub api_host: Url,
	pub default_properties: HashMap<String, Value>,
}

#[derive(Debug)]
pub enum ConfigError {
	InvalidApiHost,
	MissingEnvironment(String),
}

impl Display for ConfigError {
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		match self {
			ConfigError::InvalidApiHost => write!(f, "Invalid API host"),
			ConfigError::MissingEnvironment(env_var) => {
				write!(f, "Missing environment variable: {}", env_var)
			}
		}
	}
}

impl std::error::Error for ConfigError {}

impl PosthogConfig {
	pub fn from_env() -> Result<Self, ConfigError> {
		let api_host = env::var("POSTHOG_HOST")
			.map_err(|_| ConfigError::MissingEnvironment("POSTHOG_HOST".to_string()))?;
		let api_key = env::var("POSTHOG_KEY")
			.map_err(|_| ConfigError::MissingEnvironment("POSTHOG_KEY".to_string()))?;

		Self::new(api_host.as_str(), api_key)
	}

	pub fn new(api_host: impl TryInto<Url>, api_key: impl Display) -> Result<Self, ConfigError> {
		let random_uid = fastrand::u128(u128::MIN..u128::MAX);
		let user_id = format!("user:{:X}", random_uid);

		Ok(Self {
			enabled: true,
			api_host: api_host
				.try_into()
				.map_err(|_| ConfigError::InvalidApiHost)?,
			api_key: api_key.to_string(),
			current_user: random_uid,
			default_properties: {
				let mut map = HashMap::new();
				map.insert("distinct_id".to_string(), Value::String(user_id));
				map.insert("$process_person_profile".to_string(), Value::Bool(false));
				map.insert(
					"$lib".to_string(),
					Value::String(format!("weirdboi_posthog@{}", env!("CARGO_PKG_VERSION"))),
				);
				map
			},
		})
	}

	pub fn with_user(mut self, user_id: u128) -> Self {
		let mut default_properties = std::mem::take(&mut self.default_properties);
		default_properties.insert(
			"distinct_id".to_string(),
			Value::String(make_user_id(user_id)),
		);
		Self {
			current_user: user_id,
			default_properties,
			..self
		}
	}

	pub fn with_property(mut self, key: impl ToString, value: Value) -> Self {
		let mut default_properties = std::mem::take(&mut self.default_properties);
		default_properties.insert(key.to_string(), value);
		Self {
			default_properties,
			..self
		}
	}

	pub fn set_property(&mut self, key: impl ToString, value: Value) -> &mut Self {
		self.default_properties.insert(key.to_string(), value);
		self
	}

	pub fn identify(&mut self, user_id: u128) {
		self.current_user = user_id;
		self.default_properties.insert(
			"distinct_id".to_string(),
			Value::String(make_user_id(user_id)),
		);
	}

	pub fn token(&self) -> String {
		self.api_key.clone()
	}
	pub fn disable(&mut self) {
		self.enabled = false;
	}
	pub fn enable(&mut self) {
		self.enabled = true;
	}
	pub fn is_enabled(&self) -> bool {
		self.enabled
	}
	pub fn set_enabled(&mut self, enabled: bool) {
		self.enabled = enabled;
	}
	pub fn user_id(&self) -> String {
		make_user_id(self.current_user)
	}
}

fn make_user_id(user_id: u128) -> String {
	format!("user:{:X}", user_id)
}