1 /** 2 * The client API 3 */ 4 module eskomcalendar.calendar; 5 6 import std.stdio; 7 import std.json; 8 import std.net.curl : get, CurlException; 9 import std.conv : to; 10 import eskomcalendar.schedule; 11 import std.datetime : SysTime; 12 import eskomcalendar.exceptions; 13 14 /** 15 * Client API wrapper for the eskom-calendar service 16 */ 17 public class EskomCalendar 18 { 19 private string calendarServer; 20 21 /** 22 * Constructs a new `EskomCalendar` using the provided 23 * custom server 24 * 25 * Params: 26 * calendarServer = URL of the server to use 27 */ 28 this(string calendarServer) 29 { 30 import std.string : stripRight; 31 this.calendarServer = stripRight(calendarServer, "/"); 32 } 33 34 /** 35 * Constructs a new `EskomCalendar` using the 36 * default reference server 37 */ 38 this() 39 { 40 this("https://eskom-calendar-api.shuttleapp.rs/v0.0.1/"); 41 } 42 43 /** 44 * Performs an HTTP GET request to the provided URL 45 * and wraps any exceptions in our `EskomCalendarException` 46 * type 47 * 48 * Params: 49 * url = the URL to perform a GET on 50 * Returns: the response body 51 */ 52 private final string doGet(string url) 53 { 54 try 55 { 56 return cast(string)get(url); 57 } 58 catch(CurlException e) 59 { 60 throw new EskomCalendarException(ErrType.NETWORK_ERROR, "Could not connect to server '"~calendarServer~"'"); 61 } 62 } 63 64 /** 65 * Get schedules from a given area 66 * 67 * Params: 68 * area = the area to check for schedules 69 * startTime = the lower bound to filter by (none by default) 70 * finishTime = the upper bound to filter by (none by default) 71 * Returns: an array of `Schedule`(s) 72 */ 73 public Schedule[] getSchedules(string area, SysTime startTime = SysTime.min(), SysTime finishTime = SysTime.max()) 74 { 75 Schedule[] schedules; 76 77 scope(exit) 78 { 79 version(unittest) 80 { 81 writeln("Exiting with '"~to!(string)(schedules.length)~" schedules for area '"~area~"' between '"~startTime.toSimpleString()~"' and '"~finishTime.toSimpleString()~"'"); 82 } 83 } 84 85 /** 86 * Fetch the schedules and parse 87 */ 88 string data = doGet(calendarServer~"/outages/"~area); 89 JSONValue[] schedulesJSON = parseJSON(data).array(); 90 foreach(JSONValue schedule; schedulesJSON) 91 { 92 Schedule curSchedule = Schedule.fromJSON(schedule); 93 94 if(curSchedule.getStart() >= startTime && curSchedule.getFinish() <= finishTime) 95 { 96 schedules ~= curSchedule; 97 } 98 } 99 100 if(schedules.length == 0) 101 { 102 throw new EskomCalendarException(ErrType.NO_SCHEDULES_AVAILABLE, "No schedules for area '"~area~"'"); 103 } 104 105 return schedules; 106 } 107 108 /** 109 * Gets any schedules in the given area that would be valid for 110 * the 24 hours of today's date 111 * 112 * Params: 113 * area = the area to check for schedules 114 * Returns: an array of `Schedule`(s) 115 */ 116 public Schedule[] getTodaySchedules(string area) 117 { 118 import std.datetime.systime : Clock; 119 import std.datetime.date : Date, DateTime; 120 import std.datetime.systime : SysTime; 121 import core.thread : dur; 122 123 124 // Get just the date of today (no time) 125 Date todayDate = cast(Date)Clock.currTime(); 126 127 // Get the start and end date+times but with time zeroed out 128 SysTime startTime = cast(SysTime)todayDate; 129 SysTime endTime = cast(SysTime)todayDate; 130 131 // Make end date+time 24 hours later 132 endTime += dur!("hours")(24); 133 134 version(unittest) 135 { 136 writeln("startTime: ", startTime); 137 writeln("endTime: ", endTime); 138 } 139 140 return getSchedules(area, startTime, endTime); 141 } 142 143 /** 144 * Gets schedules from a given time 145 * 146 * Params: 147 * area = the area to check for schedules 148 * startTime = the time to check from 149 * Returns: an array of `Schedule`(s) 150 */ 151 public Schedule[] getSchedulesFrom(string area, SysTime startTime) 152 { 153 return getSchedules(area, startTime); 154 } 155 156 /** 157 * Gets schedules up until a given time 158 * 159 * Params: 160 * area = the area to check for schedules 161 * finishTime = the time to check til 162 * Returns: an array of `Schedule`(s) 163 */ 164 public Schedule[] getSchedulesUntil(string area, SysTime finishTime) 165 { 166 return getSchedules(area, SysTime.min(), finishTime); 167 } 168 169 /** 170 * Get all the areas 171 * 172 * Returns: an array of `string` of the area names 173 */ 174 public string[] getAreas() 175 { 176 return getAreas(""); 177 } 178 179 /** 180 * Get all areas matching a given regular expression 181 * 182 * Params: 183 * regex = the regular expression 184 * Returns: an array of `string` of the area names 185 */ 186 public string[] getAreas(string regex) 187 { 188 // Apply any URL escaping needed 189 import std.uri : encode; 190 regex = encode(regex); 191 192 string data = doGet(calendarServer~"/list_areas/"~regex); 193 JSONValue[] areas = parseJSON(data).array(); 194 195 string[] areasStr; 196 foreach(JSONValue area; areas) 197 { 198 areasStr ~= area.str(); 199 } 200 201 if(areasStr.length == 0) 202 { 203 throw new EskomCalendarException(ErrType.NO_AREAS_AVAILABLE); 204 } 205 206 return areasStr; 207 } 208 } 209 210 /** 211 * Get the schedules that will occur within today's 24 hours 212 * in the `western-cape-worscester` area 213 */ 214 unittest 215 { 216 EskomCalendar calendar = new EskomCalendar(); 217 218 try 219 { 220 Schedule[] schedules = calendar.getTodaySchedules("western-cape-worscester"); 221 foreach(Schedule schedule; schedules) 222 { 223 writeln("Today: "~schedule.toString()); 224 } 225 } 226 catch(EskomCalendarException e) 227 { 228 writeln("Crashed with '"~e.toString()~"'"); 229 assert(false); 230 } 231 } 232 233 /** 234 * Get the first 10 areas and then all schedules 235 * of each said area 236 */ 237 unittest 238 { 239 EskomCalendar calendar = new EskomCalendar(); 240 241 /** 242 * Get all areas available 243 * and take a subset of them 244 */ 245 string[] areas = calendar.getAreas()[0..10]; 246 247 /** 248 * Get the schedules per-each of them 249 */ 250 foreach(string area; areas) 251 { 252 Schedule[] schedules = calendar.getSchedules(area); 253 } 254 } 255 256 /** 257 * Get the schedules for the `western-cape-worscester` area 258 */ 259 unittest 260 { 261 EskomCalendar calendar = new EskomCalendar(); 262 263 try 264 { 265 Schedule[] schedules = calendar.getSchedules("western-cape-worscester"); 266 assert(schedules.length > 5); 267 268 foreach(Schedule schedule; schedules) 269 { 270 writeln(schedule); 271 } 272 } 273 catch(EskomCalendarException e) 274 { 275 writeln("Crashed with '"~e.toString()~"'"); 276 assert(false); 277 } 278 } 279 280 /** 281 * Get all areas 282 */ 283 unittest 284 { 285 EskomCalendar calendar = new EskomCalendar(); 286 287 try 288 { 289 string[] areas = calendar.getAreas(); 290 writeln(areas); 291 assert(areas.length > 40); 292 } 293 catch(EskomCalendarException e) 294 { 295 writeln("Crashed with '"~e.toString()~"'"); 296 assert(false); 297 } 298 } 299 300 /** 301 * Test failing network connection 302 */ 303 unittest 304 { 305 EskomCalendar calendar = new EskomCalendar("http://sdhjdshkjdas.com"); 306 307 try 308 { 309 calendar.getAreas(); 310 assert(false); 311 } 312 catch(EskomCalendarException e) 313 { 314 assert(e.getError() == ErrType.NETWORK_ERROR); 315 } 316 }