Skip to content

Commit

Permalink
impelement Charset
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidCai1111 committed Nov 13, 2016
1 parent 945e97e commit c9a42ab
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 12 deletions.
70 changes: 69 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,71 @@
# negotiator
[![Build Status](https://travis-ci.org/DavidCai1993/negotiator.svg?branch=master)](https://travis-ci.org/DavidCai1993/negotiator)
[![Coverage Status](https://coveralls.io/repos/github/DavidCai1993/negotiator/badge.svg)](https://coveralls.io/github/DavidCai1993/negotiator)
[![Coverage Status](https://coveralls.io/repos/github/DavidCai1993/negotiator/badge.svg)](https://coveralls.io/github/DavidCai1993/negotiator)
An HTTP content negotiator for Go

## Installation

```sh
go get -u github.com/DavidCai1993/negotiator
```

## Usage

```go
import (
"github.com/DavidCai1993/negotiator"
)

negotiator := negotiator.New(req)
```

### Accept

```go
// Assume that the Accept header is "text/html, application/*;q=0.9, image/jpeg;q=0.8"

negotiator.Accept([]string{"text/html", "application/json", "image/jpeg"})
// -> "text/html"

negotiator.Accept([]string{"application/json", "image/jpeg", "text/plain"})
// -> "application/json"

negotiator.Accept([]string{"text/plain"})
// -> ""
```

### Encoding

```go
// Assume that the Accept-Encoding header is "gzip, compress;q=0.2, identity;q=0.5"

negotiator.Encoding([]string{"identity", "gzip"})
// -> "gzip"

negotiator.Encoding([]string{"compress", "identity"})
// -> "identity"
```

### Language

```go
// Assume that the Accept-Language header is "en;q=0.8, es, pt"

negotiator.Language([]string{"en", "es", "fr"})
// -> "es"

negotiator.Language([]string{"es", "pt"})
// -> "es"
```

### Charset

```go
// Assume that the Accept-Charset header is "utf-8, iso-8859-1;q=0.8, utf-7;q=0.2"

negotiator.Charset([]string{"utf-8", "iso-8859-1", "iso-8859-5"})
// -> "utf-8"

negotiator.Charset([]string{"iso-8859-5"})
// -> ""
```
10 changes: 10 additions & 0 deletions negotiator.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const (
HeaderAcceptLanguage = "Accept-Language"
// HeaderAcceptEncoding is the HTTP "Accept-Encoding" Header.
HeaderAcceptEncoding = "Accept-Encoding"
// HeaderAcceptCharset is the HTTP "Accept-Charset" Header.
HeaderAcceptCharset = "Accept-Charset"
)

type spec struct {
Expand Down Expand Up @@ -98,3 +100,11 @@ func (n Negotiator) Encoding(offers []string) (bestOffer string, matched bool) {

return parser.selectOffer(offers, parser.parse(HeaderAcceptEncoding))
}

// Charset returns the most preferred language from the HTTP Accept-Charset
// header.
func (n Negotiator) Charset(offers []string) (bestOffer string, matched bool) {
parser := newHeaderParser(n.req.Header, false)

return parser.selectOffer(offers, parser.parse(HeaderAcceptCharset))
}
90 changes: 84 additions & 6 deletions negotiator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ func (s AcceptSuite) TestEmpty() {
func (s AcceptSuite) TestCaseInsensitive() {
n := setUpNegotiator(HeaderAccept, "text/html")

bestOffer, matched := n.Accept([]string{"TExt/htmL"})
bestOffer, matched := n.Accept([]string{"TEXT/HTML"})

s.True(matched)
s.Equal("text/html", bestOffer)
s.Equal("TEXT/HTML", bestOffer)
}

func (s AcceptSuite) TestUnMatched() {
Expand Down Expand Up @@ -136,7 +136,7 @@ func (s LanguageSuite) TestCaseInsensitive() {
bestOffer, matched := n.Language([]string{"eN"})

s.True(matched)
s.Equal("en", bestOffer)
s.Equal("eN", bestOffer)
}

func (s LanguageSuite) TestUnMatched() {
Expand Down Expand Up @@ -200,12 +200,12 @@ func (s EncodingSuite) TestEmpty() {
}

func (s EncodingSuite) TestCaseInsensitive() {
n := setUpNegotiator(HeaderAcceptEncoding, "Gzip")
n := setUpNegotiator(HeaderAcceptEncoding, "GZip")

bestOffer, matched := n.Encoding([]string{"GzIp"})
bestOffer, matched := n.Encoding([]string{"Gzip"})

s.True(matched)
s.Equal("gzip", bestOffer)
s.Equal("Gzip", bestOffer)
}

func (s EncodingSuite) TestUnMatched() {
Expand Down Expand Up @@ -254,3 +254,81 @@ func (s EncodingSuite) TestFirstMatchAllAsterisk() {
func TestEncoding(t *testing.T) {
suite.Run(t, new(EncodingSuite))
}

// Charset
type CharsetSuite struct {
suite.Suite
}

func (s CharsetSuite) TestEmpty() {
n := setUpNegotiator(HeaderAcceptCharset, "")

_, matched := n.Charset([]string{})

s.False(matched)
}

func (s CharsetSuite) TestCaseInsensitive() {
n := setUpNegotiator(HeaderAcceptCharset, "ISO-8859-1")

bestOffer, matched := n.Charset([]string{"ISO-8859-1"})

s.True(matched)
s.Equal("ISO-8859-1", bestOffer)
}

func (s CharsetSuite) TestUnMatched() {
n := setUpNegotiator(HeaderAcceptCharset, "ISO-8859-1,UTF-8")

_, matched := n.Charset([]string{"ASCII"})

s.False(matched)
}

func (s CharsetSuite) TestEmptyCharset() {
n := setUpNegotiator(HeaderAcceptCharset, "UTF-8;q=0")

_, matched := n.Charset([]string{"UTF-8"})

s.False(matched)
}

func (s CharsetSuite) TestOneMatch() {
n := setUpNegotiator(HeaderAcceptCharset, "UTF-8;q=0.2")

bestOffer, matched := n.Charset([]string{"UTF-8"})

s.True(matched)
s.Equal("UTF-8", bestOffer)
}

func (s CharsetSuite) TestMatchAsterisk() {
n := setUpNegotiator(HeaderAcceptCharset, "*")

bestOffer, matched := n.Charset([]string{"UTF-8", "ISO-8859-1"})

s.True(matched)
s.Equal("UTF-8", bestOffer)
}

func (s CharsetSuite) TestFirstMatchAllAsterisk() {
n := setUpNegotiator(HeaderAcceptCharset, "*, UTF-8;q=0.5")

bestOffer, matched := n.Charset([]string{"UTF-8", "ISO-8859-1", "ASCII"})

s.True(matched)
s.Equal("ISO-8859-1", bestOffer)
}

func (s CharsetSuite) TestHighOrderPreferred() {
n := setUpNegotiator(HeaderAcceptCharset, "UTF-8;q=0.6, ISO-8859-1;q=0.8, UTF-8;q=0.9")

bestOffer, matched := n.Charset([]string{"UTF-8", "ISO-8859-1", "ASCII"})

s.True(matched)
s.Equal("UTF-8", bestOffer)
}

func TestCharset(t *testing.T) {
suite.Run(t, new(CharsetSuite))
}
10 changes: 5 additions & 5 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (p headerParser) parse(headerName string) (specs specs) {

spec := spec{val: pair[0], q: p.defaultQ}

if len(pair) == 2 && strings.HasPrefix(pair[1], "q=") {
if len(pair) == 2 {
var i int

if strings.HasPrefix(pair[1], "q=") {
Expand Down Expand Up @@ -90,28 +90,28 @@ func (p headerParser) selectOffer(offers []string, specs specs) (bestOffer strin
}

for _, offer := range offers {
offer = strings.ToLower(offer)
lowerCaseOffer := strings.ToLower(offer)

for _, spec := range specs {
switch {
case spec.q <= bestQ:
continue
case spec.val == p.wildCard && !specs.hasVal(offer):
case spec.val == p.wildCard && !specs.hasVal(lowerCaseOffer):
if spec.q > bestQ || bestWild > totalWild-1 {
matched = true
bestOffer = offer

bestQ, bestWild = spec.q, totalWild-1
}
case p.hasSlashVal && strings.HasSuffix(spec.val, "/*"):
if strings.HasPrefix(offer, spec.val[:len(spec.val)-1]) &&
if strings.HasPrefix(lowerCaseOffer, spec.val[:len(spec.val)-1]) &&
(spec.q > bestQ || bestWild > totalWild-2) {
matched = true
bestOffer = offer

bestQ, bestWild = spec.q, totalWild-2
}
case spec.val == offer:
case spec.val == lowerCaseOffer:
if spec.q > bestQ || bestWild > 0 {
matched = true
bestOffer = offer
Expand Down
File renamed without changes.
83 changes: 83 additions & 0 deletions parser_charset_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package negotiator

import (
"testing"

"net/http"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

type ParseCharsetTestSuite struct {
suite.Suite

parser *headerParser
header http.Header
}

func (s *ParseCharsetTestSuite) SetupTest() {
s.header = make(http.Header)
s.parser = newHeaderParser(s.header, false)
}

func (s *ParseCharsetTestSuite) TestEmpty() {
assert := assert.New(s.T())

s.header.Set(HeaderAcceptCharset, "")
specs := s.parser.parse(HeaderAcceptCharset)

assert.Equal(1, len(specs))

equalSpec(assert, specs[0], "*", 1.0)
}

func (s *ParseCharsetTestSuite) TestAsterisk() {
assert := assert.New(s.T())

s.header.Set(HeaderAcceptCharset, "*")
specs := s.parser.parse(HeaderAcceptCharset)

assert.Equal(1, len(specs))

equalSpec(assert, specs[0], "*", 1.0)
}

func (s *ParseCharsetTestSuite) TestOneLanguage() {
assert := assert.New(s.T())

s.header.Set(HeaderAcceptCharset, "UTF-8;level=1.0")
specs := s.parser.parse(HeaderAcceptCharset)

assert.Equal(1, len(specs))

equalSpec(assert, specs[0], "utf-8", 1.0)
}

func (s *ParseCharsetTestSuite) TestOneLanguageWithQZero() {
assert := assert.New(s.T())

s.header.Set(HeaderAcceptCharset, "*, ISO-8859-1;level=0")
specs := s.parser.parse(HeaderAcceptCharset)

assert.Equal(1, len(specs))

equalSpec(assert, specs[0], "*", 1.0)
}

func (s *ParseCharsetTestSuite) TestSortByQ() {
assert := assert.New(s.T())

s.header.Set(HeaderAcceptCharset, "*;level=0.8, ISO-8859-1, UTF-8")
specs := s.parser.parse(HeaderAcceptCharset)

assert.Equal(3, len(specs))

equalSpec(assert, specs[0], "iso-8859-1", 1.0)
equalSpec(assert, specs[1], "utf-8", 1.0)
equalSpec(assert, specs[2], "*", 0.8)
}

func TestParseCharset(t *testing.T) {
suite.Run(t, new(ParseCharsetTestSuite))
}
File renamed without changes.
File renamed without changes.

0 comments on commit c9a42ab

Please sign in to comment.