negotiation

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 17, 2026 License: MIT Imports: 6 Imported by: 0

README

Negotiation

tag Go Reference Go Report Card CI codecov License

Go library for HTTP content negotiation based on RFC 7231 with support for media types, languages, charsets, and encodings. Provides comprehensive content negotiation tools for building robust HTTP services.

Features

  • Media Type Negotiation - Negotiate based on Accept headers
  • Language Negotiation - Negotiate based on Accept-Language headers
  • Charset Negotiation - Negotiate based on Accept-Charset headers
  • Encoding Negotiation - Negotiate based on Accept-Encoding headers
  • RFC 7231 Compliant - Follows HTTP content negotiation standards
  • Quality Value Support - Handles q-values for preference ordering
  • Wildcard Support - Supports wildcard matching (*/*, text/*, etc.)
  • Parameter Matching - Matches media type parameters (e.g., charset=UTF-8)
  • Plus-Segment Matching - Supports media types with plus segments (e.g., application/vnd.api+json)

Quick Start

package main

import (
    "fmt"
    "github.com/talav/negotiation"
)

func main() {
    // Create a media type negotiator
    negotiator := negotiation.NewMediaNegotiator()

    // Negotiate based on Accept header
    acceptHeader := "text/html, application/json;q=0.9, */*;q=0.8"
    priorities := []string{"application/json", "text/html"}

    best, err := negotiator.Negotiate(acceptHeader, priorities, false)
    if err != nil {
        panic(err)
    }

    if best != nil {
        fmt.Printf("Best match: %s\n", best.Type)
        // Output: Best match: text/html
    }
}

Installation

go get github.com/talav/negotiation

Usage

Media Type Negotiation
package main

import (
    "fmt"
    "github.com/talav/negotiation"
)

func main() {
    negotiator := negotiation.NewMediaNegotiator()
    
    acceptHeader := "text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8"
    priorities := []string{"application/json", "application/xml", "text/html"}
    
    best, err := negotiator.Negotiate(acceptHeader, priorities, false)
    if err != nil {
        panic(err)
    }
    
    if best != nil {
        fmt.Printf("Best match: %s\n", best.Type)
        // Output: Best match: text/html
    }
}
Language Negotiation
negotiator := negotiation.NewLanguageNegotiator()

acceptLanguageHeader := "en; q=0.1, fr; q=0.4, fu; q=0.9, de; q=0.2"
priorities := []string{"en", "fu", "de"}

best, err := negotiator.GetBest(acceptLanguageHeader, priorities, false)
if err != nil {
    panic(err)
}

if best != nil {
    fmt.Printf("Best language: %s\n", best.Type)
    // Output: Best language: fu
    fmt.Printf("Quality: %f\n", best.Quality)
    // Output: Quality: 0.900000
}
Charset Negotiation
negotiator := negotiation.NewCharsetNegotiator()

acceptCharsetHeader := "ISO-8859-1, UTF-8; q=0.9"
priorities := []string{"iso-8859-1;q=0.3", "utf-8;q=0.9", "utf-16;q=1.0"}

best, err := negotiator.GetBest(acceptCharsetHeader, priorities, false)
if err != nil {
    panic(err)
}

if best != nil {
    fmt.Printf("Best charset: %s\n", best.Type)
    // Output: Best charset: utf-8
}
Encoding Negotiation
negotiator := negotiation.NewEncodingNegotiator()

acceptEncodingHeader := "gzip;q=1.0, identity; q=0.5, *;q=0"
priorities := []string{"identity", "gzip"}

best, err := negotiator.GetBest(acceptEncodingHeader, priorities, false)
if err != nil {
    panic(err)
}

if best != nil {
    fmt.Printf("Best encoding: %s\n", best.Type)
    // Output: Best encoding: identity
}
Getting Ordered Elements

You can also get all accept header elements ordered by quality:

negotiator := negotiation.NewMediaNegotiator()

elements, err := negotiator.GetOrderedElements("text/html;q=0.3, text/html;q=0.7")
if err != nil {
    panic(err)
}

for _, elem := range elements {
    fmt.Printf("%s (q=%f)\n", elem.Value, elem.Quality)
}
// Output:
// text/html;q=0.7 (q=0.700000)
// text/html;q=0.3 (q=0.300000)

Error Handling

The package defines several error types:

  • InvalidArgumentError - Invalid argument provided
  • InvalidHeaderError - Header cannot be parsed
  • InvalidMediaTypeError - Invalid media type format
  • InvalidLanguageError - Invalid language tag format
  • ErrNoMatch - No matching header found

Limitations and Best Practices

Quality Value Handling

⚠️ Important: Quality values (q-values) are clamped to the range [0.0, 1.0]:

// These are equivalent:
"application/json;q=1.5"  // Treated as q=1.0
"application/json;q=-0.5" // Treated as q=0.0
Header Parsing
  • Headers are parsed case-insensitively for media types and charsets
  • Language tags are normalized to lowercase
  • Parameters are sorted alphabetically for consistent matching
  • Malformed headers return InvalidHeaderError

API Stability

This library follows semantic versioning. The public API is stable for v1.x:

Stable APIs:

  • NewMediaNegotiator(), NewLanguageNegotiator(), NewCharsetNegotiator(), NewEncodingNegotiator()
  • Negotiator.Negotiate(header, priorities, strict), Negotiator.GetOrderedElements(header)
  • Header struct and all exported fields
  • All exported error types: InvalidArgumentError, InvalidHeaderError, InvalidMediaTypeError, InvalidLanguageError, ErrNoMatch

Development Commands

# Run tests
go test -v ./...

# Run with race detector
go test -race ./...

# Run linter
golangci-lint run

# Run tests with coverage and generate report
go test -v -coverpkg=./... -covermode=atomic -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

# Run benchmarks (when implemented)
go test -bench=. -benchmem

# Generate coverage report for CI
go test -coverprofile=coverage.out -covermode=atomic ./...

License

MIT License - see LICENSE file for details.

Credits

Developed by Talav.

Questions? Open an issue or discussion on GitHub.

Found a bug? Please report it with a minimal reproduction case.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrNoMatch = &InvalidArgumentError{Message: "no matching header found"}

ErrNoMatch is returned when no matching header is found.

Functions

This section is empty.

Types

type Header struct {
	// Value is the original header value.
	Value string
	// Type is the accept type (e.g., "text/html", "en", "utf-8").
	Type string
	// Quality is the quality value (q-value), defaulting to 1.0.
	Quality float64
	// Parameters contains all parameters except 'q'.
	Parameters map[string]string
	// BasePart is the base part (e.g., "text" from "text/html", "en" from "en-US").
	// Empty for types that don't use base/sub parts.
	BasePart string
	// SubPart is the sub part (e.g., "html" from "text/html", "US" from "en-US").
	// Empty for types that don't use base/sub parts.
	SubPart string

	// NormalizedValue is the normalized value with sorted parameters.
	NormalizedValue string
	// contains filtered or unexported fields
}

Header represents a parsed Accept* header value. Fields are exported for direct access (idiomatic Go).

type InvalidArgumentError

type InvalidArgumentError struct {
	Message string
}

InvalidArgumentError is returned when an invalid argument is provided.

func (*InvalidArgumentError) Error

func (e *InvalidArgumentError) Error() string

type InvalidHeaderError

type InvalidHeaderError struct {
	Header string
}

InvalidHeaderError is returned when a header cannot be parsed.

func (*InvalidHeaderError) Error

func (e *InvalidHeaderError) Error() string

type InvalidLanguageError

type InvalidLanguageError struct{}

InvalidLanguageError is returned when a language tag is invalid.

func (*InvalidLanguageError) Error

func (e *InvalidLanguageError) Error() string

type InvalidMediaTypeError

type InvalidMediaTypeError struct{}

InvalidMediaTypeError is returned when a media type is invalid.

func (*InvalidMediaTypeError) Error

func (e *InvalidMediaTypeError) Error() string

type Negotiator

type Negotiator struct {
	// contains filtered or unexported fields
}

Negotiator handles all negotiation logic.

func NewCharsetNegotiator

func NewCharsetNegotiator() *Negotiator

NewCharsetNegotiator creates a new Negotiator for charsets.

func NewEncodingNegotiator

func NewEncodingNegotiator() *Negotiator

NewEncodingNegotiator creates a new Negotiator for encodings.

func NewLanguageNegotiator

func NewLanguageNegotiator() *Negotiator

NewLanguageNegotiator creates a new Negotiator for languages.

func NewMediaNegotiator

func NewMediaNegotiator() *Negotiator

NewMediaNegotiator creates a new Negotiator for media types.

func (*Negotiator) GetOrderedElements

func (c *Negotiator) GetOrderedElements(header string) ([]*Header, error)

GetOrderedElements returns all accept header elements ordered by quality.

func (*Negotiator) Negotiate

func (c *Negotiator) Negotiate(header string, priorities []string, strict bool) (*Header, error)

GetBest returns the best matching accept header from priorities based on the header. If strict is true, returns errors for invalid headers; otherwise skips invalid entries.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL