Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions gist_resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package core

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"regexp"
)

// GistResolver is able to understand gist locators and fetch
// them returning a stream
//
// • It implements the infertace Resolver
type GistResolver struct {
}

// UserGistFile represents a gist file in a Gisthub response
type UserGistFile struct {
Filename string
Raw_URL string
}

// UserGistsResponse represents the response of Github when
// fetching all the gist of a user
type UserGistsResponse struct {
ID string
Files map[string]UserGistFile
}

// GithubAPIURL constant reference the base url of Github API
var GithubAPIURL string = "https://api.github.com"

// GistLocatorRegexp regexp to validate and match the parts of the gist locator
var GistLocatorRegexp string = `^[a-zA-Z0-9]+://(?P<username>[a-zA-Z0-9_]+[a-zA-Z0-9])/(?P<resource>[^/<>|:&]+)$`

// ObtainUsernameAndResourceFromGistLocator return an array
// with the username and resource of a gist locator
func ObtainUsernameAndResourceFromGistLocator(locator string) ([2]string, error) {
re := regexp.MustCompile(GistLocatorRegexp)
if !re.MatchString(locator) {
return [2]string{}, fmt.Errorf("The locator %s doesn't match a well formed gist locator", locator)
}
matchList := re.FindAllStringSubmatch(locator, -1)[0]
return [2]string{matchList[1], matchList[2]}, nil
}

// GetUserGistURL builds a URL which points to all the gist of
// a user
func GetUserGistURL(username string) string {
return fmt.Sprintf("%s/users/%s/gists", GithubAPIURL, username)
}

// FetchData function fetch the code where the resource Locator
// points to, in this case the resource points to a gist
func (resolver *GistResolver) FetchData(resource *Resource) (io.ReadCloser, error) {
values, err := ObtainUsernameAndResourceFromGistLocator(resource.Locator)
if err != nil {
return nil, err
}
url := GetUserGistURL(values[0])
// Fetch all the gists of the user
res, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("Unable to fetch the url %s %v", url, err)
}
// Defer the close of the gists stream
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
// Decode the response
var gistList []UserGistsResponse

if err = json.Unmarshal(body, &gistList); err != nil {
return nil, fmt.Errorf("Unable to decode the request to %s %v", url, err)
}
// Look for the resource in all the gist
var found UserGistFile = UserGistFile{"", ""}
for idxGist := range gistList {
for filename, fileList := range gistList[idxGist].Files {
// Check if the filename is equals to the resource ol the locator
if filename == values[1] {
found = fileList
break
}
}
}
if found.Filename == "" {
return nil, fmt.Errorf("%s not found for the user %s", values[1], values[0])
}
// Leave the fetch to HTTPResolver
httpResolver := &HTTPResolver{}
return httpResolver.FetchData(&Resource{
found.Raw_URL,
HTTPS,
})
}
86 changes: 86 additions & 0 deletions gist_resolver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package core

import (
"regexp"
"testing"
)

func TestGistLocatorRegexpShouldBeWellFormed(t *testing.T) {
re := regexp.MustCompile(GistLocatorRegexp)
locator := "gist://Kelvur/multiply.py"
username := "Kelvur"
resource := "multiply.py"
if !re.MatchString(locator) {
t.Errorf("GistLocatorRegexp doesn't match the locator: %s", locator)
}
listMatches := re.FindAllStringSubmatch("gist://Kelvur/multiply.py", -1)[0]
if listMatches[1] != "Kelvur" {
t.Errorf("GistLocatorRegexp doesn't match correctly the username, expected %s get %s", username, listMatches[1])
}
if listMatches[2] != "multiply.py" {
t.Errorf("GistLocatorRegexp doesn't match correctly the resource, expected %s get %s", resource, listMatches[2])
}
}

func TestGistLocatorRegexpShouldFailsToMatchBadFormedGistLocators(t *testing.T) {
re := regexp.MustCompile(GistLocatorRegexp)
locatorWithoutScheme := "Kelvur/multiply.py"
if re.MatchString(locatorWithoutScheme) {
t.Errorf("GistLocatorRegexp match a locator without scheme: %s", locatorWithoutScheme)
}
locatorWithoutUsername := "gist://multiply.py"
if re.MatchString(locatorWithoutUsername) {
t.Errorf("GistLocatorRegexp match a locator without username: %s", locatorWithoutUsername)
}
locatorWithoutResource := "gist://Kelvur/"
if re.MatchString(locatorWithoutResource) {
t.Errorf("GistLocatorRegexp match a locator without resource: %s", locatorWithoutResource)
}
}

func TestObtainUsernameAndResourceFromGistLocatorShouldWorkAsExpected(t *testing.T) {
gistLocator := "gist://Kelvur/multiply.py"
username := "Kelvur"
resource := "multiply.py"
values, err := ObtainUsernameAndResourceFromGistLocator(gistLocator)
if err != nil {
t.Errorf("ObtainUsernameAndResourceFromGistLocator should work with a valid gist locator as %s", gistLocator)
}
if values[0] != username {
t.Errorf("after calling ObtainUsernameAndResourceFromGistLocator with %s the expected fist value is %s, get %s", gistLocator, username, values[0])
}
if values[1] != resource {
t.Errorf("after calling ObtainUsernameAndResourceFromGistLocator with %s the expected fist value is %s, get %s", gistLocator, resource, values[1])
}
}

func TestObtainUsernameAndResourceFromGistLocatorShouldFailAsExpected(t *testing.T) {
incorrectGistLocator := "gist:/-asd/<>"
_, err := ObtainUsernameAndResourceFromGistLocator(incorrectGistLocator)
if err == nil {
t.Errorf("ObtainUsernameAndResourceFromGistLocator should fail when called with %s", incorrectGistLocator)
}
}

func TestGistResolverImplementsResolverInterface(t *testing.T) {
var resolver Resolver = &GistResolver{}
_, ok := resolver.(*GistResolver)
if !ok {
t.Error("GistResolver not implements Resolver")
}
}

func TestGistResolverShouldBeAbleToFetchAGist(t *testing.T) {
gistLocator := "gist://Kelvur/multiplication.py"
resource, err := ResourceFactory(gistLocator)
if err != nil {
t.Errorf("ResourceFactory fails with locator %s", gistLocator)
t.Error(err)
}
var resolver GistResolver = GistResolver{}
readCloser, err := resolver.FetchData(&resource)
defer readCloser.Close()
if err != nil {
t.Error(err)
}
}