From 398ef7f7d7bf785a1ecf549fe5cada1ef3bee3e3 Mon Sep 17 00:00:00 2001 From: Hilde N Date: Sun, 23 Aug 2020 12:18:40 -0400 Subject: [PATCH] handle errors better --- sbbattery/battery.go | 39 ++++++++++++------------- sbcputemp/cputemp.go | 44 ++++++++++++++-------------- sbcpuusage/cpuusage.go | 58 +++++++++++++++++++++---------------- sbdisk/disk.go | 25 ++++++++++------ sbfan/fan.go | 15 ++++++---- sbload/load.go | 15 +++++++--- sbnetwork/network.go | 27 +++++++++--------- sbnordvpn/nordvpn.go | 21 +++++++++----- sbram/ram.go | 6 ++-- sbtime/time.go | 26 +++++++++++------ sbtodo/todo.go | 22 +++++++++----- sbvolume/volume.go | 17 +++++++---- sbweather/weather.go | 65 ++++++++++++++++++++++-------------------- 13 files changed, 221 insertions(+), 159 deletions(-) diff --git a/sbbattery/battery.go b/sbbattery/battery.go index 15809ca..87f29c3 100644 --- a/sbbattery/battery.go +++ b/sbbattery/battery.go @@ -13,10 +13,10 @@ var colorEnd = "^d^" // These are the possible charging states of the battery. const ( - UNKNOWN = -1 - CHARGING = iota - DISCHARGING - FULL + statusUnknown = 0 + statusCharging = iota + statusDischarging + statusFull ) // Routine is the main type for this package. @@ -73,15 +73,15 @@ func New(colors ...[3]string) *Routine { // Update reads the current battery capacity left and calculates a percentage based on it. func (r *Routine) Update() (bool, error) { - // Handle error reading max capacity. - if r.max < 0 { + // Handle error in New or error reading max capacity. + if r.max <= 0 { return false, r.err } // Get current charge and calculate a percentage. now, err := readCharge("/sys/class/power_supply/BAT0/charge_now") if err != nil { - r.err = err + r.err = errors.New("Error reading charge") return true, err } @@ -95,19 +95,19 @@ func (r *Routine) Update() (bool, error) { // Get charging status. status, err := ioutil.ReadFile("/sys/class/power_supply/BAT0/status") if err != nil { - r.err = err + r.err = errors.New("Error reading status") return true, err } switch strings.TrimSpace(string(status)) { case "Charging": - r.status = CHARGING + r.status = statusCharging case "Discharging": - r.status = DISCHARGING + r.status = statusDischarging case "Full": - r.status = FULL + r.status = statusFull default: - r.status = UNKNOWN + r.status = statusUnknown } return true, nil @@ -116,8 +116,6 @@ func (r *Routine) Update() (bool, error) { // String formats the percentage of battery left. func (r *Routine) String() string { var c string - var s string - if r.perc > 25 { c = r.colors.normal } else if r.perc > 10 { @@ -126,14 +124,13 @@ func (r *Routine) String() string { c = r.colors.error } - if r.status == CHARGING { - s = fmt.Sprintf("+%v%%", r.perc) - } else if r.status == DISCHARGING { - s = fmt.Sprintf("-%v%%", r.perc) - } else if r.status == FULL { + s := fmt.Sprintf("%v%%", r.perc) + if r.status == statusCharging { + s = "+" + s + } else if r.status == statusDischarging { + s = "-" + s + } else if r.status == statusFull { s = "Full" - } else { - s = fmt.Sprintf("%v%%", r.perc) } return fmt.Sprintf("%s%s BAT%s", c, s, colorEnd) diff --git a/sbcputemp/cputemp.go b/sbcputemp/cputemp.go index 754ed0b..019c1f9 100644 --- a/sbcputemp/cputemp.go +++ b/sbcputemp/cputemp.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" "io/ioutil" - "os" + "path/filepath" "strconv" "strings" ) @@ -21,11 +21,8 @@ type Routine struct { // Error encountered along the way, if any. err error - // Path to the device directory, as discovered in findDir(). - path string - // Slice of files that contain temperature readings. - files []os.FileInfo + files []string // Average temperature across all sensors, in degrees Celsius. temp int @@ -62,38 +59,41 @@ func New(colors ...[3]string) *Routine { colorEnd = "" } - // Error will be handled in Update() and String(). - r.path, r.err = findDir() - if r.err != nil { + path, err := findDir() + if err != nil { + r.err = err return &r } - // Error will be handled in Update() and String(). - r.files, r.err = findFiles(r.path) + files, err := findFiles(path) + if err != nil { + r.err = err + return &r + } + r.files = files return &r } // Update reads out the value of each sensor, gets an average of all temperatures, and converts it from milliCelsius to // Celsius. If we have trouble reading a particular sensor, then we'll skip it on this pass. func (r *Routine) Update() (bool, error) { - var n int - - if r.path == "" || len(r.files) == 0 { + // Handle error in New. + if len(r.files) == 0 { return false, r.err } r.temp = 0 for _, file := range r.files { - b, err := ioutil.ReadFile(r.path + file.Name()) + b, err := ioutil.ReadFile(file) if err != nil { - r.err = err + r.err = errors.New("Error reading " + filepath.Base(file)) return true, err } - n, err = strconv.Atoi(strings.TrimSpace(string(b))) + n, err := strconv.Atoi(strings.TrimSpace(string(b))) if err != nil { - r.err = err + r.err = errors.New("Error parsing " + filepath.Base(file)) return true, err } @@ -112,7 +112,6 @@ func (r *Routine) Update() (bool, error) { // String prints a formatted temperature average in degrees Celsius. func (r *Routine) String() string { var c string - if r.temp < 75 { c = r.colors.normal } else if r.temp < 100 { @@ -170,8 +169,8 @@ func findDir() (string, error) { // findFiles goes through the given path and builds a list of files that contain a temperature reading. These files will // begin with "temp" and end with "input". -func findFiles(path string) ([]os.FileInfo, error) { - var b []os.FileInfo +func findFiles(path string) ([]string, error) { + var b []string files, err := ioutil.ReadDir(path) if err != nil { @@ -179,9 +178,10 @@ func findFiles(path string) ([]os.FileInfo, error) { } for _, file := range files { - if strings.HasPrefix(file.Name(), "temp") && strings.HasSuffix(file.Name(), "input") { + filename := file.Name() + if strings.HasPrefix(filename, "temp") && strings.HasSuffix(filename, "input") { // We found a temperature reading. Add it to the list. - b = append(b, file) + b = append(b, filepath.Join(path, filename)) } } diff --git a/sbcpuusage/cpuusage.go b/sbcpuusage/cpuusage.go index 9fa582c..aa9caf4 100644 --- a/sbcpuusage/cpuusage.go +++ b/sbcpuusage/cpuusage.go @@ -51,6 +51,9 @@ type stats struct { func New(colors ...[3]string) *Routine { var r Routine + // Set this now so we can key off it in Update to determine whether or not New was successful. + r.threads = -1 + // Do a minor sanity check on the color codes. if len(colors) == 1 { for _, color := range colors[0] { @@ -67,12 +70,13 @@ func New(colors ...[3]string) *Routine { colorEnd = "" } + // Find out how many threads the CPU has. r.threads, r.err = numThreads() if r.err != nil { return &r } - err := readFile(&(r.oldStats)) + err := readStats(&(r.oldStats)) if err != nil { r.err = err } @@ -83,9 +87,13 @@ func New(colors ...[3]string) *Routine { // Update gets the current CPU stats, compares them to the last-read stats, and calculates the percentage of CPU // currently being used. func (r *Routine) Update() (bool, error) { - var newStats stats + // Handle error in New. + if r.threads < 0 { + return false, r.err + } - err := readFile(&newStats) + var newStats stats + err := readStats(&newStats) if err != nil { r.err = err return true, err @@ -144,28 +152,6 @@ func (r *Routine) Name() string { return "CPU Usage" } -// readFile opens /proc/stat and reads out the CPU stats from the first line. -func readFile(newStats *stats) error { - // The first line of /proc/stat will look like this: - // "cpu userVal niceVal sysVal idleVal ..." - f, err := os.Open("/proc/stat") - if err != nil { - return err - } - defer f.Close() - - reader := bufio.NewReader(f) - - line, err := reader.ReadString('\n') - if err != nil { - return err - } - - // Error will be handled in String(). - _, err = fmt.Sscanf(line, "cpu %v %v %v %v", &(newStats.user), &(newStats.nice), &(newStats.sys), &(newStats.idle)) - return err -} - // The shell command 'lscpu' will return a variety of CPU information, including the number of threads // per CPU core. We don't care about the number of cores, because we're already reading in the // averaged total. We only want to know if we need to be changing its range. To get this number, we're @@ -191,3 +177,25 @@ func numThreads() (int, error) { // If we made it this far, then we didn't find anything. return -1, errors.New("Failed to find number of threads") } + +// readStats opens /proc/stat and reads out the CPU stats from the first line. +func readStats(newStats *stats) error { + // The first line of /proc/stat will look like this: + // "cpu userVal niceVal sysVal idleVal ..." + f, err := os.Open("/proc/stat") + if err != nil { + return err + } + defer f.Close() + + reader := bufio.NewReader(f) + + line, err := reader.ReadString('\n') + if err != nil { + return err + } + + // Error will be handled in String(). + _, err = fmt.Sscanf(line, "cpu %v %v %v %v", &(newStats.user), &(newStats.nice), &(newStats.sys), &(newStats.idle)) + return err +} diff --git a/sbdisk/disk.go b/sbdisk/disk.go index eaa5eab..d22479e 100644 --- a/sbdisk/disk.go +++ b/sbdisk/disk.go @@ -56,8 +56,9 @@ type fs struct { func New(paths []string, colors ...[3]string) *Routine { var r Routine - for _, path := range paths { - r.disks = append(r.disks, fs{path: path}) + if len(paths) == 0 { + r.err = errors.New("No paths provided") + return &r } // Do a minor sanity check on the color codes. @@ -76,18 +77,27 @@ func New(paths []string, colors ...[3]string) *Routine { colorEnd = "" } + // We want to do this after checking the colors so we can know in Update if New was successful or not. + for _, path := range paths { + r.disks = append(r.disks, fs{path: path}) + } + return &r } // Update gets the amount of used and total disk space and converts them into a human-readable size for each provided // filesystem. func (r *Routine) Update() (bool, error) { - var b syscall.Statfs_t + // Handle error in New. + if len(r.disks) == 0 { + return false, r.err + } for i, disk := range r.disks { + var b syscall.Statfs_t err := syscall.Statfs(disk.path, &b) if err != nil { - r.err = err + r.err = errors.New("Error checking stats") return true, err } @@ -104,9 +114,8 @@ func (r *Routine) Update() (bool, error) { // String formats and prints the amounts of disk space for each provided filesystem. func (r *Routine) String() string { - var c string - var b strings.Builder - + c := "" + b := new(strings.Builder) for i, disk := range r.disks { if disk.perc > 90 { c = r.colors.error @@ -120,7 +129,7 @@ func (r *Routine) String() string { b.WriteString(", ") } b.WriteString(c) - fmt.Fprintf(&b, "%s: %v%c/%v%c", disk.path, disk.used, disk.usedUnit, disk.total, disk.totalUnit) + fmt.Fprintf(b, "%s: %v%c/%v%c", disk.path, disk.used, disk.usedUnit, disk.total, disk.totalUnit) b.WriteString(colorEnd) } diff --git a/sbfan/fan.go b/sbfan/fan.go index 4e27531..7bfc60a 100644 --- a/sbfan/fan.go +++ b/sbfan/fan.go @@ -62,7 +62,6 @@ func New(colors ...[3]string) *Routine { colorEnd = "" } - // Find the max fan speed file and read its value. // Find the files holding the values for the maximum fan speed and the current fan speed. maxFile, outFile, err := findFiles() if err != nil { @@ -70,10 +69,10 @@ func New(colors ...[3]string) *Routine { return &r } - // Error will be handled later in Update() and String(). + // Find the max fan speed file and read its value. r.max, r.err = readSpeed(maxFile) - r.fanPath = outFile + r.fanPath = outFile return &r } @@ -84,8 +83,14 @@ func (r *Routine) Update() (bool, error) { return false, r.err } - r.speed, r.err = readSpeed(r.fanPath) - return true, r.err + speed, err := readSpeed(r.fanPath) + if err != nil { + r.err = errors.New("Error reading speed") + return true, err + } + + r.speed = speed + return true, nil } // String prints the current speed in RPM. diff --git a/sbload/load.go b/sbload/load.go index b7af4fa..119bc22 100644 --- a/sbload/load.go +++ b/sbload/load.go @@ -22,7 +22,7 @@ type Routine struct { load5 float64 // Load average over the last 15 seconds. - load15 float64 + load15 float64 // Trio of user-provided colors for displaying various states. colors struct { @@ -61,11 +61,15 @@ func New(colors ...[3]string) *Routine { // Update calls Sysinfo() and calculates load averages. func (r *Routine) Update() (bool, error) { - var info syscall.Sysinfo_t + // Handle any error encountered in New. + if r.err != nil { + return true, r.err + } + var info syscall.Sysinfo_t err := syscall.Sysinfo(&info) if err != nil { - r.err = err + r.err = errors.New("Error getting stats") return true, err } @@ -98,7 +102,10 @@ func (r *Routine) Error() string { r.err = errors.New("Unknown error") } - return r.colors.error + r.err.Error() + colorEnd + s := r.colors.error + r.err.Error() + colorEnd + r.err = nil + + return s } // Name returns the display name of this module. diff --git a/sbnetwork/network.go b/sbnetwork/network.go index c50907a..1ed2bee 100644 --- a/sbnetwork/network.go +++ b/sbnetwork/network.go @@ -60,8 +60,6 @@ type sbiface struct { // 3. Error color, one of more interface is running at greater than Mbps speeds. func New(inames []string, colors ...[3]string) *Routine { var r Routine - var ilist []string - var err error // Do a minor sanity check on the color codes. if len(colors) == 1 { @@ -79,6 +77,8 @@ func New(inames []string, colors ...[3]string) *Routine { colorEnd = "" } + var ilist []string + var err error if len(inames) == 0 { // Nothing was passed in. We'll grab the default interfaces. ilist, err = getInterfaces() @@ -87,7 +87,6 @@ func New(inames []string, colors ...[3]string) *Routine { // Make sure we have a valid interface name. _, err = net.InterfaceByName(iname) if err != nil { - // Error will be handled in Update() and String(). err = errors.New(iname + ": " + err.Error()) break } @@ -98,6 +97,7 @@ func New(inames []string, colors ...[3]string) *Routine { // Handle any problems that came up, or build up list of interfaces for later use. if err != nil { r.err = err + r.ilist = nil } else if len(ilist) == 0 { r.err = errors.New("No interfaces found") } else { @@ -113,30 +113,31 @@ func New(inames []string, colors ...[3]string) *Routine { // Update gets the current readings of the rx/tx files for each interface. func (r *Routine) Update() (bool, error) { + // Handle any error from New. + if len(r.ilist) == 0 { + return false, r.err + } + for i, iface := range r.ilist { r.ilist[i].oldDown = iface.newDown r.ilist[i].oldUp = iface.newUp down, err := readFile(iface.downPath) if err != nil { - if r.err == nil { - r.err = err - } - continue + r.err = errors.New("Error reading " + iface.name + " (Down)") + return true, err } r.ilist[i].newDown = down up, err := readFile(iface.upPath) if err != nil { - if r.err == nil { - r.err = err - } - continue + r.err = errors.New("Error reading " + iface.name + " (Up)") + return true, err } r.ilist[i].newUp = up } - return true, r.err + return true, nil } // String calculates the byte difference for each interface, and formats and prints it. @@ -160,7 +161,7 @@ func (r *Routine) String() string { b.WriteString(", ") } b.WriteString(c) - fmt.Fprintf(&b, "%s: %4v%c↓/%4v%c↑", iface.name, down, downUnit, up, upUnit) + fmt.Fprintf(&b, "%s: %4v%c↓|%4v%c↑", iface.name, down, downUnit, up, upUnit) b.WriteString(colorEnd) } diff --git a/sbnordvpn/nordvpn.go b/sbnordvpn/nordvpn.go index 7b2561d..84446c3 100644 --- a/sbnordvpn/nordvpn.go +++ b/sbnordvpn/nordvpn.go @@ -63,12 +63,16 @@ func (r *Routine) Update() (bool, error) { cmd := exec.Command("nordvpn", "status") output, err := cmd.Output() if err != nil { + r.err = errors.New("Error getting status") + return true, err + } + + if err := r.parseOutput(string(output)); err != nil { r.err = err return true, err } - r.parseOutput(string(output)) - return true, r.err + return true, nil } // String formats and prints the current connection status. @@ -91,7 +95,7 @@ func (r *Routine) Name() string { } // parseOutput parses the command's output. -func (r *Routine) parseOutput(output string) { +func (r *Routine) parseOutput(output string) error { // If there is a connection to the VPN, the command will return this format: // Status: Connected // Current server: @@ -124,8 +128,9 @@ func (r *Routine) parseOutput(output string) { } } if field == -1 { - r.err = errors.New(lines[0]) - return + return errors.New(lines[0]) + } else if len(fields) <= field+1 { + return errors.New("Bad response") } switch fields[field+1] { @@ -157,9 +162,11 @@ func (r *Routine) parseOutput(output string) { r.parsed = "Disconnected" r.color = r.colors.warning case "Please check your internet connection and try again.": - r.err = errors.New("Internet Down") + return errors.New("Internet Down") default: // If we're here, then we have an unknown error. - r.err = errors.New(lines[0]) + return errors.New(lines[0]) } + + return nil } diff --git a/sbram/ram.go b/sbram/ram.go index d7d19fa..4d8f34b 100644 --- a/sbram/ram.go +++ b/sbram/ram.go @@ -73,7 +73,7 @@ func New(colors ...[3]string) *Routine { func (r *Routine) Update() (bool, error) { file, err := ioutil.ReadFile("/proc/meminfo") if err != nil { - r.err = err + r.err = errors.New("Error reading file") return true, err } @@ -139,7 +139,7 @@ func parseFile(output string) (int, int, error) { } total, err = strconv.Atoi(fields[1]) if err != nil { - return 0, 0, err + return 0, 0, errors.New("Error parsing MemTotal fields") } } else if strings.HasPrefix(line, "MemAvailable") { @@ -149,7 +149,7 @@ func parseFile(output string) (int, int, error) { } avail, err = strconv.Atoi(fields[1]) if err != nil { - return 0, 0, err + return 0, 0, errors.New("Error parsing MemAvailable fields") } } } diff --git a/sbtime/time.go b/sbtime/time.go index 8559996..4d04bd5 100644 --- a/sbtime/time.go +++ b/sbtime/time.go @@ -40,11 +40,6 @@ type Routine struct { func New(format string, colors ...[3]string) *Routine { var r Routine - // Replace all colons in the format string with spaces, to get the blinking effect later. - r.formatA = format - r.formatB = strings.Replace(format, ":", " ", -1) - r.time = time.Now() - // Do a minor sanity check on the color codes. if len(colors) == 1 { for _, color := range colors[0] { @@ -61,11 +56,24 @@ func New(format string, colors ...[3]string) *Routine { colorEnd = "" } + // Replace all colons in the format string with spaces, to get the blinking effect later. + r.formatA = format + r.formatB = strings.Replace(format, ":", " ", -1) + r.time = time.Now() + return &r } // Update updates the routine's current time. func (r *Routine) Update() (bool, error) { + // Handle error in New. + if r.formatA == "" || r.formatB == "" { + if r.err == nil { + r.err = Errors.New("Missing time format") + } + return false, r.err + } + r.time = time.Now() return true, nil @@ -73,10 +81,12 @@ func (r *Routine) Update() (bool, error) { // String prints the time in the provided format. func (r *Routine) String() string { - if r.time.Second()%2 == 0 { - return r.colors.normal + r.time.Format(r.formatA) + colorEnd + format := r.formatA + if r.time.Second()%2 != 0 { + format = r.formatB } - return r.colors.normal + r.time.Format(r.formatB) + colorEnd + + return r.colors.normal + r.time.Format(format) + colorEnd } // Error formats and returns an error message. diff --git a/sbtodo/todo.go b/sbtodo/todo.go index 97eec9d..c1aadd9 100644 --- a/sbtodo/todo.go +++ b/sbtodo/todo.go @@ -63,26 +63,34 @@ func New(path string, colors ...[3]string) *Routine { } // Grab the base details of the TODO file. - r.info, r.err = os.Stat(path) - if r.err != nil { - // We'll print the error in String(). + info, err := os.Stat(path) + if err != nil { + r.err = err return &r } if err := r.readFile(); err != nil { - // We'll print the error in String(). - r.err = err + r.err = errors.New("Error reading file") return &r } + r.info = info return &r } // Update reads the TODO file again, if it was modified since the last read. func (r *Routine) Update() (bool, error) { + // Handle any error from New. + if r.info.Name() == "" { + if r.err == nil { + r.err = errors.New("Invalid file") + } + return false, r.err + } + newInfo, err := os.Stat(r.path) if err != nil { - r.err = err + r.err = errors.New("Error getting file stats") return true, err } @@ -92,7 +100,7 @@ func (r *Routine) Update() (bool, error) { if newMtime > oldMtime { // The file was modified. Let's parse it. if err := r.readFile(); err != nil { - r.err = err + r.err = errors.New("Error reading file") return true, err } } diff --git a/sbvolume/volume.go b/sbvolume/volume.go index 4e5969b..4e45e6c 100644 --- a/sbvolume/volume.go +++ b/sbvolume/volume.go @@ -42,8 +42,6 @@ type Routine struct { func New(control string, colors ...[3]string) *Routine { var r Routine - r.control = control - // Do a minor sanity check on the color codes. if len(colors) == 1 { for _, color := range colors[0] { @@ -60,18 +58,27 @@ func New(control string, colors ...[3]string) *Routine { colorEnd = "" } + r.control = control return &r } // Update runs the 'amixer' command and parses the output for mute status and volume percentage. func (r *Routine) Update() (bool, error) { + // Handle error from New. + if r.control == "" { + if r.err == nil { + r.err = errors.New("Invalid control") + } + return false, r.err + } + r.muted = false r.vol = -1 cmd := exec.Command("amixer", "get", r.control) out, err := cmd.Output() if err != nil { - r.err = err + r.err = errors.New("Error getting volume") return true, err } @@ -89,7 +96,7 @@ func (r *Routine) Update() (bool, error) { s := strings.TrimRight(field, "%") vol, err := strconv.Atoi(s) if err != nil { - r.err = err + r.err = errors.New("Error parsing volume") return true, err } // Ensure that the volume is a multiple of 10 (so it looks nicer). @@ -101,7 +108,7 @@ func (r *Routine) Update() (bool, error) { } if r.vol < 0 { - r.err = errors.New("No volume found for " + r.control) + r.err = errors.New("No volume found") } return true, nil diff --git a/sbweather/weather.go b/sbweather/weather.go index ad6d68e..db859b9 100644 --- a/sbweather/weather.go +++ b/sbweather/weather.go @@ -55,18 +55,6 @@ type Routine struct { func New(zip string, colors ...[3]string) *Routine { var r Routine - if len(zip) != 5 { - r.err = errors.New("Invalid Zip Code length") - return &r - } - - _, err := strconv.Atoi(zip) - if err != nil { - r.err = err - return &r - } - r.zip = zip - // Do a minor sanity check on the color codes. if len(colors) == 1 { for _, color := range colors[0] { @@ -83,31 +71,46 @@ func New(zip string, colors ...[3]string) *Routine { colorEnd = "" } + if len(zip) != 5 { + r.err = errors.New("Invalid zip code length") + return &r + } + + _, err := strconv.Atoi(zip) + if err != nil { + r.err = errors.New("Invalid zip code") + return &r + } + r.zip = zip + + // Initialize the routine. First, convert the provided zip code into geographic coordinates. If this fails, then + // it's most likely a connectivity problem. + lat, long, err := getCoords(r.client, r.zip) + if err != nil { + r.err = errors.New("Connection failed") + return &r + } + + // Get the URL for the forecast at the geographic coordinates. We don't want to retry this on failure because + // there was some problem handling the coordinates. + url, err := getURL(r.client, lat, long) + if err != nil { + r.err = errors.New("No Forecast Data") + return &r + } + r.url = url + return &r } // Update gets the current hourly temperature. func (r *Routine) Update() (bool, error) { - r.err = nil - - // If this is the first run of the session, initialize the routine. - if r.url == "" { - // Convert the provided zip into geographic coordinates. If this fails, then it's most likely a connectivity - // problem. - lat, long, err := getCoords(r.client, r.zip) - if err != nil { - r.err = errors.New("Cannot connect") - return false, err - } - - // Get the URL for the forecast at the geographic coordinates. We don't want to retry this on failure because - // there was some problem handling the coordinates. - url, err := getURL(r.client, lat, long) - if err != nil { - r.err = errors.New("No Forecast Data") - return false, err + // Handle any error from New. + if r.zip == "" || r.url == "" { + if r.err == nil { + r.err = errors.New("Bad parameters") } - r.url = url + return false, r.err } // Get hourly temperature.