use crate::success; use clap; use parse_display_derive::Display; use hmac::{Hmac, Mac}; use serde::{ Serialize, Deserialize, de::DeserializeOwned, }; use sha2::Sha512; use ureq::post; type HmacSHA512 = Hmac; #[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, clap::Args, Clone, Display)] #[display("{api_id}")] /// Used to specify API credentials. These are not required as long as LUNANODE_KEY_ID and LUNANODE_API_KEY are specified in the environment. pub struct ApiKeys { #[clap(long, env="LUNANODE_KEY_ID", help="The API key ID from the Lunanode Dashboard.", long_help="The API key ID from the Lunanode dashboard. You may also specify this option via the LUNANODE_KEY_ID environment variable.", required=false)] /// The API id string as used by LunaNode. This should be exactly 16 bytes. This will get enforced at a later date. pub api_id: String, #[serde(skip)] #[clap(long, env="LUNANODE_API_KEY", help="The Lunanode API key received from the dashboard.", long_help="The Lunanode API key recieved from the dashboard. This option may also be set by the LUNANODE_API_KEY environemnt variable.", required=false)] /// Used for convenience. It is not serialized nor deserializes; this means that this object can NOT be deserialized at all, since this is not an Option. /// This is used to fill in the api_paritlakey field, and to give the request a way to access the api key locally. pub api_key: String, #[clap(long, env="LUNANODE_API_PARTIALKEY", help="The Lunanode API key received from the dashboard, but only the first 64 bytes of it.", long_help="The Lunanode API key recieved from the dashboard. This option may also be set by the LUNANODE_API_PARTIALKEY environemnt variable. This should be autogenerated based on the LUNANODE_API_KEY, but it isn't. TODO.", required=false)] /// The api_paritalkey field as used by LunaNode (the first 64 bytes of the api_key). Note that this field is autofilled. /// Since this should always be a 64 byte array, this will be enforced at some point in the future. TODO pub api_partialkey: String, } pub trait LunaNodeResponse: DeserializeOwned {} 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. /// See: make_request type Response: LunaNodeResponse; /// Can't genericize this one out. You need to implenent a function which can return a set of API keys for creating an API requst. fn get_keys(&self) -> ApiKeys; /// The LunaNode API endpoint you'll be using. /// Note: do *NOT* add a leading slash, but *DO* include a trailing slash. /// The three wrong ways: "/vm/list", "vm/list", "/vm/list", /// 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 { make_request::(self) } } #[derive(Serialize, Deserialize, Debug)] pub struct LNErrorResponse { #[serde(with="success")] success: bool, error: String, // proper type, full accoutnign of the error from the API } impl std::fmt::Display for LNErrorResponse { 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 {} #[derive(Debug)] pub enum LNError { RequestError(ureq::Error), DeserializationError(std::io::Error), TimeError(std::time::SystemTimeError), LunaNodeError(LNErrorResponse), SerdeError(serde_json::Error, String), // the serde error, accompanied by a raw string GetFuckedError, } impl std::fmt::Display for LNError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "An error has occured! {}", self) } } impl std::error::Error for LNError {} 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))), }; let epoch = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) { Err(e) => return Err(LNError::TimeError(e)), Ok(ue) => ue, }.as_secs(); let nonce = format!("{}", epoch); let mut hmac = HmacSHA512::new_from_slice(req.get_keys().api_key.as_bytes()).expect("SHA512 accepts text of any size."); let hmac_input = format!("{}|{}|{}", req.url_endpoint(), &json_req_data, nonce); hmac.update(hmac_input.as_bytes()); let signature = hex::encode(hmac.finalize().into_bytes()); let full_url = format!("https://dynamic.lunanode.com/api/{}", req.url_endpoint()); match post(&full_url) .send_form(&[ ("req", &json_req_data), ("signature", &signature), ("nonce", &nonce), ]) { Err(e) => Err(LNError::RequestError(e)), Ok(resp) => { let resp_str = match resp.into_string() { Err(e) => return Err(LNError::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(s) => Ok(s), Err(e) => Err(LNError::SerdeError(e, resp_str)), } }, } }