use crate::api::endpoint::{Endpoint, HttpMethod};
use crate::models::voucher::Voucher;
use crate::response::{DeleteResponse, MutationResponse, SiteResponse};
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone)]
pub struct GetVouchers {
pub site_id: String,
pub offset: Option<usize>,
pub limit: Option<usize>,
}
impl GetVouchers {
pub fn new(site_id: impl Into<String>) -> Self {
Self {
site_id: site_id.into(),
offset: None,
limit: None,
}
}
pub fn with_pagination(site_id: impl Into<String>, offset: usize, limit: usize) -> Self {
Self {
site_id: site_id.into(),
offset: Some(offset),
limit: Some(limit),
}
}
pub fn offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
}
impl Endpoint for GetVouchers {
const PATH: &'static str = "sites/{site_id}/hotspot/vouchers";
const METHOD: HttpMethod = HttpMethod::Get;
type Response = SiteResponse<Voucher>;
fn build_path(&self) -> String {
format!("sites/{}/hotspot/vouchers", self.site_id)
}
fn query_params(&self) -> Vec<(&'static str, String)> {
let mut params = vec![];
if let Some(offset) = self.offset {
params.push(("offset", offset.to_string()));
}
if let Some(limit) = self.limit {
params.push(("limit", limit.to_string()));
}
params
}
}
#[derive(Debug, Clone)]
pub struct GetVoucher {
pub site_id: String,
pub id: String,
}
impl GetVoucher {
pub fn new(site_id: impl Into<String>, id: impl Into<String>) -> Self {
Self {
site_id: site_id.into(),
id: id.into(),
}
}
}
impl Endpoint for GetVoucher {
const PATH: &'static str = "sites/{site_id}/hotspot/vouchers/{id}";
const METHOD: HttpMethod = HttpMethod::Get;
type Response = SiteResponse<Voucher>;
fn build_path(&self) -> String {
format!("sites/{}/hotspot/vouchers/{}", self.site_id, self.id)
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GenerateVouchersRequest {
pub count: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub duration_minutes: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data_limit_mb: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bandwidth_limit_down: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bandwidth_limit_up: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub note: Option<String>,
}
impl GenerateVouchersRequest {
pub fn new(count: u32) -> Self {
Self {
count,
duration_minutes: None,
data_limit_mb: None,
bandwidth_limit_down: None,
bandwidth_limit_up: None,
note: None,
}
}
pub fn duration_minutes(mut self, minutes: u32) -> Self {
self.duration_minutes = Some(minutes);
self
}
pub fn data_limit_mb(mut self, mb: u64) -> Self {
self.data_limit_mb = Some(mb);
self
}
pub fn bandwidth_limit_down(mut self, kbps: u64) -> Self {
self.bandwidth_limit_down = Some(kbps);
self
}
pub fn bandwidth_limit_up(mut self, kbps: u64) -> Self {
self.bandwidth_limit_up = Some(kbps);
self
}
pub fn note(mut self, note: impl Into<String>) -> Self {
self.note = Some(note.into());
self
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GeneratedVouchers {
pub vouchers: Vec<Voucher>,
}
#[derive(Debug, Clone)]
pub struct GenerateVouchers {
pub site_id: String,
pub request: GenerateVouchersRequest,
}
impl GenerateVouchers {
pub fn new(site_id: impl Into<String>, request: GenerateVouchersRequest) -> Self {
Self {
site_id: site_id.into(),
request,
}
}
}
impl Endpoint for GenerateVouchers {
const PATH: &'static str = "sites/{site_id}/hotspot/vouchers";
const METHOD: HttpMethod = HttpMethod::Post;
type Response = MutationResponse<GeneratedVouchers>;
fn build_path(&self) -> String {
format!("sites/{}/hotspot/vouchers", self.site_id)
}
fn request_body(&self) -> Result<Option<Value>, serde_json::Error> {
Ok(Some(serde_json::to_value(&self.request)?))
}
}
#[derive(Debug, Clone)]
pub struct DeleteVoucher {
pub site_id: String,
pub id: String,
}
impl DeleteVoucher {
pub fn new(site_id: impl Into<String>, id: impl Into<String>) -> Self {
Self {
site_id: site_id.into(),
id: id.into(),
}
}
}
impl Endpoint for DeleteVoucher {
const PATH: &'static str = "sites/{site_id}/hotspot/vouchers/{id}";
const METHOD: HttpMethod = HttpMethod::Delete;
type Response = DeleteResponse;
fn build_path(&self) -> String {
format!("sites/{}/hotspot/vouchers/{}", self.site_id, self.id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_vouchers_build_path() {
let endpoint = GetVouchers::new("site-123");
assert_eq!(endpoint.build_path(), "sites/site-123/hotspot/vouchers");
}
#[test]
fn test_get_vouchers_path_and_method() {
assert_eq!(GetVouchers::PATH, "sites/{site_id}/hotspot/vouchers");
assert!(matches!(GetVouchers::METHOD, HttpMethod::Get));
}
#[test]
fn test_get_vouchers_no_pagination() {
let endpoint = GetVouchers::new("site-123");
let params = endpoint.query_params();
assert!(params.is_empty());
}
#[test]
fn test_get_vouchers_with_pagination() {
let endpoint = GetVouchers::with_pagination("site-123", 10, 25);
let params = endpoint.query_params();
assert_eq!(params.len(), 2);
assert!(params.contains(&("offset", "10".to_string())));
assert!(params.contains(&("limit", "25".to_string())));
}
#[test]
fn test_get_vouchers_offset_only() {
let endpoint = GetVouchers::new("site-123").offset(5);
let params = endpoint.query_params();
assert_eq!(params.len(), 1);
assert!(params.contains(&("offset", "5".to_string())));
}
#[test]
fn test_get_vouchers_limit_only() {
let endpoint = GetVouchers::new("site-123").limit(50);
let params = endpoint.query_params();
assert_eq!(params.len(), 1);
assert!(params.contains(&("limit", "50".to_string())));
}
#[test]
fn test_get_voucher_build_path() {
let endpoint = GetVoucher::new("site-123", "voucher-456");
assert_eq!(
endpoint.build_path(),
"sites/site-123/hotspot/vouchers/voucher-456"
);
}
#[test]
fn test_get_voucher_path_and_method() {
assert_eq!(GetVoucher::PATH, "sites/{site_id}/hotspot/vouchers/{id}");
assert!(matches!(GetVoucher::METHOD, HttpMethod::Get));
}
#[test]
fn test_generate_vouchers_build_path() {
let request = GenerateVouchersRequest::new(5);
let endpoint = GenerateVouchers::new("site-123", request);
assert_eq!(endpoint.build_path(), "sites/site-123/hotspot/vouchers");
}
#[test]
fn test_generate_vouchers_path_and_method() {
assert_eq!(GenerateVouchers::PATH, "sites/{site_id}/hotspot/vouchers");
assert!(matches!(GenerateVouchers::METHOD, HttpMethod::Post));
}
#[test]
fn test_generate_vouchers_request_body_minimal() {
let request = GenerateVouchersRequest::new(3);
let endpoint = GenerateVouchers::new("site-123", request);
let body = endpoint.request_body().unwrap().unwrap();
assert_eq!(body["count"], 3);
assert!(body.get("durationMinutes").is_none());
assert!(body.get("dataLimitMb").is_none());
assert!(body.get("bandwidthLimitDown").is_none());
assert!(body.get("bandwidthLimitUp").is_none());
assert!(body.get("note").is_none());
}
#[test]
fn test_generate_vouchers_request_body_full() {
let request = GenerateVouchersRequest::new(10)
.duration_minutes(60)
.data_limit_mb(1024)
.bandwidth_limit_down(10000)
.bandwidth_limit_up(5000)
.note("Test vouchers");
let endpoint = GenerateVouchers::new("site-123", request);
let body = endpoint.request_body().unwrap().unwrap();
assert_eq!(body["count"], 10);
assert_eq!(body["durationMinutes"], 60);
assert_eq!(body["dataLimitMb"], 1024);
assert_eq!(body["bandwidthLimitDown"], 10000);
assert_eq!(body["bandwidthLimitUp"], 5000);
assert_eq!(body["note"], "Test vouchers");
}
#[test]
fn test_delete_voucher_build_path() {
let endpoint = DeleteVoucher::new("site-123", "voucher-789");
assert_eq!(
endpoint.build_path(),
"sites/site-123/hotspot/vouchers/voucher-789"
);
}
#[test]
fn test_delete_voucher_path_and_method() {
assert_eq!(DeleteVoucher::PATH, "sites/{site_id}/hotspot/vouchers/{id}");
assert!(matches!(DeleteVoucher::METHOD, HttpMethod::Delete));
}
}