2022-10-21 12:02:03 -05:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"net/http"
|
2022-10-22 01:06:30 -05:00
|
|
|
"sync"
|
2022-10-21 12:02:03 -05:00
|
|
|
)
|
|
|
|
|
2022-10-22 01:13:42 -05:00
|
|
|
// If you're using this module and wondering why it's slow, it's the API, not
|
|
|
|
// this code.
|
|
|
|
|
2022-10-21 12:02:03 -05:00
|
|
|
const API_PREFIX = "https://swapi.dev/api"
|
|
|
|
|
|
|
|
// Source: https://swapi.dev/documentation#people
|
|
|
|
type Person struct {
|
|
|
|
Name string
|
|
|
|
// ... other fields unused by this application
|
|
|
|
}
|
|
|
|
|
|
|
|
// Source: https://swapi.dev/documentation#starships
|
|
|
|
type Starship struct {
|
|
|
|
Name string
|
|
|
|
Pilots []string // URLs to retrieve [People]
|
|
|
|
// ... other fields unused by this application
|
|
|
|
}
|
|
|
|
|
2022-10-22 01:06:30 -05:00
|
|
|
// Represents a single page of data as returned by swapi
|
|
|
|
type PageOf[T interface{}] struct {
|
|
|
|
Count uint
|
|
|
|
Next *string
|
|
|
|
Results []T
|
|
|
|
// Previous *string
|
2022-10-21 12:02:03 -05:00
|
|
|
}
|
|
|
|
|
2022-10-22 01:06:30 -05:00
|
|
|
// Used for retrieving data from swapi.dev, decoding the unmarshalling the JSON
|
|
|
|
// body, and storing the result in the provided struct reference.
|
2022-10-21 12:02:03 -05:00
|
|
|
//
|
2022-10-22 01:06:30 -05:00
|
|
|
// myData := interface{}
|
|
|
|
// err := Get("/starships", &myData)
|
2022-10-21 12:02:03 -05:00
|
|
|
//
|
|
|
|
// If [path] does not start with "/", it assumes you have provided a full URL to
|
2022-10-22 01:06:30 -05:00
|
|
|
// make following [PageOf]'s Next and Previous simpler.
|
2022-10-21 12:02:03 -05:00
|
|
|
func Get[T interface{}](path string, data *T) error {
|
|
|
|
var url string
|
|
|
|
if path[0] == '/' {
|
|
|
|
url = API_PREFIX + path
|
|
|
|
} else {
|
|
|
|
url = path
|
|
|
|
}
|
|
|
|
resp, err := http.Get(url)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Error GET'ing %s: %+v", url, err)
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
err = json.NewDecoder(resp.Body).Decode(data)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Failed to decode response JSON: %+v", err)
|
|
|
|
}
|
|
|
|
log.Debugf("GET %s: %+v", url, resp)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-10-22 01:06:30 -05:00
|
|
|
// Retrieves all starships from swapi by fetching the first page and continuing
|
|
|
|
// to fetch pages synchronously until no Next page is specified. We collect each
|
|
|
|
// request's set of results into a slice.
|
|
|
|
func AllStarships() ([]*Starship, error) {
|
|
|
|
results := make([]*Starship, 0)
|
2022-10-21 12:02:03 -05:00
|
|
|
var page PageOf[Starship]
|
|
|
|
|
|
|
|
err := Get("/starships", &page)
|
|
|
|
if err != nil {
|
2022-10-22 01:06:30 -05:00
|
|
|
return nil, err
|
2022-10-21 12:02:03 -05:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Error decoding response body: %+v", err)
|
|
|
|
}
|
2022-10-22 01:06:30 -05:00
|
|
|
for r := range page.Results {
|
|
|
|
results = append(results, &page.Results[r])
|
|
|
|
}
|
2022-10-21 12:02:03 -05:00
|
|
|
|
|
|
|
for page.Next != nil {
|
2022-10-22 01:06:30 -05:00
|
|
|
next := *page.Next
|
|
|
|
page = PageOf[Starship]{}
|
|
|
|
err := Get(next, &page)
|
2022-10-21 12:02:03 -05:00
|
|
|
if err != nil {
|
2022-10-22 01:06:30 -05:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for r := range page.Results {
|
|
|
|
results = append(results, &page.Results[r])
|
2022-10-21 12:02:03 -05:00
|
|
|
}
|
|
|
|
}
|
2022-10-22 01:06:30 -05:00
|
|
|
return results, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetches a [Person].
|
|
|
|
func GetPerson(url string) (*Person, error) {
|
|
|
|
person := Person{}
|
|
|
|
err := Get(url, &person)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
|
|
|
return &person, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retrieves the set of pilot [Person]s for all [Starship]s simultaneously.
|
|
|
|
func GetStarshipsPilots(starships []*Starship) map[*Starship][]*Person {
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
|
|
|
log.Infof("Fetching pilots for %d starships...", len(starships))
|
|
|
|
|
|
|
|
personUrlChan := make(chan string)
|
|
|
|
personChan := make(chan *Person)
|
|
|
|
|
|
|
|
fetchedPersons := make(map[string]bool)
|
|
|
|
persons := make(map[string]*Person)
|
|
|
|
|
|
|
|
result := make(map[*Starship][]*Person, len(starships))
|
|
|
|
|
|
|
|
n := 0
|
|
|
|
for _, s := range starships {
|
|
|
|
for _, url := range s.Pilots {
|
|
|
|
if fetchedPersons[url] {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
fetchedPersons[url] = true
|
|
|
|
wg.Add(1)
|
|
|
|
n += 1
|
|
|
|
go func(url string) {
|
|
|
|
defer wg.Done()
|
|
|
|
person, _ := GetPerson(url)
|
|
|
|
if person != nil {
|
|
|
|
personUrlChan <- url
|
|
|
|
personChan <- person
|
|
|
|
} else {
|
|
|
|
log.Errorf("Failed to fetch person at url %s", url)
|
|
|
|
}
|
|
|
|
}(url)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
go func() {
|
|
|
|
wg.Wait()
|
|
|
|
close(personUrlChan)
|
|
|
|
close(personChan)
|
|
|
|
}()
|
|
|
|
for url := range personUrlChan {
|
|
|
|
persons[url] = <-personChan
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, starship := range starships {
|
|
|
|
pilots := make([]*Person, len(starship.Pilots))
|
|
|
|
for _, url := range starship.Pilots {
|
|
|
|
pilots = append(pilots, persons[url])
|
|
|
|
}
|
|
|
|
result[starship] = pilots
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|