cal_core/rest/
account.rs

1// File: cal-core/src/rest/account.rs
2
3use crate::account::*;
4use crate::accounting::Address;
5use crate::device::device::DeviceStruct;
6use crate::rest::common::{
7    ApiError, ApiResponse, ListRequest, PaginatedResponse,
8};
9use crate::RecordReference;
10use serde::{Deserialize, Serialize};
11
12#[cfg(feature = "openapi")]
13use utoipa::{ToSchema, IntoParams};
14
15#[derive(Debug, Deserialize)]
16#[cfg_attr(feature = "openapi", derive(ToSchema, IntoParams))]
17pub struct AccountIdQuery {
18    /// Account ID for the operation
19    pub account_id: String,
20}
21
22// Create Account Request
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[cfg_attr(feature = "openapi", derive(ToSchema))]
25#[serde(rename_all = "camelCase")]
26pub struct CreateAccountRequest {
27    pub name: String,
28    pub mbn: String,
29    pub domain: String,
30    pub organisation: RecordReference,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub address: Option<Address>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub teams: Option<TeamsAccount>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub environment: Option<Environment>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub spend_cap: Option<SpendCap>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub concurrency: Option<Concurrency>,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub class_of_service: Option<ClassOfService>,
43    pub cluster_settings: Cluster,
44}
45
46// Update Account Request (for PUT)
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[cfg_attr(feature = "openapi", derive(ToSchema))]
49#[serde(rename_all = "camelCase")]
50pub struct UpdateAccountRequest {
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub name: Option<String>,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub mbn: Option<String>,
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub domain: Option<String>,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub organisation: Option<RecordReference>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub address: Option<Address>,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub teams: Option<TeamsAccount>,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub environment: Option<Environment>,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub spend_cap: Option<SpendCap>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub concurrency: Option<Concurrency>,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub class_of_service: Option<ClassOfService>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub cluster_settings: Option<Cluster>,
73}
74
75// Account List Request
76#[derive(Debug, Clone, Serialize, Deserialize)]
77#[cfg_attr(feature = "openapi", derive(ToSchema))]
78#[serde(rename_all = "camelCase")]
79pub struct AccountListRequest {
80    #[serde(flatten)]
81    pub common: ListRequest,
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub organisation_id: Option<String>,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub name: Option<String>,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub mbn: Option<String>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub domain: Option<String>,
90}
91
92// Type aliases
93pub type AccountResponse = ApiResponse<Account>;
94pub type AccountListResponse = ApiResponse<PaginatedResponse<Account>>;
95pub type DDIResponse = ApiResponse<DDI>;
96pub type DeviceResponse = ApiResponse<DeviceStruct>;
97pub type TrunkResponse = ApiResponse<Trunk>;
98pub type HookResponse = ApiResponse<Hook>;
99pub type AssetResponse = ApiResponse<Asset>;
100pub type AddressResponse = ApiResponse<Address>;
101
102// Error codes
103#[derive(Debug, Clone)]
104pub enum AccountErrorCode {
105    NotFound,
106    DuplicateMbn,
107    DuplicateDomain,
108    InvalidOrganisation,
109    InvalidCluster,
110    MissingRequiredField,
111}
112
113impl AccountErrorCode {
114    pub fn as_str(&self) -> &'static str {
115        match self {
116            Self::NotFound => "ACCOUNT_NOT_FOUND",
117            Self::DuplicateMbn => "DUPLICATE_MBN",
118            Self::DuplicateDomain => "DUPLICATE_DOMAIN",
119            Self::InvalidOrganisation => "INVALID_ORGANISATION",
120            Self::InvalidCluster => "INVALID_CLUSTER",
121            Self::MissingRequiredField => "MISSING_REQUIRED_FIELD",
122        }
123    }
124}
125
126// Helper implementations
127impl CreateAccountRequest {
128    pub fn validate(&self) -> Result<(), ApiError> {
129        if self.name.trim().is_empty() {
130            return Err(ApiError::new(
131                AccountErrorCode::MissingRequiredField.as_str(),
132                "Account name is required",
133            )
134                .with_field("name"));
135        }
136
137        if self.mbn.trim().is_empty() {
138            return Err(ApiError::new(
139                AccountErrorCode::MissingRequiredField.as_str(),
140                "MBN is required",
141            )
142                .with_field("mbn"));
143        }
144
145        if self.domain.trim().is_empty() {
146            return Err(ApiError::new(
147                AccountErrorCode::MissingRequiredField.as_str(),
148                "Domain is required",
149            )
150                .with_field("domain"));
151        }
152
153        Ok(())
154    }
155}
156
157impl UpdateAccountRequest {
158    pub fn is_empty(&self) -> bool {
159        self.name.is_none()
160            && self.mbn.is_none()
161            && self.domain.is_none()
162            && self.organisation.is_none()
163            && self.address.is_none()
164            && self.teams.is_none()
165            && self.environment.is_none()
166            && self.spend_cap.is_none()
167            && self.concurrency.is_none()
168            && self.class_of_service.is_none()
169            && self.cluster_settings.is_none()
170    }
171}
172
173impl Default for AccountListRequest {
174    fn default() -> Self {
175        Self {
176            common: ListRequest {
177                pagination: crate::rest::common::PaginationParams::default(),
178                sort: crate::rest::common::SortParams::default(),
179                search: None,
180                time_range: None,
181                filters: None,
182            },
183            organisation_id: None,
184            name: None,
185            mbn: None,
186            domain: None,
187        }
188    }
189}
190
191impl AccountListRequest {
192    pub fn new() -> Self {
193        Self::default()
194    }
195
196    pub fn with_pagination(mut self, page: u32, page_size: u32) -> Self {
197        self.common.pagination = crate::rest::common::PaginationParams::new(page, page_size);
198        self
199    }
200
201    pub fn with_organisation_id(mut self, organisation_id: String) -> Self {
202        self.organisation_id = Some(organisation_id);
203        self
204    }
205}