Initial
This commit is contained in:
commit
d543c15203
16
README.mkd
Normal file
16
README.mkd
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Golang Link Header Parser
|
||||||
|
|
||||||
|
Library for parsing HTTP Link headers.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
header := "<https://api.github.com/user/58276/repos?page=2>; rel=\"next\", <https://api.github.com/user/58276/repos?page=2>; rel=\"last\""
|
||||||
|
links := linkheader.Parse(header)
|
||||||
|
|
||||||
|
for _, link := range links {
|
||||||
|
fmt.Printf("URL: %s; Rel: %s\n", link.URL, link.Rel)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
49
examples_test.go
Normal file
49
examples_test.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package linkheader_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tomnomnom/linkheader"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleParse() {
|
||||||
|
header := "<https://api.github.com/user/58276/repos?page=2>; rel=\"next\", <https://api.github.com/user/58276/repos?page=2>; rel=\"last\""
|
||||||
|
links := linkheader.Parse(header)
|
||||||
|
|
||||||
|
for _, link := range links {
|
||||||
|
fmt.Printf("URL: %s; Rel: %s\n", link.URL, link.Rel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// URL: https://api.github.com/user/58276/repos?page=2; Rel: next
|
||||||
|
// URL: https://api.github.com/user/58276/repos?page=2; Rel: last
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleParseMultiple() {
|
||||||
|
headers := []string{
|
||||||
|
"<https://api.github.com/user/58276/repos?page=2>; rel=\"next\"",
|
||||||
|
"<https://api.github.com/user/58276/repos?page=2>; rel=\"last\"",
|
||||||
|
}
|
||||||
|
links := linkheader.ParseMultiple(headers)
|
||||||
|
|
||||||
|
for _, link := range links {
|
||||||
|
fmt.Printf("URL: %s; Rel: %s\n", link.URL, link.Rel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// URL: https://api.github.com/user/58276/repos?page=2; Rel: next
|
||||||
|
// URL: https://api.github.com/user/58276/repos?page=2; Rel: last
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleFilterByRel() {
|
||||||
|
header := "<https://api.github.com/user/58276/repos?page=2>; rel=\"next\", <https://api.github.com/user/58276/repos?page=2>; rel=\"last\""
|
||||||
|
links := linkheader.Parse(header)
|
||||||
|
|
||||||
|
for _, link := range links.FilterByRel("last") {
|
||||||
|
fmt.Printf("URL: %s; Rel: %s\n", link.URL, link.Rel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// URL: https://api.github.com/user/58276/repos?page=2; Rel: last
|
||||||
|
|
||||||
|
}
|
119
main.go
Normal file
119
main.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package linkheader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Link is a single URL and related parameters
|
||||||
|
type Link struct {
|
||||||
|
URL string
|
||||||
|
Rel string
|
||||||
|
Params map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasParam returns if a Link has a particular parameter or not
|
||||||
|
func (l Link) HasParam(key string) bool {
|
||||||
|
for p, _ := range l.Params {
|
||||||
|
if p == key {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Param returns the value of a parameter, or an error on failure
|
||||||
|
func (l Link) Param(key string) (string, error) {
|
||||||
|
for k, v := range l.Params {
|
||||||
|
if key == k {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("Could not find param '%s'", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type Links is a slice of Link structs
|
||||||
|
type Links []Link
|
||||||
|
|
||||||
|
// FilterByRel filters a group of Links by the provided Rel attribute
|
||||||
|
func (l Links) FilterByRel(r string) Links {
|
||||||
|
links := make(Links, 0)
|
||||||
|
for _, link := range l {
|
||||||
|
if link.Rel == r {
|
||||||
|
links = append(links, link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses a raw Link header in the form:
|
||||||
|
// <url>; rel="foo", <url>; rel="bar"; wat="dis"
|
||||||
|
// returning a slice of Link structs
|
||||||
|
func Parse(raw string) Links {
|
||||||
|
links := make(Links, 0)
|
||||||
|
|
||||||
|
// One chunk: <url>; rel="foo"
|
||||||
|
for _, chunk := range strings.Split(raw, ",") {
|
||||||
|
|
||||||
|
link := Link{URL: "", Rel: "", Params: make(map[string]string)}
|
||||||
|
|
||||||
|
// Figure out what each piece of the chunk is
|
||||||
|
for _, piece := range strings.Split(chunk, ";") {
|
||||||
|
|
||||||
|
piece = strings.Trim(piece, " ")
|
||||||
|
if piece == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL
|
||||||
|
if piece[0] == '<' && piece[len(piece)-1] == '>' {
|
||||||
|
link.URL = strings.Trim(piece, "<>")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Params
|
||||||
|
key, val := parseParam(piece)
|
||||||
|
if key == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for rel
|
||||||
|
if strings.ToLower(key) == "rel" {
|
||||||
|
link.Rel = val
|
||||||
|
}
|
||||||
|
|
||||||
|
link.Params[key] = val
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
links = append(links, link)
|
||||||
|
}
|
||||||
|
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMultiple is like Parse, but accepts a slice of headers
|
||||||
|
// rather than just one header string
|
||||||
|
func ParseMultiple(headers []string) Links {
|
||||||
|
links := make(Links, 0)
|
||||||
|
for _, header := range headers {
|
||||||
|
links = append(links, Parse(header)...)
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseParam takes a raw param in the form key="val" and
|
||||||
|
// returns the key and value as seperate strings
|
||||||
|
func parseParam(raw string) (key, val string) {
|
||||||
|
|
||||||
|
parts := strings.SplitN(raw, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
key = parts[0]
|
||||||
|
val = strings.Trim(parts[1], "\"")
|
||||||
|
|
||||||
|
return key, val
|
||||||
|
|
||||||
|
}
|
105
main_test.go
Normal file
105
main_test.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package linkheader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSimple(t *testing.T) {
|
||||||
|
// Test case stolen from https://github.com/thlorenz/parse-link-header :)
|
||||||
|
header := "<https://api.github.com/user/9287/repos?page=3&per_page=100>; rel=\"next\", " +
|
||||||
|
"<https://api.github.com/user/9287/repos?page=1&per_page=100>; rel=\"prev\"; pet=\"cat\", " +
|
||||||
|
"<https://api.github.com/user/9287/repos?page=5&per_page=100>; rel=\"last\""
|
||||||
|
|
||||||
|
links := Parse(header)
|
||||||
|
|
||||||
|
if len(links) != 3 {
|
||||||
|
t.Errorf("Should have been 3 links returned, got %d", len(links))
|
||||||
|
}
|
||||||
|
|
||||||
|
if links[0].URL != "https://api.github.com/user/9287/repos?page=3&per_page=100" {
|
||||||
|
t.Errorf("First link should have URL 'https://api.github.com/user/9287/repos?page=3&per_page=100'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if links[0].Rel != "next" {
|
||||||
|
t.Errorf("First link should have rel=\"next\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(links[0].Params) != 1 {
|
||||||
|
t.Errorf("First link should have exactly 1 params, but has %d", len(links[0].Params))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(links[1].Params) != 2 {
|
||||||
|
t.Errorf("Second link should have exactly 2 params, but has %d", len(links[1].Params))
|
||||||
|
}
|
||||||
|
|
||||||
|
if links[1].Params["pet"] != "cat" {
|
||||||
|
t.Errorf("Second link's 'pet' param should be 'cat', but was %s", links[1].Params["pet"])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinkMethods(t *testing.T) {
|
||||||
|
header := "<https://api.github.com/user/9287/repos?page=1&per_page=100>; rel=\"prev\"; pet=\"cat\""
|
||||||
|
links := Parse(header)
|
||||||
|
link := links[0]
|
||||||
|
|
||||||
|
if !link.HasParam("rel") {
|
||||||
|
t.Errorf("Link should have param 'rel'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if link.HasParam("foo") {
|
||||||
|
t.Errorf("Link should not have param 'foo'")
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := link.Param("pet")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error value should be nil")
|
||||||
|
}
|
||||||
|
if val != "cat" {
|
||||||
|
t.Errorf("Link should have param pet=\"cat\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err = link.Param("foo")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Error value should not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLinksMethods(t *testing.T) {
|
||||||
|
header := "<https://api.github.com/user/9287/repos?page=3&per_page=100>; rel=\"next\", " +
|
||||||
|
"<https://api.github.com/user/9287/repos?page=1&per_page=100>; rel=\"stylesheet\"; pet=\"cat\", " +
|
||||||
|
"<https://api.github.com/user/9287/repos?page=5&per_page=100>; rel=\"stylesheet\""
|
||||||
|
|
||||||
|
links := Parse(header)
|
||||||
|
|
||||||
|
filtered := links.FilterByRel("next")
|
||||||
|
|
||||||
|
if filtered[0].URL != "https://api.github.com/user/9287/repos?page=3&per_page=100" {
|
||||||
|
t.Errorf("URL did not match expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = links.FilterByRel("stylesheet")
|
||||||
|
if len(filtered) != 2 {
|
||||||
|
t.Errorf("Filter for stylesheet should yield 2 results but got %d", len(filtered))
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = links.FilterByRel("notarel")
|
||||||
|
if len(filtered) != 0 {
|
||||||
|
t.Errorf("Filter by non-existant rel should yeild no results")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func testParseMultiple(t *testing.T) {
|
||||||
|
headers := []string{
|
||||||
|
"<https://api.github.com/user/58276/repos?page=2>; rel=\"next\"",
|
||||||
|
"<https://api.github.com/user/58276/repos?page=2>; rel=\"last\"",
|
||||||
|
}
|
||||||
|
|
||||||
|
links := ParseMultiple(headers)
|
||||||
|
|
||||||
|
if len(links) != 2 {
|
||||||
|
t.Errorf("Should have returned 2 links")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user