<commit_before>// Package geo is a generic framework to develop geocode/reverse geocode clients
package geo

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// ErrTimeout occurs when no response returned within timeoutInSeconds
var ErrTimeout = errors.New("TIMEOUT")
var timeoutInSeconds = time.Second * 8

// ErrNoResult occurs when no result returned
var ErrNoResult = errors.New("NO_RESULT")

// Location is the output of Geocode
type Location struct {
	Lat, Lng float64
}

// EndpointBuilder defines functions that build urls for geocode/reverse geocode
type EndpointBuilder interface {
	GeocodeURL(string) string
	ReverseGeocodeURL(Location) string
}

// ResponseParser defines functions that parse response of geocode/reverse geocode
type ResponseParser interface {
	Location() Location
	Address() string
	ResponseObject() ResponseParser
}

// Geocoder has EndpointBuilder and ResponseParser
type Geocoder struct {
	EndpointBuilder
	ResponseParser
}

// Geocode returns location for address
func (g Geocoder) Geocode(address string) (Location, error) {
	ch := make(chan Location)
	go func() {
		response(g.GeocodeURL(url.QueryEscape(address)), g.ResponseObject())
		ch <- g.Location()
	}()

	select {
	case location := <-ch:
		return location, anyError(location)
	case <-time.After(timeoutInSeconds):
		return Location{}, ErrTimeout
	}
}

// ReverseGeocode returns address for location
func (g Geocoder) ReverseGeocode(lat, lng float64) (string, error) {
	ch := make(chan string)
	go func() {
		response(g.ReverseGeocodeURL(Location{lat, lng}), g.ResponseObject())
		ch <- g.Address()
	}()

	select {
	case address := <-ch:
		return address, anyError(address)
	case <-time.After(timeoutInSeconds):
		return "", ErrTimeout
	}
}

// Response gets response from url
func response(url string, obj ResponseParser) {
	if req, err := http.NewRequest("GET", url, nil); err == nil {
		if resp, err := (&http.Client{}).Do(req); err == nil {
			defer resp.Body.Close()
			if data, err := ioutil.ReadAll(resp.Body); err == nil {
				json.Unmarshal([]byte(strings.Trim(string(data), " []")), obj)
			}
		}
	}
}

func anyError(v interface{}) (err error) {
	switch v := v.(type) {
	case Location:
		if v.Lat == 0 && v.Lng == 0 {
			return ErrNoResult
		}
	case string:
		if v == "" {
			return ErrNoResult
		}
	}
	return
}
<commit_msg>The channel ch needs a buffer size of 1. If it were an unbuffered channel and the response took more than timeoutInSeconds, the channel send statement [e.g. ch <- g.Location()] would block forever as there is no receiver anymore. The sending goroutine would never be destroyed by the garbage collector. See: https://github.com/golang/go/wiki/Timeouts<commit_after>// Package geo is a generic framework to develop geocode/reverse geocode clients
package geo

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// ErrTimeout occurs when no response returned within timeoutInSeconds
var ErrTimeout = errors.New("TIMEOUT")
var timeoutInSeconds = time.Second * 8

// ErrNoResult occurs when no result returned
var ErrNoResult = errors.New("NO_RESULT")

// Location is the output of Geocode
type Location struct {
	Lat, Lng float64
}

// EndpointBuilder defines functions that build urls for geocode/reverse geocode
type EndpointBuilder interface {
	GeocodeURL(string) string
	ReverseGeocodeURL(Location) string
}

// ResponseParser defines functions that parse response of geocode/reverse geocode
type ResponseParser interface {
	Location() Location
	Address() string
	ResponseObject() ResponseParser
}

// Geocoder has EndpointBuilder and ResponseParser
type Geocoder struct {
	EndpointBuilder
	ResponseParser
}

// Geocode returns location for address
func (g Geocoder) Geocode(address string) (Location, error) {
	ch := make(chan Location, 1)
	go func() {
		response(g.GeocodeURL(url.QueryEscape(address)), g.ResponseObject())
		ch <- g.Location()
	}()

	select {
	case location := <-ch:
		return location, anyError(location)
	case <-time.After(timeoutInSeconds):
		return Location{}, ErrTimeout
	}
}

// ReverseGeocode returns address for location
func (g Geocoder) ReverseGeocode(lat, lng float64) (string, error) {
	ch := make(chan string, 1)
	go func() {
		response(g.ReverseGeocodeURL(Location{lat, lng}), g.ResponseObject())
		ch <- g.Address()
	}()

	select {
	case address := <-ch:
		return address, anyError(address)
	case <-time.After(timeoutInSeconds):
		return "", ErrTimeout
	}
}

// Response gets response from url
func response(url string, obj ResponseParser) {
	if req, err := http.NewRequest("GET", url, nil); err == nil {
		if resp, err := (&http.Client{}).Do(req); err == nil {
			defer resp.Body.Close()
			if data, err := ioutil.ReadAll(resp.Body); err == nil {
				json.Unmarshal([]byte(strings.Trim(string(data), " []")), obj)
			}
		}
	}
}

func anyError(v interface{}) (err error) {
	switch v := v.(type) {
	case Location:
		if v.Lat == 0 && v.Lng == 0 {
			return ErrNoResult
		}
	case string:
		if v == "" {
			return ErrNoResult
		}
	}
	return
}

{'fraction_non_alphanumeric': 0.12069982307843523, 'fraction_numerical': 0.0033418517790446234, 'mean_word_length': 4.234567901234568, 'pattern_counts': {'":': 0, '<': 16, '<?xml version=': 0, '>': 3, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6662749', 'n_tokens_mistral': 1722, 'n_tokens_neox': 1620, 'n_words': 506}