6
\$\begingroup\$

I am learning Go and wrote a small application that queries the Skyscanner API as my first attempt at learning. I was hoping someone more expert than me in the language could look over it at broad strokes and let me know if I'm on the right track. I really appreciate any feedback I can get!

Also, I know this is a fair amount to read over. Feedback on even one or two of the files is appreciated.

GitHub repository

main.go

package main

import (
    "fmt"
    "log"
    "os"
    "text/tabwriter"

    "github.com/sdstolworthy/go-fly/skyscanner"
)

func main() {
    fmt.Println()
    params := skyscanner.Parameters{
        Adults:           1,
        Country:          "US",
        Currency:         "USD",
        Locale:           "en-US",
        OriginPlace:      "SLC-sky",
        DestinationPlace: "BNA-sky",
        OutbandDate:      "anytime",
        InboundDate:      "anytime",
    }

    for _, v := range DestinationAirports {
        params.DestinationPlace = v
        fmt.Println(v)
        SkyscannerQuotes := skyscanner.BrowseQuotes(params)

        w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
        quote, err := SkyscannerQuotes.LowestPrice()
        if err != nil {
            log.Printf("%v\n\n", err)
            continue
        }
        fmt.Fprintf(w, "Price:\t$%v\nDeparture:\t%v\nReturn:\t%v\t\n\n", quote.Price, quote.DepartureDate, quote.InboundDate)
    }
}

destinations.go

package main

// DestinationAirports is an array of all airports that should be queried
var DestinationAirports = [...]string{
    "CDG-sky",
    "BNA-sky",
    "LHR-sky",
    "ZAG-sky",
    "SAN-sky",
    "MEX-sky",
}

skyscanner/config.go

package skyscanner

import (
    "io/ioutil"
    "log"

    "gopkg.in/yaml.v2"
)

// Config contains application secrets
type Config struct {
    MashapeKey  string `yaml:"mashape_key"`
    MashapeHost string `yaml:"mashape_host"`
    BaseURL     string `yaml:"base_url"`
}

func (c *Config) getConfig() *Config {
    yamlFile, err := ioutil.ReadFile("conf.yaml")
    if err != nil {
        log.Printf("yamlFile.Get err   #%v ", err)
    }
    err = yaml.Unmarshal(yamlFile, c)
    if err != nil {
        log.Fatalf("Unmarshal: %v", err)
    }
    return c
}

skyscanner/network.go

package skyscanner

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "time"
)

func prettyPrint(apiResponse Response) {
    fmt.Printf("%+v\n", apiResponse.Quotes)
}

func parseResponse(response *http.Response) Response {
    body, readErr := ioutil.ReadAll(response.Body)
    if readErr != nil {
        log.Fatal(readErr)
    }

    apiResponse := Response{}
    jsonErr := json.Unmarshal(body, &apiResponse)
    if jsonErr != nil {
        log.Fatal(jsonErr)
    }
    return apiResponse
}

func getRequest(url string) *http.Request {
    req, err := http.NewRequest(http.MethodGet, url, nil)
    var config Config
    config.getConfig()
    req.Header.Set("X-Mashape-Key", config.MashapeKey)
    req.Header.Set("X-Mashape-Host", config.MashapeHost)

    if err != nil {
        log.Fatal(err)
    }
    return req
}

func getClient() *http.Client {
    return &http.Client{
        Timeout: time.Second * 2,
    }
}

func formatURL(path string) string {
    var c Config
    c.getConfig()
    baseURL := c.getConfig().BaseURL

    return fmt.Sprintf("%v%v", baseURL, path)
}

func get(url string) *http.Response {
    res, getErr := getClient().Do(getRequest(url))

    if getErr != nil {
        log.Fatal(getErr)
    }

    return res

}

/*
BrowseQuotes stub
*/
func BrowseQuotes(parameters Parameters) Response {
    browseQuotes := formatURL(fmt.Sprintf("browsequotes/v1.0/%v/%v/%v/%v/%v/%v/%v",
        parameters.Country,
        parameters.Currency,
        parameters.Locale,
        parameters.OriginPlace,
        parameters.DestinationPlace,
        parameters.OutbandDate,
        parameters.InboundDate,
    ))
    res := get(browseQuotes)
    return parseResponse(res)
}

skyscanner/parameters.go

package skyscanner

// Parameters generic request type for skyscanner api
type Parameters struct {
    Country          string `json:"country"`
    Currency         string `json:"currency"`
    Locale           string `json:"locale"`
    OriginPlace      string `json:"originPlace"`
    DestinationPlace string `json:"destinationPlace"`
    OutbandDate      string `json:"outboundDate"`
    Adults           int    `json:"adults"`
    InboundDate      string `json:"inboundDate"`
}

skyscanner/response.go

package skyscanner

import "errors"

// Response generic response from skyscanner api
type Response struct {
    Quotes []struct {
        QuoteID     int     `json:"QuoteId"`
        MinPrice    float64 `json:"MinPrice"`
        Direct      bool    `json:"Direct"`
        OutboundLeg struct {
            CarrierIds    []int  `json:"CarrierIds"`
            OriginID      int    `json:"OriginId"`
            DestinationID int    `json:"DestinationId"`
            DepartureDate string `json:"DepartureDate"`
        } `json:"OutboundLeg"`
        InboundLeg struct {
            CarrierIds    []int  `json:"CarrierIds"`
            OriginID      int    `json:"OriginId"`
            DestinationID int    `json:"DestinationId"`
            DepartureDate string `json:"DepartureDate"`
        } `json:"InboundLeg"`
        QuoteDateTime string `json:"QuoteDateTime"`
    } `json:"Quotes"`
    Places []struct {
        PlaceID        int    `json:"PlaceId"`
        IataCode       string `json:"IataCode"`
        Name           string `json:"Name"`
        Type           string `json:"Type"`
        SkyscannerCode string `json:"SkyscannerCode"`
        CityName       string `json:"CityName"`
        CityID         string `json:"CityId"`
        CountryName    string `json:"CountryName"`
    } `json:"Places"`
    Carriers []struct {
        CarrierID int    `json:"CarrierId"`
        Name      string `json:"Name"`
    } `json:"Carriers"`
    Currencies []struct {
        Code                        string `json:"Code"`
        Symbol                      string `json:"Symbol"`
        ThousandsSeparator          string `json:"ThousandsSeparator"`
        DecimalSeparator            string `json:"DecimalSeparator"`
        SymbolOnLeft                bool   `json:"SymbolOnLeft"`
        SpaceBetweenAmountAndSymbol bool   `json:"SpaceBetweenAmountAndSymbol"`
        RoundingCoefficient         int    `json:"RoundingCoefficient"`
        DecimalDigits               int    `json:"DecimalDigits"`
    } `json:"Currencies"`
}

// Prices returns an array of the lowest prices for a route and date
func (r *Response) Prices() []float64 {
    priceList := make([]float64, len(r.Quotes))
    for i, v := range r.Quotes {
        priceList[i] = v.MinPrice
    }
    return priceList
}

// A QuoteSummary is a summary of a outbound trip with its price and date
type QuoteSummary struct {
    Price         float64
    DepartureDate string
    InboundDate   string
}

// LowestPrice prints a list of the lowest prices and their accompanying dates
func (r *Response) LowestPrice() (*QuoteSummary, error) {
    quote := QuoteSummary{
        99999999,
        "",
        "",
    }
    for _, v := range r.Quotes {
        if v.MinPrice < quote.Price {
            quote.Price = v.MinPrice
            quote.DepartureDate = v.OutboundLeg.DepartureDate
            quote.InboundDate = v.InboundLeg.DepartureDate
        }
    }
    if quote.Price == 99999999 {
        return nil, errors.New("No quote found")
    }
    return &quote, nil

}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.