glaciers-in-archives/snowman

View on GitHub
internal/sparql/sparql.go

Summary

Maintainability
A
1 hr
Test Coverage
package sparql

import (
    "bytes"
    "encoding/json"
    "errors"
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "net/url"
    "strconv"
    "strings"

    "github.com/glaciers-in-archives/snowman/internal/cache"
    "github.com/glaciers-in-archives/snowman/internal/config"
    "github.com/knakk/rdf"
    "github.com/spf13/cast"
)

type Repository struct {
    client       config.ClientConfig
    httpClient   *http.Client
    verbose      bool
    CacheManager *cache.CacheManager
    QueryIndex   map[string]string
}

var CurrentRepository Repository

func NewRepository(cacheStrategy string, queryIndex map[string]string, verbose bool) error {
    repo := Repository{
        client:     config.CurrentSiteConfig.Client,
        QueryIndex: queryIndex,
        verbose:    verbose,
    }
    repo.httpClient = http.DefaultClient

    cm, err := cache.NewCacheManager(cacheStrategy)
    if err != nil {
        return errors.New("Failed to initiate cache handler. " + " Error: " + err.Error())
    }

    repo.CacheManager = cm

    CurrentRepository = repo

    return nil
}

func (r *Repository) QueryCall(query string) (*string, error) {
    form := url.Values{}
    form.Set("query", query)
    b := form.Encode()

    req, err := http.NewRequest("POST", r.client.Endpoint, bytes.NewBufferString(b))
    if err != nil {
        return nil, err
    }

    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    req.Header.Set("Content-Length", strconv.Itoa(len(b)))
    req.Header.Set("Accept", "application/sparql-results+json")

    for header, content := range r.client.Headers {
        req.Header.Set(header, content)
    }

    resp, err := r.httpClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    bodyBytes, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    responseString := string(bodyBytes)

    if resp.StatusCode != http.StatusOK {
        fmt.Println("Received bad(HTTP: " + resp.Status + ") response from SPARQL endpoint:")
        fmt.Println(responseString)
        return nil, errors.New("Received bad response from SPARQL endpoint")
    }

    return &responseString, nil
}

func (r *Repository) Query(queryLocation string, arguments ...interface{}) ([]map[string]rdf.Term, error) {
    query, exists := r.QueryIndex[queryLocation] // QueryIndex includes query/, wanted or not? not?
    if !exists {
        return nil, errors.New("The given query could not be found. " + queryLocation)
    }

    if len(arguments) > 0 {
        for _, argument := range arguments {
            argument := cast.ToString(argument)
            query = strings.Replace(query, "{{.}}", argument, 1)
        }
    }

    if r.verbose {
        if len(arguments) > 0 {
            promt := fmt.Sprintf("Issuing parameterized query %v with arguments: %v.", queryLocation, arguments)
            fmt.Println(promt)
        } else {
            fmt.Println("Issuing query: " + queryLocation)
        }
    }

    file, err := r.CacheManager.GetCache(queryLocation, query)
    if err != nil {
        return nil, err
    }

    var parsedResponse []map[string]rdf.Term
    if file != nil {
        parsedResponse := ParseSPARQLJSON(file)
        if err != nil {
            return nil, err
        }

        file.Close()
        return parsedResponse, nil
    }

    jsonString, err := r.QueryCall(query)
    if err != nil {
        return nil, err
    }

    if err := r.CacheManager.SetCache(queryLocation, query, *jsonString); err != nil {
        return nil, err
    }

    var resultReader = strings.NewReader(*jsonString)
    parsedResponse = ParseSPARQLJSON(resultReader)
    if err != nil {
        return nil, err
    }

    return parsedResponse, nil
}

type Results struct {
    Variables []string
    Results   results
}

type results struct {
    Bindings []map[string]binding
}

type binding struct {
    Type     string
    Value    string
    Lang     string `json:"xml:lang"`
    DataType string
}

var xsdString, _ = rdf.NewIRI("http://www.w3.org/2001/XMLSchema#string")

func ParseSPARQLJSON(r io.Reader) []map[string]rdf.Term {
    var results Results
    err := json.NewDecoder(r).Decode(&results)

    if err != nil {
        return nil
    }

    var parsedResults []map[string]rdf.Term
    for _, binding := range results.Results.Bindings {
        parsedBinding := make(map[string]rdf.Term)
        for key, value := range binding {
            var term rdf.Term
            var err error
            switch value.Type {
            case "bnode":
                term, err = rdf.NewBlank(value.Value)
            case "uri":
                term, err = rdf.NewIRI(value.Value)
            case "literal":
                // Untyped literals are typed as xsd:string
                if value.Lang != "" {
                    term, err = rdf.NewLangLiteral(value.Value, value.Lang)
                }
                term = rdf.NewTypedLiteral(value.Value, xsdString)
            case "typed-literal":
                iri, err := rdf.NewIRI(value.DataType)
                term = rdf.NewTypedLiteral(value.Value, iri)
                if err != nil {
                    term = nil
                    err = nil
                }
            default:
                term = nil
                err = errors.New("Unknown RDF type")
            }

            if err == nil {
                parsedBinding[key] = term
            }
        }
        parsedResults = append(parsedResults, parsedBinding)
    }

    return parsedResults
}