package main import ( "encoding/json" log "github.com/sirupsen/logrus" "net/http" "sync" ) // If you're using this module and wondering why it's slow, it's the API, not // this code. 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 } // Represents a single page of data as returned by swapi type PageOf[T interface{}] struct { Count uint Next *string Results []T // Previous *string } // Used for retrieving data from swapi.dev, decoding the unmarshalling the JSON // body, and storing the result in the provided struct reference. // // myData := interface{} // err := Get("/starships", &myData) // // If [path] does not start with "/", it assumes you have provided a full URL to // make following [PageOf]'s Next and Previous simpler. 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 } // 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) var page PageOf[Starship] err := Get("/starships", &page) if err != nil { return nil, err } if err != nil { log.Errorf("Error decoding response body: %+v", err) } for r := range page.Results { results = append(results, &page.Results[r]) } for page.Next != nil { next := *page.Next page = PageOf[Starship]{} err := Get(next, &page) if err != nil { return nil, err } for r := range page.Results { results = append(results, &page.Results[r]) } } 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 } // Retrieves all the pilots for a given [Starship] simultaneously func GetPilots(starship *Starship) []*Person { var wg sync.WaitGroup c := make(chan *Person) for i := range starship.Pilots { wg.Add(1) go func(url string) { person, _ := GetPerson(url) if person != nil { c <- person } }(starship.Pilots[i]) } go func() { wg.Wait() close(c) }() var result []*Person for r := range c { result = append(result, r) } return result }