diff --git a/src/records/dns_record.rs b/src/dns/mod.rs similarity index 100% rename from src/records/dns_record.rs rename to src/dns/mod.rs diff --git a/src/godaddy/api.rs b/src/godaddy/api.rs new file mode 100644 index 0000000..6e07d58 --- /dev/null +++ b/src/godaddy/api.rs @@ -0,0 +1,65 @@ +use serde::Deserialize; +use std::fmt; + +use crate::dns::DNSRecord; + +pub enum Api { + Patch(DNSRecord), + Delete(DNSRecord), + Get(DNSRecord), + List(String), +} + +#[derive(Deserialize, Debug)] +pub struct ResponseError { + pub code: String, + pub message: String, + pub fields: Vec, +} + +#[derive(Deserialize, Debug)] +pub struct ResponseField { + #[serde(default)] + pub code: String, + + #[serde(default)] + pub message: String, + + #[serde(default)] + pub path: String, + + #[serde(default)] + #[serde(rename = "pathRelated")] + pub path_related: String, +} + +impl fmt::Display for Api { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Api::Patch(record) => write!( + f, + "https://api.godaddy.com/v1/domains/{domain}/records", + domain = record.domain, + ), + Api::Delete(record) => write!( + f, + "https://api.godaddy.com/v1/domains/{domain}/records/{record_type}/{name}", + domain = record.domain, + record_type = record.record_type, + name = record.name + ), + Api::Get(record) => write!( + f, + "https://api.godaddy.com/v1/domains/{domain}/records/{record_type}/{name}", + domain = record.domain, + record_type = record.record_type, + name = record.name + ), + Api::List(domain) => write!( + f, + "https://api.godaddy.com/v1/domains/{domain}/records", + domain = domain, + ), + } + } +} diff --git a/src/godaddy/mod.rs b/src/godaddy/mod.rs index b1ab2d2..8ad833b 100644 --- a/src/godaddy/mod.rs +++ b/src/godaddy/mod.rs @@ -1,65 +1,131 @@ -use serde::Deserialize; -use std::fmt; +use crate::dns::DNSRecord; -use crate::records::dns_record::DNSRecord; +pub mod api; -pub enum Api { - Patch(DNSRecord), - Delete(DNSRecord), - Get(DNSRecord), - List(String), +use log::{debug, info}; + +use crate::auth::Auth; +use api::{Api, ResponseError}; +use reqwest::Response; + +/// Sends a put request to the GoDaddy API to update a DNS record. +pub async fn update_record(record: DNSRecord, auth: &Auth) -> () { + let api: Api = Api::Patch(record.clone()); + + let body = vec![record.clone()]; + debug!("{:?}", body); + + let header = auth.as_header(); + + let client = reqwest::Client::new(); + + let req = client + .patch(api.to_string()) + .json(&body) + .header("accept", "application/json") + .header("content-type", "application/json") + .header("authorization", &header); + debug!("{:?}", api.to_string()); + + let response = req.send().await.expect("Error updating records."); + parse_response(response, record).await; } -#[derive(Deserialize, Debug)] -pub struct ResponseError { - pub code: String, - pub message: String, - pub fields: Vec, +pub async fn delete_record(record: DNSRecord, auth: &Auth) -> () { + let api: Api = Api::Delete(record.clone()); + + let header = auth.as_header(); + + let client = reqwest::Client::new(); + + let req = client + .delete(api.to_string()) + .header("accept", "application/json") + .header("authorization", &header); + + let response = req.send().await.expect("Error deleting record."); + parse_response(response, record).await; } -#[derive(Deserialize, Debug)] -pub struct ResponseField { - #[serde(default)] - pub code: String, - - #[serde(default)] - pub message: String, - - #[serde(default)] - pub path: String, - - #[serde(default)] - #[serde(rename = "pathRelated")] - pub path_related: String, -} - -impl fmt::Display for Api { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Api::Patch(record) => write!( - f, - "https://api.godaddy.com/v1/domains/{domain}/records", - domain = record.domain, - ), - Api::Delete(record) => write!( - f, - "https://api.godaddy.com/v1/domains/{domain}/records/{record_type}/{name}", - domain = record.domain, - record_type = record.record_type, - name = record.name - ), - Api::Get(record) => write!( - f, - "https://api.godaddy.com/v1/domains/{domain}/records/{record_type}/{name}", - domain = record.domain, - record_type = record.record_type, - name = record.name - ), - Api::List(domain) => write!( - f, - "https://api.godaddy.com/v1/domains/{domain}/records", - domain = domain, - ), +async fn parse_response(response: Response, record: DNSRecord) -> () { + match response.status() { + s if s.is_success() => { + info!("success: {:?}", record); + } + s if s.is_client_error() => { + let body = response.text().await.unwrap(); + match serde_json::from_str::(&body) { + Ok(json) => { + info!("client error [{}]: {}\n{:?}", s, json.message, json.fields); + } + Err(e) => { + eprintln!("Failed to parse JSON: {:?}", e); + eprintln!("Raw response body: {:?}", body); + } + }; + } + s => { + let body = response.text().await.unwrap(); + match serde_json::from_str::(&body) { + Ok(json) => { + info!("client error [{}]: {}\n{:?}", s, json.message, json.fields); + } + Err(e) => { + eprintln!("Failed to parse JSON: {:?}", e); + eprintln!("Raw response body: {:?}", body); + } + } } } } + +pub async fn get_record(record: &DNSRecord, auth: &Auth) -> Vec { + let api: Api = Api::Get(record.clone()); + + let header = auth.as_header(); + + let client = reqwest::Client::new(); + + let req = client + .get(api.to_string()) + .header("accept", "application/json") + .header("authorization", &header); + + let response = req.send().await.expect("Error listing records."); + if response.status().is_success() { + let data = &response.text().await.expect("Error reading response text"); + let record: Vec = serde_json::from_str(data).expect("Error parsing response"); + return record; + } else { + return vec![]; + } +} + +pub async fn list_records(domain: &str, auth: &Auth) -> () { + let api: Api = Api::List(domain.to_string()); + + let header = auth.as_header(); + + let client = reqwest::Client::new(); + + let req = client + .get(api.to_string()) + .header("accept", "application/json") + .header("authorization", &header); + + let response = req.send().await.expect("Error listing records."); + if response.status().is_success() { + let body = response.text().await.expect("Error reading response text"); + let records: Vec = serde_json::from_str(&body).unwrap(); + // .expect("Error parsing records into object"); + println!("{:<5} {:<25} {:<30}", "Type", "Name", "Value"); // Header + for record in records { + println!( + "{:<5} {:<25} {:<30}", + record.record_type, record.name, record.data + ); + } + } else { + println!("Request failed with status: {}", response.status()); + } +} diff --git a/src/ip_handler/mod.rs b/src/ip/mod.rs similarity index 100% rename from src/ip_handler/mod.rs rename to src/ip/mod.rs diff --git a/src/main.rs b/src/main.rs index 081b2d5..c03f31c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -extern crate dotenv; +use clap::{Parser, Subcommand}; use dotenv::dotenv; use std::env; @@ -8,13 +8,11 @@ use log::LevelFilter; use simple_logger::SimpleLogger; mod auth; +mod dns; mod godaddy; -mod ip_handler; -pub mod records; -use crate::auth::Auth; -use records::dns_record::{DNSRecord, RecordType}; -use clap::{Parser, Subcommand}; +use auth::Auth; +use dns::{DNSRecord, RecordType}; #[derive(Parser, Debug)] #[clap( @@ -107,7 +105,7 @@ async fn main() -> Result<(), Box> { ttl: 600, ..Default::default() }; - records::update_record(record, &auth).await; + godaddy::update_record(record, &auth).await; return Ok(()); } SubCommands::Delete(args) => { @@ -117,12 +115,12 @@ async fn main() -> Result<(), Box> { record_type: args.record_type, ..Default::default() }; - records::delete_record(record, &auth).await; + godaddy::delete_record(record, &auth).await; return Ok(()); } SubCommands::List(args) => { let domain = args.domain; - records::list_records(&domain, &auth).await; + godaddy::list_records(&domain, &auth).await; return Ok(()); } } diff --git a/src/records/mod.rs b/src/records/mod.rs deleted file mode 100644 index d5d87a3..0000000 --- a/src/records/mod.rs +++ /dev/null @@ -1,130 +0,0 @@ -use log::{debug, info}; - -use crate::auth::Auth; -use crate::godaddy::{Api, ResponseError}; -use crate::records::dns_record::DNSRecord; -use reqwest::Response; - -pub mod dns_record; - -/// Sends a put request to the GoDaddy API to update a DNS record. -pub async fn update_record(record: DNSRecord, auth: &Auth) -> () { - let api: Api = Api::Patch(record.clone()); - - let body = vec![record.clone()]; - debug!("{:?}", body); - - let header = auth.as_header(); - - let client = reqwest::Client::new(); - - let req = client - .patch(api.to_string()) - .json(&body) - .header("accept", "application/json") - .header("content-type", "application/json") - .header("authorization", &header); - debug!("{:?}", api.to_string()); - - let response = req.send().await.expect("Error updating records."); - parse_response(response, record).await; -} - -pub async fn delete_record(record: DNSRecord, auth: &Auth) -> () { - let api: Api = Api::Delete(record.clone()); - - let header = auth.as_header(); - - let client = reqwest::Client::new(); - - let req = client - .delete(api.to_string()) - .header("accept", "application/json") - .header("authorization", &header); - - let response = req.send().await.expect("Error deleting record."); - parse_response(response, record).await; -} - -async fn parse_response(response: Response, record: DNSRecord) -> () { - match response.status() { - s if s.is_success() => { - info!("success: {:?}", record); - } - s if s.is_client_error() => { - let body = response.text().await.unwrap(); - match serde_json::from_str::(&body) { - Ok(json) => { - info!("client error [{}]: {}\n{:?}", s, json.message, json.fields); - } - Err(e) => { - eprintln!("Failed to parse JSON: {:?}", e); - eprintln!("Raw response body: {:?}", body); - } - }; - } - s => { - let body = response.text().await.unwrap(); - match serde_json::from_str::(&body) { - Ok(json) => { - info!("client error [{}]: {}\n{:?}", s, json.message, json.fields); - } - Err(e) => { - eprintln!("Failed to parse JSON: {:?}", e); - eprintln!("Raw response body: {:?}", body); - } - } - } - } -} - -pub async fn get_record(record: &DNSRecord, auth: &Auth) -> Vec { - let api: Api = Api::Get(record.clone()); - - let header = auth.as_header(); - - let client = reqwest::Client::new(); - - let req = client - .get(api.to_string()) - .header("accept", "application/json") - .header("authorization", &header); - - let response = req.send().await.expect("Error listing records."); - if response.status().is_success() { - let data = &response.text().await.expect("Error reading response text"); - let record: Vec = serde_json::from_str(data).expect("Error parsing response"); - return record; - } else { - return vec![]; - } -} - -pub async fn list_records(domain: &str, auth: &Auth) -> () { - let api: Api = Api::List(domain.to_string()); - - let header = auth.as_header(); - - let client = reqwest::Client::new(); - - let req = client - .get(api.to_string()) - .header("accept", "application/json") - .header("authorization", &header); - - let response = req.send().await.expect("Error listing records."); - if response.status().is_success() { - let body = response.text().await.expect("Error reading response text"); - let records: Vec = serde_json::from_str(&body).unwrap(); - // .expect("Error parsing records into object"); - println!("{:<5} {:<25} {:<30}", "Type", "Name", "Value"); // Header - for record in records { - println!( - "{:<5} {:<25} {:<30}", - record.record_type, record.name, record.data - ); - } - } else { - println!("Request failed with status: {}", response.status()); - } -}