Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions crates/common/src/geo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,76 @@ use crate::constants::{
HEADER_X_GEO_INFO_AVAILABLE, HEADER_X_GEO_METRO_CODE,
};

/// Geographic information extracted from a request.
///
/// Contains all available geographic data from Fastly's geolocation service,
/// including city, country, continent, coordinates, and DMA/metro code.
#[derive(Debug, Clone, serde::Serialize)]
pub struct GeoInfo {
/// City name
pub city: String,
/// Two-letter country code (e.g., "US", "GB")
pub country: String,
/// Continent name
pub continent: String,
/// Latitude coordinate
pub latitude: f64,
/// Longitude coordinate
pub longitude: f64,
/// DMA (Designated Market Area) / metro code
pub metro_code: i64,
/// Region code
pub region: Option<String>,
}

impl GeoInfo {
/// Creates a new `GeoInfo` from a request by performing a geo lookup.
///
/// This constructor performs a geo lookup based on the client's IP address and returns
/// all available geographic data in a structured format. It does not modify the request
/// or set headers.
///
/// # Arguments
///
/// * `req` - The request to extract geographic information from
///
/// # Returns
///
/// `Some(GeoInfo)` if geo data is available, `None` otherwise
///
/// # Example
///
/// ```ignore
/// if let Some(geo_info) = GeoInfo::from_request(&req) {
/// println!("User is in {} ({})", geo_info.city, geo_info.country);
/// println!("Coordinates: {}", geo_info.coordinates_string());
/// }
/// ```
pub fn from_request(req: &Request) -> Option<Self> {
req.get_client_ip_addr()
.and_then(geo_lookup)
.map(|geo| GeoInfo {
city: geo.city().to_string(),
country: geo.country_code().to_string(),
continent: format!("{:?}", geo.continent()),
latitude: geo.latitude(),
longitude: geo.longitude(),
metro_code: geo.metro_code(),
region: geo.region().map(str::to_string),
})
}

/// Returns coordinates as a formatted string "latitude,longitude"
pub fn coordinates_string(&self) -> String {
format!("{},{}", self.latitude, self.longitude)
}

/// Checks if a valid metro code is available (non-zero)
pub fn has_metro_code(&self) -> bool {
self.metro_code > 0
}
}

/// Extracts the DMA (Designated Market Area) code from the request's geolocation data.
///
/// This function:
Expand Down
17 changes: 14 additions & 3 deletions crates/common/src/prebid_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use serde_json::{json, Value};
use crate::backend::ensure_backend_from_url;
use crate::constants::{HEADER_SYNTHETIC_FRESH, HEADER_SYNTHETIC_TRUSTED_SERVER};
use crate::error::TrustedServerError;
use crate::geo::GeoInfo;
use crate::settings::Settings;
use crate::synthetic::{generate_synthetic_id, get_or_generate_synthetic_id};

Expand Down Expand Up @@ -162,9 +163,19 @@ fn enhance_openrtb_request(
}

// Add geo information if available
// Note: get_dma_code requires a mutable reference, but we only have immutable
// For now, we'll skip DMA code in the enhancement
// TODO: Refactor get_dma_code to accept immutable reference
if let Some(geo_info) = GeoInfo::from_request(req) {
let geo_obj = json!({
"type": 2, // 2 = IP address location
"country": geo_info.country, // Note: OpenRTB expects ISO 3166-1 alpha-3, but Fastly provides alpha-2
"city": geo_info.city,
"region": geo_info.region,
});

if !request["device"].is_object() {
request["device"] = json!({});
}
request["device"]["geo"] = geo_obj.clone();
}

// Add site information if missing
if !request["site"].is_object() {
Expand Down