Back in part 1 we identified a few imperfections in my code that I wanted to address. We have done the unwraps, done the error handling. This post addresses splitting up the api.rs into multiple files.

Back in part 3 we already started the path of splitting up the api module by creating an api folder and the api.rs got moved into it and was called mod.rs

We also added a file for all of the errors. Now its time to consider how we could break up the mod.rs

I think the first thing is the session – that warrants its own file

Currently, the ‘Session’ struct is inside src/api/mod.rs, but I want to move it out into its new home which will be in a new private module called ‘types’. I want to keep this module private as I dont want my users to have to use a deeply nested path to it, so I will re export it in src/api/mod.rs.

So, first, I create a new file called src/api/types/mod.rs and move both the ‘Session’ and ‘Config’ structs into it (not the impl blocks) so it looks like this

src/api/types/mod.rs

// A Session is where everything begins.  My plan (for now) is that we go via the session
// for everything, but we will see how the API develops.
#[derive(Debug)]
pub struct Session {
pub config: Config,
pub api_key: String
}

// The Config struct is used to gather all of the configuration into one place.
// At the moment, it is a combination of hard coding and environment variables.
// I have not really planned ahead to thing where it is going to get the config from long term
// but, lets learn to walk before we run ?
#[derive(Debug)]
pub struct Config {
api_url: String,
api_password: String,
api_username: String
}

we now need to change src/api/mod.rs by adding this near the top

src/api/mod.rs

mod types;
pub use types::Session;
pub use types::Config

This effectively tells rust that we want the types module, but it is defined in an external file / folder. Then, we ‘re export’ the types::Session and types::Config – so the path to these is still

crate::api::Session and crate::api::Config – the fact that these are in a sub module called ‘types’ is hidden.

Ok, if we try to compile now – we have a problem – it says things like

field api_url of struct api::types::Config is private

If we look at the Config struct we can see why – the struct itself is public but the elements arent. This didn’t matter before because we were using them within the same module but now we are not – so we need to fix that. So if we change src/api/types/mod.rs to

pub struct Config {
pub api_url: String,
pub api_password: String,
pub api_username: String
}

and try and run it again – all is now fine.

But,this is still not good enough – if I had all my types in one file along with their implementations (which I think I want !), it would get big. So, my next little challenge is to create a separate file for the session, then one for the config.

So, lets focus on the ‘Session’ to start with – moving the struct into a new file called ‘src/api/types/session.rs’ which looks like this.

src/api/types/session.rs

// A Session is where everything begins.  My plan (for now) is that we go via the session
// for everything, but we will see how the API develops.
use crate::api::types::Config;

#[derive(Debug)]
pub struct Session {
pub config: Config,
pub api_key: String
}

Then change src/api/types/mod.rs by adding this at the top

src/api/types/mod.rs

mod session;
pub use session::Session

Again, we are saying use a private module called session (as it is in a different file, it is in a different module), but we re export the session using ‘pub use‘ so that the it is seen as src:api:types:Session – which if you remember got re exported to just src:api:Session earlier on. Im sure we can simplify this and only re export it in one place, but this kind of feels logical so I am sticking with it.

So, if we now run this – all is good – next is the ‘Config’ struct. We literally apply the same principles – first move the struct into a new file called src/api/types/config.rs

src/api/types/config.rs

// The Config struct is used to gather all of the configuration into one place.
// At the moment, it is a combination of hard coding and environment variables.
// I have not really planned ahead to thing where it is going to get the config from long term
// but, lets learn to walk before we run ?
#[derive(Debug)]
pub struct Config {
pub api_url: String,
pub api_password: String,
pub api_username: String
}

then modify

Then change src/api/types/mod.rs by adding this at the top

src/api/types/mod.rs

mod config;
pub use config::Config

So our src/api/types/mod.rs is now just pulling things together like this

src/api/types/mod.rs

mod session;
mod config;
pub use session::Session;
pub use config::Config;

If we now run – all is ok – so thats that baby step complete.

Ok, so now we have the structs defined in their own files, but what about the implementations for those structs. Im not sure if this is the rubyist in me, but for now I would like to see that in the same file.

So, we take the ‘impl Session’ block from src/api/mod.rs and move it into the src/api/types/session.rs which ends up looking like this

src/api/types/session.rs

// A Session is where everything begins.  My plan (for now) is that we go via the session
// for everything, but we will see how the API develops.
use crate::api::types::Config;
use crate::api::errors;

#[derive(Debug)]
pub struct Session {
pub config: Config,
pub api_key: String
}

impl Session {
/// Starts a new session
pub fn start() -> Result<Session, errors::ApiError> {
let config = Config::new();
let request_url = format!("{api_base}/users/profile", api_base = &config.api_url);
let result = reqwest::Client::new()
.get(&request_url)
.basic_auth(&config.api_username, Some(&config.api_password))
.header("Accept", "application/json")
.send();
match result {
Ok(mut resp) => {
match resp.json() {
Ok(json) => Ok(Session::parse_new_session_response(config, json)),
Err(_err) => Err(errors::ApiError::InvalidLoginResponse)
}

},
Err(err) => Err(errors::ApiError::LoginFailure)
}
}

fn parse_new_session_response(config: Config, json: EnergenieResponse<EnergenieUserProfile>) -> Session {
Session {
config: config,
api_key: json.data.api_key
}
}
}

But, this doesnt compile as we have those EnergenieResponse and EnergenieUserProfile structs which are no longer defined in the same module. So, lets sort that one out. Lets move those into src/api/types for now so it looks like this

src/api/types/mod.rs

mod session;
mod config;
pub use session::Session;
pub use config::Config;

// Below are the data structures returned by the API - I am hoping to move these into a different file

#[derive(Deserialize, Debug)]
struct EnergenieUserProfile {
id: i64,
email_address: String,
api_key: String
}

#[derive(Deserialize, Debug)]
struct EnergenieResponse<T> {
status: String,
time: f64,
data: T
}

and change the src/api/types/session.rs, adding the following to the top

src/api/types/session.rs

use reqwest;
use crate::api::types::EnergenieResponse;
use crate::api::types::EnergenieUserProfile

If we now run this code, it will compile and run

But, we still have the ‘Config’ implementation in the src/api/mod.rs file and I want it in the src/api/types/config.rs file – so we can just move that code across, but because it uses std::env, we need to move that use statement across as well.

So, our src/api/types/config.rs looks like this

src/api/types/config.rs

use std::env;

// The Config struct is used to gather all of the configuration into one place.
// At the moment, it is a combination of hard coding and environment variables.
// I have not really planned ahead to thing where it is going to get the config from long term
// but, lets learn to walk before we run ?
#[derive(Debug)]
pub struct Config {
pub api_url: String,
pub api_password: String,
pub api_username: String
}

impl Config {
pub fn new() -> Config {
Config {
api_url: String::from("https://mihome4u.co.uk/api/v1"),
api_username: env::var("MIHOME_USERNAME").expect("MIHOME_USERNAME must be set"),
api_password: env::var("MIHOME_PASSWORD").expect("MIHOME_PASSWORD must be set")
}
}
}

Ok, we still have 2 structs in src/api/types/mod.rs – those Energenie structs – they don’t really fit in there and they are not types that we are going to use apart from for json parsing and generating, so I think they should go into a ‘json’ module.

So, lets move those structs into a file called src/api/json/mod.rs. Then, in our src/api/types/session.rs, we need to change those 2 use statements for the Energenie structs to look like this

src/api/types/session.rs

use crate::api::json::EnergenieResponse;
use crate::api::json::EnergenieUserProfile;

and we need to re export them in src/api/mod.rs so add the following :-

src/api/mod.rs

mod json;
pub use json::EnergenieUserProfile;
pub use json::EnergenieResponse;

If we now run – all compiles and is good.

So, now just a bit of tidying up – lets tidy up src/api/mod.rs

src/api/mod.rs

//! The api stuff
extern crate reqwest;
extern crate serde_derive;
extern crate serde_json;
extern crate serde;

pub mod errors;
mod types;
mod json;

pub use types::Session;
pub use types::Config;


The key change was removing the use reqwest::Client as this is now in the session.rs.

So, my code is now a bit more structured. Now, time to think about what to do next. More in the next post

Categories: rubyrust

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *