master
Tait Hoyem 2 years ago
commit e226e6f13f

@ -2,8 +2,16 @@
name = "lunanode" name = "lunanode"
version = "0.1.1" version = "0.1.1"
edition = "2021" edition = "2021"
authors = ["Tait Hoyem <tait@tait.tech>"]
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] [dependencies]
clap = { version = "4.0.7", features = ["env", "derive"] } clap = { version = "4.0.7", features = ["env", "derive"] }
@ -12,7 +20,7 @@ envy = "0.4.2"
getset = "0.1.2" getset = "0.1.2"
hex = "0.4.3" hex = "0.4.3"
hmac = "0.12.1" hmac = "0.12.1"
lunanode_macros = { path = "./lunanode_macros" } lunanode_macros = "0.1.0"
parse-display-derive = "0.6.0" parse-display-derive = "0.6.0"
sha2 = "0.10.5" sha2 = "0.10.5"
serde = { version = "1.0.0", features = ["derive"] } serde = { version = "1.0.0", features = ["derive"] }

@ -1,6 +1,131 @@
# `lunanode` # `lunanode`
This is an API to work with the LunaNode's OpenStack-compatible [citation needed] API. 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 ## Installation
@ -8,7 +133,7 @@ This is an API to work with the LunaNode's OpenStack-compatible [citation needed
## API Keys ## 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 ## 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. * [ ] 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! * [ ] 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. * [ ] 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. * [ ] 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)). * [ ] 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. * [ ] 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 This Project
* [Support me on librapay](https://liberapay.com/tait/). * [Support me on librapay](https://liberapay.com/tait/).

@ -2,12 +2,20 @@
name = "lunanode_macros" name = "lunanode_macros"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
authors = ["Tait Hoyem <tait@tait.tech>"]
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] [lib]
proc_macro = true proc_macro = true
[dependencies] [dependencies]
syn = "1.0" syn = { version = "1.0.102", features = ["full"] }
quote = "1.0" quote = "1.0"

@ -0,0 +1,3 @@
# `lunanode_macros`
This crate contains macros for help with the [`lunanode` crate](https://crates.io/crates/lunanode).

@ -3,20 +3,18 @@
/// Usages: /// Usages:
/// ```rust /// ```rust
/// #[lunanode_request(response="ImageListResponse", endpoint="image/list/")] /// #[lunanode_request(response="ImageListResponse", endpoint="image/list/")]
/// #[derive(Serialize, Deserialize, Debug, ...)] /// #[derive(Serialize, Deserialize, Debug)]
/// struct MyStruct { /// struct MyStruct {
/// ... /// ...
/// ``` /// ```
/// ///
/// TODO: Improve error messages. /// TODO: Improve error messages.
use std::collections::HashMap;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::parse::{Parse, ParseStream, Parser, Result}; use syn::parse::Parser;
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_macro_input, AttributeArgs, ItemStruct, Lit, NestedMeta, Meta, MetaNameValue, Type};
#[derive(Debug, Hash, Eq, PartialEq)]
enum LunanodeRequestParam { enum LunanodeRequestParam {
Invalid, Invalid,
Response(Type), Response(Type),
@ -33,7 +31,7 @@ impl LunanodeRequestParam {
} }
#[proc_macro_attribute] #[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 // Get the structs below the macro
let mut item_struct = parse_macro_input!(input as ItemStruct); let mut item_struct = parse_macro_input!(input as ItemStruct);
// clone the name to keep compiler happy :) // clone the name to keep compiler happy :)
@ -67,7 +65,7 @@ pub fn lunanode_request(attr: TokenStream, input: TokenStream) -> TokenStream {
.into_iter() .into_iter()
.filter_map(|nm| match nm { .filter_map(|nm| match nm {
// Only select certain tokens // 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 // Convert the sigment of the path to a string
(path.segments (path.segments
.into_iter() .into_iter()
@ -102,6 +100,7 @@ pub fn lunanode_request(attr: TokenStream, input: TokenStream) -> TokenStream {
.parse2(quote! { .parse2(quote! {
#[serde(flatten)] #[serde(flatten)]
#[clap(flatten)] #[clap(flatten)]
/// A set of keys that must be set by the user if ANY request is to be made.
pub keys: ApiKeys pub keys: ApiKeys
}) })
.unwrap(), .unwrap(),

@ -268,30 +268,30 @@ pub struct BillingCreditResponse {
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct LNErrorResponse { pub struct LunaNodeErrorResponse {
#[serde(with="success")] #[serde(with="success")]
success: bool, success: bool,
error: String, // proper type, full accoutnign of the error from the API 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 { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "An API error has occured! {}", self.error) write!(f, "An API error has occured! {}", self.error)
} }
} }
impl std::error::Error for LNErrorResponse {} impl std::error::Error for LunaNodeErrorResponse {}
#[derive(Debug)] #[derive(Debug)]
pub enum LNError { pub enum LunaNodeError {
RequestError(ureq::Error), RequestError(ureq::Error),
DeserializationError(std::io::Error), DeserializationError(std::io::Error),
TimeError(std::time::SystemTimeError), TimeError(std::time::SystemTimeError),
LunaNodeError(LNErrorResponse), LunaNodeError(LunaNodeErrorResponse),
SerdeError(serde_json::Error, String), // the serde error, accompanied by a raw string SerdeError(serde_json::Error, String), // the serde error, accompanied by a raw string
GetFuckedError, GetFuckedError,
} }
impl std::fmt::Display for LNError { impl std::fmt::Display for LunaNodeError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "An error has occured! {}", self) write!(f, "An error has occured! {}", self)
} }
} }
impl std::error::Error for LNError {} impl std::error::Error for LunaNodeError {}

@ -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::{ use serde::{
de::{ de::{
self, self,
@ -8,7 +12,7 @@ use serde::{
Deserialize, 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<S>(succ: &bool, serializer: S) -> Result<S::Ok, S::Error> pub fn serialize<S>(succ: &bool, serializer: S) -> Result<S::Ok, S::Error>
where where
S: Serializer { 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<bool, D::Error> pub fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,

@ -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 success;
pub mod external; pub mod external;
pub mod requests; pub mod requests;
pub mod responses; pub mod responses;
pub mod types; pub mod types;
use serde::{ use requests::Args;
Serialize,
Deserialize,
};
use requests::{
Args,
VMListRequest,
};
use clap::Parser; use clap::Parser;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {

@ -1,9 +1,10 @@
//! All possible requests which can be made to the Lunanode API.
use crate::{ use crate::{
responses::{ responses::{
IPAddress, VmListResponse,
VMListResponse,
BillingCreditResponse, BillingCreditResponse,
LNImageListResponse, ImageListResponse,
VolumeListResponse, VolumeListResponse,
FloatingIpListResponse, FloatingIpListResponse,
NetworkListResponse, NetworkListResponse,
@ -34,72 +35,89 @@ use crate::{
VmIpListResponse, VmIpListResponse,
}, },
types::{ types::{
LNError, LunaNodeError,
LunaNodeRequest, LunaNodeRequest,
ApiKeys, ApiKeys,
}, },
}; };
use lunanode_macros::lunanode_request; use lunanode_macros::lunanode_request;
use parse_display_derive::Display;
use ureq::post;
use serde::{ use serde::{
Serialize, Serialize,
Serializer,
Deserialize, Deserialize,
de::DeserializeOwned,
}; };
use serde_with::serde_as;
use hmac::Hmac;
use sha2::Sha512;
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
/// All possible `ssh-key` subcommands.
pub enum SshKeySubArgs { pub enum SshKeySubArgs {
/// `ssh-key list`
List(SshKeyRequest), List(SshKeyRequest),
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
/// All possible `dyn` subcommands.
pub enum DynSubArgs { pub enum DynSubArgs {
/// `dyn list`
List(DynListRequest), List(DynListRequest),
/// `dyn remove`
Remove(DynRemoveRequest), Remove(DynRemoveRequest),
/// `dyn add`
Add(DynAddRequest), Add(DynAddRequest),
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
/// All possible `zone` subcommands.
pub enum ZoneSubArgs { pub enum ZoneSubArgs {
/// `zone add`
Add(ZoneAddRequest), Add(ZoneAddRequest),
/// `zone list`
List(ZoneListRequest), List(ZoneListRequest),
/// `zone remove`
Remove(ZoneRemoveRequest), Remove(ZoneRemoveRequest),
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
/// All possible `email domain` subcommands.
pub enum EmailDomainSubArgs { pub enum EmailDomainSubArgs {
/// `email domain list`
List(EmailDomainListRequest), List(EmailDomainListRequest),
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
/// All possible `email user` subcommands.
pub enum EmailUserSubArgs { pub enum EmailUserSubArgs {
/// `email user list`
List(EmailUserListRequest), List(EmailUserListRequest),
/// `email user add`
Add(EmailUserAddRequest), Add(EmailUserAddRequest),
/// `email user remove`
Remove(EmailUserRemoveRequest), Remove(EmailUserRemoveRequest),
/// `email user set-password`
SetPassword(EmailUserSetPasswordRequest), SetPassword(EmailUserSetPasswordRequest),
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
/// All possible `email` subcommands.
pub enum EmailSubArgs { pub enum EmailSubArgs {
#[clap(subcommand)] #[clap(subcommand)]
/// `email domain`
Domain(EmailDomainSubArgs), Domain(EmailDomainSubArgs),
/// `email usage`
Usage(EmailUsageRequest), Usage(EmailUsageRequest),
#[clap(subcommand)] #[clap(subcommand)]
/// `email user`
User(EmailUserSubArgs), User(EmailUserSubArgs),
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
#[serde(untagged)] #[serde(untagged)]
/// All possible `floating` subcommands.
pub enum FloatingSubArgs { pub enum FloatingSubArgs {
/// List all images on my account. /// List all images on my account.
List(FloatingIpListRequest), List(FloatingIpListRequest),
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
#[serde(untagged)] #[serde(untagged)]
/// All possible `image` subcommands.
pub enum ImageSubArgs { pub enum ImageSubArgs {
/// List all images on my account. /// List all images on my account.
List(ImageListRequest), List(ImageListRequest),
@ -107,21 +125,26 @@ pub enum ImageSubArgs {
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
#[serde(untagged)] #[serde(untagged)]
/// All possible `record` subcommands.
pub enum RecordSubArgs { pub enum RecordSubArgs {
/// List all records in a domain.
List(RecordListRequest), List(RecordListRequest),
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
#[serde(untagged)] #[serde(untagged)]
/// All `vm ip` subcommands.
pub enum VmIpSubArgs { pub enum VmIpSubArgs {
/// `vm ip list`
List(VmIpListRequest), List(VmIpListRequest),
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
#[serde(untagged)] #[serde(untagged)]
/// All `vm` subcommands.
pub enum VmSubArgs { pub enum VmSubArgs {
/// List all VMs on my account. /// List all VMs on my account.
List(VMListRequest), List(VmListRequest),
/// Restart a VM, given an ID. /// Restart a VM, given an ID.
Reboot(VmRebootRequest), Reboot(VmRebootRequest),
/// Stop a VM, given an ID. /// Stop a VM, given an ID.
@ -134,12 +157,14 @@ pub enum VmSubArgs {
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
#[serde(untagged)] #[serde(untagged)]
/// All `volume` subcommands.
pub enum VolumeSubArgs { pub enum VolumeSubArgs {
/// List all volumes I'm paying for. /// List all volumes I'm paying for.
List(VolumeListRequest), List(VolumeListRequest),
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
#[serde(untagged)] #[serde(untagged)]
/// All `billing` subcommands.
pub enum BillingSubArgs { pub enum BillingSubArgs {
/// How much money is left in my account. /// How much money is left in my account.
Credit(BillingCreditRequest), Credit(BillingCreditRequest),
@ -147,42 +172,56 @@ pub enum BillingSubArgs {
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
#[serde(untagged)] #[serde(untagged)]
/// All `network` subcommands.
pub enum NetworkSubArgs { pub enum NetworkSubArgs {
/// Networks on your accounts /// Networks on your accounts
List(NetworkListRequest), List(NetworkListRequest),
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
/// All `plan` subcommands.
pub enum PlanSubArgs { pub enum PlanSubArgs {
/// `plan list`
List(PlanListRequest), List(PlanListRequest),
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
/// All `region` subcommands.
pub enum RegionSubArgs { pub enum RegionSubArgs {
/// `region list`
List(RegionListRequest), List(RegionListRequest),
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
/// All `monitor check` subcommands.
pub enum MonitorCheckSubArgs { pub enum MonitorCheckSubArgs {
/// `monitor check list`
List(MonitorCheckListRequest), List(MonitorCheckListRequest),
/// `monitor check types`
Types(MonitorCheckTypesRequest), Types(MonitorCheckTypesRequest),
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
/// All `monitor contact` subcommands.
pub enum MonitorContactSubArgs { pub enum MonitorContactSubArgs {
/// `monitor contact list`
List(MonitorContactListRequest), List(MonitorContactListRequest),
} }
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
/// All `monitor` subcommands.
pub enum MonitorSubArgs { pub enum MonitorSubArgs {
#[clap(subcommand)] #[clap(subcommand)]
/// All `monitor check` subcommands.
Check(MonitorCheckSubArgs), Check(MonitorCheckSubArgs),
#[clap(subcommand)] #[clap(subcommand)]
/// All `monitor contact` subcommands.
Contact(MonitorContactSubArgs), Contact(MonitorContactSubArgs),
} }
#[derive(Serialize, Deserialize, Debug, clap::Parser)] #[derive(Serialize, Deserialize, Debug, clap::Parser)]
#[serde(untagged)] #[serde(untagged)]
/// All possible commands that may be used at the command line.
pub enum Args { pub enum Args {
#[clap(subcommand)] #[clap(subcommand)]
/// See `lunanode image help` /// 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. /// 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<T: LunaNodeRequest>(req: &T) -> Result<(), LNError> fn print_req<T: LunaNodeRequest>(req: &T) -> Result<(), LunaNodeError>
where where
<T as LunaNodeRequest>::Response: std::fmt::Debug <T as LunaNodeRequest>::Response: std::fmt::Debug
{ {
@ -238,7 +277,9 @@ where
Ok(()) Ok(())
} }
impl Args { 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 { match self {
Self::Monitor(MonitorSubArgs::Contact(MonitorContactSubArgs::List(contact_list))) => print_req(contact_list)?, 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)?, 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. /// 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 {} pub struct ImageListRequest {}
/// BillingCreditRequest handles the /billing/credits/ endpoint. It will produce a BillingCreditResponse. /// BillingCreditRequest handles the /billing/credits/ endpoint. It will produce a BillingCreditResponse.
@ -283,8 +324,8 @@ pub struct ImageListRequest {}
pub struct BillingCreditRequest {} pub struct BillingCreditRequest {}
/// VMListRequest is used to create a new request for the /vm/list endpoint. /// VMListRequest is used to create a new request for the /vm/list endpoint.
#[lunanode_request(response="VMListResponse", endpoint="vm/list/")] #[lunanode_request(response="VmListResponse", endpoint="vm/list/")]
pub struct VMListRequest {} pub struct VmListRequest {}
/// VolumeListRequest is used to create a new request for the /volume/list endpoint. /// VolumeListRequest is used to create a new request for the /volume/list endpoint.
#[lunanode_request(response="VolumeListResponse", endpoint="volume/list/")] #[lunanode_request(response="VolumeListResponse", endpoint="volume/list/")]
@ -313,80 +354,112 @@ pub struct RecordListRequest {
pub struct DynListRequest {} pub struct DynListRequest {}
#[lunanode_request(response="SshKeyResponse", endpoint="sshkey/list/")] #[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 {} pub struct SshKeyRequest {}
#[lunanode_request(response="PlanListResponse", endpoint="plan/list/")] #[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 {} pub struct PlanListRequest {}
#[lunanode_request(response="RegionListResponse", endpoint="region/list/")] #[lunanode_request(response="RegionListResponse", endpoint="region/list/")]
/// A request so show all regions available for the user to choose from.
pub struct RegionListRequest {} pub struct RegionListRequest {}
#[lunanode_request(response="EmailUsageResponse", endpoint="email/usage/")] #[lunanode_request(response="EmailUsageResponse", endpoint="email/usage/")]
/// A request to show an email usage report.
pub struct EmailUsageRequest {} pub struct EmailUsageRequest {}
#[lunanode_request(response="EmailDomainListResponse", endpoint="email/domain-list/")] #[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 {} pub struct EmailDomainListRequest {}
#[lunanode_request(response="MonitorCheckListResponse", endpoint="monitor/check-list/")] #[lunanode_request(response="MonitorCheckListResponse", endpoint="monitor/check-list/")]
/// A request to show all monitor checks (active and inactive).
pub struct MonitorCheckListRequest {} pub struct MonitorCheckListRequest {}
#[lunanode_request(response="MonitorCheckTypesResponse", endpoint="monitor/check-types/")] #[lunanode_request(response="MonitorCheckTypesResponse", endpoint="monitor/check-types/")]
/// A request to check all possible monitor check types.
pub struct MonitorCheckTypesRequest {} pub struct MonitorCheckTypesRequest {}
/// A request to show all contacts on the monitor list.
#[lunanode_request(response="MonitorContactListResponse", endpoint="monitor/contact-list/")] #[lunanode_request(response="MonitorContactListResponse", endpoint="monitor/contact-list/")]
pub struct MonitorContactListRequest {} pub struct MonitorContactListRequest {}
#[lunanode_request(response="EmailUserListResponse", endpoint="email/user-list/")] #[lunanode_request(response="EmailUserListResponse", endpoint="email/user-list/")]
/// A request to show all email users in a domain.
pub struct EmailUserListRequest { pub struct EmailUserListRequest {
/// The domain ID to list users in.
domain_id: i32, domain_id: i32,
} }
#[lunanode_request(response="VmStartResponse", endpoint="vm/start/")] #[lunanode_request(response="VmStartResponse", endpoint="vm/start/")]
/// A request to start a VM.
pub struct VmStartRequest { pub struct VmStartRequest {
/// The ID of the VM to start. See [`VmListRequest`]
vm_id: String, vm_id: String,
} }
#[lunanode_request(response="VmStopResponse", endpoint="vm/stop/")] #[lunanode_request(response="VmStopResponse", endpoint="vm/stop/")]
/// A request to stop a VM.
pub struct VmStopRequest { pub struct VmStopRequest {
/// The ID of the VM to stop. See [`VmListRequest`]
vm_id: String, vm_id: String,
} }
#[lunanode_request(response="VmRebootResponse", endpoint="vm/reboot/")] #[lunanode_request(response="VmRebootResponse", endpoint="vm/reboot/")]
/// A request to reboot a VM.
pub struct VmRebootRequest { pub struct VmRebootRequest {
/// The ID of the VM to reboot. See [`VmListRequest`]
vm_id: String, vm_id: String,
} }
#[lunanode_request(response="EmailUserAddResponse", endpoint="email/user-add/")] #[lunanode_request(response="EmailUserAddResponse", endpoint="email/user-add/")]
/// A request to add a new email account to a domain.
pub struct EmailUserAddRequest { pub struct EmailUserAddRequest {
/// The domain ID to add the user to. See [`EmailDomainListRequest`]
domain_id: i32, domain_id: i32,
/// The username of the new user.
username: String, username: String,
/// The password of the new user.
password: String, password: String,
} }
#[lunanode_request(response="EmailUserRemoveResponse", endpoint="email/user-remove/")] #[lunanode_request(response="EmailUserRemoveResponse", endpoint="email/user-remove/")]
/// A request to remove a user from a domain's email.
pub struct EmailUserRemoveRequest { pub struct EmailUserRemoveRequest {
/// An ID of the domain to remove the user from.
domain_id: i32, domain_id: i32,
/// The ID of the user to remove. See [`EmailUserListRequest`]
user_id: i32, user_id: i32,
} }
#[lunanode_request(response="EmailUserSetPasswordResponse", endpoint="email/user-set-password/")] #[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 { pub struct EmailUserSetPasswordRequest {
/// The ID of the domain the user is on.
domain_id: i32, domain_id: i32,
/// The user ID of the user on the domain.
user_id: i32, user_id: i32,
/// The new password.
password: String, password: String,
} }
#[lunanode_request(response="VmIpListResponse", endpoint="vm/iplist/")] #[lunanode_request(response="VmIpListResponse", endpoint="vm/iplist/")]
/// A request to list all IPs associated with a VM.
pub struct VmIpListRequest { pub struct VmIpListRequest {
/// The VM whoes IPs to list.
vm_id: String, vm_id: String,
} }
#[lunanode_request(response="DynRemoveResponse", endpoint="dns/dyn-remove/")] #[lunanode_request(response="DynRemoveResponse", endpoint="dns/dyn-remove/")]
/// A request to remove a dyn record.
pub struct DynRemoveRequest { pub struct DynRemoveRequest {
/// The ID of the dyn record to remove. See [`DynListrRequest`]
dyn_id: i32, dyn_id: i32,
} }
#[lunanode_request(response="DynAddResponse", endpoint="dns/dyn-add/")] #[lunanode_request(response="DynAddResponse", endpoint="dns/dyn-add/")]
/// A request to add a new dyn record.
pub struct DynAddRequest { 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. /// 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, name: String,
@ -395,6 +468,7 @@ pub struct DynAddRequest {
} }
#[lunanode_request(response="ZoneAddResponse", endpoint="dns2/zone-add/")] #[lunanode_request(response="ZoneAddResponse", endpoint="dns2/zone-add/")]
/// A request to add a new zone.
pub struct ZoneAddRequest { pub struct ZoneAddRequest {
/// A new zone, a FQDN. /// A new zone, a FQDN.
name: String, name: String,
@ -403,7 +477,11 @@ pub struct ZoneAddRequest {
} }
#[lunanode_request(response="ZoneRemoveResponse", endpoint="dns2/zone-remove/")] #[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 { pub struct ZoneRemoveRequest {
/// The zone to remove. See also: [`ZoneListRequest`]
zone_id: i32, zone_id: i32,
} }

@ -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::success;
use crate::external; use crate::external;
use crate::types::{ use crate::types::{
LunaNodeResponse, LunaNodeResponse,
LunaNodeRequest,
}; };
use lunanode_macros::lunanode_response; use lunanode_macros::lunanode_response;
use serde_json;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use serde_with::{ use serde_with::{
DisplayFromStr, DisplayFromStr,
serde_as, serde_as,
}; };
use uuid;
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, PartialEq, Debug)] #[derive(Serialize, Deserialize, PartialEq, Debug)]
@ -52,42 +52,53 @@ pub struct VirtualMachine {
os_status: String, // should be nmore strict os_status: String, // should be nmore strict
} }
#[derive(Serialize, Deserialize, Debug)] #[lunanode_response]
pub struct VMListResponse { /// 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<VirtualMachine>, vms: Vec<VirtualMachine>,
#[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)] #[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all="lowercase")] #[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 { enum AttachmentType {
/// An unattached floating IP.
Unattached, Unattached,
/// An attached floating IP.
Vm, Vm,
} }
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// Detailed information about a floating IP
struct FloatingIp { struct FloatingIp {
/// A String with a VM UUID if attached, otherwise None.
attached_id: Option<String>, attached_id: Option<String>,
/// A String with the VM's name if attached, otherwise None.
attached_name: Option<String>, attached_name: Option<String>,
/// Vm/Unattached
attached_type: AttachmentType, attached_type: AttachmentType,
/// The hostname of the floating IP address, usually something like
/// xxx-xxx-xxx-xxx.rdns.lunanode.com
hostname: String, hostname: String,
/// The IP address of the floating IP.
ip: std::net::Ipv4Addr, ip: std::net::Ipv4Addr,
/// The region in which the IP address has been allocated.
region: LNRegion, region: LNRegion,
/// The rDNS domain, if specified, otherwise, None.
reverse: Option<String>, reverse: Option<String>,
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
/// The last time the record was updated.
time_updated: chrono::DateTime<chrono::Utc>, time_updated: chrono::DateTime<chrono::Utc>,
} }
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// Extra detailed information for a VM.
pub struct VMInfoExtra { pub struct VMInfoExtra {
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
/// bandwidth allowed over a month in GB /// bandwidth allowed over a month in GB
@ -126,36 +137,50 @@ pub struct VMInfoExtra {
#[serde(untagged)] #[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. /// 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 { pub enum IPAddress {
/// IPv4 variant
V4(std::net::Ipv4Addr), V4(std::net::Ipv4Addr),
/// IPv6 variant
V6(std::net::Ipv6Addr), V6(std::net::Ipv6Addr),
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// An IP address type enum, specifying either V4 or V6.
pub enum IPAddressType { pub enum IPAddressType {
#[serde(rename="4")] #[serde(rename="4")]
/// IPv4 variant
V4, V4,
#[serde(rename="6")] #[serde(rename="6")]
/// IPv6 variant
V6, V6,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// VM IP address
pub struct VMAddress { pub struct VMAddress {
/// The IP address of the VM.
addr: IPAddress, addr: IPAddress,
#[serde(with="external")] #[serde(with="external")]
/// Whether the IP is an internal or external kind.
external: bool, external: bool,
/// The version of IP address used.
version: IPAddressType, version: IPAddressType,
#[serde(rename="reverse")] #[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<String>, rdns: Option<String>,
} }
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// Detailed VM info.
pub struct VMInfo { pub struct VMInfo {
#[serde(rename="additionalip")] #[serde(rename="additionalip")]
/// A list of additional IPs (which are not the primary IP) which have been assigned to the VM.
additional_ip: Vec<VMAddress>, additional_ip: Vec<VMAddress>,
#[serde(rename="additionalprivateip")] #[serde(rename="additionalprivateip")]
/// Any additional private IPs assigned to the VM.
additional_private_ip: Vec<VMAddress>, additional_private_ip: Vec<VMAddress>,
/// A list of addresses inherently part of the VM. Think of a primary private IP, or where
/// outgoing internet traffic is routed.
addresses: Vec<VMAddress>, addresses: Vec<VMAddress>,
/// a possibly empty string containing an error message /// a possibly empty string containing an error message
error_detail: Option<String>, error_detail: Option<String>,
@ -198,80 +223,114 @@ pub struct VMInfo {
volumes: String, volumes: String,
} }
#[derive(Serialize, Deserialize, Debug)] #[lunanode_response]
/// The result from a [`crate::requests::VmInfoRequest`].
pub struct VMInfoResponse { pub struct VMInfoResponse {
/// Extra information (cached)
extra: VMInfoExtra, extra: VMInfoExtra,
/// Detailed VM info (uncached)
info: VMInfo, info: VMInfo,
#[serde(with="success")]
success: bool,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub enum LNPlanCategory { /// A plan category.
pub enum PlanCategory {
#[serde(rename="Compute-Optimized")] #[serde(rename="Compute-Optimized")]
/// For CPU-bound tasks.
ComputeOptimized, ComputeOptimized,
#[serde(rename="General Purpose")] #[serde(rename="General Purpose")]
/// Pretty good for most projects.
GeneralPurpose, GeneralPurpose,
#[serde(rename="Memory-Optimized")] #[serde(rename="Memory-Optimized")]
/// For memory-bound tasks.
MemoryOptimized, MemoryOptimized,
#[serde(rename="SSD-Cached High-Memory")] #[serde(rename="SSD-Cached High-Memory")]
/// For IO/memory-bound tasks.
SSDCacheHighMemory, SSDCacheHighMemory,
#[serde(rename="SSD-Cached Standard")] #[serde(rename="SSD-Cached Standard")]
/// For IO-bound tasks.
SSDCacheStandard, SSDCacheStandard,
} }
#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq)] #[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
/// Regions where Lunanode is supported.
pub enum LNRegion { pub enum LNRegion {
/// Montreal, Quebec, Canada.
Montreal, Montreal,
/// Roubaix, France.
Roubaix, Roubaix,
/// Toronto, Ontario, Canada.
Toronto Toronto
} }
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct LNPlan { /// A VM plan/level of serivce.
pub struct Plan {
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
/// Does the plan support all regions?
all_regions: i32, // may need to be more strict? all_regions: i32, // may need to be more strict?
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
/// The maxmimum bandwidth in Mbps
bandwidth: i32, // in Mbps bandwidth: i32, // in Mbps
category: LNPlanCategory, /// Category of plan
category: PlanCategory,
#[serde_as(as="DisplayFromStr")] #[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 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. 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")] #[serde_as(as="DisplayFromStr")]
/// The plan ID. Could be useful if creating a new VM.
plan_id: i32, // can be strictly typed, if needed plan_id: i32, // can be strictly typed, if needed
#[serde_as(as="DisplayFromStr")] #[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 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 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 price_nice: String, // same as above, but for the hour
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
/// The amount of RAM in MB.
ram: i32, // in MB ram: i32, // in MB
/// Regions where the plan can be dpeloyed.
regions: Vec<LNRegion>, // list of regions by name regions: Vec<LNRegion>, // 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 regions_nice: String, // list of regions concatonated with commans
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
/// Storage in GB.
storage: i32, // per GB storage: i32, // per GB
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
/// Number of vCPU cores.
vcpu: i32, // number of vCPU cores vcpu: i32, // number of vCPU cores
} }
#[lunanode_response] #[lunanode_response]
/// A list of plans from a [`crate::requests::PlanListRequest`]
pub struct PlanListResponse { pub struct PlanListResponse {
plans: Vec<LNPlan>, /// List of plans; see [`Plan`] for more detail.
plans: Vec<Plan>,
} }
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct LNImage { /// An ISO or QCOW2 image stored on Lunanode.
pub struct Image {
#[serde_as(as="DisplayFromStr")] #[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 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 name: String, // set by the user or LunaNode
/// The region in which the image is stored.
region: LNRegion, region: LNRegion,
/// The status of the image; at least "active" and "inactive" are possible.
status: String, // should be stricter, at least "active", "inactive"(?) status: String, // should be stricter, at least "active", "inactive"(?)
} }
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// A storage volume on the user's Lunanode account.
pub struct Volume { pub struct Volume {
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
/// The personal ID used for the volume. /// The personal ID used for the volume.
@ -298,20 +357,37 @@ pub struct Volume {
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[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", 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 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" 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", 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", 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 hw_vif_model: String, // appropriately vague
/// A read only image?
#[serde(with="success")] #[serde(with="success")]
is_read_only: bool, // "yes"/"no" 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", libvrt_cpu_mode: String, // should be stricter, at least: "host-model",
/// Unkown list of metadata.
metadata: Vec<()>, // vec of what? metadata: Vec<()>, // vec of what?
/// The name of the image; often set by the user.
name: String, // sufficiently vague name: String, // sufficiently vague
/// The region of the image.
region: LNRegion, // sufficiently typed region: LNRegion, // sufficiently typed
/// The size of the image in MB.
size: i32, // in MB, maybe? 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", status: String, // should be stricter, at least: "active",
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
/// An (explicitly UTC) datetime of when the VM was created /// An (explicitly UTC) datetime of when the VM was created
@ -319,16 +395,21 @@ pub struct LNImageDetails {
} }
#[lunanode_response] #[lunanode_response]
pub struct LNImageDetailResponse { /// A response from [`crate::requests::ImageDetailRequest`]
details: LNImageDetails, pub struct ImageDetailResponse {
/// Details of a specific image.
details: ImageDetails,
} }
#[lunanode_response] #[lunanode_response]
pub struct LNImageListResponse { /// See also: [`crate::requests::ImageListRequest`]
images: Vec<LNImage>, pub struct ImageListResponse {
/// List of images and their information.
images: Vec<Image>,
} }
#[lunanode_response] #[lunanode_response]
/// See also: [`crate::requests::BillingCreditRequest`]
pub struct BillingCreditResponse { pub struct BillingCreditResponse {
/// Money left in the account in USD /// Money left in the account in USD
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
@ -336,33 +417,42 @@ pub struct BillingCreditResponse {
} }
#[lunanode_response] #[lunanode_response]
/// See also: [`crate::requests::VolumeListRequest`]
pub struct VolumeListResponse { pub struct VolumeListResponse {
/// List of volumes and their associated information.
volumes: Vec<Volume>, volumes: Vec<Volume>,
} }
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// A network with a subnet.
pub struct Network { pub struct Network {
/// The name set by the user when creating the network. /// The name set by the user when creating the network.
name: String, name: String,
#[serde_as(as="DisplayFromStr")] #[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, net_id: i32,
/// The network subnet. This should be strictly typed as an IP address w/ a subnet attached. TODO: subnet type /// The network subnet. This should be strictly typed as an IP address w/ a subnet attached. TODO: subnet type
subnet: String, subnet: String,
} }
#[lunanode_response] #[lunanode_response]
/// A response from [`crate::requests::NetworkListRequest`]
pub struct NetworkListResponse { pub struct NetworkListResponse {
/// A list of networks.
networks: Vec<Network>, networks: Vec<Network>,
} }
#[lunanode_response] #[lunanode_response]
/// A response from [`crate::requests::FloatingIpListRequest`]
pub struct FloatingIpListResponse { pub struct FloatingIpListResponse {
/// A list of [`FloatingIp`]s.
ips: Vec<FloatingIp>, ips: Vec<FloatingIp>,
} }
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// A domain "zone". This is the same as a root domain.
pub struct Zone { pub struct Zone {
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
/// The lunanode id of the zone. /// The lunanode id of the zone.
@ -375,26 +465,54 @@ pub struct Zone {
} }
#[lunanode_response] #[lunanode_response]
/// A response from [`crate::requests::ZoneListRequest`]
pub struct ZoneListResponse { pub struct ZoneListResponse {
/// A map of all zones, with their IDs as their keys.
zones: std::collections::HashMap<i32, Zone>, zones: std::collections::HashMap<i32, Zone>,
} }
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash)] #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash)]
#[serde(rename="UPPERCASE")] #[serde(rename="UPPERCASE")]
/// Possible record types for [`Record`]
pub enum RecordType { pub enum RecordType {
/// An A record is an IPv4 pointer with a key (domain or subdomain) and value (IP address).
A, A,
/// An AAAA record (usually said "quadrupal A") is an IPv6 pointer with a key (domain or subdomain) and
/// value (IP address).
AAAA, 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, 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, ALIAS,
/// An MX record tells email providers where to send incoming mail.
MX, 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, 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, TXT,
/// An SPF record is a deprecated form of email authentication record.
SPF, 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 SRV
} }
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// A DNS record.
pub struct Record { pub struct Record {
/// The record id /// The record id
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
@ -445,26 +563,37 @@ pub struct Record {
} }
#[lunanode_response] #[lunanode_response]
/// The result of requesting all DNS records for a domain.
/// See also: [`crate::requests::RecordListRequest`], [`ZoneListResponse`]
pub struct RecordListResponse { pub struct RecordListResponse {
records: std::collections::HashMap<i32, Record>, records: std::collections::HashMap<i32, Record>,
} }
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// A detailed dyn record.
pub struct DynRecord { pub struct DynRecord {
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
/// An id you may need later to edit this.
/// See also: [`DynUpdateResponse`], [`DynRemoveResponse`]
id: i32, id: i32,
/// The name, or key (usually a subddomain) of the dyn record.
name: String, name: String,
/// An IP address (a value).
ip: IPAddress, ip: IPAddress,
} }
#[lunanode_response] #[lunanode_response]
/// The result of requesting all dyn records on a domain.
/// See also: [`crate::requests::DynListRequest`]
pub struct DynListResponse { pub struct DynListResponse {
dyns: std::collections::HashMap<i32, DynRecord>, dyns: std::collections::HashMap<i32, DynRecord>,
} }
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// Detailed information about an SSH key.
/// See also: [`SshKeyResponse`]
pub struct SshKey { pub struct SshKey {
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
id: i32, id: i32,
@ -475,11 +604,16 @@ pub struct SshKey {
} }
#[lunanode_response] #[lunanode_response]
/// The result of requesting a list of all regions.
/// See also: [`crate::requests::RegionListRequest`]
pub struct RegionListResponse { pub struct RegionListResponse {
/// A map of all regions supported by Lunanode.
regions: std::collections::HashMap<LNRegion, String>, regions: std::collections::HashMap<LNRegion, String>,
} }
#[lunanode_response] #[lunanode_response]
/// The result of requesting all ssh keys connected to an account.
/// See also: [`crate::requests::SshKeyRequest`]
pub struct SshKeyResponse { pub struct SshKeyResponse {
#[serde(flatten)] #[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: /// 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] #[lunanode_response]
/// The reuslt of requesting email usage statistics from Lunanode.
pub struct EmailUsageResponse { pub struct EmailUsageResponse {
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
/// The number of domains being used by the account.
domains: i32, domains: i32,
/// Usage of email in GB, this should be an i32. /// Usage of email in GB, this should be an i32.
storage: String, storage: String,
@ -500,21 +636,31 @@ pub struct EmailUsageResponse {
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// The result of requesting an email's domain information.
pub struct EmailDomainInfo { pub struct EmailDomainInfo {
#[serde_as(as="DisplayFromStr")] #[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, id: i32,
/// A String of the domain.
name: String, name: String,
/// Storage used
storage: String, storage: String,
/// Storage used in MB/GB, with " MB/GB" attached.
storage_nice: String, storage_nice: String,
} }
#[lunanode_response] #[lunanode_response]
/// The result of requesting a list of email domains on the Lunanode account.
pub struct EmailDomainListResponse { pub struct EmailDomainListResponse {
#[serde(flatten)] #[serde(flatten)]
domains: std::collections::HashMap<String,EmailDomainInfo>, /// Key/value pairs where the key is a String of the domain.
domains: std::collections::HashMap<String, EmailDomainInfo>,
} }
#[lunanode_response] #[lunanode_response]
/// The result of requesting all monitor checks (active or inactive).
/// See also: [`crate::requests::MonitorCheckListRequest`]
pub struct MonitorCheckListResponse { pub struct MonitorCheckListResponse {
/// This is not done yet. A Check type will be required. TODO /// This is not done yet. A Check type will be required. TODO
checks: Vec<String>, checks: Vec<String>,
@ -522,45 +668,74 @@ pub struct MonitorCheckListResponse {
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)] #[serde(untagged)]
/// The type of parameter being asked for by the monitor check.
/// See also: [`Param`]
pub enum ParamType { pub enum ParamType {
/// A single, simple parameter.
Single(String), 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<String, String>), Options(std::collections::HashMap<String, String>),
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// Detailed information about a parameter of a monitor check.
/// See also [`MonitorTypes`]
pub struct Param { pub struct Param {
#[serde(with="external")] #[serde(with="external")]
/// Is the parameter required when creating this monitor check.
required: bool, required: bool,
/// What kind of parameter is this.
r#type: ParamType, r#type: ParamType,
/// The name of the parameter, this would be the name of a field in a request.
name: String, name: String,
/// A placeholder, or example of what may by used in this parameter.
placeholder: Option<String>, placeholder: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// Detailed information about various monitor types in a category.
/// See also: [`MonitorCheckTypesResponse`]
pub struct MonitorTypes { pub struct MonitorTypes {
/// The name of the monitor type. /// The name of the monitor type.
name: String, name: String,
/// The short name of the monitor type. /// The short name of the monitor type.
short_name: String, short_name: String,
/// A list of parameters for the given monitor type.
params: std::collections::HashMap<String, Param> params: std::collections::HashMap<String, Param>
} }
#[lunanode_response] #[lunanode_response]
/// The result of asking for possible monitor check types.
/// See also: [`crate::requests::MonitorCheckTypesRequest`]
pub struct MonitorCheckTypesResponse { 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<String, MonitorTypes>, types: std::collections::HashMap<String, MonitorTypes>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all="lowercase")] #[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 { pub enum ContactType {
/// A (presumably) valid email.
Email, Email,
/// A (presumably) valid SMS-captable phone or VOIP number.
Sms, Sms,
/// A (presumably) valid voice-captable phone or VOIP number.
Voice, Voice,
/// A (presumably) valid HTTP(S) URL.
Http, Http,
} }
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// A contact for monitoring purposes.
/// See also: [`Contacts`], [`MonitorContactListResponse`]
pub struct Contact { pub struct Contact {
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
id: i32, id: i32,
@ -570,6 +745,8 @@ pub struct Contact {
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)] #[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 { pub enum Contacts {
/// Need an empty vec of some kind to hold the blank value, which appears as []. /// Need an empty vec of some kind to hold the blank value, which appears as [].
Empty(Vec<String>), Empty(Vec<String>),
@ -578,6 +755,8 @@ pub enum Contacts {
} }
#[lunanode_response] #[lunanode_response]
/// The result of requesting a contact list for monitoring purposes.
/// See also: [`crate::requests::MonitorContactListRequest`]
pub struct MonitorContactListResponse { pub struct MonitorContactListResponse {
/// A list of contacts in your LunaNode account. This should be deserialized as Option<HashMap<String, Contact>>, but I haven't gotten around to serializing this in that way. TODO. /// A list of contacts in your LunaNode account. This should be deserialized as Option<HashMap<String, Contact>>, but I haven't gotten around to serializing this in that way. TODO.
contacts: Contacts, contacts: Contacts,
@ -585,6 +764,7 @@ pub struct MonitorContactListResponse {
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// An email user on the Lunanode email system.
pub struct EmailUser { pub struct EmailUser {
/// The user's ID. /// The user's ID.
#[serde_as(as="DisplayFromStr")] #[serde_as(as="DisplayFromStr")]
@ -599,31 +779,47 @@ pub struct EmailUser {
} }
#[lunanode_response] #[lunanode_response]
/// The result of requesting a list of email users in a certain domain.
/// See also: [`crate::requests::EmailUserListRequest`]
pub struct EmailUserListResponse { pub struct EmailUserListResponse {
#[serde(flatten)] #[serde(flatten)]
/// The users of a domain's email. The String is an id (and should be an i32 TODO).
users: std::collections::HashMap<String, EmailUser>, users: std::collections::HashMap<String, EmailUser>,
} }
#[lunanode_response] #[lunanode_response]
/// The result of sending a start command to a VM.
/// See also: [`crate::requests::VmStartRequest`]
pub struct VmStartResponse {} pub struct VmStartResponse {}
#[lunanode_response] #[lunanode_response]
/// The result of sending a stop command to a VM.
/// See also: [`crate::requests::VmStopRequest`]
pub struct VmStopResponse {} pub struct VmStopResponse {}
#[lunanode_response] #[lunanode_response]
/// The result of sending a reboot command to a VM.
/// See also: [`crate::requests::VmRebootRequest`]
pub struct VmRebootResponse {} pub struct VmRebootResponse {}
#[lunanode_response] #[lunanode_response]
/// The result of adding an email user to a domain.
/// See also: [`crate::requests::EmailUserAddRequest`]
pub struct EmailUserAddResponse {} pub struct EmailUserAddResponse {}
#[lunanode_response] #[lunanode_response]
/// The result of removing an email user from a domain.
/// See also: [`crate::requests::EmailUserRemoveRequest`]
pub struct EmailUserRemoveResponse {} pub struct EmailUserRemoveResponse {}
#[lunanode_response] #[lunanode_response]
/// The result of setting an email user's password in the specified domain.
/// See also: `EmailUserSetPasswordRequest`
pub struct EmailUserSetPasswordResponse {} pub struct EmailUserSetPasswordResponse {}
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// A VM-specific IP
pub struct VmIp { pub struct VmIp {
floating: std::net::Ipv4Addr, floating: std::net::Ipv4Addr,
/// A network UUID /// A network UUID
@ -637,20 +833,30 @@ pub struct VmIp {
} }
#[lunanode_response] #[lunanode_response]
/// The result of querying for a list of ips in a specific VM.
/// See also: [`crate::requests::VmIpListRequest`]
pub struct VmIpListResponse { pub struct VmIpListResponse {
ips: std::collections::HashMap<String, VmIp>, ips: std::collections::HashMap<String, VmIp>,
} }
#[lunanode_response] #[lunanode_response]
/// the result of removing a dynamic record.
/// See also: [`crate::requests::DynRemoveRequest`]
pub struct DynRemoveResponse {} pub struct DynRemoveResponse {}
#[lunanode_response] #[lunanode_response]
/// the result of adding a dynamic record.
/// See also: [`crate::requests::DynAddRequest`]
pub struct DynAddResponse {} pub struct DynAddResponse {}
#[lunanode_response] #[lunanode_response]
/// the result of adding a zone.
/// See also: [`crate::requests::ZoneAddRequest`]
pub struct ZoneAddResponse {} pub struct ZoneAddResponse {}
#[lunanode_response] #[lunanode_response]
/// the result of removing a zone.
/// See also: [`crate::requests::ZoneRemoveRequest`]
pub struct ZoneRemoveResponse {} pub struct ZoneRemoveResponse {}
#[lunanode_response] #[lunanode_response]

@ -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::{ use serde::{
de::{ de::{
self, self,

@ -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 crate::success;
use clap; use clap;
use parse_display_derive::Display; use parse_display_derive::Display;
@ -29,7 +40,15 @@ pub struct ApiKeys {
pub api_partialkey: String, pub api_partialkey: String,
} }
/// A trait that must be implemented by any response type.
pub trait LunaNodeResponse: DeserializeOwned {} 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 { pub trait LunaNodeRequest: Serialize + std::fmt::Debug {
/// The resposne type you expect after making this request. /// 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. /// 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/". /// The only correct way: "vm/list/".
fn url_endpoint(&self) -> String; fn url_endpoint(&self) -> String;
/// A generic function to recieve a specific relsponse type from the server. /// 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. /// 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 LNError, including timezone errors due to not being able to create a nonce value for the request. /// 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 LNError. /// See LunaNodeError.
fn make_request(&self) -> Result<Self::Response, LNError> { fn make_request(&self) -> Result<Self::Response, LunaNodeError> {
make_request::<Self, Self::Response>(self) make_request::<Self, Self::Response>(self)
} }
} }
#[derive(Serialize, Deserialize, Debug)] #[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")] #[serde(with="success")]
success: bool, success: bool,
error: String, // proper type, full accoutnign of the error from the API 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 { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "An API error has occured! {}", self.error) write!(f, "An API error has occured! {}", self.error)
} }
} }
impl std::error::Error for LNErrorResponse {} impl std::error::Error for LunaNodeErrorResponse {}
#[derive(Debug)] #[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), 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), DeserializationError(std::io::Error),
/// Pass through an error when finding the system time.
TimeError(std::time::SystemTimeError), 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 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 { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "An error has occured! {}", self) write!(f, "An error has occured! {}", self)
} }
} }
impl std::error::Error for LNError {} impl std::error::Error for LunaNodeError {}
fn make_request<S, D>(req: &S) -> Result<D, LNError> fn make_request<S, D>(req: &S) -> Result<D, LunaNodeError>
where S: LunaNodeRequest + ?Sized, where S: LunaNodeRequest + ?Sized,
D: LunaNodeResponse D: LunaNodeResponse
{ {
let json_req_data = match serde_json::to_string(req) { let json_req_data = match serde_json::to_string(req) {
Ok(jrd) => jrd, 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) { 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, Ok(ue) => ue,
}.as_secs(); }.as_secs();
let nonce = format!("{}", epoch); let nonce = format!("{}", epoch);
@ -105,20 +132,20 @@ fn make_request<S, D>(req: &S) -> Result<D, LNError>
("signature", &signature), ("signature", &signature),
("nonce", &nonce), ("nonce", &nonce),
]) { ]) {
Err(e) => Err(LNError::RequestError(e)), Err(e) => Err(LunaNodeError::RequestError(e)),
Ok(resp) => { Ok(resp) => {
let resp_str = match resp.into_string() { let resp_str = match resp.into_string() {
Err(e) => return Err(LNError::DeserializationError(e)), Err(e) => return Err(LunaNodeError::DeserializationError(e)),
Ok(s) => s, Ok(s) => s,
}; };
println!("RESP: {}", resp_str); println!("RESP: {}", resp_str);
match serde_json::from_str::<LNErrorResponse>(&resp_str) { match serde_json::from_str::<LunaNodeErrorResponse>(&resp_str) {
Ok(e) => return Err(LNError::LunaNodeError(e)), Ok(e) => return Err(LunaNodeError::LunaNodeError(e)),
Err(e) => false, Err(_e) => false,
}; };
match serde_json::from_str::<D>(&resp_str) { match serde_json::from_str::<D>(&resp_str) {
Ok(s) => Ok(s), Ok(s) => Ok(s),
Err(e) => Err(LNError::SerdeError(e, resp_str)), Err(e) => Err(LunaNodeError::SerdeError(e, resp_str)),
} }
}, },
} }

Loading…
Cancel
Save