1use crate::types::sql::{
11 Database, DatabaseCreateRequest, DatabaseListResult, EnableServerAuditingRequest, FirewallRule,
12 FirewallRuleCreateRequest, FirewallRuleListResult, Server, ServerBlobAuditingPolicy,
13 ServerCreateRequest, ServerListResult,
14};
15use crate::{AzureHttpClient, Result};
16use urlencoding::encode;
17
18pub struct SqlOps<'a> {
25 pub(crate) client: &'a AzureHttpClient,
26}
27
28impl<'a> SqlOps<'a> {
29 pub(crate) fn new(client: &'a AzureHttpClient) -> Self {
30 Self { client }
31 }
32
33 fn base_url(&self) -> &str {
34 #[cfg(any(test, feature = "test-support"))]
35 {
36 if let Some(ref base) = self.client.base_url {
37 return base.trim_end_matches('/');
38 }
39 }
40 "https://management.azure.com"
41 }
42
43 #[allow(dead_code)]
53 pub(crate) async fn list_servers(&self, subscription_id: &str) -> Result<ServerListResult> {
54 let url = format!(
55 "{}/subscriptions/{}/providers/Microsoft.Sql/servers",
56 self.base_url(),
57 encode(subscription_id),
58 );
59 let sep = if url.contains('?') { "&" } else { "?" };
60 let url = format!("{}{}api-version=2023-08-01-preview", url, sep);
61 let response = self.client.get(&url).await?;
62 let response = response.error_for_status().await?;
63 let response_bytes =
64 response
65 .bytes()
66 .await
67 .map_err(|e| crate::AzureError::InvalidResponse {
68 message: format!("Failed to read list_servers response: {e}"),
69 body: None,
70 })?;
71 serde_json::from_slice(&response_bytes).map_err(|e| crate::AzureError::InvalidResponse {
72 message: format!("Failed to parse list_servers response: {e}"),
73 body: Some(String::from_utf8_lossy(&response_bytes).to_string()),
74 })
75 }
76
77 #[allow(dead_code)]
89 pub(crate) async fn get_server(
90 &self,
91 subscription_id: &str,
92 resource_group_name: &str,
93 server_name: &str,
94 ) -> Result<Server> {
95 let url = format!(
96 "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Sql/servers/{}",
97 self.base_url(),
98 encode(subscription_id),
99 encode(resource_group_name),
100 encode(server_name),
101 );
102 let sep = if url.contains('?') { "&" } else { "?" };
103 let url = format!("{}{}api-version=2023-08-01-preview", url, sep);
104 let response = self.client.get(&url).await?;
105 let response = response.error_for_status().await?;
106 let response_bytes =
107 response
108 .bytes()
109 .await
110 .map_err(|e| crate::AzureError::InvalidResponse {
111 message: format!("Failed to read get_server response: {e}"),
112 body: None,
113 })?;
114 serde_json::from_slice(&response_bytes).map_err(|e| crate::AzureError::InvalidResponse {
115 message: format!("Failed to parse get_server response: {e}"),
116 body: Some(String::from_utf8_lossy(&response_bytes).to_string()),
117 })
118 }
119
120 #[allow(dead_code)]
135 pub(crate) async fn create_server(
136 &self,
137 subscription_id: &str,
138 resource_group_name: &str,
139 server_name: &str,
140 body: &ServerCreateRequest,
141 ) -> Result<Server> {
142 let url = format!(
143 "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Sql/servers/{}",
144 self.base_url(),
145 encode(subscription_id),
146 encode(resource_group_name),
147 encode(server_name),
148 );
149 let sep = if url.contains('?') { "&" } else { "?" };
150 let url = format!("{}{}api-version=2023-08-01-preview", url, sep);
151 let body_bytes =
152 serde_json::to_vec(body).map_err(|e| crate::AzureError::InvalidResponse {
153 message: format!("Failed to serialize create_server request: {e}"),
154 body: None,
155 })?;
156 let response = self.client.put(&url, &body_bytes).await?;
157 let response = response.error_for_status().await?;
158 let response_bytes =
159 response
160 .bytes()
161 .await
162 .map_err(|e| crate::AzureError::InvalidResponse {
163 message: format!("Failed to read create_server response: {e}"),
164 body: None,
165 })?;
166 serde_json::from_slice(&response_bytes).map_err(|e| crate::AzureError::InvalidResponse {
167 message: format!("Failed to parse create_server response: {e}"),
168 body: Some(String::from_utf8_lossy(&response_bytes).to_string()),
169 })
170 }
171
172 #[allow(dead_code)]
181 pub(crate) async fn delete_server(
182 &self,
183 subscription_id: &str,
184 resource_group_name: &str,
185 server_name: &str,
186 ) -> Result<()> {
187 let url = format!(
188 "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Sql/servers/{}",
189 self.base_url(),
190 encode(subscription_id),
191 encode(resource_group_name),
192 encode(server_name),
193 );
194 let sep = if url.contains('?') { "&" } else { "?" };
195 let url = format!("{}{}api-version=2023-08-01-preview", url, sep);
196 let response = self.client.delete(&url).await?;
197 response.error_for_status().await?;
198 Ok(())
199 }
200
201 #[allow(dead_code)]
213 pub(crate) async fn list_databases(
214 &self,
215 subscription_id: &str,
216 resource_group_name: &str,
217 server_name: &str,
218 ) -> Result<DatabaseListResult> {
219 let url = format!(
220 "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Sql/servers/{}/databases",
221 self.base_url(),
222 encode(subscription_id),
223 encode(resource_group_name),
224 encode(server_name),
225 );
226 let sep = if url.contains('?') { "&" } else { "?" };
227 let url = format!("{}{}api-version=2023-08-01-preview", url, sep);
228 let response = self.client.get(&url).await?;
229 let response = response.error_for_status().await?;
230 let response_bytes =
231 response
232 .bytes()
233 .await
234 .map_err(|e| crate::AzureError::InvalidResponse {
235 message: format!("Failed to read list_databases response: {e}"),
236 body: None,
237 })?;
238 serde_json::from_slice(&response_bytes).map_err(|e| crate::AzureError::InvalidResponse {
239 message: format!("Failed to parse list_databases response: {e}"),
240 body: Some(String::from_utf8_lossy(&response_bytes).to_string()),
241 })
242 }
243
244 #[allow(dead_code)]
257 pub(crate) async fn get_database(
258 &self,
259 subscription_id: &str,
260 resource_group_name: &str,
261 server_name: &str,
262 database_name: &str,
263 ) -> Result<Database> {
264 let url = format!(
265 "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Sql/servers/{}/databases/{}",
266 self.base_url(),
267 encode(subscription_id),
268 encode(resource_group_name),
269 encode(server_name),
270 encode(database_name),
271 );
272 let sep = if url.contains('?') { "&" } else { "?" };
273 let url = format!("{}{}api-version=2023-08-01-preview", url, sep);
274 let response = self.client.get(&url).await?;
275 let response = response.error_for_status().await?;
276 let response_bytes =
277 response
278 .bytes()
279 .await
280 .map_err(|e| crate::AzureError::InvalidResponse {
281 message: format!("Failed to read get_database response: {e}"),
282 body: None,
283 })?;
284 serde_json::from_slice(&response_bytes).map_err(|e| crate::AzureError::InvalidResponse {
285 message: format!("Failed to parse get_database response: {e}"),
286 body: Some(String::from_utf8_lossy(&response_bytes).to_string()),
287 })
288 }
289
290 #[allow(dead_code)]
306 pub(crate) async fn create_database(
307 &self,
308 subscription_id: &str,
309 resource_group_name: &str,
310 server_name: &str,
311 database_name: &str,
312 body: &DatabaseCreateRequest,
313 ) -> Result<Database> {
314 let url = format!(
315 "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Sql/servers/{}/databases/{}",
316 self.base_url(),
317 encode(subscription_id),
318 encode(resource_group_name),
319 encode(server_name),
320 encode(database_name),
321 );
322 let sep = if url.contains('?') { "&" } else { "?" };
323 let url = format!("{}{}api-version=2023-08-01-preview", url, sep);
324 let body_bytes =
325 serde_json::to_vec(body).map_err(|e| crate::AzureError::InvalidResponse {
326 message: format!("Failed to serialize create_database request: {e}"),
327 body: None,
328 })?;
329 let response = self.client.put(&url, &body_bytes).await?;
330 let response = response.error_for_status().await?;
331 let response_bytes =
332 response
333 .bytes()
334 .await
335 .map_err(|e| crate::AzureError::InvalidResponse {
336 message: format!("Failed to read create_database response: {e}"),
337 body: None,
338 })?;
339 serde_json::from_slice(&response_bytes).map_err(|e| crate::AzureError::InvalidResponse {
340 message: format!("Failed to parse create_database response: {e}"),
341 body: Some(String::from_utf8_lossy(&response_bytes).to_string()),
342 })
343 }
344
345 #[allow(dead_code)]
355 pub(crate) async fn delete_database(
356 &self,
357 subscription_id: &str,
358 resource_group_name: &str,
359 server_name: &str,
360 database_name: &str,
361 ) -> Result<()> {
362 let url = format!(
363 "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Sql/servers/{}/databases/{}",
364 self.base_url(),
365 encode(subscription_id),
366 encode(resource_group_name),
367 encode(server_name),
368 encode(database_name),
369 );
370 let sep = if url.contains('?') { "&" } else { "?" };
371 let url = format!("{}{}api-version=2023-08-01-preview", url, sep);
372 let response = self.client.delete(&url).await?;
373 response.error_for_status().await?;
374 Ok(())
375 }
376
377 #[allow(dead_code)]
389 pub(crate) async fn list_firewall_rules(
390 &self,
391 subscription_id: &str,
392 resource_group_name: &str,
393 server_name: &str,
394 ) -> Result<FirewallRuleListResult> {
395 let url = format!(
396 "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Sql/servers/{}/firewallRules",
397 self.base_url(),
398 encode(subscription_id),
399 encode(resource_group_name),
400 encode(server_name),
401 );
402 let sep = if url.contains('?') { "&" } else { "?" };
403 let url = format!("{}{}api-version=2023-08-01-preview", url, sep);
404 let response = self.client.get(&url).await?;
405 let response = response.error_for_status().await?;
406 let response_bytes =
407 response
408 .bytes()
409 .await
410 .map_err(|e| crate::AzureError::InvalidResponse {
411 message: format!("Failed to read list_firewall_rules response: {e}"),
412 body: None,
413 })?;
414 serde_json::from_slice(&response_bytes).map_err(|e| crate::AzureError::InvalidResponse {
415 message: format!("Failed to parse list_firewall_rules response: {e}"),
416 body: Some(String::from_utf8_lossy(&response_bytes).to_string()),
417 })
418 }
419
420 #[allow(dead_code)]
432 pub(crate) async fn get_server_audit_policy(
433 &self,
434 subscription_id: &str,
435 resource_group_name: &str,
436 server_name: &str,
437 ) -> Result<ServerBlobAuditingPolicy> {
438 let url = format!(
439 "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Sql/servers/{}/auditingSettings/default",
440 self.base_url(),
441 encode(subscription_id),
442 encode(resource_group_name),
443 encode(server_name),
444 );
445 let sep = if url.contains('?') { "&" } else { "?" };
446 let url = format!("{}{}api-version=2021-11-01-preview", url, sep);
447 let response = self.client.get(&url).await?;
448 let response = response.error_for_status().await?;
449 let response_bytes =
450 response
451 .bytes()
452 .await
453 .map_err(|e| crate::AzureError::InvalidResponse {
454 message: format!("Failed to read get_server_audit_policy response: {e}"),
455 body: None,
456 })?;
457 serde_json::from_slice(&response_bytes).map_err(|e| crate::AzureError::InvalidResponse {
458 message: format!("Failed to parse get_server_audit_policy response: {e}"),
459 body: Some(String::from_utf8_lossy(&response_bytes).to_string()),
460 })
461 }
462
463 #[allow(dead_code)]
478 pub(crate) async fn enable_server_auditing(
479 &self,
480 subscription_id: &str,
481 resource_group_name: &str,
482 server_name: &str,
483 body: &EnableServerAuditingRequest,
484 ) -> Result<ServerBlobAuditingPolicy> {
485 let url = format!(
486 "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Sql/servers/{}/auditingSettings/default",
487 self.base_url(),
488 encode(subscription_id),
489 encode(resource_group_name),
490 encode(server_name),
491 );
492 let sep = if url.contains('?') { "&" } else { "?" };
493 let url = format!("{}{}api-version=2021-11-01-preview", url, sep);
494 let body_bytes =
495 serde_json::to_vec(body).map_err(|e| crate::AzureError::InvalidResponse {
496 message: format!("Failed to serialize enable_server_auditing request: {e}"),
497 body: None,
498 })?;
499 let response = self.client.put(&url, &body_bytes).await?;
500 let response = response.error_for_status().await?;
501 let response_bytes =
502 response
503 .bytes()
504 .await
505 .map_err(|e| crate::AzureError::InvalidResponse {
506 message: format!("Failed to read enable_server_auditing response: {e}"),
507 body: None,
508 })?;
509 serde_json::from_slice(&response_bytes).map_err(|e| crate::AzureError::InvalidResponse {
510 message: format!("Failed to parse enable_server_auditing response: {e}"),
511 body: Some(String::from_utf8_lossy(&response_bytes).to_string()),
512 })
513 }
514
515 #[allow(dead_code)]
531 pub(crate) async fn create_firewall_rule(
532 &self,
533 subscription_id: &str,
534 resource_group_name: &str,
535 server_name: &str,
536 firewall_rule_name: &str,
537 body: &FirewallRuleCreateRequest,
538 ) -> Result<FirewallRule> {
539 let url = format!(
540 "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Sql/servers/{}/firewallRules/{}",
541 self.base_url(),
542 encode(subscription_id),
543 encode(resource_group_name),
544 encode(server_name),
545 encode(firewall_rule_name),
546 );
547 let sep = if url.contains('?') { "&" } else { "?" };
548 let url = format!("{}{}api-version=2023-08-01-preview", url, sep);
549 let body_bytes =
550 serde_json::to_vec(body).map_err(|e| crate::AzureError::InvalidResponse {
551 message: format!("Failed to serialize create_firewall_rule request: {e}"),
552 body: None,
553 })?;
554 let response = self.client.put(&url, &body_bytes).await?;
555 let response = response.error_for_status().await?;
556 let response_bytes =
557 response
558 .bytes()
559 .await
560 .map_err(|e| crate::AzureError::InvalidResponse {
561 message: format!("Failed to read create_firewall_rule response: {e}"),
562 body: None,
563 })?;
564 serde_json::from_slice(&response_bytes).map_err(|e| crate::AzureError::InvalidResponse {
565 message: format!("Failed to parse create_firewall_rule response: {e}"),
566 body: Some(String::from_utf8_lossy(&response_bytes).to_string()),
567 })
568 }
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574
575 #[tokio::test]
576 async fn test_list_servers() {
577 let mut mock = crate::MockClient::new();
578
579 mock.expect_get("/subscriptions/test-subscriptionId/providers/Microsoft.Sql/servers")
580 .returning_json(serde_json::to_value(ServerListResult::fixture()).unwrap());
581
582 let client = crate::AzureHttpClient::from_mock(mock);
583 let ops = SqlOps::new(&client);
584
585 let result = ops.list_servers("test-subscriptionId").await;
586 assert!(result.is_ok());
587 }
588
589 #[tokio::test]
590 async fn test_get_server() {
591 let mut mock = crate::MockClient::new();
592
593 mock.expect_get("/subscriptions/test-subscriptionId/resourceGroups/test-resourceGroupName/providers/Microsoft.Sql/servers/test-serverName")
594 .returning_json(serde_json::to_value(Server::fixture()).unwrap());
595
596 let client = crate::AzureHttpClient::from_mock(mock);
597 let ops = SqlOps::new(&client);
598
599 let result = ops
600 .get_server(
601 "test-subscriptionId",
602 "test-resourceGroupName",
603 "test-serverName",
604 )
605 .await;
606 assert!(result.is_ok());
607 }
608
609 #[tokio::test]
610 async fn test_create_server() {
611 let mut mock = crate::MockClient::new();
612
613 mock.expect_put("/subscriptions/test-subscriptionId/resourceGroups/test-resourceGroupName/providers/Microsoft.Sql/servers/test-serverName")
614 .returning_json(serde_json::to_value(Server::fixture()).unwrap());
615
616 let client = crate::AzureHttpClient::from_mock(mock);
617 let ops = SqlOps::new(&client);
618
619 let body = ServerCreateRequest::fixture();
620 let result = ops
621 .create_server(
622 "test-subscriptionId",
623 "test-resourceGroupName",
624 "test-serverName",
625 &body,
626 )
627 .await;
628 assert!(result.is_ok());
629 }
630
631 #[tokio::test]
632 async fn test_delete_server() {
633 let mut mock = crate::MockClient::new();
634
635 mock.expect_delete("/subscriptions/test-subscriptionId/resourceGroups/test-resourceGroupName/providers/Microsoft.Sql/servers/test-serverName")
636 .returning_json(serde_json::json!({}));
637
638 let client = crate::AzureHttpClient::from_mock(mock);
639 let ops = SqlOps::new(&client);
640
641 let result = ops
642 .delete_server(
643 "test-subscriptionId",
644 "test-resourceGroupName",
645 "test-serverName",
646 )
647 .await;
648 assert!(result.is_ok());
649 }
650
651 #[tokio::test]
652 async fn test_list_databases() {
653 let mut mock = crate::MockClient::new();
654
655 mock.expect_get("/subscriptions/test-subscriptionId/resourceGroups/test-resourceGroupName/providers/Microsoft.Sql/servers/test-serverName/databases")
656 .returning_json(serde_json::to_value(DatabaseListResult::fixture()).unwrap());
657
658 let client = crate::AzureHttpClient::from_mock(mock);
659 let ops = SqlOps::new(&client);
660
661 let result = ops
662 .list_databases(
663 "test-subscriptionId",
664 "test-resourceGroupName",
665 "test-serverName",
666 )
667 .await;
668 assert!(result.is_ok());
669 }
670
671 #[tokio::test]
672 async fn test_get_database() {
673 let mut mock = crate::MockClient::new();
674
675 mock.expect_get("/subscriptions/test-subscriptionId/resourceGroups/test-resourceGroupName/providers/Microsoft.Sql/servers/test-serverName/databases/test-databaseName")
676 .returning_json(serde_json::to_value(Database::fixture()).unwrap());
677
678 let client = crate::AzureHttpClient::from_mock(mock);
679 let ops = SqlOps::new(&client);
680
681 let result = ops
682 .get_database(
683 "test-subscriptionId",
684 "test-resourceGroupName",
685 "test-serverName",
686 "test-databaseName",
687 )
688 .await;
689 assert!(result.is_ok());
690 }
691
692 #[tokio::test]
693 async fn test_create_database() {
694 let mut mock = crate::MockClient::new();
695
696 mock.expect_put("/subscriptions/test-subscriptionId/resourceGroups/test-resourceGroupName/providers/Microsoft.Sql/servers/test-serverName/databases/test-databaseName")
697 .returning_json(serde_json::to_value(Database::fixture()).unwrap());
698
699 let client = crate::AzureHttpClient::from_mock(mock);
700 let ops = SqlOps::new(&client);
701
702 let body = DatabaseCreateRequest::fixture();
703 let result = ops
704 .create_database(
705 "test-subscriptionId",
706 "test-resourceGroupName",
707 "test-serverName",
708 "test-databaseName",
709 &body,
710 )
711 .await;
712 assert!(result.is_ok());
713 }
714
715 #[tokio::test]
716 async fn test_delete_database() {
717 let mut mock = crate::MockClient::new();
718
719 mock.expect_delete("/subscriptions/test-subscriptionId/resourceGroups/test-resourceGroupName/providers/Microsoft.Sql/servers/test-serverName/databases/test-databaseName")
720 .returning_json(serde_json::json!({}));
721
722 let client = crate::AzureHttpClient::from_mock(mock);
723 let ops = SqlOps::new(&client);
724
725 let result = ops
726 .delete_database(
727 "test-subscriptionId",
728 "test-resourceGroupName",
729 "test-serverName",
730 "test-databaseName",
731 )
732 .await;
733 assert!(result.is_ok());
734 }
735
736 #[tokio::test]
737 async fn test_list_firewall_rules() {
738 let mut mock = crate::MockClient::new();
739
740 mock.expect_get("/subscriptions/test-subscriptionId/resourceGroups/test-resourceGroupName/providers/Microsoft.Sql/servers/test-serverName/firewallRules")
741 .returning_json(serde_json::to_value(FirewallRuleListResult::fixture()).unwrap());
742
743 let client = crate::AzureHttpClient::from_mock(mock);
744 let ops = SqlOps::new(&client);
745
746 let result = ops
747 .list_firewall_rules(
748 "test-subscriptionId",
749 "test-resourceGroupName",
750 "test-serverName",
751 )
752 .await;
753 assert!(result.is_ok());
754 }
755
756 #[tokio::test]
757 async fn test_create_firewall_rule() {
758 let mut mock = crate::MockClient::new();
759
760 mock.expect_put("/subscriptions/test-subscriptionId/resourceGroups/test-resourceGroupName/providers/Microsoft.Sql/servers/test-serverName/firewallRules/test-firewallRuleName")
761 .returning_json(serde_json::to_value(FirewallRule::fixture()).unwrap());
762
763 let client = crate::AzureHttpClient::from_mock(mock);
764 let ops = SqlOps::new(&client);
765
766 let body = FirewallRuleCreateRequest::fixture();
767 let result = ops
768 .create_firewall_rule(
769 "test-subscriptionId",
770 "test-resourceGroupName",
771 "test-serverName",
772 "test-firewallRuleName",
773 &body,
774 )
775 .await;
776 assert!(result.is_ok());
777 }
778}