(ctx context.Context, method, endpoint string, body any, fieldMask string, out any)
| 138 | } |
| 139 | |
| 140 | func (c *PlacesClient) doJSON(ctx context.Context, method, endpoint string, body any, fieldMask string, out any) error { |
| 141 | if strings.TrimSpace(c.apiKey) == "" { |
| 142 | return errMissingPlacesAPIKey |
| 143 | } |
| 144 | |
| 145 | var reader io.Reader |
| 146 | |
| 147 | if body != nil { |
| 148 | b, err := json.Marshal(body) |
| 149 | if err != nil { |
| 150 | return fmt.Errorf("encode Places API request: %w", err) |
| 151 | } |
| 152 | reader = bytes.NewReader(b) |
| 153 | } |
| 154 | |
| 155 | req, err := http.NewRequestWithContext(ctx, method, endpoint, reader) |
| 156 | if err != nil { |
| 157 | return fmt.Errorf("build Places API request: %w", err) |
| 158 | } |
| 159 | |
| 160 | req.Header.Set("X-Goog-Api-Key", c.apiKey) |
| 161 | req.Header.Set("X-Goog-FieldMask", fieldMask) |
| 162 | |
| 163 | if body != nil { |
| 164 | req.Header.Set("Content-Type", "application/json") |
| 165 | } |
| 166 | |
| 167 | resp, err := c.client.Do(req) |
| 168 | if err != nil { |
| 169 | return fmt.Errorf("send Places API request: %w", err) |
| 170 | } |
| 171 | defer resp.Body.Close() |
| 172 | |
| 173 | respBody, err := io.ReadAll(io.LimitReader(resp.Body, 2<<20)) |
| 174 | if err != nil { |
| 175 | return fmt.Errorf("read Places API response: %w", err) |
| 176 | } |
| 177 | |
| 178 | if resp.StatusCode < 200 || resp.StatusCode >= 300 { |
| 179 | return placesAPIError(resp.StatusCode, respBody) |
| 180 | } |
| 181 | |
| 182 | if err := json.Unmarshal(respBody, out); err != nil { |
| 183 | return fmt.Errorf("decode Places API response: %w", err) |
| 184 | } |
| 185 | |
| 186 | return nil |
| 187 | } |
| 188 | |
| 189 | //nolint:tagliatelle // Google Places API uses lowerCamelCase JSON fields. |
| 190 | type placeResponse struct { |
no test coverage detected