├── .github └── workflows │ └── test.yml ├── README.md ├── client.go ├── client_test.go ├── cmd └── swapi │ ├── .gitignore │ └── main.go ├── film.go ├── go.mod ├── list.go ├── options.go ├── options_test.go ├── person.go ├── planet.go ├── species.go ├── starship.go └── vehicle.go /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Test code 3 | jobs: 4 | Test: 5 | strategy: 6 | matrix: 7 | go-version: ["1.20.x", "1.19.x"] 8 | os: [ubuntu-latest] 9 | runs-on: ${{ matrix.os }} 10 | steps: 11 | - name: Install Go 12 | uses: actions/setup-go@v3 13 | with: 14 | go-version: ${{ matrix.go-version }} 15 | - name: Checkout code 16 | uses: actions/checkout@v3 17 | - name: Test code 18 | run: go test -v ./... 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A [SWAPI](http://swapi.dev) client written in Go 2 | ================================================ 3 | 4 | [![Build status](https://github.com/peterhellberg/swapi/actions/workflows/test.yml/badge.svg)](https://github.com/peterhellberg/swapi/actions/workflows/test.yml) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/peterhellberg/swapi)](https://goreportcard.com/report/github.com/peterhellberg/swapi) 6 | [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/peterhellberg/swapi) 7 | [![License MIT](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](https://github.com/peterhellberg/swapi#license-mit) 8 | 9 | ## Installation 10 | 11 | ```bash 12 | go get -u github.com/peterhellberg/swapi 13 | ``` 14 | 15 | ## Examples 16 | 17 | ### atst.go 18 | 19 | ```go 20 | package main 21 | 22 | import ( 23 | "context" 24 | "fmt" 25 | 26 | "github.com/peterhellberg/swapi" 27 | ) 28 | 29 | func main() { 30 | c := swapi.DefaultClient 31 | 32 | if atst, err := c.Vehicle(context.Background(), 19); err == nil { 33 | fmt.Println("name: ", atst.Name) 34 | fmt.Println("model:", atst.Model) 35 | } 36 | } 37 | ``` 38 | 39 | ## Command line tool 40 | 41 | ### Installation 42 | 43 | ```bash 44 | go install github.com/peterhellberg/swapi/cmd/swapi@latest 45 | ``` 46 | 47 | ### Usage 48 | 49 | ```bash 50 | Commands: 51 | film [id] 52 | person [id] 53 | planet [id] 54 | species [id] 55 | starship [id] 56 | vehicle [id] 57 | films 58 | people 59 | planets 60 | films 61 | species 62 | vehicles 63 | starships 64 | ``` 65 | 66 | ```json 67 | $ swapi planet 1 68 | { 69 | "name": "Tatooine", 70 | "rotation_period": "23", 71 | "orbital_period": "304", 72 | "diameter": "10465", 73 | "climate": "arid", 74 | "gravity": "1 standard", 75 | "terrain": "desert", 76 | "surface_water": "1", 77 | "population": "200000", 78 | "residents": [ 79 | "http://swapi.dev/api/people/1/", 80 | "http://swapi.dev/api/people/2/", 81 | "http://swapi.dev/api/people/4/", 82 | "http://swapi.dev/api/people/6/", 83 | "http://swapi.dev/api/people/7/", 84 | "http://swapi.dev/api/people/8/", 85 | "http://swapi.dev/api/people/9/", 86 | "http://swapi.dev/api/people/11/", 87 | "http://swapi.dev/api/people/43/", 88 | "http://swapi.dev/api/people/62/" 89 | ], 90 | "films": [ 91 | "http://swapi.dev/api/films/1/", 92 | "http://swapi.dev/api/films/3/", 93 | "http://swapi.dev/api/films/4/", 94 | "http://swapi.dev/api/films/5/", 95 | "http://swapi.dev/api/films/6/" 96 | ], 97 | "created": "2014-12-09T13:50:49.641000Z", 98 | "edited": "2014-12-21T20:48:04.175778Z", 99 | "url": "http://swapi.dev/api/planets/1/" 100 | } 101 | ``` 102 | 103 | 104 | 105 | ## License (MIT) 106 | 107 | Copyright (c) 2014-2023 [Peter Hellberg](https://c7.se) 108 | 109 | > Permission is hereby granted, free of charge, to any person obtaining 110 | > a copy of this software and associated documentation files (the "Software"), 111 | > to deal in the Software without restriction, including without limitation 112 | > the rights to use, copy, modify, merge, publish, distribute, sublicense, 113 | > and/or sell copies of the Software, and to permit persons to whom the 114 | > Software is furnished to do so, subject to the following conditions: 115 | > 116 | > The above copyright notice and this permission notice shall be included 117 | > in all copies or substantial portions of the Software. 118 | > 119 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 120 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 121 | > OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 122 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 123 | > DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 124 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 125 | > OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 126 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package swapi 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "net/url" 9 | ) 10 | 11 | const ( 12 | defaultBaseURLScheme = "https" 13 | defaultBaseURLHost = "swapi.dev" 14 | defaultBasePath = "/api/" 15 | defaultUserAgent = "swapi.go" 16 | ) 17 | 18 | // DefaultClient is the default SWAPI client 19 | var DefaultClient = NewClient() 20 | 21 | // A Client communicates with SWAPI 22 | type Client struct { 23 | // baseURL is the base url for SWAPI 24 | baseURL *url.URL 25 | 26 | // basePath is the base path for the endpoints 27 | basePath string 28 | 29 | // User agent used for HTTP requests to SWAPI 30 | userAgent string 31 | 32 | // HTTP client used to communicate with the SWAPI 33 | httpClient *http.Client 34 | } 35 | 36 | // NewClient returns a new SWAPI client. 37 | func NewClient(options ...Option) *Client { 38 | c := &Client{ 39 | baseURL: &url.URL{ 40 | Scheme: defaultBaseURLScheme, 41 | Host: defaultBaseURLHost, 42 | }, 43 | basePath: defaultBasePath, 44 | userAgent: defaultUserAgent, 45 | httpClient: http.DefaultClient, 46 | } 47 | 48 | for _, option := range options { 49 | option(c) 50 | } 51 | 52 | return c 53 | } 54 | 55 | // getRequest creates a GET request based on the provided rawurl 56 | func (c *Client) getRequest(ctx context.Context, rawurl string) (*http.Request, error) { 57 | req, err := http.NewRequest("GET", rawurl, nil) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | req.Header.Add("User-Agent", c.userAgent) 63 | 64 | return req.WithContext(ctx), nil 65 | } 66 | 67 | // newRequest creates an API request. 68 | func (c *Client) newRequest(ctx context.Context, s string) (*http.Request, error) { 69 | rel, err := url.Parse(c.basePath + s) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | q := rel.Query() 75 | q.Set("format", "json") 76 | 77 | rel.RawQuery = q.Encode() 78 | 79 | rawurl := c.baseURL.ResolveReference(rel).String() 80 | 81 | return c.getRequest(ctx, rawurl) 82 | } 83 | 84 | // do sends an API request and returns the API response. The API response is 85 | // decoded and stored in the value pointed to by v, or returned as an error if 86 | // an API error has occurred. 87 | func (c *Client) do(req *http.Request, v interface{}) (*http.Response, error) { 88 | // Make sure to close the connection after replying to this request 89 | req.Close = true 90 | 91 | resp, err := c.httpClient.Do(req) 92 | if err != nil { 93 | return nil, err 94 | } 95 | defer resp.Body.Close() 96 | 97 | if v != nil { 98 | err = json.NewDecoder(resp.Body).Decode(v) 99 | } 100 | 101 | if err != nil { 102 | return nil, fmt.Errorf("error reading response from %s %s: %s", req.Method, req.URL.RequestURI(), err) 103 | } 104 | 105 | return resp, nil 106 | } 107 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package swapi 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func TestNewClient(t *testing.T) { 12 | userAgent := "test-agent" 13 | 14 | c := NewClient(UserAgent(userAgent)) 15 | 16 | if got, want := c.userAgent, userAgent; got != want { 17 | t.Fatalf("c.userAgent = %q, want %q", got, want) 18 | } 19 | } 20 | 21 | func TestClientNewRequest(t *testing.T) { 22 | ctx := context.Background() 23 | 24 | c := NewClient() 25 | 26 | r, err := c.newRequest(ctx, "test") 27 | if err != nil { 28 | t.Fatalf("unexpected error: %v", err) 29 | } 30 | 31 | if got, want := r.UserAgent(), defaultUserAgent; got != want { 32 | t.Fatalf("r.UserAgent() = %q, want %q", got, want) 33 | } 34 | 35 | if got, want := r.URL.String(), "https://swapi.dev/api/test?format=json"; got != want { 36 | t.Fatalf("r.URL.String() = %q, want %q", got, want) 37 | } 38 | } 39 | 40 | func TestClientDo(t *testing.T) { 41 | hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 42 | json.NewEncoder(w).Encode(Film{Title: "Example"}) 43 | }) 44 | 45 | ts := httptest.NewServer(hf) 46 | defer ts.Close() 47 | 48 | ctx := context.Background() 49 | 50 | c := NewClient(BaseURL(ts.URL)) 51 | 52 | r, err := c.newRequest(ctx, "test") 53 | if err != nil { 54 | t.Fatalf("unexpected error: %v", err) 55 | } 56 | 57 | var v Film 58 | 59 | if _, err := c.do(r, &v); err != nil { 60 | t.Fatalf("unexpected error: %v", err) 61 | } 62 | 63 | if got, want := v.Title, "Example"; got != want { 64 | t.Fatalf("v.Title = %q, want %q", got, want) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /cmd/swapi/.gitignore: -------------------------------------------------------------------------------- 1 | swapi 2 | -------------------------------------------------------------------------------- /cmd/swapi/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | "strconv" 9 | "strings" 10 | "time" 11 | 12 | "github.com/peterhellberg/swapi" 13 | ) 14 | 15 | func main() { 16 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 17 | defer cancel() 18 | 19 | c := swapi.DefaultClient 20 | 21 | if len(os.Args) < 2 { 22 | usage() 23 | return 24 | } 25 | 26 | command := os.Args[1] 27 | 28 | if len(os.Args) > 2 { 29 | id, err := strconv.Atoi(os.Args[2]) 30 | if err != nil { 31 | return 32 | } 33 | 34 | switch command { 35 | case "film": 36 | dump(c.Film(ctx, id)) 37 | case "person": 38 | dump(c.Person(ctx, id)) 39 | case "planet": 40 | dump(c.Planet(ctx, id)) 41 | case "species": 42 | dump(c.Species(ctx, id)) 43 | case "starship": 44 | dump(c.Starship(ctx, id)) 45 | case "vehicle": 46 | dump(c.Vehicle(ctx, id)) 47 | } 48 | } else { 49 | switch command { 50 | case "people": 51 | dump(c.AllPeople(ctx)) 52 | case "planets": 53 | dump(c.AllPlanets(ctx)) 54 | case "films": 55 | dump(c.AllFilms(ctx)) 56 | case "species": 57 | dump(c.AllSpecies(ctx)) 58 | case "vehicles": 59 | dump(c.AllVehicles(ctx)) 60 | case "starships": 61 | dump(c.AllStarships(ctx)) 62 | } 63 | } 64 | } 65 | 66 | func usage() { 67 | fmt.Println(strings.Join([]string{ 68 | "Commands:", 69 | "film [id]", 70 | "person [id]", 71 | "planet [id]", 72 | "species [id]", 73 | "starship [id]", 74 | "vehicle [id]", 75 | "films", 76 | "people", 77 | "planets", 78 | "films", 79 | "species", 80 | "vehicles", 81 | "starships", 82 | }, "\n\t")) 83 | } 84 | 85 | func dump(data interface{}, err error) { 86 | if err != nil { 87 | enc := json.NewEncoder(os.Stderr) 88 | enc.SetIndent("", " ") 89 | enc.Encode(map[string]string{ 90 | "error": err.Error(), 91 | }) 92 | 93 | return 94 | } 95 | 96 | if j, err := json.MarshalIndent(data, "", " "); err == nil { 97 | fmt.Printf("%s\n", j) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /film.go: -------------------------------------------------------------------------------- 1 | package swapi 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | // A Film is an single film. 9 | type Film struct { 10 | Title string `json:"title"` 11 | EpisodeID int `json:"episode_id"` 12 | OpeningCrawl string `json:"opening_crawl"` 13 | Director string `json:"director"` 14 | Producer string `json:"producer"` 15 | CharacterURLs []string `json:"characters"` 16 | PlanetURLs []string `json:"planets"` 17 | StarshipURLs []string `json:"starships"` 18 | VehicleURLs []string `json:"vehicles"` 19 | SpeciesURLs []string `json:"species"` 20 | Created string `json:"created"` 21 | Edited string `json:"edited"` 22 | URL string `json:"url"` 23 | } 24 | 25 | // Film retrieves the film with the given id 26 | func (c *Client) Film(ctx context.Context, id int) (Film, error) { 27 | req, err := c.newRequest(ctx, fmt.Sprintf("films/%d", id)) 28 | if err != nil { 29 | return Film{}, err 30 | } 31 | 32 | var film Film 33 | 34 | if _, err = c.do(req, &film); err != nil { 35 | return Film{}, err 36 | } 37 | 38 | return film, nil 39 | } 40 | 41 | func (c *Client) AllFilms(ctx context.Context) ([]Film, error) { 42 | var films []Film 43 | 44 | req, err := c.newRequest(ctx, "films/") 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | for { 50 | var list List[Film] 51 | 52 | if _, err = c.do(req, &list); err != nil { 53 | return nil, err 54 | } 55 | 56 | films = append(films, list.Results...) 57 | 58 | if list.Next == nil { 59 | break 60 | } 61 | 62 | req, err = c.getRequest(ctx, *list.Next) 63 | if err != nil { 64 | return nil, err 65 | } 66 | } 67 | 68 | return films, nil 69 | } 70 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/peterhellberg/swapi 2 | 3 | go 1.20 4 | -------------------------------------------------------------------------------- /list.go: -------------------------------------------------------------------------------- 1 | package swapi 2 | 3 | type Entity interface { 4 | Film | Person | Planet | Species | Starship | Vehicle 5 | } 6 | 7 | type List[E Entity] struct { 8 | Count int 9 | Next *string 10 | Results []E 11 | } 12 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package swapi 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | ) 7 | 8 | type Option func(*Client) 9 | 10 | // HTTPClient to use by the client 11 | func HTTPClient(hc *http.Client) Option { 12 | return func(c *Client) { 13 | c.httpClient = hc 14 | } 15 | } 16 | 17 | // BaseURL for the client parsed from provided rawurl 18 | func BaseURL(rawurl string) Option { 19 | return func(c *Client) { 20 | if baseURL, err := url.Parse(rawurl); err == nil { 21 | c.baseURL = baseURL 22 | } 23 | } 24 | } 25 | 26 | // UserAgent to use by the client 27 | func UserAgent(ua string) Option { 28 | return func(c *Client) { 29 | c.userAgent = ua 30 | } 31 | } 32 | 33 | // FromEnv client configuration 34 | func FromEnv(getenv func(string) string) Option { 35 | return func(c *Client) { 36 | env := func(key, fallback string) string { 37 | if v := getenv(key); v != "" { 38 | return v 39 | } 40 | 41 | return fallback 42 | } 43 | 44 | c.baseURL = &url.URL{ 45 | Scheme: env("SWAPI_BASE_URL_SCHEME", defaultBaseURLScheme), 46 | Host: env("SWAPI_BASE_URL_HOST", defaultBaseURLHost), 47 | } 48 | 49 | c.basePath = env("SWAPI_BASE_PATH", defaultBasePath) 50 | 51 | c.userAgent = env("SWAPI_USER_AGENT", defaultUserAgent) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /options_test.go: -------------------------------------------------------------------------------- 1 | package swapi 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | ) 7 | 8 | func TestOptions(t *testing.T) { 9 | t.Run("HTTPClient", func(t *testing.T) { 10 | c := &Client{} 11 | 12 | HTTPClient(http.DefaultClient)(c) 13 | 14 | if c.httpClient == nil { 15 | t.Fatalf("c.httpClient not set") 16 | } 17 | }) 18 | 19 | t.Run("FromEnv", func(t *testing.T) { 20 | c := &Client{} 21 | 22 | t.Run("fallback", func(t *testing.T) { 23 | FromEnv(func(string) string { return "" })(c) 24 | 25 | if got, want := c.baseURL.Scheme, defaultBaseURLScheme; got != want { 26 | t.Fatalf("c.baseURL.Scheme = %q, want %q", got, want) 27 | } 28 | 29 | if got, want := c.baseURL.Host, defaultBaseURLHost; got != want { 30 | t.Fatalf("c.baseURL.Host = %q, want %q", got, want) 31 | } 32 | 33 | if got, want := c.basePath, defaultBasePath; got != want { 34 | t.Fatalf("c.basePath = %q, want %q", got, want) 35 | } 36 | 37 | if got, want := c.userAgent, defaultUserAgent; got != want { 38 | t.Fatalf("c.userAgent = %q, want %q", got, want) 39 | } 40 | }) 41 | 42 | t.Run("with values", func(t *testing.T) { 43 | scheme, host, path, userAgent := "http", "example.com", "/path/", "user-agent" 44 | 45 | FromEnv(func(key string) string { 46 | switch key { 47 | case "SWAPI_BASE_URL_SCHEME": 48 | return scheme 49 | case "SWAPI_BASE_URL_HOST": 50 | return host 51 | case "SWAPI_BASE_PATH": 52 | return path 53 | case "SWAPI_USER_AGENT": 54 | return userAgent 55 | default: 56 | return "" 57 | } 58 | })(c) 59 | 60 | if got, want := c.baseURL.Scheme, scheme; got != want { 61 | t.Fatalf("c.baseURL.Scheme = %q, want %q", got, want) 62 | } 63 | 64 | if got, want := c.baseURL.Host, host; got != want { 65 | t.Fatalf("c.baseURL.Host = %q, want %q", got, want) 66 | } 67 | 68 | if got, want := c.basePath, path; got != want { 69 | t.Fatalf("c.basePath = %q, want %q", got, want) 70 | } 71 | 72 | if got, want := c.userAgent, userAgent; got != want { 73 | t.Fatalf("c.userAgent = %q, want %q", got, want) 74 | } 75 | }) 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /person.go: -------------------------------------------------------------------------------- 1 | package swapi 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | // A Person is an individual person or character within the Star Wars universe. 9 | type Person struct { 10 | Name string `json:"name"` 11 | Height string `json:"height"` 12 | Mass string `json:"mass"` 13 | HairColor string `json:"hair_color"` 14 | SkinColor string `json:"skin_color"` 15 | EyeColor string `json:"eye_color"` 16 | BirthYear string `json:"birth_year"` 17 | Gender string `json:"gender"` 18 | Homeworld string `json:"homeworld"` 19 | FilmURLs []string `json:"films"` 20 | SpeciesURLs []string `json:"species"` 21 | VehicleURLs []string `json:"vehicles"` 22 | StarshipURLs []string `json:"starships"` 23 | Created string `json:"created"` 24 | Edited string `json:"edited"` 25 | URL string `json:"url"` 26 | } 27 | 28 | // Person retrieves the person with the given id 29 | func (c *Client) Person(ctx context.Context, id int) (Person, error) { 30 | req, err := c.newRequest(ctx, fmt.Sprintf("people/%d", id)) 31 | if err != nil { 32 | return Person{}, err 33 | } 34 | 35 | var person Person 36 | 37 | if _, err = c.do(req, &person); err != nil { 38 | return Person{}, err 39 | } 40 | 41 | return person, nil 42 | } 43 | 44 | func (c *Client) AllPeople(ctx context.Context) ([]Person, error) { 45 | var people []Person 46 | 47 | req, err := c.newRequest(ctx, "people/") 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | for { 53 | var list List[Person] 54 | 55 | if _, err = c.do(req, &list); err != nil { 56 | return nil, err 57 | } 58 | 59 | people = append(people, list.Results...) 60 | 61 | if list.Next == nil { 62 | break 63 | } 64 | 65 | req, err = c.getRequest(ctx, *list.Next) 66 | if err != nil { 67 | return nil, err 68 | } 69 | } 70 | 71 | return people, nil 72 | } 73 | -------------------------------------------------------------------------------- /planet.go: -------------------------------------------------------------------------------- 1 | package swapi 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | // A Planet is a large mass, planet or planetoid in the Star Wars Universe, at the time of 0 ABY. 9 | type Planet struct { 10 | Name string `json:"name"` 11 | RotationPeriod string `json:"rotation_period"` 12 | OrbitalPeriod string `json:"orbital_period"` 13 | Diameter string `json:"diameter"` 14 | Climate string `json:"climate"` 15 | Gravity string `json:"gravity"` 16 | Terrain string `json:"terrain"` 17 | SurfaceWater string `json:"surface_water"` 18 | Population string `json:"population"` 19 | ResidentURLs []string `json:"residents"` 20 | FilmURLs []string `json:"films"` 21 | Created string `json:"created"` 22 | Edited string `json:"edited"` 23 | URL string `json:"url"` 24 | } 25 | 26 | // Planet retrieves the planet with the given id 27 | func (c *Client) Planet(ctx context.Context, id int) (Planet, error) { 28 | req, err := c.newRequest(ctx, fmt.Sprintf("planets/%d", id)) 29 | if err != nil { 30 | return Planet{}, err 31 | } 32 | 33 | var planet Planet 34 | 35 | if _, err = c.do(req, &planet); err != nil { 36 | return Planet{}, err 37 | } 38 | 39 | return planet, nil 40 | } 41 | 42 | func (c *Client) AllPlanets(ctx context.Context) ([]Planet, error) { 43 | var planets []Planet 44 | 45 | req, err := c.newRequest(ctx, "planets/") 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | for { 51 | var list List[Planet] 52 | 53 | if _, err = c.do(req, &list); err != nil { 54 | return nil, err 55 | } 56 | 57 | planets = append(planets, list.Results...) 58 | 59 | if list.Next == nil { 60 | break 61 | } 62 | 63 | req, err = c.getRequest(ctx, *list.Next) 64 | if err != nil { 65 | return nil, err 66 | } 67 | } 68 | 69 | return planets, nil 70 | } 71 | -------------------------------------------------------------------------------- /species.go: -------------------------------------------------------------------------------- 1 | package swapi 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | // A Species is a type of person or character within the Star Wars Universe. 9 | type Species struct { 10 | Name string `json:"name"` 11 | Classification string `json:"classification"` 12 | Designation string `json:"designation"` 13 | AverageHeight string `json:"average_height"` 14 | SkinColors string `json:"skin_colors"` 15 | HairColors string `json:"hair_colors"` 16 | EyeColors string `json:"eye_colors"` 17 | AverageLifespan string `json:"average_lifespan"` 18 | Homeworld string `json:"homeworld"` 19 | Language string `json:"language"` 20 | PeopleURLs []string `json:"people"` 21 | FilmURLs []string `json:"films"` 22 | Created string `json:"created"` 23 | Edited string `json:"edited"` 24 | URL string `json:"url"` 25 | } 26 | 27 | // Species retrieves the species with the given id 28 | func (c *Client) Species(ctx context.Context, id int) (Species, error) { 29 | req, err := c.newRequest(ctx, fmt.Sprintf("species/%d", id)) 30 | if err != nil { 31 | return Species{}, err 32 | } 33 | 34 | var species Species 35 | 36 | if _, err = c.do(req, &species); err != nil { 37 | return Species{}, err 38 | } 39 | 40 | return species, nil 41 | } 42 | 43 | func (c *Client) AllSpecies(ctx context.Context) ([]Species, error) { 44 | var species []Species 45 | 46 | req, err := c.newRequest(ctx, "species/") 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | for { 52 | var list List[Species] 53 | 54 | if _, err = c.do(req, &list); err != nil { 55 | return nil, err 56 | } 57 | 58 | species = append(species, list.Results...) 59 | 60 | if list.Next == nil { 61 | break 62 | } 63 | 64 | req, err = c.getRequest(ctx, *list.Next) 65 | if err != nil { 66 | return nil, err 67 | } 68 | } 69 | 70 | return species, nil 71 | } 72 | -------------------------------------------------------------------------------- /starship.go: -------------------------------------------------------------------------------- 1 | package swapi 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | // A Starship is a single transport craft that has hyperdrive capability. 9 | type Starship struct { 10 | Name string `json:"name"` 11 | Model string `json:"model"` 12 | Manufacturer string `json:"manufacturer"` 13 | CostInCredits string `json:"cost_in_credits"` 14 | Length string `json:"length"` 15 | MaxAtmospheringSpeed string `json:"max_atmosphering_speed"` 16 | Crew string `json:"crew"` 17 | Passengers string `json:"passengers"` 18 | CargoCapacity string `json:"cargo_capacity"` 19 | Consumables string `json:"consumables"` 20 | HyperdriveRating string `json:"hyperdrive_rating"` 21 | MGLT string `json:"MGLT"` 22 | StarshipClass string `json:"starship_class"` 23 | PilotURLs []string `json:"pilots"` 24 | FilmURLs []string `json:"films"` 25 | Created string `json:"created"` 26 | Edited string `json:"edited"` 27 | URL string `json:"url"` 28 | } 29 | 30 | // Starship retrieves the starship with the given id 31 | func (c *Client) Starship(ctx context.Context, id int) (Starship, error) { 32 | req, err := c.newRequest(ctx, fmt.Sprintf("starships/%d", id)) 33 | if err != nil { 34 | return Starship{}, err 35 | } 36 | 37 | var starship Starship 38 | 39 | if _, err = c.do(req, &starship); err != nil { 40 | return Starship{}, err 41 | } 42 | 43 | return starship, nil 44 | } 45 | 46 | func (c *Client) AllStarships(ctx context.Context) ([]Starship, error) { 47 | var starships []Starship 48 | 49 | req, err := c.newRequest(ctx, "starships/") 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | for { 55 | var list List[Starship] 56 | 57 | if _, err = c.do(req, &list); err != nil { 58 | return nil, err 59 | } 60 | 61 | starships = append(starships, list.Results...) 62 | 63 | if list.Next == nil { 64 | break 65 | } 66 | 67 | req, err = c.getRequest(ctx, *list.Next) 68 | if err != nil { 69 | return nil, err 70 | } 71 | } 72 | 73 | return starships, nil 74 | } 75 | -------------------------------------------------------------------------------- /vehicle.go: -------------------------------------------------------------------------------- 1 | package swapi 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | // A Vehicle is a single transport craft that does not have hyperdrive capability. 9 | type Vehicle struct { 10 | Name string `json:"name"` 11 | Model string `json:"model"` 12 | Manufacturer string `json:"manufacturer"` 13 | CostInCredits string `json:"cost_in_credits"` 14 | Length string `json:"length"` 15 | MaxAtmospheringSpeed string `json:"max_atmosphering_speed"` 16 | Crew string `json:"crew"` 17 | Passengers string `json:"passengers"` 18 | CargoCapacity string `json:"cargo_capacity"` 19 | Consumables string `json:"consumables"` 20 | VehicleClass string `json:"vehicle_class"` 21 | PilotURLs []string `json:"pilots"` 22 | FilmURLs []string `json:"films"` 23 | Created string `json:"created"` 24 | Edited string `json:"edited"` 25 | URL string `json:"url"` 26 | } 27 | 28 | // Vehicle retrieves the vehicle with the given id 29 | func (c *Client) Vehicle(ctx context.Context, id int) (Vehicle, error) { 30 | req, err := c.newRequest(ctx, fmt.Sprintf("vehicles/%d", id)) 31 | if err != nil { 32 | return Vehicle{}, err 33 | } 34 | 35 | var vehicle Vehicle 36 | 37 | if _, err = c.do(req, &vehicle); err != nil { 38 | return Vehicle{}, err 39 | } 40 | 41 | return vehicle, nil 42 | } 43 | 44 | func (c *Client) AllVehicles(ctx context.Context) ([]Vehicle, error) { 45 | var vehicles []Vehicle 46 | 47 | req, err := c.newRequest(ctx, "vehicles/") 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | for { 53 | var list List[Vehicle] 54 | 55 | if _, err = c.do(req, &list); err != nil { 56 | return nil, err 57 | } 58 | 59 | vehicles = append(vehicles, list.Results...) 60 | 61 | if list.Next == nil { 62 | break 63 | } 64 | 65 | req, err = c.getRequest(ctx, *list.Next) 66 | if err != nil { 67 | return nil, err 68 | } 69 | } 70 | 71 | return vehicles, nil 72 | } 73 | --------------------------------------------------------------------------------