sfv

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Mar 2, 2021 License: MIT Imports: 7 Imported by: 2

README

sfv

Go Reference

github.com/ucarion/sfv is a Golang implementation of Structured Field Values, aka RFC 8941. You can use sfv to encode and decode data in well-formatted HTTP headers. This package is fully compliant with the standard SFV test suite.

Installation

You can install this package by running:

go get github.com/ucarion/sfv

Example

The cleverness of the Structured Field Values specification is that it retroactively makes sense of a lot of existing HTTP headers. For instance, the Content-Type header happens to be a well-formed SFV header:

Content-Type: text/html; charset=utf-8

Here's a struct you can use with sfv to read and write this sort of data:

type ContentType struct {
    MediaType string
    Charset   string `sfv:"charset"`
}

So now you can parse Content-Type headers:

var contentType ContentType
if err := sfv.Unmarshal("text/html; charset=utf-8", &contentType); err != nil {
    panic(err)
}

fmt.Println(contentType.MediaType) // Outputs: text/html
fmt.Println(contentType.Charset)   // Outputs: utf-8

Or write them out:

contentType := ContentType{MediaType: "text/html", Charset: "utf-8"}
out, err := sfv.Marshal(contentType)

fmt.Println(err) // Outputs: <nil>
fmt.Println(out) // Outputs: text/html;charset=utf-8

The online reference documentation has dozens of examples of how you can convert SFV items, lists, or dictionaries to/from their Golang equivalents.

Losslessly round-tripping SFV data

When you use sfv with custom types, as shown in the example above, you may lose some information. For instance, sfv.Unmarshal will ignore parameters that aren't specified in your struct, and will not preserve the order of parameters or dictionaries.

If you need to round-trip data exactly, you can use sfv.Marshal or sfv.Unmarshal with the sfv.Item, sfv.List, and sfv.Dictionary types. These types are treated specially by sfv, and guarantee that no data (except for things like extra whitespace) will be lost in the process of serializing/deserializing data.

For instance:

var dict sfv.Dictionary
sfv.Unmarshal("a=1,c=3,b=2", &dict)

// You can use sfv.Dictionary to iterate over keys in the order they appeared in
// the input.
fmt.Println(dict.Keys) // Outputs: [a c b]

// If dict were a map[string]int, the order of keys in this output would not be
// guaranteed.
fmt.Println(sfv.Marshal(dict)) // Outputs: a=1,c=3,b=2 <nil>

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Marshal

func Marshal(v interface{}) (string, error)
Example (Bare_item)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	fmt.Println(sfv.Marshal("foo"))
}
Output:

foo <nil>
Example (Custom_basic_list)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	fmt.Println(sfv.Marshal([]string{"foo", "bar", "baz"}))
}
Output:

foo, bar, baz <nil>
Example (Custom_basic_map)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	fmt.Println(sfv.Marshal(map[string]int{"foo": 1}))
}
Output:

foo=1 <nil>
Example (Custom_item)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	type contentType struct {
		MediaType string
		Charset   string `sfv:"charset"`
		Boundary  string `sfv:"boundary"`
	}

	fmt.Println(sfv.Marshal(contentType{MediaType: "text/html", Charset: "UTF-8"}))
	fmt.Println(sfv.Marshal(contentType{
		MediaType: "multipart/form-data",
		Charset:   "UTF-8",
		Boundary:  "xxx",
	}))

}
Output:

text/html;charset=UTF-8 <nil>
multipart/form-data;charset=UTF-8;boundary=xxx <nil>
Example (Custom_list)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	type language struct {
		Tag    string
		Weight float64 `sfv:"q"`
	}

	fmt.Println(sfv.Marshal([]language{
		language{Tag: "fr-CH"},
		language{Tag: "fr", Weight: 0.9},
		language{Tag: "*", Weight: 0.5},
	}))

}
Output:

fr-CH, fr;q=0.9, *;q=0.5 <nil>
Example (Custom_list_with_inner_list)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	fmt.Println(sfv.Marshal([][]string{
		[]string{"gzip", "fr"},
		[]string{"identity", "fr"},
	}))

}
Output:

(gzip fr), (identity fr) <nil>
Example (Custom_list_with_inner_list_with_nested_params)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	type itemWithParams struct {
		Name string
		XXX  string `sfv:"xxx"`
	}

	type innerListWithParams struct {
		Names []itemWithParams
		Foo   string `sfv:"foo"`
	}

	fmt.Println(sfv.Marshal([]innerListWithParams{
		innerListWithParams{
			Names: []itemWithParams{
				itemWithParams{Name: "gzip", XXX: "yyy"},
				itemWithParams{Name: "fr"},
			},
			Foo: "bar",
		},
		innerListWithParams{
			Names: []itemWithParams{
				itemWithParams{Name: "identity"},
				itemWithParams{Name: "fr", XXX: "zzz"},
			},
			Foo: "baz",
		},
	}))

}
Output:

(gzip;xxx=yyy fr);foo=bar, (identity fr;xxx=zzz);foo=baz <nil>
Example (Custom_list_with_inner_list_with_params)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	type innerListWithParams struct {
		Names []string
		Foo   string `sfv:"foo"`
	}

	fmt.Println(sfv.Marshal([]innerListWithParams{
		innerListWithParams{
			Names: []string{"gzip", "fr"},
			Foo:   "bar",
		},
		innerListWithParams{
			Names: []string{"identity", "fr"},
			Foo:   "baz",
		},
	}))

}
Output:

(gzip fr);foo=bar, (identity fr);foo=baz <nil>
Example (Custom_map)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	type thing struct {
		Value int
		Foo   string `sfv:"foo"`
	}

	fmt.Println(sfv.Marshal(map[string]thing{
		"xxx": thing{Value: 1, Foo: "bar"},
	}))

}
Output:

xxx=1;foo=bar <nil>
Example (Custom_map_inner_list)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	fmt.Println(sfv.Marshal(map[string][]string{
		"accept-encoding": []string{"gzip", "br"},
	}))

}
Output:

accept-encoding=(gzip br) <nil>
Example (Custom_map_with_inner_list_with_nested_params)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	type itemWithParams struct {
		Name string
		XXX  string `sfv:"xxx"`
	}

	type innerListWithParams struct {
		Names []itemWithParams
		Foo   string `sfv:"foo"`
	}

	fmt.Println(sfv.Marshal(map[string]innerListWithParams{
		"a": innerListWithParams{
			Names: []itemWithParams{
				itemWithParams{Name: "gzip", XXX: "yyy"},
				itemWithParams{Name: "fr"},
			},
			Foo: "bar",
		},
	}))

}
Output:

a=(gzip;xxx=yyy fr);foo=bar <nil>
Example (Custom_map_with_inner_list_with_params)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	type innerListWithParams struct {
		Names []string
		Foo   string `sfv:"foo"`
	}

	fmt.Println(sfv.Marshal(map[string]innerListWithParams{
		"a": innerListWithParams{
			Names: []string{"gzip", "fr"},
			Foo:   "bar",
		},
	}))

}
Output:

a=(gzip fr);foo=bar <nil>
Example (List_of_bytes)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	fmt.Println(sfv.Marshal([][]byte{
		[]byte{1, 2, 3, 4},
		[]byte{1, 2, 3, 4},
	}))

}
Output:

:AQIDBA==:, :AQIDBA==: <nil>
Example (List_of_list_of_bytes)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	fmt.Println(sfv.Marshal([][][]byte{
		[][]byte{
			[]byte{1, 2, 3, 4},
			[]byte{1, 2, 3, 4},
		},
		[][]byte{
			[]byte{1, 2, 3, 4},
		},
	}))

}
Output:

(:AQIDBA==: :AQIDBA==:), (:AQIDBA==:) <nil>
Example (Map_of_bytes)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	fmt.Println(sfv.Marshal(map[string][]byte{
		"sig1": []byte{1, 2, 3, 4},
		"sig2": []byte{1, 2, 3, 4},
	}))

}
Output:

sig1=:AQIDBA==:, sig2=:AQIDBA==: <nil>
Example (Raw_dict)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	dict := sfv.Dictionary{
		Keys: []string{"public", "max-age", "immutable"},
		Map: map[string]sfv.Member{
			"public": sfv.Member{
				IsItem: true,
				Item: sfv.Item{
					BareItem: sfv.BareItem{
						Type:    sfv.BareItemTypeBoolean,
						Boolean: true,
					},
				},
			},
			"max-age": sfv.Member{
				IsItem: true,
				Item: sfv.Item{
					BareItem: sfv.BareItem{
						Type:    sfv.BareItemTypeInteger,
						Integer: 604800,
					},
				},
			},
			"immutable": sfv.Member{
				IsItem: true,
				Item: sfv.Item{
					BareItem: sfv.BareItem{
						Type:    sfv.BareItemTypeBoolean,
						Boolean: true,
					},
				},
			},
		},
	}

	fmt.Println(sfv.Marshal(dict))
}
Output:

public, max-age=604800, immutable <nil>
Example (Raw_item)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	item := sfv.Item{
		BareItem: sfv.BareItem{
			Type:  sfv.BareItemTypeToken,
			Token: "text/html",
		},
		Params: sfv.Params{
			Keys: []string{"charset"},
			Map: map[string]sfv.BareItem{
				"charset": sfv.BareItem{
					Type:  sfv.BareItemTypeToken,
					Token: "UTF-8",
				},
			},
		},
	}

	fmt.Println(sfv.Marshal(item))
}
Output:

text/html;charset=UTF-8 <nil>
Example (Raw_list)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	list := sfv.List{
		sfv.Member{
			IsItem: true,
			Item: sfv.Item{
				BareItem: sfv.BareItem{
					Type:  sfv.BareItemTypeToken,
					Token: "fr-CH",
				},
			},
		},
		sfv.Member{
			IsItem: true,
			Item: sfv.Item{
				BareItem: sfv.BareItem{
					Type:  sfv.BareItemTypeToken,
					Token: "fr",
				},
				Params: sfv.Params{
					Keys: []string{"q"},
					Map: map[string]sfv.BareItem{
						"q": sfv.BareItem{
							Type:    sfv.BareItemTypeDecimal,
							Decimal: 0.9,
						},
					},
				},
			},
		},
		sfv.Member{
			IsItem: true,
			Item: sfv.Item{
				BareItem: sfv.BareItem{
					Type:  sfv.BareItemTypeToken,
					Token: "*",
				},
				Params: sfv.Params{
					Keys: []string{"q"},
					Map: map[string]sfv.BareItem{
						"q": sfv.BareItem{
							Type:    sfv.BareItemTypeDecimal,
							Decimal: 0.5,
						},
					},
				},
			},
		},
	}

	fmt.Println(sfv.Marshal(list))
}
Output:

fr-CH, fr;q=0.9, *;q=0.5 <nil>

func Unmarshal

func Unmarshal(s string, v interface{}) error
Example (Custom_bare_item)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	var data string
	fmt.Println(sfv.Unmarshal("text/html; charset=UTF-8", &data))
	fmt.Println(data)

}
Output:

<nil>
text/html
Example (Custom_basic_list)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	var data []string
	fmt.Println(sfv.Unmarshal("foo, bar, baz", &data))
	fmt.Println(data)

}
Output:

<nil>
[foo bar baz]
Example (Custom_basic_map)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	var data map[string]int
	fmt.Println(sfv.Unmarshal("foo=1, bar=2, baz=3", &data))
	fmt.Println(data)

}
Output:

<nil>
map[bar:2 baz:3 foo:1]
Example (Custom_item)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	type contentType struct {
		MediaType string
		Charset   string `sfv:"charset"`
		Boundary  string `sfv:"boundary"`
	}

	var data1 contentType
	fmt.Println(sfv.Unmarshal("text/html; charset=UTF-8", &data1))
	fmt.Println(data1)

	var data2 contentType
	fmt.Println(sfv.Unmarshal("multipart/form-data; charset=UTF-8; boundary=xxx", &data2))
	fmt.Println(data2)

}
Output:

<nil>
{text/html UTF-8 }
<nil>
{multipart/form-data UTF-8 xxx}
Example (Custom_list)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	type language struct {
		Tag    string
		Weight float64 `sfv:"q"`
	}

	var data []language
	fmt.Println(sfv.Unmarshal("fr-CH, fr;q=0.9, *;q=0.5", &data))
	fmt.Println(data)

}
Output:

<nil>
[{fr-CH 0} {fr 0.9} {* 0.5}]
Example (Custom_list_iterated_calls)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	type language struct {
		Tag    string
		Weight float64 `sfv:"q"`
	}

	var data []language
	fmt.Println(sfv.Unmarshal("fr-CH", &data))
	fmt.Println(sfv.Unmarshal("fr;q=0.9", &data))
	fmt.Println(sfv.Unmarshal("*;q=0.5", &data))
	fmt.Println(data)

}
Output:

<nil>
<nil>
<nil>
[{fr-CH 0} {fr 0.9} {* 0.5}]
Example (Custom_list_with_inner_list)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	var data [][]string
	fmt.Println(sfv.Unmarshal("(gzip fr), (identity fr)", &data))
	fmt.Println(data)

}
Output:

<nil>
[[gzip fr] [identity fr]]
Example (Custom_list_with_inner_list_with_nested_params)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	type itemWithParams struct {
		Name string
		XXX  string `sfv:"xxx"`
	}

	type innerListWithParams struct {
		Names []itemWithParams
		Foo   string `sfv:"foo"`
	}

	var data []innerListWithParams
	fmt.Println(sfv.Unmarshal("(gzip;xxx=yyy fr);foo=bar, (identity fr;xxx=zzz);foo=baz", &data))
	fmt.Println(data)

}
Output:

<nil>
[{[{gzip yyy} {fr }] bar} {[{identity } {fr zzz}] baz}]
Example (Custom_list_with_inner_list_with_params)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	type innerListWithParams struct {
		Names []string
		Foo   string `sfv:"foo"`
	}

	var data []innerListWithParams
	fmt.Println(sfv.Unmarshal("(gzip fr);foo=bar, (identity fr);foo=baz", &data))
	fmt.Println(data)

}
Output:

<nil>
[{[gzip fr] bar} {[identity fr] baz}]
Example (Custom_map)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	type thing struct {
		Value int
		Foo   string `sfv:"foo"`
	}

	var data map[string]thing
	fmt.Println(sfv.Unmarshal("xxx=1;foo=bar, yyy=2;foo=baz", &data))
	fmt.Println(data)

}
Output:

<nil>
map[xxx:{1 bar} yyy:{2 baz}]
Example (Custom_map_inner_list)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	var data map[string][]string
	fmt.Println(sfv.Unmarshal("accept-encoding=(gzip br), accept-language=(en fr)", &data))
	fmt.Println(data)

}
Output:

<nil>
map[accept-encoding:[gzip br] accept-language:[en fr]]
Example (Custom_map_with_inner_list_with_nested_params)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	type itemWithParams struct {
		Name string
		XXX  string `sfv:"xxx"`
	}

	type innerListWithParams struct {
		Names []itemWithParams
		Foo   string `sfv:"foo"`
	}

	var data map[string]innerListWithParams
	fmt.Println(sfv.Unmarshal("a=(gzip;xxx=yyy fr);foo=bar, b=(identity fr;xxx=zzz);foo=baz", &data))
	fmt.Println(data)

}
Output:

<nil>
map[a:{[{gzip yyy} {fr }] bar} b:{[{identity } {fr zzz}] baz}]
Example (Custom_map_with_inner_list_with_params)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	type innerListWithParams struct {
		Names []string
		Foo   string `sfv:"foo"`
	}

	var data map[string]innerListWithParams
	fmt.Println(sfv.Unmarshal("a=(gzip fr);foo=bar, b=(identity fr);foo=baz", &data))
	fmt.Println(data)

}
Output:

<nil>
map[a:{[gzip fr] bar} b:{[identity fr] baz}]
Example (List_of_bytes)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	var data [][]byte
	fmt.Println(sfv.Unmarshal(":AQIDBA==:, :AQIDBA==:", &data))
	fmt.Println(data)

}
Output:

<nil>
[[1 2 3 4] [1 2 3 4]]
Example (List_of_list_of_bytes)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	var data [][][]byte
	fmt.Println(sfv.Unmarshal("(:AQIDBA==: :AQIDBA==:), (:AQIDBA==:)", &data))
	fmt.Println(data)

}
Output:

<nil>
[[[1 2 3 4] [1 2 3 4]] [[1 2 3 4]]]
Example (Map_of_bytes)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	var data map[string][]byte
	fmt.Println(sfv.Unmarshal("sig1=:AQIDBA==:, sig2=:AQIDBA==:", &data))
	fmt.Println(data)

}
Output:

<nil>
map[sig1:[1 2 3 4] sig2:[1 2 3 4]]
Example (Raw_dict)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	var dict sfv.Dictionary
	fmt.Println(sfv.Unmarshal("public, max-age=604800, immutable", &dict))

	for _, k := range dict.Keys {
		fmt.Println(k, dict.Map[k].Item.BareItem)
	}

}
Output:

<nil>
public {boolean 0 0   [] true}
max-age {integer 604800 0   [] false}
immutable {boolean 0 0   [] true}
Example (Raw_item)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	var item sfv.Item
	fmt.Println(sfv.Unmarshal("text/html; charset=UTF-8", &item))
	fmt.Println(item.BareItem.Token)
	fmt.Println(item.Params.Map["charset"].Token)

}
Output:

<nil>
text/html
UTF-8
Example (Raw_list)
package main

import (
	"fmt"

	"github.com/ucarion/sfv"
)

func main() {
	var list []sfv.Member
	fmt.Println(sfv.Unmarshal("fr-CH, fr;q=0.9, *;q=0.5", &list))

	for _, m := range list {
		fmt.Println(m.Item.BareItem.Token)

		if _, ok := m.Item.Params.Map["q"]; ok {
			fmt.Println(m.Item.Params.Map["q"].Decimal)
		}
	}

}
Output:

<nil>
fr-CH
fr
0.9
*
0.5

Types

type BareItem

type BareItem struct {
	Type    BareItemType
	Integer int64
	Decimal float64
	String  string
	Token   string
	Binary  []byte
	Boolean bool
}

type BareItemType

type BareItemType int
const (
	BareItemTypeInteger BareItemType = iota + 1
	BareItemTypeDecimal
	BareItemTypeString
	BareItemTypeToken
	BareItemTypeBinary
	BareItemTypeBoolean
)

func (BareItemType) String

func (t BareItemType) String() string

type Dictionary

type Dictionary struct {
	Map  map[string]Member
	Keys []string
}

type InnerList

type InnerList struct {
	Items  []Item
	Params Params
}

type Item

type Item struct {
	BareItem BareItem
	Params   Params
}

type List

type List = []Member

type Member

type Member struct {
	IsItem    bool
	Item      Item
	InnerList InnerList
}

type Params

type Params struct {
	Map  map[string]BareItem
	Keys []string
}

type ParseError

type ParseError struct {
	Offset int
	// contains filtered or unexported fields
}

func (ParseError) Error

func (pe ParseError) Error() string

Jump to

Keyboard shortcuts

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