I wrote a program that concurrently reads multiple files and makes a request to an API for each line of each file. The JSON response is then parsed and the "available_filters" field is extracted. The program then evaluates if each "filters" field inside "available_filters" contains numbers in its name. Each numeric filter is then written to a single file.
I'm new to go, so my main concern is: am I using concurrently correctly/ to its full potential? How could I improve the handling of my goroutines? (in particular: having two wait groups in main() feels a little wonky to me). And any general tips you could give me.
Thanks!
Here's the code:
package main
import (
"bufio"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"net/http"
"os"
"regexp"
"strings"
"sync"
)
var filterBlacklist = map[string]bool{
"blacklistedName1": true,
"blacklistedName2": true,
"blacklistedName3": true,
"blacklistedName4": true,
}
type AvailableFilters struct {
Available_filters []Filter
}
type Filter struct {
ID string
Name string
Type string
Values []Value
}
type Value struct {
ID string
Name string
Results int64
}
func (f *Filter) toString() string {
var ret string = fmt.Sprintf("%s {\n", f.Name)
for _, value := range f.Values {
ret += fmt.Sprintf("\t%s\n", value.Name)
}
ret += "}"
return ret
}
func check(e error) {
if e != nil {
panic(e)
}
}
func readFileAndRequest(filepath string, c chan<- string) {
file, err := os.Open(filepath)
check(err)
defer file.Close()
scanner := bufio.NewScanner(file)
var wg sync.WaitGroup
for scanner.Scan() {
var line string = scanner.Text()
var url string = fmt.Sprintf("https://apirequestmock?q=%s", line)
wg.Add(1)
go func(url string, c chan<- string) {
writeResponse(url, c)
wg.Done()
}(url, c)
}
wg.Wait()
}
func writeResponse(url string, c chan<- string) {
var filters = new(AvailableFilters)
getJSON(url, &filters)
var wg sync.WaitGroup
for _, filter := range filters.Available_filters {
wg.Add(1)
go func(filter Filter, c chan<- string) {
if isNumericFilter(filter) {
c <- filter.toString()
}
wg.Done()
}(filter, c)
}
wg.Wait()
}
func writeToFile(c <-chan string, outputpath string, wg *sync.WaitGroup) {
output, err := os.Create(outputpath)
check(err)
defer output.Close()
w := bufio.NewWriter(output)
for v := range c {
_, err := w.WriteString(v + "\n")
check(err)
}
fmt.Println("Writing output to file ", output.Name())
check(w.Flush())
wg.Done()
return
}
func getJSON(url string, target interface{}) error {
r, err := http.Get(url)
check(err)
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
check(err)
return json.Unmarshal(body, target)
}
func isNumericFilter(filter Filter) bool {
if filterBlacklist[strings.ToLower(filter.Name)] {
return false
}
var hasNumber bool = true
var valuesLen int = len(filter.Values)
for i, v := range filter.Values {
has, err := regexp.MatchString("^.*[0-9]+.*$", v.Name)
check(err)
hasNumber = hasNumber && (has || (valuesLen > 1 && i == (valuesLen-1)))
}
return hasNumber
}
func main() {
path := flag.String("path", ".", "Path to files.")
outputpath := flag.String("output", "output", "Output filepath")
flag.Parse()
fileNames := flag.Args()
var c chan string = make(chan string)
var wait sync.WaitGroup
wait.Add(1)
go writeToFile(c, *outputpath, &wait)
var wg sync.WaitGroup
for _, f := range fileNames {
wg.Add(1)
go func(f string, path string, c chan string) {
var file string = fmt.Sprintf("%s/%s", path, f)
readFileAndRequest(file, c)
wg.Done()
}(f, *path, c)
}
wg.Wait()
close(c)
wait.Wait()
}
Here's a mock of the an input file:
QUERY1
QUERY2
QUERY3
Here's a mock of the api response:
{
"field1": "value",
"field2": "value",
"available_filters": [
{
"id": "id value",
"name": "name value",
"type": "text",
"values": [
{
"id": "filter id1",
"name": "11 GB",
"results": 1674
},
{
"id": "filter id2",
"name": "40 GB",
"results": 333
},
{
"id": "filter id3",
"name": "3 GB",
"results": 23
},
{
"id": "filter id3",
"name": "Other values",
"results": 23
},
]
},
{
"id": "id value",
"name": "name value",
"type": "text",
"values": [
{
"id": "filter id1",
"name": "23 miles",
"results": 1674
},
{
"id": "filter id2",
"name": "1 mile",
"results": 333
}
]
}
]
}