diff --git a/Cargo.toml b/Cargo.toml index 1db9743..0736a5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,16 @@ name = "lunanode" version = "0.1.1" edition = "2021" +authors = ["Tait Hoyem "] +description = "Tired of using a web interface for your VPS, Email, and domain provider? Check out this tool! It's a CLI tool for Lunanode: A Canadian VPS hosting company." +license = "AGPL-3.0-only" +readme = "README.md" +repository = "https://git.tait.tech/tait/lunanode/" +keywords = ["vps", "cli", "email", "domain", "hosting"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[package.metadata.release] +release = true +publish = true [dependencies] clap = { version = "4.0.7", features = ["env", "derive"] } @@ -12,7 +20,7 @@ envy = "0.4.2" getset = "0.1.2" hex = "0.4.3" hmac = "0.12.1" -lunanode_macros = { path = "./lunanode_macros" } +lunanode_macros = "0.1.0" parse-display-derive = "0.6.0" sha2 = "0.10.5" serde = { version = "1.0.0", features = ["derive"] } diff --git a/README.md b/README.md index fbd086c..1a3065f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,131 @@ # `lunanode` This is an API to work with the LunaNode's OpenStack-compatible [citation needed] API. +This crate currently covers only the most basic functionality from the Lunanode API. Currently 33/98 (34%) API calls. +If you'd like to contribute additional functionality, please feel free to make an MR. + +* VM [8/21] + * List [X] + * Start [X] + * Stop [X] + * Reboot [X] + * Diskswap [ ] + * Rescue [ ] + * Shelve [ ] + * Unshelve [ ] + * Delete [ ] + * Info [X] + * Reimage [ ] + * Resize [ ] + * VNC [ ] + * IP [1/3] + * Floaitng IP Add [ ] + * Floating IP Delete [ ] + * IP List [X] + * IP Add [X] + * IP Delete [X] + * Security group Add [ ] + * Security group Delete [ ] +* Image [4/7] + * Fetch [ ] + * List [X] + * Details [X] + * Delete [X] + * Replicate [X] + * Rename [ ] + * Retrieve [ ] +* Volume [1/12] + * Create [ ] + * List [X] + * Info [ ] + * Attach [ ] + * Detach [ ] + * Extend [ ] + * Rename [ ] + * Delete [ ] + * Snapshot [0/4] + * Create [ ] + * List [ ] + * Replicate [ ] + * Delete [ ] +* Floating IP [1/3] + * List [X] + * Add [ ] + * Delete [ ] +* Network [1/3] + * List [X] + * Add [ ] + * Delete [ ] +* Security Group [0/7] + * List [ ] + * Create [ ] + * Delete [ ] + * Rename [ ] + * Rule List [ ] + * Rule Insert [ ] + * Rule Delete [ ] +* Script [0/5] + * List [ ] + * Get [ ] + * Create [ ] + * Update [ ] + * Delete [ ] +* SSH Key [1/3] + * List [X] + * Add [ ] + * Remove [ ] +* Plan [1/1] + * List [X] +* Region [1/1] + * List [X] +* Monitor [3/10] + * Check [2/4] + * List [X] + * Types [X] + * Add [ ] + * Remove [ ] + * Contact [1/4] + * List [X] + * Add [ ] + * Remove [ ] + * Alert [0/3] + * List [ ] + * Add [ ] + * Remove [ ] +* Email [6/14] + * Usage [X] + * Domain [1/5] + * List [X] + * Add [ ] + * Remove [ ] + * DKIM [0/2] + * Set [ ] + * Unset [ ] + * User [4/4] + * List [X] + * Add [X] + * Remove [X] + * Set Password [X] + * Alias [0/3] + * List [ ] + * Add [ ] + * Remove [ ] +* DNS [6/10] + * Zone [2/3] + * List [X] + * Add [X] + * Remove [ ] + * Record [1/3] + * List [X] + * Add [ ] + * Remove [ ] + * Dyn [3/4] + * List [X] + * Add [X] + * Update [ ] + * Remove [X] +* Billing [1/1] + * Credit [X] ## Installation @@ -8,7 +133,7 @@ This is an API to work with the LunaNode's OpenStack-compatible [citation needed ## API Keys -Add the `LUNANODE_API_KEY` and `LUNANODE_KEY_ID` to your environment variables. +Add the `LUNANODE_API_KEY`, `LUNANODE_KEY_ID` and `LUNANODE_API_PARTIALKEY` (the first 64 chars of `LUNANODE_API_KEY`) to your environment variables. ## Usage @@ -35,13 +160,15 @@ See section help for more details, i.e.: `lunanode image help` * [ ] Simplify code so there is less boilerplate. In particular, writing out every enum variant is pretty annoying when I need the same info out of them either way. * [ ] Tests! +* [ ] Create more detailed documentation. `#[deny(missing_docs)]` is on, but some of the decisions in the code aren't explained very well. +* [ ] Cover EVERY API endpoint. * [ ] Stricter typing. Certain attributes, even though they are recieved as Strings, should really be some kind of `enum`. Make every non-String type some kind of enum. * [ ] UUIDs should be UUID types and not string. The only library I could find to do this, Uuid, seems to serialize them without the dashes, which screws up Lunanode... annoyingly. * [ ] Subnets should be more strictly types. They should always be an IP/subnet combo as two fields (std::net::Ipv4Addr, i32(0..32)). * [ ] Automatically create IDs from names, or other identifiying info instead of using the RESP API's id system. + * For example: `email user add tait@tait.tech` should automatically fetch the domain ID required to make this call normally: `email user add DOMAIN_ID tait@tait.tech`. + * Another example: `vm shutdown my_webserver` would automatically exapnd to find the VM with the name my_webserver and have it shutdown expanding the name into the UUID: `vm shutdown ffff-ffff-ffff-ffffffffffffffff`. ## Support This Project * [Support me on librapay](https://liberapay.com/tait/). - - diff --git a/lunanode_macros/Cargo.toml b/lunanode_macros/Cargo.toml index 786b805..86fdff4 100644 --- a/lunanode_macros/Cargo.toml +++ b/lunanode_macros/Cargo.toml @@ -2,12 +2,20 @@ name = "lunanode_macros" version = "0.1.0" edition = "2021" +authors = ["Tait Hoyem "] +description = "Macros to assist in creation of the lunanode crate." +license = "AGPL-3.0-only" +readme = "README.md" +repository = "https://git.tait.tech/tait/lunanode/" +keywords = ["macros", "helper"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[package.metadata.release] +release = true +publish = true [lib] proc_macro = true [dependencies] -syn = "1.0" +syn = { version = "1.0.102", features = ["full"] } quote = "1.0" diff --git a/lunanode_macros/README.md b/lunanode_macros/README.md new file mode 100644 index 0000000..3b5262e --- /dev/null +++ b/lunanode_macros/README.md @@ -0,0 +1,3 @@ +# `lunanode_macros` + +This crate contains macros for help with the [`lunanode` crate](https://crates.io/crates/lunanode). diff --git a/lunanode_macros/src/lib.rs b/lunanode_macros/src/lib.rs index c27b579..aa14627 100644 --- a/lunanode_macros/src/lib.rs +++ b/lunanode_macros/src/lib.rs @@ -3,20 +3,18 @@ /// Usages: /// ```rust /// #[lunanode_request(response="ImageListResponse", endpoint="image/list/")] -/// #[derive(Serialize, Deserialize, Debug, ...)] +/// #[derive(Serialize, Deserialize, Debug)] /// struct MyStruct { /// ... /// ``` /// /// TODO: Improve error messages. -use std::collections::HashMap; use proc_macro::TokenStream; use quote::quote; -use syn::parse::{Parse, ParseStream, Parser, Result}; -use syn::{parse, parse_macro_input, punctuated::Punctuated, Attribute, AttributeArgs, Expr, Ident, ItemStruct, Local, Lit, LitStr, NestedMeta, Meta, MetaNameValue, Pat, Stmt, Type, Token, DeriveInput}; +use syn::parse::Parser; +use syn::{parse_macro_input, AttributeArgs, ItemStruct, Lit, NestedMeta, Meta, MetaNameValue, Type}; -#[derive(Debug, Hash, Eq, PartialEq)] enum LunanodeRequestParam { Invalid, Response(Type), @@ -33,7 +31,7 @@ impl LunanodeRequestParam { } #[proc_macro_attribute] -pub fn lunanode_response(attr: TokenStream, input: TokenStream) -> TokenStream { +pub fn lunanode_response(_attr: TokenStream, input: TokenStream) -> TokenStream { // Get the structs below the macro let mut item_struct = parse_macro_input!(input as ItemStruct); // clone the name to keep compiler happy :) @@ -67,7 +65,7 @@ pub fn lunanode_request(attr: TokenStream, input: TokenStream) -> TokenStream { .into_iter() .filter_map(|nm| match nm { // Only select certain tokens - NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, eq_token, lit: Lit::Str(lstr) })) => Some( + NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, eq_token: _, lit: Lit::Str(lstr) })) => Some( // Convert the sigment of the path to a string (path.segments .into_iter() @@ -102,6 +100,7 @@ pub fn lunanode_request(attr: TokenStream, input: TokenStream) -> TokenStream { .parse2(quote! { #[serde(flatten)] #[clap(flatten)] + /// A set of keys that must be set by the user if ANY request is to be made. pub keys: ApiKeys }) .unwrap(), diff --git a/src/LNTypes.rs b/src/LNTypes.rs index 0f3ad1d..d3abc20 100644 --- a/src/LNTypes.rs +++ b/src/LNTypes.rs @@ -268,30 +268,30 @@ pub struct BillingCreditResponse { } #[derive(Serialize, Deserialize, Debug)] -pub struct LNErrorResponse { +pub struct LunaNodeErrorResponse { #[serde(with="success")] success: bool, error: String, // proper type, full accoutnign of the error from the API } -impl std::fmt::Display for LNErrorResponse { +impl std::fmt::Display for LunaNodeErrorResponse { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "An API error has occured! {}", self.error) } } -impl std::error::Error for LNErrorResponse {} +impl std::error::Error for LunaNodeErrorResponse {} #[derive(Debug)] -pub enum LNError { +pub enum LunaNodeError { RequestError(ureq::Error), DeserializationError(std::io::Error), TimeError(std::time::SystemTimeError), - LunaNodeError(LNErrorResponse), + LunaNodeError(LunaNodeErrorResponse), SerdeError(serde_json::Error, String), // the serde error, accompanied by a raw string GetFuckedError, } -impl std::fmt::Display for LNError { +impl std::fmt::Display for LunaNodeError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "An error has occured! {}", self) } } -impl std::error::Error for LNError {} +impl std::error::Error for LunaNodeError {} diff --git a/src/external.rs b/src/external.rs index d495388..d933f7b 100644 --- a/src/external.rs +++ b/src/external.rs @@ -1,3 +1,7 @@ +//! Convert serialized "1"/"" to true/false. +//! This behaviour is extremely odd, and seemingly unique to Lunanode, so I had to use a custom +//! serialize/deserialize pair of functions. + use serde::{ de::{ self, @@ -8,7 +12,7 @@ use serde::{ Deserialize, }; -/// Seiralize bool into "yes"/"no", just like the LunaNode API does. +/// Seiralize bool into "1"/"", just like the LunaNode API does. pub fn serialize(succ: &bool, serializer: S) -> Result where S: Serializer { @@ -18,7 +22,7 @@ where } } -/// Deserialize bool from String with custom value mapping "yes" => true, "no" => false +/// Deserialize bool from String with custom value mapping "1" => true, "" => false pub fn deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, diff --git a/src/main.rs b/src/main.rs index e0a363f..69e9e62 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,14 @@ +//! The main program which runs using any of the various commands defined in +//! [`crate::requests::Args`]. +#![deny(missing_docs)] + pub mod success; pub mod external; pub mod requests; pub mod responses; pub mod types; -use serde::{ - Serialize, - Deserialize, -}; -use requests::{ - Args, - VMListRequest, -}; +use requests::Args; use clap::Parser; fn main() -> Result<(), Box> { diff --git a/src/requests.rs b/src/requests.rs index 8efbb78..1a36f76 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -1,9 +1,10 @@ +//! All possible requests which can be made to the Lunanode API. + use crate::{ responses::{ - IPAddress, - VMListResponse, + VmListResponse, BillingCreditResponse, - LNImageListResponse, + ImageListResponse, VolumeListResponse, FloatingIpListResponse, NetworkListResponse, @@ -34,72 +35,89 @@ use crate::{ VmIpListResponse, }, types::{ - LNError, + LunaNodeError, LunaNodeRequest, ApiKeys, }, }; use lunanode_macros::lunanode_request; -use parse_display_derive::Display; -use ureq::post; use serde::{ Serialize, - Serializer, Deserialize, - de::DeserializeOwned, }; -use serde_with::serde_as; -use hmac::Hmac; -use sha2::Sha512; #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All possible `ssh-key` subcommands. pub enum SshKeySubArgs { + /// `ssh-key list` List(SshKeyRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All possible `dyn` subcommands. pub enum DynSubArgs { + /// `dyn list` List(DynListRequest), + /// `dyn remove` Remove(DynRemoveRequest), + /// `dyn add` Add(DynAddRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All possible `zone` subcommands. pub enum ZoneSubArgs { + /// `zone add` Add(ZoneAddRequest), + /// `zone list` List(ZoneListRequest), + /// `zone remove` Remove(ZoneRemoveRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All possible `email domain` subcommands. pub enum EmailDomainSubArgs { + /// `email domain list` List(EmailDomainListRequest), } + #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All possible `email user` subcommands. pub enum EmailUserSubArgs { + /// `email user list` List(EmailUserListRequest), + /// `email user add` Add(EmailUserAddRequest), + /// `email user remove` Remove(EmailUserRemoveRequest), + /// `email user set-password` SetPassword(EmailUserSetPasswordRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All possible `email` subcommands. pub enum EmailSubArgs { #[clap(subcommand)] + /// `email domain` Domain(EmailDomainSubArgs), + /// `email usage` Usage(EmailUsageRequest), #[clap(subcommand)] + /// `email user` User(EmailUserSubArgs), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All possible `floating` subcommands. pub enum FloatingSubArgs { /// List all images on my account. List(FloatingIpListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All possible `image` subcommands. pub enum ImageSubArgs { /// List all images on my account. List(ImageListRequest), @@ -107,21 +125,26 @@ pub enum ImageSubArgs { #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All possible `record` subcommands. pub enum RecordSubArgs { + /// List all records in a domain. List(RecordListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All `vm ip` subcommands. pub enum VmIpSubArgs { + /// `vm ip list` List(VmIpListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All `vm` subcommands. pub enum VmSubArgs { /// List all VMs on my account. - List(VMListRequest), + List(VmListRequest), /// Restart a VM, given an ID. Reboot(VmRebootRequest), /// Stop a VM, given an ID. @@ -134,12 +157,14 @@ pub enum VmSubArgs { } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All `volume` subcommands. pub enum VolumeSubArgs { /// List all volumes I'm paying for. List(VolumeListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All `billing` subcommands. pub enum BillingSubArgs { /// How much money is left in my account. Credit(BillingCreditRequest), @@ -147,42 +172,56 @@ pub enum BillingSubArgs { #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All `network` subcommands. pub enum NetworkSubArgs { /// Networks on your accounts List(NetworkListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All `plan` subcommands. pub enum PlanSubArgs { + /// `plan list` List(PlanListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All `region` subcommands. pub enum RegionSubArgs { + /// `region list` List(RegionListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All `monitor check` subcommands. pub enum MonitorCheckSubArgs { + /// `monitor check list` List(MonitorCheckListRequest), + /// `monitor check types` Types(MonitorCheckTypesRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All `monitor contact` subcommands. pub enum MonitorContactSubArgs { + /// `monitor contact list` List(MonitorContactListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All `monitor` subcommands. pub enum MonitorSubArgs { #[clap(subcommand)] + /// All `monitor check` subcommands. Check(MonitorCheckSubArgs), #[clap(subcommand)] + /// All `monitor contact` subcommands. Contact(MonitorContactSubArgs), } #[derive(Serialize, Deserialize, Debug, clap::Parser)] #[serde(untagged)] +/// All possible commands that may be used at the command line. pub enum Args { #[clap(subcommand)] /// See `lunanode image help` @@ -229,7 +268,7 @@ pub enum Args { } /// Specifies an internal function which can only be called on LunaNodeRequests whoes response type implements Debug. This could be set in the structure itself, and probably should be. -fn print_req(req: &T) -> Result<(), LNError> +fn print_req(req: &T) -> Result<(), LunaNodeError> where ::Response: std::fmt::Debug { @@ -238,7 +277,9 @@ where Ok(()) } impl Args { - pub fn make_request(&self) -> Result<(), LNError> { + /// A generic make_request that unwraps the inner `impl LunaNodeRequest` type, and runs + /// make_request on those. + pub fn make_request(&self) -> Result<(), LunaNodeError> { match self { Self::Monitor(MonitorSubArgs::Contact(MonitorContactSubArgs::List(contact_list))) => print_req(contact_list)?, Self::Monitor(MonitorSubArgs::Check(MonitorCheckSubArgs::Types(monitor_check_types))) => print_req(monitor_check_types)?, @@ -275,7 +316,7 @@ impl Args { } /// ImageListRequest is used to create a new request for the /image/list endpoint. -#[lunanode_request(response="LNImageListResponse", endpoint="image/list/")] +#[lunanode_request(response="ImageListResponse", endpoint="image/list/")] pub struct ImageListRequest {} /// BillingCreditRequest handles the /billing/credits/ endpoint. It will produce a BillingCreditResponse. @@ -283,8 +324,8 @@ pub struct ImageListRequest {} pub struct BillingCreditRequest {} /// VMListRequest is used to create a new request for the /vm/list endpoint. -#[lunanode_request(response="VMListResponse", endpoint="vm/list/")] -pub struct VMListRequest {} +#[lunanode_request(response="VmListResponse", endpoint="vm/list/")] +pub struct VmListRequest {} /// VolumeListRequest is used to create a new request for the /volume/list endpoint. #[lunanode_request(response="VolumeListResponse", endpoint="volume/list/")] @@ -313,80 +354,112 @@ pub struct RecordListRequest { pub struct DynListRequest {} #[lunanode_request(response="SshKeyResponse", endpoint="sshkey/list/")] +/// A request to show all SSH public keys stored on the user's Lunanode account. pub struct SshKeyRequest {} #[lunanode_request(response="PlanListResponse", endpoint="plan/list/")] +/// A request to list all possible plans the user may create a new VM with. pub struct PlanListRequest {} #[lunanode_request(response="RegionListResponse", endpoint="region/list/")] +/// A request so show all regions available for the user to choose from. pub struct RegionListRequest {} #[lunanode_request(response="EmailUsageResponse", endpoint="email/usage/")] +/// A request to show an email usage report. pub struct EmailUsageRequest {} #[lunanode_request(response="EmailDomainListResponse", endpoint="email/domain-list/")] +/// A request to show all email domains managed by the user's Lunanode account. pub struct EmailDomainListRequest {} #[lunanode_request(response="MonitorCheckListResponse", endpoint="monitor/check-list/")] +/// A request to show all monitor checks (active and inactive). pub struct MonitorCheckListRequest {} #[lunanode_request(response="MonitorCheckTypesResponse", endpoint="monitor/check-types/")] +/// A request to check all possible monitor check types. pub struct MonitorCheckTypesRequest {} +/// A request to show all contacts on the monitor list. #[lunanode_request(response="MonitorContactListResponse", endpoint="monitor/contact-list/")] pub struct MonitorContactListRequest {} #[lunanode_request(response="EmailUserListResponse", endpoint="email/user-list/")] +/// A request to show all email users in a domain. pub struct EmailUserListRequest { + /// The domain ID to list users in. domain_id: i32, } #[lunanode_request(response="VmStartResponse", endpoint="vm/start/")] +/// A request to start a VM. pub struct VmStartRequest { + /// The ID of the VM to start. See [`VmListRequest`] vm_id: String, } #[lunanode_request(response="VmStopResponse", endpoint="vm/stop/")] +/// A request to stop a VM. pub struct VmStopRequest { + /// The ID of the VM to stop. See [`VmListRequest`] vm_id: String, } #[lunanode_request(response="VmRebootResponse", endpoint="vm/reboot/")] +/// A request to reboot a VM. pub struct VmRebootRequest { + /// The ID of the VM to reboot. See [`VmListRequest`] vm_id: String, } #[lunanode_request(response="EmailUserAddResponse", endpoint="email/user-add/")] +/// A request to add a new email account to a domain. pub struct EmailUserAddRequest { + /// The domain ID to add the user to. See [`EmailDomainListRequest`] domain_id: i32, + /// The username of the new user. username: String, + /// The password of the new user. password: String, } #[lunanode_request(response="EmailUserRemoveResponse", endpoint="email/user-remove/")] +/// A request to remove a user from a domain's email. pub struct EmailUserRemoveRequest { + /// An ID of the domain to remove the user from. domain_id: i32, + /// The ID of the user to remove. See [`EmailUserListRequest`] user_id: i32, } #[lunanode_request(response="EmailUserSetPasswordResponse", endpoint="email/user-set-password/")] +/// A request to set an email user's password on a specific domain. pub struct EmailUserSetPasswordRequest { + /// The ID of the domain the user is on. domain_id: i32, + /// The user ID of the user on the domain. user_id: i32, + /// The new password. password: String, } #[lunanode_request(response="VmIpListResponse", endpoint="vm/iplist/")] +/// A request to list all IPs associated with a VM. pub struct VmIpListRequest { + /// The VM whoes IPs to list. vm_id: String, } #[lunanode_request(response="DynRemoveResponse", endpoint="dns/dyn-remove/")] +/// A request to remove a dyn record. pub struct DynRemoveRequest { + /// The ID of the dyn record to remove. See [`DynListrRequest`] dyn_id: i32, } #[lunanode_request(response="DynAddResponse", endpoint="dns/dyn-add/")] +/// A request to add a new dyn record. pub struct DynAddRequest { /// The data string for the record. This is usually a FQDN, followed by a period. If you wanted to set a record for `dev.example.org`, you would put `dev.example.com.` in this field. name: String, @@ -395,6 +468,7 @@ pub struct DynAddRequest { } #[lunanode_request(response="ZoneAddResponse", endpoint="dns2/zone-add/")] +/// A request to add a new zone. pub struct ZoneAddRequest { /// A new zone, a FQDN. name: String, @@ -403,7 +477,11 @@ pub struct ZoneAddRequest { } #[lunanode_request(response="ZoneRemoveResponse", endpoint="dns2/zone-remove/")] +/// A request to remove a zone entirely from Luanode's control. +/// Be very careful doing this, as this will likely remove ***ALL*** of your records associated +/// with the domain. pub struct ZoneRemoveRequest { + /// The zone to remove. See also: [`ZoneListRequest`] zone_id: i32, } diff --git a/src/responses.rs b/src/responses.rs index 4261721..37bf215 100644 --- a/src/responses.rs +++ b/src/responses.rs @@ -1,19 +1,19 @@ -use ureq; +//! All responses which are recieved by this application from the Lunanode API. +//! These responses are all strictly typed, and documented. Although in some cases it is noted that +//! it should have an even stricter type, or that the field meaning is unkown. + use crate::success; use crate::external; use crate::types::{ LunaNodeResponse, - LunaNodeRequest, }; use lunanode_macros::lunanode_response; -use serde_json; use serde::{Serialize, Deserialize}; use serde_with::{ DisplayFromStr, serde_as, }; -use uuid; #[serde_as] #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -52,42 +52,53 @@ pub struct VirtualMachine { os_status: String, // should be nmore strict } -#[derive(Serialize, Deserialize, Debug)] -pub struct VMListResponse { +#[lunanode_response] +/// The result of requesting a list of VMs on the user's Lunanode account. +/// See also: [`crate::requests::VmListRequest`] +pub struct VmListResponse { + /// A list of VMs. vms: Vec, - #[serde(with="success")] - success: bool, // should be more strict "yes" or "no" as optiobs } -impl ToString for VMListResponse { - fn to_string(&self) -> String { - "N/A".to_string() - } -} -impl LunaNodeResponse for VMListResponse {} #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all="lowercase")] +/// An enum defining whether an IP is attached to a VM or is simply allocated without being +/// attached to any VM in particular. +/// See also: [`FloatingIp`] enum AttachmentType { + /// An unattached floating IP. Unattached, + /// An attached floating IP. Vm, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// Detailed information about a floating IP struct FloatingIp { + /// A String with a VM UUID if attached, otherwise None. attached_id: Option, + /// A String with the VM's name if attached, otherwise None. attached_name: Option, + /// Vm/Unattached attached_type: AttachmentType, + /// The hostname of the floating IP address, usually something like + /// xxx-xxx-xxx-xxx.rdns.lunanode.com hostname: String, + /// The IP address of the floating IP. ip: std::net::Ipv4Addr, + /// The region in which the IP address has been allocated. region: LNRegion, + /// The rDNS domain, if specified, otherwise, None. reverse: Option, #[serde_as(as="DisplayFromStr")] + /// The last time the record was updated. time_updated: chrono::DateTime, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// Extra detailed information for a VM. pub struct VMInfoExtra { #[serde_as(as="DisplayFromStr")] /// bandwidth allowed over a month in GB @@ -126,36 +137,50 @@ pub struct VMInfoExtra { #[serde(untagged)] /// A generic IP address type, with two variants: V4, and V6. This is used for generic types returned from the API. For example, a list of IP addresses without the IP type specified. pub enum IPAddress { + /// IPv4 variant V4(std::net::Ipv4Addr), + /// IPv6 variant V6(std::net::Ipv6Addr), } #[derive(Serialize, Deserialize, Debug)] +/// An IP address type enum, specifying either V4 or V6. pub enum IPAddressType { #[serde(rename="4")] + /// IPv4 variant V4, #[serde(rename="6")] + /// IPv6 variant V6, } #[derive(Serialize, Deserialize, Debug)] +/// VM IP address pub struct VMAddress { + /// The IP address of the VM. addr: IPAddress, #[serde(with="external")] + /// Whether the IP is an internal or external kind. external: bool, + /// The version of IP address used. version: IPAddressType, #[serde(rename="reverse")] - /// The reverse DNS assigned to the VM. This is optional. + /// The reverse DNS assigned to the VM, if specified. Otherwise, None. rdns: Option, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// Detailed VM info. pub struct VMInfo { #[serde(rename="additionalip")] + /// A list of additional IPs (which are not the primary IP) which have been assigned to the VM. additional_ip: Vec, #[serde(rename="additionalprivateip")] + /// Any additional private IPs assigned to the VM. additional_private_ip: Vec, + /// A list of addresses inherently part of the VM. Think of a primary private IP, or where + /// outgoing internet traffic is routed. addresses: Vec, /// a possibly empty string containing an error message error_detail: Option, @@ -198,80 +223,114 @@ pub struct VMInfo { volumes: String, } -#[derive(Serialize, Deserialize, Debug)] +#[lunanode_response] +/// The result from a [`crate::requests::VmInfoRequest`]. pub struct VMInfoResponse { + /// Extra information (cached) extra: VMInfoExtra, + /// Detailed VM info (uncached) info: VMInfo, - #[serde(with="success")] - success: bool, } #[derive(Serialize, Deserialize, Debug)] -pub enum LNPlanCategory { +/// A plan category. +pub enum PlanCategory { #[serde(rename="Compute-Optimized")] + /// For CPU-bound tasks. ComputeOptimized, #[serde(rename="General Purpose")] + /// Pretty good for most projects. GeneralPurpose, #[serde(rename="Memory-Optimized")] + /// For memory-bound tasks. MemoryOptimized, #[serde(rename="SSD-Cached High-Memory")] + /// For IO/memory-bound tasks. SSDCacheHighMemory, #[serde(rename="SSD-Cached Standard")] + /// For IO-bound tasks. SSDCacheStandard, } #[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq)] #[serde(rename_all = "lowercase")] +/// Regions where Lunanode is supported. pub enum LNRegion { + /// Montreal, Quebec, Canada. Montreal, + /// Roubaix, France. Roubaix, + /// Toronto, Ontario, Canada. Toronto } #[serde_as] #[derive(Serialize, Deserialize, Debug)] -pub struct LNPlan { +/// A VM plan/level of serivce. +pub struct Plan { #[serde_as(as="DisplayFromStr")] + /// Does the plan support all regions? all_regions: i32, // may need to be more strict? #[serde_as(as="DisplayFromStr")] + /// The maxmimum bandwidth in Mbps bandwidth: i32, // in Mbps - category: LNPlanCategory, + /// Category of plan + category: PlanCategory, #[serde_as(as="DisplayFromStr")] + /// Lunanode uses a CPU points system to determine how powerful the CPU is. cpu_points: f32, // no idea what this meas + /// The name of the plan, set by Lunanode themselves. name: String, // plan name, this could potentially be strictly typed as the types of plans don't often change "s.half", "s.1", "m.1", "m.2", etc. #[serde_as(as="DisplayFromStr")] + /// The plan ID. Could be useful if creating a new VM. plan_id: i32, // can be strictly typed, if needed #[serde_as(as="DisplayFromStr")] + /// The cost in USD per hour. price: f32, // up to 7 decmial points (f32), and this is the number of US dollars per hour + /// The price is USD per month, with a $ attached. price_monthly_nice: String, // instead of calculating it on your own, this provides a nice reading of the price for clients + /// The price in USD per hour, with a $ attached. price_nice: String, // same as above, but for the hour #[serde_as(as="DisplayFromStr")] + /// The amount of RAM in MB. ram: i32, // in MB + /// Regions where the plan can be dpeloyed. regions: Vec, // list of regions by name + /// Regions where the plan can be deployed, separated by a comma. regions_nice: String, // list of regions concatonated with commans #[serde_as(as="DisplayFromStr")] + /// Storage in GB. storage: i32, // per GB #[serde_as(as="DisplayFromStr")] + /// Number of vCPU cores. vcpu: i32, // number of vCPU cores } #[lunanode_response] +/// A list of plans from a [`crate::requests::PlanListRequest`] pub struct PlanListResponse { - plans: Vec, + /// List of plans; see [`Plan`] for more detail. + plans: Vec, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] -pub struct LNImage { +/// An ISO or QCOW2 image stored on Lunanode. +pub struct Image { #[serde_as(as="DisplayFromStr")] + /// ID of the image. This is useful for later if you would like to interact with it in some way. image_id: i32, // id of the image + /// Name of the Image set by the user, or Lunanode. name: String, // set by the user or LunaNode + /// The region in which the image is stored. region: LNRegion, + /// The status of the image; at least "active" and "inactive" are possible. status: String, // should be stricter, at least "active", "inactive"(?) } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// A storage volume on the user's Lunanode account. pub struct Volume { #[serde_as(as="DisplayFromStr")] /// The personal ID used for the volume. @@ -298,20 +357,37 @@ pub struct Volume { #[serde_as] #[derive(Serialize, Deserialize, Debug)] -pub struct LNImageDetails { +/// Details of a Lunanode image +pub struct ImageDetails { + /// This should be more strict. At least one possible value is "writeback". cache_mode: String, // should be stricter, at least "writeback", + /// This should be more strict; this should be a strict checksum type. checksum: String, // should be stricter, to check of checksum type + /// This should be more strict. At least one possible value is "iso". disk_format: String, // should be stricter, at least "iso" + /// This should be more strict. I think the only possible values are "ide" and "sata". hw_disk_bus: String, // should be stricter, at least "ide", + /// It's not clear exactly what this means; the Lunanode documentation isn't extremely well done. + /// But at least one possible value here is "cirrus"; unsure if other values are possible, or + /// what they are. hw_video_model: String, // could, in theory, be stricter, at least: "cirrus", + /// The model of the virtual interface (I think) hw_vif_model: String, // appropriately vague + /// A read only image? #[serde(with="success")] is_read_only: bool, // "yes"/"no" + /// There should not be many possible values, but one of them is "host-model". libvrt_cpu_mode: String, // should be stricter, at least: "host-model", + /// Unkown list of metadata. metadata: Vec<()>, // vec of what? + /// The name of the image; often set by the user. name: String, // sufficiently vague + /// The region of the image. region: LNRegion, // sufficiently typed + /// The size of the image in MB. size: i32, // in MB, maybe? + /// In theory this could be stricter, but Lunanode does not enumerate all possible return types. + /// Possible values are at least: "active". status: String, // should be stricter, at least: "active", #[serde_as(as="DisplayFromStr")] /// An (explicitly UTC) datetime of when the VM was created @@ -319,16 +395,21 @@ pub struct LNImageDetails { } #[lunanode_response] -pub struct LNImageDetailResponse { - details: LNImageDetails, +/// A response from [`crate::requests::ImageDetailRequest`] +pub struct ImageDetailResponse { + /// Details of a specific image. + details: ImageDetails, } #[lunanode_response] -pub struct LNImageListResponse { - images: Vec, +/// See also: [`crate::requests::ImageListRequest`] +pub struct ImageListResponse { + /// List of images and their information. + images: Vec, } #[lunanode_response] +/// See also: [`crate::requests::BillingCreditRequest`] pub struct BillingCreditResponse { /// Money left in the account in USD #[serde_as(as="DisplayFromStr")] @@ -336,33 +417,42 @@ pub struct BillingCreditResponse { } #[lunanode_response] +/// See also: [`crate::requests::VolumeListRequest`] pub struct VolumeListResponse { + /// List of volumes and their associated information. volumes: Vec, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// A network with a subnet. pub struct Network { /// The name set by the user when creating the network. name: String, #[serde_as(as="DisplayFromStr")] + /// The ID of the network; this is useful if you'd like to modify it in another request. net_id: i32, /// The network subnet. This should be strictly typed as an IP address w/ a subnet attached. TODO: subnet type subnet: String, } #[lunanode_response] +/// A response from [`crate::requests::NetworkListRequest`] pub struct NetworkListResponse { + /// A list of networks. networks: Vec, } #[lunanode_response] +/// A response from [`crate::requests::FloatingIpListRequest`] pub struct FloatingIpListResponse { + /// A list of [`FloatingIp`]s. ips: Vec, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// A domain "zone". This is the same as a root domain. pub struct Zone { #[serde_as(as="DisplayFromStr")] /// The lunanode id of the zone. @@ -375,26 +465,54 @@ pub struct Zone { } #[lunanode_response] +/// A response from [`crate::requests::ZoneListRequest`] pub struct ZoneListResponse { + /// A map of all zones, with their IDs as their keys. zones: std::collections::HashMap, } #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash)] #[serde(rename="UPPERCASE")] +/// Possible record types for [`Record`] pub enum RecordType { + /// An A record is an IPv4 pointer with a key (domain or subdomain) and value (IP address). A, + /// An AAAA record (usually said "quadrupal A") is an IPv6 pointer with a key (domain or subdomain) and + /// value (IP address). AAAA, + /// A CNAME record basically forwards requests from a domain or subdomain to a second domain or + /// subdomain. It is very common to see this in places where www.example.com and ftp.example.com + /// use the same server (and IP address) at example.com; now, when switching IP addresses, the address only + /// needs to be updated for example.com, and all the subdomains will still function like normal. CNAME, + /// An ALIAS record states that writing a domain one way, for example: ca.example.com, is the + /// same as another, for example: canada.example.com. ALIAS, + /// An MX record tells email providers where to send incoming mail. MX, + /// An NS record decides the nameserver record for the domain. This is necessary to use in the + /// case of Lunanode, since you cannot purchase a domain through them. You must purchase the + /// domain elsewhere, then set your NS records at your registrar to point to lunanode's + /// nameservers. NS, + /// A TXT record is a static record generally used for verification of some kind or another. Some + /// email providers (ProtonMail), will require you to set up a TXT record to prove that you own + /// the domain. TXT, + /// An SPF record is a deprecated form of email authentication record. SPF, + /// An SRV record can be used so that a port is not necessary when running multiple (similar or + /// the same) services on different ports. For example, suppose you run two web servers, one at + /// 80 and 443 (standard HTTP and HTTPS ports) and one on 8080 and 4443 (commonly used in live + /// updating prototypes. An SRV record can help you use a subdomain instead of a port number + /// at the end of the domain. So beta.example.com and www.example.com could point a browser to + /// different ports of the same IP. SRV } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// A DNS record. pub struct Record { /// The record id #[serde_as(as="DisplayFromStr")] @@ -445,26 +563,37 @@ pub struct Record { } #[lunanode_response] +/// The result of requesting all DNS records for a domain. +/// See also: [`crate::requests::RecordListRequest`], [`ZoneListResponse`] pub struct RecordListResponse { records: std::collections::HashMap, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// A detailed dyn record. pub struct DynRecord { #[serde_as(as="DisplayFromStr")] + /// An id you may need later to edit this. + /// See also: [`DynUpdateResponse`], [`DynRemoveResponse`] id: i32, + /// The name, or key (usually a subddomain) of the dyn record. name: String, + /// An IP address (a value). ip: IPAddress, } #[lunanode_response] +/// The result of requesting all dyn records on a domain. +/// See also: [`crate::requests::DynListRequest`] pub struct DynListResponse { dyns: std::collections::HashMap, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// Detailed information about an SSH key. +/// See also: [`SshKeyResponse`] pub struct SshKey { #[serde_as(as="DisplayFromStr")] id: i32, @@ -475,11 +604,16 @@ pub struct SshKey { } #[lunanode_response] +/// The result of requesting a list of all regions. +/// See also: [`crate::requests::RegionListRequest`] pub struct RegionListResponse { + /// A map of all regions supported by Lunanode. regions: std::collections::HashMap, } #[lunanode_response] +/// The result of requesting all ssh keys connected to an account. +/// See also: [`crate::requests::SshKeyRequest`] pub struct SshKeyResponse { #[serde(flatten)] /// List of keys stored in the LunaNode cloud. The String is just an index that I can't get to flatten :grumpy programmer noises: @@ -487,8 +621,10 @@ pub struct SshKeyResponse { } #[lunanode_response] +/// The reuslt of requesting email usage statistics from Lunanode. pub struct EmailUsageResponse { #[serde_as(as="DisplayFromStr")] + /// The number of domains being used by the account. domains: i32, /// Usage of email in GB, this should be an i32. storage: String, @@ -500,21 +636,31 @@ pub struct EmailUsageResponse { #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// The result of requesting an email's domain information. pub struct EmailDomainInfo { #[serde_as(as="DisplayFromStr")] + /// An id for the domain's email info. This will be useful later if you want to change + /// information about how the email domain operates. id: i32, + /// A String of the domain. name: String, + /// Storage used storage: String, + /// Storage used in MB/GB, with " MB/GB" attached. storage_nice: String, } #[lunanode_response] +/// The result of requesting a list of email domains on the Lunanode account. pub struct EmailDomainListResponse { #[serde(flatten)] - domains: std::collections::HashMap, + /// Key/value pairs where the key is a String of the domain. + domains: std::collections::HashMap, } #[lunanode_response] +/// The result of requesting all monitor checks (active or inactive). +/// See also: [`crate::requests::MonitorCheckListRequest`] pub struct MonitorCheckListResponse { /// This is not done yet. A Check type will be required. TODO checks: Vec, @@ -522,45 +668,74 @@ pub struct MonitorCheckListResponse { #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] +/// The type of parameter being asked for by the monitor check. +/// See also: [`Param`] pub enum ParamType { + /// A single, simple parameter. Single(String), + /// Posssible options in the case of a fixed set of choices. Generally speaking, the key/value + /// Strings will be the same, unless the choice has a space or some other character that would + /// make it annoying to parse over an HTTP request. Options(std::collections::HashMap), } #[derive(Serialize, Deserialize, Debug)] +/// Detailed information about a parameter of a monitor check. +/// See also [`MonitorTypes`] pub struct Param { #[serde(with="external")] + /// Is the parameter required when creating this monitor check. required: bool, + /// What kind of parameter is this. r#type: ParamType, + /// The name of the parameter, this would be the name of a field in a request. name: String, + /// A placeholder, or example of what may by used in this parameter. placeholder: Option, } #[derive(Serialize, Deserialize, Debug)] +/// Detailed information about various monitor types in a category. +/// See also: [`MonitorCheckTypesResponse`] pub struct MonitorTypes { /// The name of the monitor type. name: String, /// The short name of the monitor type. short_name: String, + /// A list of parameters for the given monitor type. params: std::collections::HashMap } #[lunanode_response] +/// The result of asking for possible monitor check types. +/// See also: [`crate::requests::MonitorCheckTypesRequest`] pub struct MonitorCheckTypesResponse { + /// All possible monitor check types as supported by Lunanode. + /// Note that the String key matches a category name, and all monitor types under that category + /// will be listed under each. types: std::collections::HashMap, } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all="lowercase")] +/// The type of contact as stated by lunanode as possible options. This type helps decide how to +/// use the contact in the case of a monitor trip. +/// See also: [`Contact`] pub enum ContactType { + /// A (presumably) valid email. Email, + /// A (presumably) valid SMS-captable phone or VOIP number. Sms, + /// A (presumably) valid voice-captable phone or VOIP number. Voice, + /// A (presumably) valid HTTP(S) URL. Http, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// A contact for monitoring purposes. +/// See also: [`Contacts`], [`MonitorContactListResponse`] pub struct Contact { #[serde_as(as="DisplayFromStr")] id: i32, @@ -570,6 +745,8 @@ pub struct Contact { #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] +/// Possible values for contacts listed in Lunanode. This is required because what the API sends +/// back an empty list when there are no contacts, but a hashmap if there is one or more contacts. pub enum Contacts { /// Need an empty vec of some kind to hold the blank value, which appears as []. Empty(Vec), @@ -578,6 +755,8 @@ pub enum Contacts { } #[lunanode_response] +/// The result of requesting a contact list for monitoring purposes. +/// See also: [`crate::requests::MonitorContactListRequest`] pub struct MonitorContactListResponse { /// A list of contacts in your LunaNode account. This should be deserialized as Option>, but I haven't gotten around to serializing this in that way. TODO. contacts: Contacts, @@ -585,6 +764,7 @@ pub struct MonitorContactListResponse { #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// An email user on the Lunanode email system. pub struct EmailUser { /// The user's ID. #[serde_as(as="DisplayFromStr")] @@ -599,31 +779,47 @@ pub struct EmailUser { } #[lunanode_response] +/// The result of requesting a list of email users in a certain domain. +/// See also: [`crate::requests::EmailUserListRequest`] pub struct EmailUserListResponse { #[serde(flatten)] + /// The users of a domain's email. The String is an id (and should be an i32 TODO). users: std::collections::HashMap, } #[lunanode_response] +/// The result of sending a start command to a VM. +/// See also: [`crate::requests::VmStartRequest`] pub struct VmStartResponse {} #[lunanode_response] +/// The result of sending a stop command to a VM. +/// See also: [`crate::requests::VmStopRequest`] pub struct VmStopResponse {} #[lunanode_response] +/// The result of sending a reboot command to a VM. +/// See also: [`crate::requests::VmRebootRequest`] pub struct VmRebootResponse {} #[lunanode_response] +/// The result of adding an email user to a domain. +/// See also: [`crate::requests::EmailUserAddRequest`] pub struct EmailUserAddResponse {} #[lunanode_response] +/// The result of removing an email user from a domain. +/// See also: [`crate::requests::EmailUserRemoveRequest`] pub struct EmailUserRemoveResponse {} #[lunanode_response] +/// The result of setting an email user's password in the specified domain. +/// See also: `EmailUserSetPasswordRequest` pub struct EmailUserSetPasswordResponse {} #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// A VM-specific IP pub struct VmIp { floating: std::net::Ipv4Addr, /// A network UUID @@ -637,20 +833,30 @@ pub struct VmIp { } #[lunanode_response] +/// The result of querying for a list of ips in a specific VM. +/// See also: [`crate::requests::VmIpListRequest`] pub struct VmIpListResponse { ips: std::collections::HashMap, } #[lunanode_response] +/// the result of removing a dynamic record. +/// See also: [`crate::requests::DynRemoveRequest`] pub struct DynRemoveResponse {} #[lunanode_response] +/// the result of adding a dynamic record. +/// See also: [`crate::requests::DynAddRequest`] pub struct DynAddResponse {} #[lunanode_response] +/// the result of adding a zone. +/// See also: [`crate::requests::ZoneAddRequest`] pub struct ZoneAddResponse {} #[lunanode_response] +/// the result of removing a zone. +/// See also: [`crate::requests::ZoneRemoveRequest`] pub struct ZoneRemoveResponse {} #[lunanode_response] diff --git a/src/success.rs b/src/success.rs index 338de68..28b776d 100644 --- a/src/success.rs +++ b/src/success.rs @@ -1,3 +1,8 @@ +//! A module to convert "yes"/"no" from a string to a bool. +//! This is exceptionally strange behaviour from an API. Just use `bool`. +//! But anyway, since Lunanode does indeed use this system, a custom module to serialize and +//! deserialize their "yes"/"no" responses was necessary. + use serde::{ de::{ self, diff --git a/src/types.rs b/src/types.rs index c6d4e5e..12dea21 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,3 +1,14 @@ +//! This crate handles all the common types used from request and response, as well as how they +//! should be linked together. +//! For example, an `impl LunaNodeRequest` has an associated type `Response` that must be set to a +//! valid LunaNodeResponse on creation. +//! +//! Although this can all be done automatically through macros, it is important that the +//! relationships be defined somewhere. +//! This is the location for those relationships. +//! +//! If you'd like to see how to implement the macros, check the the `lunanode_macros` subcrate. + use crate::success; use clap; use parse_display_derive::Display; @@ -29,7 +40,15 @@ pub struct ApiKeys { pub api_partialkey: String, } +/// A trait that must be implemented by any response type. pub trait LunaNodeResponse: DeserializeOwned {} +/// A trait that must be implemented by any request type. +/// This can be done with: +/// ```rust +/// // struct ResponseType {} +/// #[lunanode_request(response="ResponseType", endpoint="vm/list/")] +/// pub struct VmListRequest {} +/// ``` pub trait LunaNodeRequest: Serialize + std::fmt::Debug { /// The resposne type you expect after making this request. /// Setting this will allow acces to a generic function, make_request, that will expect this type to be recieved. @@ -44,53 +63,61 @@ pub trait LunaNodeRequest: Serialize + std::fmt::Debug { /// The only correct way: "vm/list/". fn url_endpoint(&self) -> String; /// A generic function to recieve a specific relsponse type from the server. - /// The Self::response type is the type you're expecting. In the case that this type is incorrect, or the server returns bad data, you will receive an LNError::SerdeError(serde::Error, String), with the String being a raw response from the server. - /// You may also recieve any other error defined in LNError, including timezone errors due to not being able to create a nonce value for the request. - /// See LNError. - fn make_request(&self) -> Result { + /// The Self::response type is the type you're expecting. In the case that this type is incorrect, or the server returns bad data, you will receive an LunaNodeError::SerdeError(serde::Error, String), with the String being a raw response from the server. + /// You may also recieve any other error defined in LunaNodeError, including timezone errors due to not being able to create a nonce value for the request. + /// See LunaNodeError. + fn make_request(&self) -> Result { make_request::(self) } } #[derive(Serialize, Deserialize, Debug)] -pub struct LNErrorResponse { +/// This response is given if the request was successful (technically), but some error has occured +/// to make the request not possible from Lunanode's standpoint. +pub struct LunaNodeErrorResponse { #[serde(with="success")] success: bool, error: String, // proper type, full accoutnign of the error from the API } -impl std::fmt::Display for LNErrorResponse { +impl std::fmt::Display for LunaNodeErrorResponse { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "An API error has occured! {}", self.error) } } -impl std::error::Error for LNErrorResponse {} +impl std::error::Error for LunaNodeErrorResponse {} #[derive(Debug)] -pub enum LNError { +/// A common error type for the entire crate. +pub enum LunaNodeError { + /// Pass through a request error from ureq. RequestError(ureq::Error), + /// Pass through a deserializeation error; this is just an std::io::Error that occurs when it is + /// not possible to read the string where deserialization would occur. DeserializationError(std::io::Error), + /// Pass through an error when finding the system time. TimeError(std::time::SystemTimeError), - LunaNodeError(LNErrorResponse), + /// An error from the Lunanode API + LunaNodeError(LunaNodeErrorResponse), + /// Pass through a serde error to the user. SerdeError(serde_json::Error, String), // the serde error, accompanied by a raw string - GetFuckedError, } -impl std::fmt::Display for LNError { +impl std::fmt::Display for LunaNodeError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "An error has occured! {}", self) } } -impl std::error::Error for LNError {} +impl std::error::Error for LunaNodeError {} -fn make_request(req: &S) -> Result +fn make_request(req: &S) -> Result where S: LunaNodeRequest + ?Sized, D: LunaNodeResponse { let json_req_data = match serde_json::to_string(req) { Ok(jrd) => jrd, - Err(e) => return Err(LNError::SerdeError(e, format!("{:?}", req))), + Err(e) => return Err(LunaNodeError::SerdeError(e, format!("{:?}", req))), }; let epoch = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) { - Err(e) => return Err(LNError::TimeError(e)), + Err(e) => return Err(LunaNodeError::TimeError(e)), Ok(ue) => ue, }.as_secs(); let nonce = format!("{}", epoch); @@ -105,20 +132,20 @@ fn make_request(req: &S) -> Result ("signature", &signature), ("nonce", &nonce), ]) { - Err(e) => Err(LNError::RequestError(e)), + Err(e) => Err(LunaNodeError::RequestError(e)), Ok(resp) => { let resp_str = match resp.into_string() { - Err(e) => return Err(LNError::DeserializationError(e)), + Err(e) => return Err(LunaNodeError::DeserializationError(e)), Ok(s) => s, }; println!("RESP: {}", resp_str); - match serde_json::from_str::(&resp_str) { - Ok(e) => return Err(LNError::LunaNodeError(e)), - Err(e) => false, + match serde_json::from_str::(&resp_str) { + Ok(e) => return Err(LunaNodeError::LunaNodeError(e)), + Err(_e) => false, }; match serde_json::from_str::(&resp_str) { Ok(s) => Ok(s), - Err(e) => Err(LNError::SerdeError(e, resp_str)), + Err(e) => Err(LunaNodeError::SerdeError(e, resp_str)), } }, }