use serde::{Deserialize, Deserializer, Serialize};
use serde_json::{Map, Value};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SolariRegion {
#[default]
UsWest,
}
impl SolariRegion {
pub fn base_url(&self) -> &'static str {
match self {
SolariRegion::UsWest => "https://api.getsolari.com",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SameSite {
Strict,
Lax,
None,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Cookie {
pub name: String,
pub value: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub domain: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub http_only: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub secure: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub same_site: Option<SameSite>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LocalStorageEntry {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StorageOrigin {
pub origin: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub local_storage: Option<Vec<LocalStorageEntry>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StorageState {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cookies: Option<Vec<Cookie>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub origins: Option<Vec<StorageOrigin>>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ProxyTier {
Residential,
Static,
Mobile,
}
#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProxyRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub country: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tier: Option<ProxyTier>,
#[serde(skip_serializing_if = "Option::is_none")]
pub asn: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_duration: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub city: Option<String>,
}
impl ProxyRequest {
pub fn country(code: impl Into<String>) -> Self {
ProxyRequest {
country: Some(code.into()),
..Default::default()
}
}
pub fn tier(mut self, tier: ProxyTier) -> Self {
self.tier = Some(tier);
self
}
pub fn asn(mut self, asn: impl Into<String>) -> Self {
self.asn = Some(asn.into());
self
}
pub fn session(mut self, id: impl Into<String>) -> Self {
self.session = Some(id.into());
self
}
pub fn session_duration(mut self, minutes: u32) -> Self {
self.session_duration = Some(minutes);
self
}
pub fn state(mut self, state: impl Into<String>) -> Self {
self.state = Some(state.into());
self
}
pub fn city(mut self, city: impl Into<String>) -> Self {
self.city = Some(city.into());
self
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum ProxySpec {
Shorthand(String),
Request(ProxyRequest),
}
impl ProxySpec {
pub fn country(code: impl Into<String>) -> Self {
ProxySpec::Shorthand(code.into())
}
pub fn off() -> Self {
ProxySpec::Shorthand("off".to_string())
}
pub fn smart() -> Self {
ProxySpec::Shorthand("smart".to_string())
}
}
impl From<ProxyRequest> for ProxySpec {
fn from(r: ProxyRequest) -> Self {
ProxySpec::Request(r)
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResolvedProxyConfig {
pub server: String,
pub username: String,
pub password: String,
pub timezone_id: String,
pub country: String,
#[serde(default)]
pub tier: Option<ProxyTier>,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct ProxyCountries {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub countries: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct Session {
pub id: String,
pub ws_endpoint: String,
pub cdp_endpoint: String,
pub expires_at: String,
pub storage_state: Option<Option<StorageState>>,
pub proxy: Option<ResolvedProxyConfig>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionView {
#[serde(default)]
pub session_id: Option<String>,
#[serde(default)]
pub status: Option<String>,
#[serde(default)]
pub expires_at: Option<String>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone)]
pub struct ReplayUrl {
pub url: String,
pub expires_in_seconds: u64,
pub content_encoding: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Profile {
pub id: String,
#[serde(default)]
pub name: String,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone, Default)]
pub struct ProfileSaveResult {
pub version: u64,
pub size_bytes: u64,
}
#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CreateSessionRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub profile_id: Option<String>,
#[serde(skip_serializing_if = "is_false")]
pub recording: bool,
#[serde(skip_serializing_if = "is_false")]
pub stealth: bool,
#[serde(skip_serializing_if = "is_false")]
pub captcha: bool,
#[serde(skip_serializing_if = "is_false")]
pub web_bot_auth: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub proxy: Option<ProxySpec>,
}
fn is_false(b: &bool) -> bool {
!*b
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct StorageStateUrl {
#[serde(default)]
pub url: Option<String>,
#[allow(dead_code)]
#[serde(default)]
pub expires_in_seconds: Option<u64>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CreateSessionResponse {
#[serde(default)]
pub session_id: Option<String>,
#[serde(default)]
pub ws_endpoint: Option<String>,
#[serde(default)]
pub cdp_endpoint: Option<String>,
#[serde(default)]
pub expires_at: Option<String>,
#[serde(default, deserialize_with = "double_option")]
pub storage_state_url: Option<Option<StorageStateUrl>>,
#[serde(default, deserialize_with = "double_option")]
pub storage_state: Option<Option<StorageState>>,
#[serde(default)]
pub proxy: Option<ResolvedProxyConfig>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ReplayUrlResponse {
#[serde(default)]
pub url: Option<String>,
#[serde(default)]
pub expires_in_seconds: Option<u64>,
#[serde(default)]
pub content_encoding: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ProfileSaveResponse {
#[serde(default)]
pub version: Option<u64>,
#[serde(default)]
pub size_bytes: Option<u64>,
}
fn double_option<'de, T, D>(de: D) -> Result<Option<Option<T>>, D::Error>
where
T: serde::Deserialize<'de>,
D: Deserializer<'de>,
{
serde::Deserialize::deserialize(de).map(Some)
}