use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tag {
pub id: String,
pub name: String,
pub color: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TagSummary {
#[serde(flatten)]
pub tag: Tag,
#[serde(default)]
pub link_count: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ListTagsResult {
#[serde(default)]
pub tags: Vec<TagSummary>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct CreateTagInput {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
}
impl CreateTagInput {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
color: None,
}
}
#[must_use]
pub fn with_color(mut self, color: impl Into<String>) -> Self {
self.color = Some(color.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct UpdateTagInput {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
}
impl UpdateTagInput {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn with_color(mut self, color: impl Into<String>) -> Self {
self.color = Some(color.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeleteTagResult {
#[serde(default)]
pub deleted: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoutingRule {
pub condition_type: String,
pub condition_op: String,
pub condition_value: String,
pub target_url: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AbVariant {
pub id: i64,
pub target_url: String,
#[serde(default)]
pub weight: i32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CreateAbVariantInput {
pub target_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub weight: Option<i32>,
}
impl CreateAbVariantInput {
pub fn new(target_url: impl Into<String>) -> Self {
Self {
target_url: target_url.into(),
weight: None,
}
}
#[must_use]
pub fn with_weight(mut self, weight: i32) -> Self {
self.weight = Some(weight);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Link {
pub code: String,
pub short_url: String,
pub domain_hostname: Option<String>,
pub target_url: String,
pub project_id: String,
pub expires_at: Option<i64>,
pub is_active: bool,
pub seo_title: Option<String>,
pub seo_description: Option<String>,
pub seo_image_url: Option<String>,
pub seo_canonical_url: Option<String>,
#[serde(default)]
pub seo_noindex: bool,
pub seo_updated_at: Option<i64>,
pub created_at: i64,
pub updated_at: i64,
#[serde(default)]
pub tags: Vec<Tag>,
#[serde(default)]
pub password_protected: bool,
pub max_clicks: Option<i64>,
#[serde(default)]
pub click_count: i64,
#[serde(default)]
pub track_conversions: bool,
#[serde(default)]
pub routing_rules: Vec<RoutingRule>,
#[serde(default)]
pub ab_variants: Vec<AbVariant>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct CreateLinkInput {
pub target_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub domain_hostname: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seo_title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seo_description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seo_image_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seo_canonical_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seo_noindex: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_clicks: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub track_conversions: Option<bool>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub routing_rules: Vec<RoutingRule>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub ab_variants: Vec<CreateAbVariantInput>,
}
impl CreateLinkInput {
pub fn new(target_url: impl Into<String>) -> Self {
Self {
target_url: target_url.into(),
..Self::default()
}
}
#[must_use]
pub fn with_domain_hostname(mut self, domain_hostname: impl Into<String>) -> Self {
self.domain_hostname = Some(domain_hostname.into());
self
}
#[must_use]
pub fn with_code(mut self, code: impl Into<String>) -> Self {
self.code = Some(code.into());
self
}
#[must_use]
pub fn with_expires_at(mut self, expires_at: i64) -> Self {
self.expires_at = Some(expires_at);
self
}
#[must_use]
pub fn with_seo_title(mut self, seo_title: impl Into<String>) -> Self {
self.seo_title = Some(seo_title.into());
self
}
#[must_use]
pub fn with_seo_description(mut self, seo_description: impl Into<String>) -> Self {
self.seo_description = Some(seo_description.into());
self
}
#[must_use]
pub fn with_seo_image_url(mut self, seo_image_url: impl Into<String>) -> Self {
self.seo_image_url = Some(seo_image_url.into());
self
}
#[must_use]
pub fn with_seo_canonical_url(mut self, seo_canonical_url: impl Into<String>) -> Self {
self.seo_canonical_url = Some(seo_canonical_url.into());
self
}
#[must_use]
pub fn with_seo_noindex(mut self, seo_noindex: bool) -> Self {
self.seo_noindex = Some(seo_noindex);
self
}
#[must_use]
pub fn with_password(mut self, password: impl Into<String>) -> Self {
self.password = Some(password.into());
self
}
#[must_use]
pub fn with_max_clicks(mut self, max_clicks: i64) -> Self {
self.max_clicks = Some(max_clicks);
self
}
#[must_use]
pub fn with_track_conversions(mut self, track_conversions: bool) -> Self {
self.track_conversions = Some(track_conversions);
self
}
#[must_use]
pub fn with_routing_rules(mut self, routing_rules: Vec<RoutingRule>) -> Self {
self.routing_rules = routing_rules;
self
}
#[must_use]
pub fn with_ab_variants(mut self, ab_variants: Vec<CreateAbVariantInput>) -> Self {
self.ab_variants = ab_variants;
self
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct UpdateLinkInput {
#[serde(skip_serializing_if = "Option::is_none")]
pub target_url: Option<Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<Option<i64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_active: Option<Option<bool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seo_title: Option<Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seo_description: Option<Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seo_image_url: Option<Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seo_canonical_url: Option<Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seo_noindex: Option<Option<bool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_clicks: Option<Option<i64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub track_conversions: Option<Option<bool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub routing_rules: Option<Vec<RoutingRule>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ab_variants: Option<Vec<CreateAbVariantInput>>,
}
impl UpdateLinkInput {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.target_url.is_none()
&& self.expires_at.is_none()
&& self.is_active.is_none()
&& self.seo_title.is_none()
&& self.seo_description.is_none()
&& self.seo_image_url.is_none()
&& self.seo_canonical_url.is_none()
&& self.seo_noindex.is_none()
&& self.password.is_none()
&& self.max_clicks.is_none()
&& self.track_conversions.is_none()
&& self.routing_rules.is_none()
&& self.ab_variants.is_none()
}
#[must_use]
pub fn set_target_url(mut self, target_url: impl Into<String>) -> Self {
self.target_url = Some(Some(target_url.into()));
self
}
#[must_use]
pub fn set_expires_at(mut self, expires_at: i64) -> Self {
self.expires_at = Some(Some(expires_at));
self
}
#[must_use]
pub fn clear_expires_at(mut self) -> Self {
self.expires_at = Some(None);
self
}
#[must_use]
pub fn set_is_active(mut self, is_active: bool) -> Self {
self.is_active = Some(Some(is_active));
self
}
#[must_use]
pub fn set_seo_title(mut self, seo_title: impl Into<String>) -> Self {
self.seo_title = Some(Some(seo_title.into()));
self
}
#[must_use]
pub fn clear_seo_title(mut self) -> Self {
self.seo_title = Some(None);
self
}
#[must_use]
pub fn set_seo_description(mut self, seo_description: impl Into<String>) -> Self {
self.seo_description = Some(Some(seo_description.into()));
self
}
#[must_use]
pub fn clear_seo_description(mut self) -> Self {
self.seo_description = Some(None);
self
}
#[must_use]
pub fn set_seo_image_url(mut self, seo_image_url: impl Into<String>) -> Self {
self.seo_image_url = Some(Some(seo_image_url.into()));
self
}
#[must_use]
pub fn clear_seo_image_url(mut self) -> Self {
self.seo_image_url = Some(None);
self
}
#[must_use]
pub fn set_seo_canonical_url(mut self, seo_canonical_url: impl Into<String>) -> Self {
self.seo_canonical_url = Some(Some(seo_canonical_url.into()));
self
}
#[must_use]
pub fn clear_seo_canonical_url(mut self) -> Self {
self.seo_canonical_url = Some(None);
self
}
#[must_use]
pub fn set_seo_noindex(mut self, seo_noindex: bool) -> Self {
self.seo_noindex = Some(Some(seo_noindex));
self
}
#[must_use]
pub fn set_password(mut self, password: impl Into<String>) -> Self {
self.password = Some(Some(password.into()));
self
}
#[must_use]
pub fn clear_password(mut self) -> Self {
self.password = Some(None);
self
}
#[must_use]
pub fn set_max_clicks(mut self, max_clicks: i64) -> Self {
self.max_clicks = Some(Some(max_clicks));
self
}
#[must_use]
pub fn clear_max_clicks(mut self) -> Self {
self.max_clicks = Some(None);
self
}
#[must_use]
pub fn set_track_conversions(mut self, track_conversions: bool) -> Self {
self.track_conversions = Some(Some(track_conversions));
self
}
#[must_use]
pub fn set_routing_rules(mut self, routing_rules: Vec<RoutingRule>) -> Self {
self.routing_rules = Some(routing_rules);
self
}
#[must_use]
pub fn set_ab_variants(mut self, ab_variants: Vec<CreateAbVariantInput>) -> Self {
self.ab_variants = Some(ab_variants);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ListLinksResult {
pub links: Vec<Link>,
pub next_cursor: Option<i64>,
}
impl ListLinksResult {
pub fn has_more(&self) -> bool {
self.next_cursor.is_some()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StatsBreakdown {
#[serde(default)]
pub value: String,
#[serde(default)]
pub clicks: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DailyClicksPoint {
pub day: i64,
#[serde(default)]
pub clicks: i64,
#[serde(default)]
pub qr_scans: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LinkStats {
#[serde(default)]
pub code: String,
#[serde(default)]
pub days: i64,
#[serde(default)]
pub total_clicks: i64,
#[serde(default)]
pub qr_scans: i64,
#[serde(default)]
pub countries: Vec<StatsBreakdown>,
#[serde(default)]
pub referrers: Vec<StatsBreakdown>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProjectStats {
#[serde(default)]
pub days: i64,
#[serde(default)]
pub total_clicks: i64,
#[serde(default)]
pub qr_scans: i64,
#[serde(default)]
pub daily: Vec<DailyClicksPoint>,
#[serde(default)]
pub countries: Vec<StatsBreakdown>,
#[serde(default)]
pub referrers: Vec<StatsBreakdown>,
#[serde(default)]
pub devices: Vec<StatsBreakdown>,
#[serde(default)]
pub browsers: Vec<StatsBreakdown>,
#[serde(default)]
pub top_codes: Vec<StatsBreakdown>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProjectInfo {
pub id: String,
pub name: String,
pub slug: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeleteLinkResult {
#[serde(default)]
pub deleted: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct RecordConversionInput {
pub click_id: String,
pub event_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub value_cents: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub currency: Option<String>,
}
impl RecordConversionInput {
pub fn new(click_id: impl Into<String>, event_name: impl Into<String>) -> Self {
Self {
click_id: click_id.into(),
event_name: event_name.into(),
value_cents: None,
currency: None,
}
}
#[must_use]
pub fn with_value_cents(mut self, value_cents: i64) -> Self {
self.value_cents = Some(value_cents);
self
}
#[must_use]
pub fn with_currency(mut self, currency: impl Into<String>) -> Self {
self.currency = Some(currency.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConversionResult {
#[serde(default)]
pub recorded: bool,
#[serde(default)]
pub duplicate: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct BatchLinkInput {
pub target_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub domain_hostname: Option<String>,
}
impl BatchLinkInput {
pub fn new(target_url: impl Into<String>) -> Self {
Self {
target_url: target_url.into(),
..Self::default()
}
}
#[must_use]
pub fn with_code(mut self, code: impl Into<String>) -> Self {
self.code = Some(code.into());
self
}
#[must_use]
pub fn with_expires_at(mut self, expires_at: i64) -> Self {
self.expires_at = Some(expires_at);
self
}
#[must_use]
pub fn with_domain_hostname(mut self, domain_hostname: impl Into<String>) -> Self {
self.domain_hostname = Some(domain_hostname.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BatchLinkResult {
pub index: usize,
pub ok: bool,
pub code: Option<String>,
pub error: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BatchCreateLinksResult {
#[serde(default)]
pub created: usize,
#[serde(default)]
pub total: usize,
#[serde(default)]
pub results: Vec<BatchLinkResult>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum WebhookPayloadFormat {
Json,
Slack,
}
impl WebhookPayloadFormat {
pub fn as_str(self) -> &'static str {
match self {
WebhookPayloadFormat::Json => "json",
WebhookPayloadFormat::Slack => "slack",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Webhook {
pub id: String,
pub project_id: String,
pub name: String,
pub url: String,
#[serde(default)]
pub events: Vec<String>,
pub is_active: bool,
pub payload_format: String,
pub created_at: i64,
pub updated_at: i64,
pub last_delivery_at: Option<i64>,
pub last_success_at: Option<i64>,
pub last_failure_at: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct CreateWebhookInput {
pub name: String,
pub url: String,
pub events: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_active: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payload_format: Option<WebhookPayloadFormat>,
}
impl CreateWebhookInput {
pub fn new(
name: impl Into<String>,
url: impl Into<String>,
events: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
Self {
name: name.into(),
url: url.into(),
events: events.into_iter().map(Into::into).collect(),
is_active: None,
payload_format: None,
}
}
#[must_use]
pub fn with_is_active(mut self, is_active: bool) -> Self {
self.is_active = Some(is_active);
self
}
#[must_use]
pub fn with_payload_format(mut self, payload_format: WebhookPayloadFormat) -> Self {
self.payload_format = Some(payload_format);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CreatedWebhook {
pub endpoint: Webhook,
pub signing_secret: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ListWebhooksResult {
#[serde(default)]
pub endpoints: Vec<Webhook>,
#[serde(default)]
pub event_types: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeleteWebhookResult {
#[serde(default)]
pub deleted: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum QrEcc {
#[serde(rename = "L")]
L,
#[serde(rename = "M")]
M,
#[serde(rename = "Q")]
Q,
#[serde(rename = "H")]
H,
}
impl QrEcc {
pub fn as_str(self) -> &'static str {
match self {
QrEcc::L => "L",
QrEcc::M => "M",
QrEcc::Q => "Q",
QrEcc::H => "H",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QrRefresh {
On,
Value(String),
}
impl QrRefresh {
pub fn from_bool(value: bool) -> Option<Self> {
if value { Some(QrRefresh::On) } else { None }
}
pub fn from_value(value: impl Into<String>) -> Option<Self> {
let s = value.into();
if s.is_empty() {
None
} else {
Some(QrRefresh::Value(s))
}
}
pub fn as_str(&self) -> &str {
match self {
QrRefresh::On => "1",
QrRefresh::Value(s) => s.as_str(),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct QrOptions {
pub size: Option<u32>,
pub margin: Option<u32>,
pub ecc: Option<QrEcc>,
pub domain: Option<String>,
pub refresh: Option<QrRefresh>,
}
impl QrOptions {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_size(mut self, size: u32) -> Self {
self.size = Some(size);
self
}
#[must_use]
pub fn with_margin(mut self, margin: u32) -> Self {
self.margin = Some(margin);
self
}
#[must_use]
pub fn with_ecc(mut self, ecc: QrEcc) -> Self {
self.ecc = Some(ecc);
self
}
#[must_use]
pub fn with_domain(mut self, domain: impl Into<String>) -> Self {
self.domain = Some(domain.into());
self
}
#[must_use]
pub fn with_refresh(mut self, refresh: QrRefresh) -> Self {
self.refresh = Some(refresh);
self
}
pub fn is_empty(&self) -> bool {
self.size.is_none()
&& self.margin.is_none()
&& self.ecc.is_none()
&& self.domain.is_none()
&& self.refresh.is_none()
}
pub fn to_query_pairs(&self) -> Vec<(&'static str, String)> {
let mut out: Vec<(&'static str, String)> = Vec::new();
if let Some(size) = self.size {
out.push(("size", size.to_string()));
}
if let Some(margin) = self.margin {
out.push(("margin", margin.to_string()));
}
if let Some(ecc) = self.ecc {
out.push(("ecc", ecc.as_str().to_string()));
}
if let Some(ref domain) = self.domain {
out.push(("domain", domain.clone()));
}
if let Some(ref refresh) = self.refresh {
out.push(("refresh", refresh.as_str().to_string()));
}
out
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ListLinksParams {
pub cursor: Option<i64>,
pub limit: Option<u32>,
}