Adding upstream version 0.0~git20221030.f2a1913.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
958064e3c4
commit
2ef6a43d9f
19 changed files with 4554 additions and 0 deletions
18
.build.yml
Normal file
18
.build.yml
Normal file
|
@ -0,0 +1,18 @@
|
|||
image: archlinux
|
||||
packages:
|
||||
- go
|
||||
- postgresql
|
||||
sources:
|
||||
- https://github.com/go-ap/jsonld
|
||||
environment:
|
||||
GO111MODULE: 'on'
|
||||
tasks:
|
||||
- setup: |
|
||||
cd jsonld && go mod download
|
||||
- tests: |
|
||||
cd jsonld && make test
|
||||
- coverage: |
|
||||
set -a +x
|
||||
cd jsonld && make coverage
|
||||
GIT_SHA=$(git rev-parse --verify HEAD)
|
||||
GIT_BRANCH=$(git name-rev --name-only HEAD)
|
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Gogland
|
||||
.idea/
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.so
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tools
|
||||
*.out
|
||||
*.coverprofile
|
||||
|
||||
*pkg
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 Marius Orcsik
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
18
Makefile
Normal file
18
Makefile
Normal file
|
@ -0,0 +1,18 @@
|
|||
TEST := go test
|
||||
TEST_FLAGS ?= -v
|
||||
TEST_TARGET ?= ./...
|
||||
GO111MODULE=on
|
||||
PROJECT_NAME := $(shell basename $(PWD))
|
||||
|
||||
.PHONY: test coverage clean
|
||||
|
||||
test:
|
||||
$(TEST) $(TEST_FLAGS) $(TEST_TARGET)
|
||||
|
||||
coverage: TEST_TARGET := .
|
||||
coverage: TEST_FLAGS += -covermode=count -coverprofile $(PROJECT_NAME).coverprofile
|
||||
coverage: test
|
||||
|
||||
clean:
|
||||
$(RM) -v *.coverprofile
|
||||
|
15
README.md
Normal file
15
README.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
# JSON-ld for Go
|
||||
|
||||
[](https://raw.githubusercontent.com/go-ap/jsonld/master/LICENSE)
|
||||
[](https://builds.sr.ht/~mariusor/jsonld)
|
||||
[](https://codecov.io/gh/go-ap/jsonld)
|
||||
[](https://goreportcard.com/report/github.com/go-ap/jsonld)
|
||||
<!--[](https://www.codacy.com/app/go-ap/jsonld/dashboard)-->
|
||||
|
||||
Basic lib for using [activity pub](https://www.w3.org/TR/activitypub/#Overview) API in Go.
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
import "github.com/go-ap/jsonld"
|
||||
```
|
219
context.go
Normal file
219
context.go
Normal file
|
@ -0,0 +1,219 @@
|
|||
package jsonld
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// From the JSON-LD spec 3.3
|
||||
// https://www.w3.org/TR/json-ld/#dfn-keyword
|
||||
const (
|
||||
// @context
|
||||
// Used to define the short-hand names that are used throughout a JSON-LD document.
|
||||
// These short-hand names are called terms and help developers to express specific identifiers in a compact manner.
|
||||
// The @context keyword is described in detail in section 5.1 The Context.
|
||||
ContextKw Term = "@context"
|
||||
// @id
|
||||
//Used to uniquely identify things that are being described in the document with IRIs or blank node identifiers.
|
||||
// This keyword is described in section 5.3 Node Identifiers.
|
||||
IdKw Term = "@id"
|
||||
// @value
|
||||
// Used to specify the data that is associated with a particular property in the graph.
|
||||
// This keyword is described in section 6.9 String Internationalization and section 6.4 Typed Values.
|
||||
ValueKw Term = "@value"
|
||||
// @language
|
||||
// Used to specify the language for a particular string value or the default language of a JSON-LD document.
|
||||
// This keyword is described in section 6.9 String Internationalization.
|
||||
LanguageKw Term = "@language"
|
||||
//@type
|
||||
//Used to set the data type of a node or typed value. This keyword is described in section 6.4 Typed Values.
|
||||
TypeKw Term = "@type"
|
||||
// @container
|
||||
// Used to set the default container type for a term. This keyword is described in section 6.11 Sets and Lists.
|
||||
ContainerKw Term = "@container"
|
||||
//@list
|
||||
//Used to express an ordered set of data. This keyword is described in section 6.11 Sets and Lists.
|
||||
ListKw Term = "@list"
|
||||
// @set
|
||||
// Used to express an unordered set of data and to ensure that values are always represented as arrays.
|
||||
// This keyword is described in section 6.11 Sets and Lists.
|
||||
SetKw Term = "@set"
|
||||
// @reverse
|
||||
// Used to express reverse properties. This keyword is described in section 6.12 Reverse Properties.
|
||||
ReverseKw Term = "@reverse"
|
||||
// @index
|
||||
// Used to specify that a container is used to index information and that processing should continue deeper
|
||||
// into a JSON data structure. This keyword is described in section 6.16 Data Indexing.
|
||||
IndexKw Term = "@index"
|
||||
// @base
|
||||
// Used to set the base IRI against which relative IRIs are resolved. T
|
||||
// his keyword is described in section 6.1 Base IRI.
|
||||
BaseKw Term = "@base"
|
||||
// @vocab
|
||||
// Used to expand properties and values in @type with a common prefix IRI.
|
||||
// This keyword is described in section 6.2 Default Vocabulary.
|
||||
VocabKw Term = "@vocab"
|
||||
// @graph
|
||||
// Used to express a graph. This keyword is described in section 6.13 Named Graphs.
|
||||
GraphKw Term = "@graph"
|
||||
)
|
||||
|
||||
// ContentType is the content type of JsonLD documents
|
||||
const ContentType = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
|
||||
|
||||
type (
|
||||
// Ref basic type
|
||||
LangRef string
|
||||
// Term represents the JSON-LD term for @context maps
|
||||
Term string
|
||||
// IRI is a International Resource Identificator
|
||||
IRI string
|
||||
// Terms is an array of Term values
|
||||
Terms []Term
|
||||
)
|
||||
|
||||
// Nillable
|
||||
type Nillable interface {
|
||||
IsNil() bool
|
||||
}
|
||||
|
||||
type IRILike interface {
|
||||
IsCompact() bool
|
||||
IsAbsolute() bool
|
||||
IsRelative() bool
|
||||
}
|
||||
|
||||
func (i IRI) IsCompact() bool {
|
||||
return !i.IsAbsolute() && strings.Contains(string(i), ":")
|
||||
}
|
||||
func (i IRI) IsAbsolute() bool {
|
||||
return strings.Contains(string(i), "https://")
|
||||
}
|
||||
func (i IRI) IsRelative() bool {
|
||||
return !i.IsAbsolute()
|
||||
}
|
||||
|
||||
var keywords = Terms{
|
||||
BaseKw,
|
||||
ContextKw,
|
||||
ContainerKw,
|
||||
GraphKw,
|
||||
IdKw,
|
||||
IndexKw,
|
||||
LanguageKw,
|
||||
ListKw,
|
||||
ReverseKw,
|
||||
SetKw,
|
||||
TypeKw,
|
||||
ValueKw,
|
||||
VocabKw,
|
||||
}
|
||||
|
||||
const NilTerm Term = "-"
|
||||
const NilLangRef LangRef = "-"
|
||||
|
||||
type ContextObject struct {
|
||||
ID interface{} `jsonld:"@id,omitempty,collapsible"`
|
||||
Type interface{} `jsonld:"@type,omitempty,collapsible"`
|
||||
}
|
||||
|
||||
// Context is of of the basic JSON-LD elements.
|
||||
// It represents an array of ContextElements
|
||||
type Context []ContextElement
|
||||
|
||||
// ContextElement is used to map terms to IRIs or JSON objects.
|
||||
// Terms are case sensitive and any valid string that is not a reserved JSON-LD
|
||||
// keyword can be used as a term.
|
||||
type ContextElement struct {
|
||||
Term Term
|
||||
IRI IRI
|
||||
}
|
||||
|
||||
func GetContext() Context {
|
||||
return Context{}
|
||||
}
|
||||
|
||||
//type Context Collapsible
|
||||
|
||||
// Collapsible is an interface used by the JSON-LD marshaller to collapse a struct to one single value
|
||||
type Collapsible interface {
|
||||
Collapse() interface{}
|
||||
}
|
||||
|
||||
// Collapse returns the plain text collapsed value of the current Context object
|
||||
func (c Context) Collapse() interface{} {
|
||||
if len(c) == 1 && len(c[0].IRI) > 0 {
|
||||
return c[0].IRI
|
||||
}
|
||||
for _, el := range c {
|
||||
if el.Term == NilTerm {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Collapse returns the plain text collapsed value of the current IRI string
|
||||
func (i IRI) Collapse() interface{} {
|
||||
return i
|
||||
}
|
||||
|
||||
// MarshalText basic stringify function
|
||||
func (i IRI) MarshalText() ([]byte, error) {
|
||||
return []byte(i), nil
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON document represented by the current Context
|
||||
// This should return :
|
||||
// If only one element in the context and the element has no Term -> json marshaled string
|
||||
// If multiple elements in the context without Term -> json marshaled array of strings
|
||||
// If multiple elements where at least one doesn't have a Term and one has a Term -> json marshaled array
|
||||
// If multiple elements where all have Terms -> json marshaled object
|
||||
func (c Context) MarshalJSON() ([]byte, error) {
|
||||
mapIRI := make(map[Term]IRI, 0)
|
||||
arr := make([]interface{}, 0)
|
||||
i := 0
|
||||
if len(c) == 1 && len(c[0].IRI) > 0 {
|
||||
return json.Marshal(c[0].IRI)
|
||||
}
|
||||
for _, el := range c {
|
||||
t := el.Term
|
||||
iri := el.IRI
|
||||
if t.IsNil() {
|
||||
arr = append(arr, iri)
|
||||
i += 1
|
||||
} else {
|
||||
if len(iri) > 0 {
|
||||
mapIRI[t] = iri
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(mapIRI) > 0 {
|
||||
if len(arr) == 0 {
|
||||
return json.Marshal(mapIRI)
|
||||
}
|
||||
arr = append(arr, mapIRI)
|
||||
}
|
||||
return json.Marshal(arr)
|
||||
}
|
||||
|
||||
// UnmarshalJSON tries to load the Context from the incoming json value
|
||||
func (c *Context) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsNil returns if current LangRef is equal to empty string or to its nil value
|
||||
func (l LangRef) IsNil() bool {
|
||||
return len(l) == 0 || l == NilLangRef
|
||||
}
|
||||
|
||||
// IsNil returns if current IRI is equal to empty string
|
||||
func (i IRI) IsNil() bool {
|
||||
return len(i) == 0
|
||||
}
|
||||
|
||||
// IsNil returns if current Term is equal to empty string or to its nil value
|
||||
func (i Term) IsNil() bool {
|
||||
return len(i) == 0 || i == NilTerm
|
||||
}
|
101
context_test.go
Normal file
101
context_test.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package jsonld
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRef_MarshalText(t *testing.T) {
|
||||
test := "test"
|
||||
a := IRI(test)
|
||||
|
||||
out, err := a.MarshalText()
|
||||
if err != nil {
|
||||
t.Errorf("Error %s", err)
|
||||
}
|
||||
if bytes.Compare(out, []byte(test)) != 0 {
|
||||
t.Errorf("Invalid result '%s', expected '%s'", out, test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext_MarshalJSON(t *testing.T) {
|
||||
{
|
||||
url := "test"
|
||||
c := Context{{NilTerm, IRI(url)}}
|
||||
|
||||
out, err := c.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
if !strings.Contains(string(out), url) {
|
||||
t.Errorf("Json doesn't contain %s, %s", url, string(out))
|
||||
}
|
||||
jUrl, _ := json.Marshal(url)
|
||||
if !bytes.Equal(jUrl, out) {
|
||||
t.Errorf("Strings should be equal %s, %s", jUrl, out)
|
||||
}
|
||||
}
|
||||
{
|
||||
url := "example.com"
|
||||
asTerm := "testingTerm##"
|
||||
asUrl := "https://activitipubrocks.com"
|
||||
c2 := Context{{NilTerm, IRI(url)}, {Term(asTerm), IRI(asUrl)}}
|
||||
out, err := c2.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
if !strings.Contains(string(out), url) {
|
||||
t.Errorf("Json doesn't contain URL %s, %s", url, string(out))
|
||||
}
|
||||
if !strings.Contains(string(out), asUrl) {
|
||||
t.Errorf("Json doesn't contain URL %s, %s", asUrl, string(out))
|
||||
}
|
||||
if !strings.Contains(string(out), asTerm) {
|
||||
t.Errorf("Json doesn't contain Term %s, %s", asTerm, string(out))
|
||||
}
|
||||
}
|
||||
{
|
||||
url := "test"
|
||||
testTerm := "test_term"
|
||||
asTerm := "testingTerm##"
|
||||
asUrl := "https://activitipubrocks.com"
|
||||
c3 := Context{{Term(testTerm), IRI(url)}, {Term(asTerm), IRI(asUrl)}}
|
||||
out, err := c3.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
if !strings.Contains(string(out), url) {
|
||||
t.Errorf("Json doesn't contain URL %s, %s", url, string(out))
|
||||
}
|
||||
if !strings.Contains(string(out), asUrl) {
|
||||
t.Errorf("Json doesn't contain URL %s, %s", asUrl, string(out))
|
||||
}
|
||||
if !strings.Contains(string(out), asTerm) {
|
||||
t.Errorf("Json doesn't contain Term %s, %s", asTerm, string(out))
|
||||
}
|
||||
if !strings.Contains(string(out), testTerm) {
|
||||
t.Errorf("Json doesn't contain Term %s, %s", testTerm, string(out))
|
||||
}
|
||||
}
|
||||
{
|
||||
url1 := "test"
|
||||
url2 := "http://example.com"
|
||||
c := Context{
|
||||
{IRI: IRI(url1)},
|
||||
{IRI: IRI(url2)},
|
||||
}
|
||||
|
||||
out, err := c.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
if !strings.Contains(string(out), url1) {
|
||||
t.Errorf("Json doesn't contain %s, %s", url1, string(out))
|
||||
}
|
||||
if !strings.Contains(string(out), url2) {
|
||||
t.Errorf("Json doesn't contain %s, %s", url1, string(out))
|
||||
}
|
||||
}
|
||||
}
|
149
decode_test.go
Normal file
149
decode_test.go
Normal file
|
@ -0,0 +1,149 @@
|
|||
package jsonld
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnmarshalWithEmptyJsonObject(t *testing.T) {
|
||||
obj := mockTypeA{}
|
||||
err := Unmarshal([]byte("{}"), &obj)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if obj.Id != "" {
|
||||
t.Errorf("Id should have been an empty string, found %s", obj.Id)
|
||||
}
|
||||
if obj.Name != "" {
|
||||
t.Errorf("Name should have been an empty string, found %s", obj.Name)
|
||||
}
|
||||
if obj.Type != "" {
|
||||
t.Errorf("Type should have been an empty string, found %s", obj.Type)
|
||||
}
|
||||
if obj.PropA != "" {
|
||||
t.Errorf("PropA should have been an empty string, found %s", obj.PropA)
|
||||
}
|
||||
if obj.PropB != 0 {
|
||||
t.Errorf("PropB should have been 0.0, found %f", obj.PropB)
|
||||
}
|
||||
}
|
||||
|
||||
type mockWithContext struct {
|
||||
mockTypeA
|
||||
Context Context `jsonld:"@context"`
|
||||
}
|
||||
|
||||
func TestUnmarshalWithEmptyJsonObjectWithStringContext(t *testing.T) {
|
||||
obj := mockWithContext{}
|
||||
url := "http://www.habarnam.ro"
|
||||
data := []byte(`{"@context": "` + url + `" }`)
|
||||
err := Unmarshal(data, &obj)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if obj.Id != "" {
|
||||
t.Errorf("Id should have been an empty string, found %s", obj.Id)
|
||||
}
|
||||
if obj.Name != "" {
|
||||
t.Errorf("Name should have been an empty string, found %s", obj.Name)
|
||||
}
|
||||
if obj.Type != "" {
|
||||
t.Errorf("Type should have been an empty string, found %s", obj.Type)
|
||||
}
|
||||
if obj.PropA != "" {
|
||||
t.Errorf("PropA should have been an empty string, found %s", obj.PropA)
|
||||
}
|
||||
if obj.PropB != 0 {
|
||||
t.Errorf("PropB should have been 0.0, found %f", obj.PropB)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalWithEmptyJsonObjectWithObjectContext(t *testing.T) {
|
||||
obj := mockWithContext{}
|
||||
url := "http://www.habarnam.ro"
|
||||
data := []byte(`{"@context": { "@url": "` + url + `"} }`)
|
||||
err := Unmarshal(data, &obj)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if obj.Id != "" {
|
||||
t.Errorf("Id should have been an empty string, found %s", obj.Id)
|
||||
}
|
||||
if obj.Name != "" {
|
||||
t.Errorf("Name should have been an empty string, found %s", obj.Name)
|
||||
}
|
||||
if obj.Type != "" {
|
||||
t.Errorf("Type should have been an empty string, found %s", obj.Type)
|
||||
}
|
||||
if obj.PropA != "" {
|
||||
t.Errorf("PropA should have been an empty string, found %s", obj.PropA)
|
||||
}
|
||||
if obj.PropB != 0 {
|
||||
t.Errorf("PropB should have been 0.0, found %f", obj.PropB)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalWithEmptyJsonObjectWithOneLanguageContext(t *testing.T) {
|
||||
obj := mockWithContext{}
|
||||
url := "http://www.habarnam.ro"
|
||||
langEn := "en-US"
|
||||
|
||||
data := []byte(`{"@context": { "@url": "` + url + `", "@language": "` + langEn + `"} }`)
|
||||
err := Unmarshal(data, &obj)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if obj.Id != "" {
|
||||
t.Errorf("Id should have been an empty string, found %s", obj.Id)
|
||||
}
|
||||
if obj.Name != "" {
|
||||
t.Errorf("Name should have been an empty string, found %s", obj.Name)
|
||||
}
|
||||
if obj.Type != "" {
|
||||
t.Errorf("Type should have been an empty string, found %s", obj.Type)
|
||||
}
|
||||
if obj.PropA != "" {
|
||||
t.Errorf("PropA should have been an empty string, found %s", obj.PropA)
|
||||
}
|
||||
if obj.PropB != 0 {
|
||||
t.Errorf("PropB should have been 0.0, found %f", obj.PropB)
|
||||
}
|
||||
}
|
||||
func TestUnmarshalWithEmptyJsonObjectWithFullObject(t *testing.T) {
|
||||
obj := mockWithContext{}
|
||||
url := "http://www.habarnam.ro"
|
||||
langEn := "en-US"
|
||||
propA := "ana"
|
||||
var propB float32 = 6.66
|
||||
typ := "test"
|
||||
name := "test object #1"
|
||||
id := "777sdad"
|
||||
|
||||
data := []byte(`{
|
||||
"@context": { "@url": "` + url + `", "@language": "` + langEn + `"},
|
||||
"PropA": "` + propA + `",
|
||||
"PropB": ` + strconv.FormatFloat(float64(propB), 'f', 2, 32) + `,
|
||||
"Id" : "` + id + `",
|
||||
"Name" : "` + name + `",
|
||||
"Type" : "` + typ + `"
|
||||
}`)
|
||||
err := Unmarshal(data, &obj)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if obj.Id != id {
|
||||
t.Errorf("Id should have been %q, found %q", id, obj.Id)
|
||||
}
|
||||
if obj.Name != name {
|
||||
t.Errorf("Name should have been %q, found %q", name, obj.Name)
|
||||
}
|
||||
if obj.Type != typ {
|
||||
t.Errorf("Type should have been %q, found %q", typ, obj.Type)
|
||||
}
|
||||
if obj.PropA != propA {
|
||||
t.Errorf("PropA should have been %q, found %q", propA, obj.PropA)
|
||||
}
|
||||
if obj.PropB != propB {
|
||||
t.Errorf("PropB should have been %f, found %f", propB, obj.PropB)
|
||||
}
|
||||
}
|
156
encode_test.go
Normal file
156
encode_test.go
Normal file
|
@ -0,0 +1,156 @@
|
|||
package jsonld
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mockBase struct {
|
||||
Id string
|
||||
Name string
|
||||
Type string
|
||||
}
|
||||
|
||||
type mockTypeA struct {
|
||||
mockBase
|
||||
PropA string
|
||||
PropB float32
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
a := mockTypeA{mockBase{"base_id", "MockObjA", "mock_obj"}, "prop_a", 0.001}
|
||||
b := mockTypeA{}
|
||||
|
||||
url := "http://www.habarnam.ro"
|
||||
p := WithContext(IRI(url))
|
||||
|
||||
var err error
|
||||
var out []byte
|
||||
|
||||
out, err = p.Marshal(a)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
if !strings.Contains(string(out), string(ContextKw)) {
|
||||
t.Errorf("Context name not found %q in %s", ContextKw, out)
|
||||
}
|
||||
if !strings.Contains(string(out), url) {
|
||||
t.Errorf("Context url not found %q in %s", url, out)
|
||||
}
|
||||
err = Unmarshal(out, &b)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
if a.Id != b.Id {
|
||||
t.Errorf("Id isn't equal %q expected %q in %s", a.Id, b.Id, out)
|
||||
}
|
||||
if a.Name != b.Name {
|
||||
t.Errorf("Name isn't equal %q expected %q", a.Name, b.Name)
|
||||
}
|
||||
if a.Type != b.Type {
|
||||
t.Errorf("Type isn't equal %q expected %q", a.Type, b.Type)
|
||||
}
|
||||
if a.PropA != b.PropA {
|
||||
t.Errorf("PropA isn't equal %q expected %q", a.PropA, b.PropA)
|
||||
}
|
||||
if a.PropB != b.PropB {
|
||||
t.Errorf("PropB isn't equal %f expected %f", a.PropB, b.PropB)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalNullContext(t *testing.T) {
|
||||
var a = struct {
|
||||
PropA string
|
||||
PropB float64
|
||||
}{"test", 0.0004}
|
||||
|
||||
outL, errL := Marshal(a)
|
||||
if errL != nil {
|
||||
t.Errorf("%s", errL)
|
||||
}
|
||||
outJ, errJ := Marshal(a)
|
||||
if errJ != nil {
|
||||
t.Errorf("%s", errJ)
|
||||
}
|
||||
if !bytes.Equal(outL, outJ) {
|
||||
t.Errorf("Json output should be equal %q, received %q", outL, outJ)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalNullValue(t *testing.T) {
|
||||
var a = interface{}(nil)
|
||||
|
||||
url := "http://www.habarnam.ro"
|
||||
p := WithContext(IRI(url))
|
||||
outL, errL := p.Marshal(a)
|
||||
if errL != nil {
|
||||
t.Errorf("%s", errL)
|
||||
}
|
||||
outJ := []byte(fmt.Sprintf(`{"@context":"%s"}`, url))
|
||||
if !bytes.Equal(outL, outJ) {
|
||||
t.Errorf("JsonLD output is wrong: %s, expected %s", outL, outJ)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmpty(t *testing.T) {
|
||||
var a int
|
||||
if !isEmptyValue(reflect.ValueOf(a)) {
|
||||
t.Errorf("Invalid empty value %v", a)
|
||||
}
|
||||
if !isEmptyValue(reflect.ValueOf(uint(a))) {
|
||||
t.Errorf("Invalid empty value %v", uint(a))
|
||||
}
|
||||
var b float64
|
||||
if !isEmptyValue(reflect.ValueOf(b)) {
|
||||
t.Errorf("Invalid empty value %v", b)
|
||||
}
|
||||
var c string
|
||||
if !isEmptyValue(reflect.ValueOf(c)) {
|
||||
t.Errorf("Invalid empty value %s", c)
|
||||
}
|
||||
var d []byte
|
||||
if !isEmptyValue(reflect.ValueOf(d)) {
|
||||
t.Errorf("Invalid empty value %v", d)
|
||||
}
|
||||
var e *interface{}
|
||||
if !isEmptyValue(reflect.ValueOf(e)) {
|
||||
t.Errorf("Invalid empty value %v", e)
|
||||
}
|
||||
g := false
|
||||
if !isEmptyValue(reflect.ValueOf(g)) {
|
||||
t.Errorf("Invalid empty value %v", g)
|
||||
}
|
||||
h := true
|
||||
if isEmptyValue(reflect.ValueOf(h)) {
|
||||
t.Errorf("Invalid empty value %v", h)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithContext_MarshalJSON(t *testing.T) {
|
||||
tv := "value_test"
|
||||
v := struct{ Test string }{Test: tv}
|
||||
|
||||
data, err := WithContext(IRI("http://example.com")).Marshal(v)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !bytes.Contains(data, []byte(ContextKw)) {
|
||||
t.Errorf("%q not found in %s", ContextKw, data)
|
||||
}
|
||||
m := reflect.TypeOf(v)
|
||||
mv := reflect.ValueOf(v)
|
||||
for i := 0; i < m.NumField(); i++ {
|
||||
f := m.Field(i)
|
||||
v := mv.Field(i)
|
||||
if !bytes.Contains(data, []byte(f.Name)) {
|
||||
t.Errorf("%q not found in %s", f.Name, data)
|
||||
}
|
||||
if !bytes.Contains(data, []byte(v.String())) {
|
||||
t.Errorf("%q not found in %s", v.String(), data)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
143
fold.go
Normal file
143
fold.go
Normal file
|
@ -0,0 +1,143 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonld
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
|
||||
kelvin = '\u212a'
|
||||
smallLongEss = '\u017f'
|
||||
)
|
||||
|
||||
// foldFunc returns one of four different case folding equivalence
|
||||
// functions, from most general (and slow) to fastest:
|
||||
//
|
||||
// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8
|
||||
// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S')
|
||||
// 3) asciiEqualFold, no special, but includes non-letters (including _)
|
||||
// 4) simpleLetterEqualFold, no specials, no non-letters.
|
||||
//
|
||||
// The letters S and K are special because they map to 3 runes, not just 2:
|
||||
// * S maps to s and to U+017F 'ſ' Latin small letter long s
|
||||
// * k maps to K and to U+212A 'K' Kelvin sign
|
||||
// See https://play.golang.org/p/tTxjOc0OGo
|
||||
//
|
||||
// The returned function is specialized for matching against s and
|
||||
// should only be given s. It's not curried for performance reasons.
|
||||
func foldFunc(s []byte) func(s, t []byte) bool {
|
||||
nonLetter := false
|
||||
special := false // special letter
|
||||
for _, b := range s {
|
||||
if b >= utf8.RuneSelf {
|
||||
return bytes.EqualFold
|
||||
}
|
||||
upper := b & caseMask
|
||||
if upper < 'A' || upper > 'Z' {
|
||||
nonLetter = true
|
||||
} else if upper == 'K' || upper == 'S' {
|
||||
// See above for why these letters are special.
|
||||
special = true
|
||||
}
|
||||
}
|
||||
if special {
|
||||
return equalFoldRight
|
||||
}
|
||||
if nonLetter {
|
||||
return asciiEqualFold
|
||||
}
|
||||
return simpleLetterEqualFold
|
||||
}
|
||||
|
||||
// equalFoldRight is a specialization of bytes.EqualFold when s is
|
||||
// known to be all ASCII (including punctuation), but contains an 's',
|
||||
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
|
||||
// See comments on foldFunc.
|
||||
func equalFoldRight(s, t []byte) bool {
|
||||
for _, sb := range s {
|
||||
if len(t) == 0 {
|
||||
return false
|
||||
}
|
||||
tb := t[0]
|
||||
if tb < utf8.RuneSelf {
|
||||
if sb != tb {
|
||||
sbUpper := sb & caseMask
|
||||
if 'A' <= sbUpper && sbUpper <= 'Z' {
|
||||
if sbUpper != tb&caseMask {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
t = t[1:]
|
||||
continue
|
||||
}
|
||||
// sb is ASCII and t is not. t must be either kelvin
|
||||
// sign or long s; sb must be s, S, k, or K.
|
||||
tr, size := utf8.DecodeRune(t)
|
||||
switch sb {
|
||||
case 's', 'S':
|
||||
if tr != smallLongEss {
|
||||
return false
|
||||
}
|
||||
case 'k', 'K':
|
||||
if tr != kelvin {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
t = t[size:]
|
||||
|
||||
}
|
||||
if len(t) > 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// asciiEqualFold is a specialization of bytes.EqualFold for use when
|
||||
// s is all ASCII (but may contain non-letters) and contains no
|
||||
// special-folding letters.
|
||||
// See comments on foldFunc.
|
||||
func asciiEqualFold(s, t []byte) bool {
|
||||
if len(s) != len(t) {
|
||||
return false
|
||||
}
|
||||
for i, sb := range s {
|
||||
tb := t[i]
|
||||
if sb == tb {
|
||||
continue
|
||||
}
|
||||
if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
|
||||
if sb&caseMask != tb&caseMask {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// simpleLetterEqualFold is a specialization of bytes.EqualFold for
|
||||
// use when s is all ASCII letters (no underscores, etc) and also
|
||||
// doesn't contain 'k', 'K', 's', or 'S'.
|
||||
// See comments on foldFunc.
|
||||
func simpleLetterEqualFold(s, t []byte) bool {
|
||||
if len(s) != len(t) {
|
||||
return false
|
||||
}
|
||||
for i, b := range s {
|
||||
if b&caseMask != t[i]&caseMask {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
116
fold_test.go
Normal file
116
fold_test.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonld
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var foldTests = []struct {
|
||||
fn func(s, t []byte) bool
|
||||
s, t string
|
||||
want bool
|
||||
}{
|
||||
{equalFoldRight, "", "", true},
|
||||
{equalFoldRight, "a", "a", true},
|
||||
{equalFoldRight, "", "a", false},
|
||||
{equalFoldRight, "a", "", false},
|
||||
{equalFoldRight, "a", "A", true},
|
||||
{equalFoldRight, "AB", "ab", true},
|
||||
{equalFoldRight, "AB", "ac", false},
|
||||
{equalFoldRight, "sbkKc", "ſbKKc", true},
|
||||
{equalFoldRight, "SbKkc", "ſbKKc", true},
|
||||
{equalFoldRight, "SbKkc", "ſbKK", false},
|
||||
{equalFoldRight, "e", "é", false},
|
||||
{equalFoldRight, "s", "S", true},
|
||||
|
||||
{simpleLetterEqualFold, "", "", true},
|
||||
{simpleLetterEqualFold, "abc", "abc", true},
|
||||
{simpleLetterEqualFold, "abc", "ABC", true},
|
||||
{simpleLetterEqualFold, "abc", "ABCD", false},
|
||||
{simpleLetterEqualFold, "abc", "xxx", false},
|
||||
|
||||
{asciiEqualFold, "a_B", "A_b", true},
|
||||
{asciiEqualFold, "aa@", "aa`", false}, // verify 0x40 and 0x60 aren't case-equivalent
|
||||
}
|
||||
|
||||
func TestFold(t *testing.T) {
|
||||
for i, tt := range foldTests {
|
||||
if got := tt.fn([]byte(tt.s), []byte(tt.t)); got != tt.want {
|
||||
t.Errorf("%d. %q, %q = %v; want %v", i, tt.s, tt.t, got, tt.want)
|
||||
}
|
||||
truth := strings.EqualFold(tt.s, tt.t)
|
||||
if truth != tt.want {
|
||||
t.Errorf("strings.EqualFold doesn't agree with case %d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFoldAgainstUnicode(t *testing.T) {
|
||||
const bufSize = 5
|
||||
buf1 := make([]byte, 0, bufSize)
|
||||
buf2 := make([]byte, 0, bufSize)
|
||||
var runes []rune
|
||||
for i := 0x20; i <= 0x7f; i++ {
|
||||
runes = append(runes, rune(i))
|
||||
}
|
||||
runes = append(runes, kelvin, smallLongEss)
|
||||
|
||||
funcs := []struct {
|
||||
name string
|
||||
fold func(s, t []byte) bool
|
||||
letter bool // must be ASCII letter
|
||||
simple bool // must be simple ASCII letter (not 'S' or 'K')
|
||||
}{
|
||||
{
|
||||
name: "equalFoldRight",
|
||||
fold: equalFoldRight,
|
||||
},
|
||||
{
|
||||
name: "asciiEqualFold",
|
||||
fold: asciiEqualFold,
|
||||
simple: true,
|
||||
},
|
||||
{
|
||||
name: "simpleLetterEqualFold",
|
||||
fold: simpleLetterEqualFold,
|
||||
simple: true,
|
||||
letter: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, ff := range funcs {
|
||||
for _, r := range runes {
|
||||
if r >= utf8.RuneSelf {
|
||||
continue
|
||||
}
|
||||
if ff.letter && !isASCIILetter(byte(r)) {
|
||||
continue
|
||||
}
|
||||
if ff.simple && (r == 's' || r == 'S' || r == 'k' || r == 'K') {
|
||||
continue
|
||||
}
|
||||
for _, r2 := range runes {
|
||||
buf1 := append(buf1[:0], 'x')
|
||||
buf2 := append(buf2[:0], 'x')
|
||||
buf1 = buf1[:1+utf8.EncodeRune(buf1[1:bufSize], r)]
|
||||
buf2 = buf2[:1+utf8.EncodeRune(buf2[1:bufSize], r2)]
|
||||
buf1 = append(buf1, 'x')
|
||||
buf2 = append(buf2, 'x')
|
||||
want := bytes.EqualFold(buf1, buf2)
|
||||
if got := ff.fold(buf1, buf2); got != want {
|
||||
t.Errorf("%s(%q, %q) = %v; want %v", ff.name, buf1, buf2, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isASCIILetter(b byte) bool {
|
||||
return ('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z')
|
||||
}
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
|||
module github.com/go-ap/jsonld
|
||||
|
||||
go 1.13
|
632
scanner.go
Normal file
632
scanner.go
Normal file
|
@ -0,0 +1,632 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonld
|
||||
|
||||
// JSON value parser state machine.
|
||||
// Just about at the limit of what is reasonable to write by hand.
|
||||
// Some parts are a bit tedious, but overall it nicely factors out the
|
||||
// otherwise common code from the multiple scanning functions
|
||||
// in this package (Compact, Indent, checkValid, nextValue, etc).
|
||||
//
|
||||
// This file starts with two simple examples using the scanner
|
||||
// before diving into the scanner itself.
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Valid reports whether data is a valid JSON encoding.
|
||||
func Valid(data []byte) bool {
|
||||
return checkValid(data, &scanner{}) == nil
|
||||
}
|
||||
|
||||
// checkValid verifies that data is valid JSON-encoded data.
|
||||
// scan is passed in for use by checkValid to avoid an allocation.
|
||||
func checkValid(data []byte, scan *scanner) error {
|
||||
scan.reset()
|
||||
for _, c := range data {
|
||||
scan.bytes++
|
||||
if scan.step(scan, c) == scanError {
|
||||
return scan.err
|
||||
}
|
||||
}
|
||||
if scan.eof() == scanError {
|
||||
return scan.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextValue splits data after the next whole JSON value,
|
||||
// returning that value and the bytes that follow it as separate slices.
|
||||
// scan is passed in for use by nextValue to avoid an allocation.
|
||||
func nextValue(data []byte, scan *scanner) (value, rest []byte, err error) {
|
||||
scan.reset()
|
||||
for i, c := range data {
|
||||
v := scan.step(scan, c)
|
||||
if v >= scanEndObject {
|
||||
switch v {
|
||||
// probe the scanner with a space to determine whether we will
|
||||
// get scanEnd on the next character. Otherwise, if the next character
|
||||
// is not a space, scanEndTop allocates a needless error.
|
||||
case scanEndObject, scanEndArray:
|
||||
if scan.step(scan, ' ') == scanEnd {
|
||||
return data[:i+1], data[i+1:], nil
|
||||
}
|
||||
case scanError:
|
||||
return nil, nil, scan.err
|
||||
case scanEnd:
|
||||
return data[:i], data[i:], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if scan.eof() == scanError {
|
||||
return nil, nil, scan.err
|
||||
}
|
||||
return data, nil, nil
|
||||
}
|
||||
|
||||
// A SyntaxError is a description of a JSON syntax error.
|
||||
type SyntaxError struct {
|
||||
msg string // description of error
|
||||
Offset int64 // error occurred after reading Offset bytes
|
||||
}
|
||||
|
||||
func (e *SyntaxError) Error() string { return e.msg }
|
||||
|
||||
// A scanner is a JSON scanning state machine.
|
||||
// Callers call scan.reset() and then pass bytes in one at a time
|
||||
// by calling scan.step(&scan, c) for each byte.
|
||||
// The return value, referred to as an opcode, tells the
|
||||
// caller about significant parsing events like beginning
|
||||
// and ending literals, objects, and arrays, so that the
|
||||
// caller can follow along if it wishes.
|
||||
// The return value scanEnd indicates that a single top-level
|
||||
// JSON value has been completed, *before* the byte that
|
||||
// just got passed in. (The indication must be delayed in order
|
||||
// to recognize the end of numbers: is 123 a whole value or
|
||||
// the beginning of 12345e+6?).
|
||||
type scanner struct {
|
||||
// The step is a func to be called to execute the next transition.
|
||||
// Also tried using an integer constant and a single func
|
||||
// with a switch, but using the func directly was 10% faster
|
||||
// on a 64-bit Mac Mini, and it's nicer to read.
|
||||
step func(*scanner, byte) int
|
||||
|
||||
// Reached end of top-level value.
|
||||
endTop bool
|
||||
|
||||
// Stack of what we're in the middle of - array values, object keys, object values.
|
||||
parseState []int
|
||||
|
||||
// Error that happened, if any.
|
||||
err error
|
||||
|
||||
// 1-byte redo (see undo method)
|
||||
redo bool
|
||||
redoCode int
|
||||
redoState func(*scanner, byte) int
|
||||
|
||||
// total bytes consumed, updated by decoder.Decode
|
||||
bytes int64
|
||||
}
|
||||
|
||||
// These values are returned by the state transition functions
|
||||
// assigned to scanner.state and the method scanner.eof.
|
||||
// They give details about the current state of the scan that
|
||||
// callers might be interested to know about.
|
||||
// It is okay to Ignore the return value of any particular
|
||||
// call to scanner.state: if one call returns scanError,
|
||||
// every subsequent call will return scanError too.
|
||||
const (
|
||||
// Continue.
|
||||
scanContinue = iota // uninteresting byte
|
||||
scanBeginLiteral // end implied by next result != scanContinue
|
||||
scanBeginObject // begin object
|
||||
scanObjectKey // just finished object key (string)
|
||||
scanObjectValue // just finished non-last object value
|
||||
scanEndObject // end object (implies scanObjectValue if possible)
|
||||
scanBeginArray // begin array
|
||||
scanArrayValue // just finished array value
|
||||
scanEndArray // end array (implies scanArrayValue if possible)
|
||||
scanSkipSpace // space byte; can skip; known to be last "continue" result
|
||||
|
||||
// Stop.
|
||||
scanEnd // top-level value ended *before* this byte; known to be first "stop" result
|
||||
scanError // hit an error, scanner.err.
|
||||
|
||||
scanFindType // need to find the "jsonld:type" element of the object.
|
||||
)
|
||||
|
||||
// These values are stored in the parseState stack.
|
||||
// They give the current state of a composite value
|
||||
// being scanned. If the parser is inside a nested value
|
||||
// the parseState describes the nested state, outermost at entry 0.
|
||||
const (
|
||||
parseObjectKey = iota // parsing object key (before colon)
|
||||
parseObjectValue // parsing object value (after colon)
|
||||
parseArrayValue // parsing array value
|
||||
)
|
||||
|
||||
// reset prepares the scanner for use.
|
||||
// It must be called before calling s.step.
|
||||
func (s *scanner) reset() {
|
||||
s.step = stateBeginValue
|
||||
s.parseState = s.parseState[0:0]
|
||||
s.err = nil
|
||||
s.redo = false
|
||||
s.endTop = false
|
||||
}
|
||||
|
||||
// eof tells the scanner that the end of input has been reached.
|
||||
// It returns a scan status just as s.step does.
|
||||
func (s *scanner) eof() int {
|
||||
if s.err != nil {
|
||||
return scanError
|
||||
}
|
||||
if s.endTop {
|
||||
return scanEnd
|
||||
}
|
||||
s.step(s, ' ')
|
||||
if s.endTop {
|
||||
return scanEnd
|
||||
}
|
||||
if s.err == nil {
|
||||
s.err = &SyntaxError{"unexpected end of JSON input", s.bytes}
|
||||
}
|
||||
return scanError
|
||||
}
|
||||
|
||||
// pushParseState pushes a new parse state p onto the parse stack.
|
||||
func (s *scanner) pushParseState(p int) {
|
||||
s.parseState = append(s.parseState, p)
|
||||
}
|
||||
|
||||
// popParseState pops a parse state (already obtained) off the stack
|
||||
// and updates s.step accordingly.
|
||||
func (s *scanner) popParseState() {
|
||||
n := len(s.parseState) - 1
|
||||
s.parseState = s.parseState[0:n]
|
||||
s.redo = false
|
||||
if n == 0 {
|
||||
s.step = stateEndTop
|
||||
s.endTop = true
|
||||
} else {
|
||||
s.step = stateEndValue
|
||||
}
|
||||
}
|
||||
|
||||
func isSpace(c byte) bool {
|
||||
return c == ' ' || c == '\t' || c == '\r' || c == '\n'
|
||||
}
|
||||
|
||||
// stateBeginValueOrEmpty is the state after reading `[`.
|
||||
func stateBeginValueOrEmpty(s *scanner, c byte) int {
|
||||
if c <= ' ' && isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
if c == ']' {
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
return stateBeginValue(s, c)
|
||||
}
|
||||
|
||||
// stateBeginValue is the state at the beginning of the input.
|
||||
func stateBeginValue(s *scanner, c byte) int {
|
||||
if c <= ' ' && isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
switch c {
|
||||
case '{':
|
||||
s.step = stateBeginStringOrEmpty
|
||||
s.pushParseState(parseObjectKey)
|
||||
return scanBeginObject
|
||||
case '[':
|
||||
s.step = stateBeginValueOrEmpty
|
||||
s.pushParseState(parseArrayValue)
|
||||
return scanBeginArray
|
||||
case '"':
|
||||
s.step = stateInString
|
||||
return scanBeginLiteral
|
||||
case '-':
|
||||
s.step = stateNeg
|
||||
return scanBeginLiteral
|
||||
case '0': // beginning of 0.123
|
||||
s.step = state0
|
||||
return scanBeginLiteral
|
||||
case 't': // beginning of true
|
||||
s.step = stateT
|
||||
return scanBeginLiteral
|
||||
case 'f': // beginning of false
|
||||
s.step = stateF
|
||||
return scanBeginLiteral
|
||||
case 'n': // beginning of null
|
||||
s.step = stateN
|
||||
return scanBeginLiteral
|
||||
}
|
||||
if '1' <= c && c <= '9' { // beginning of 1234.5
|
||||
s.step = state1
|
||||
return scanBeginLiteral
|
||||
}
|
||||
return s.error(c, "looking for beginning of value")
|
||||
}
|
||||
|
||||
// stateBeginStringOrEmpty is the state after reading `{`.
|
||||
func stateBeginStringOrEmpty(s *scanner, c byte) int {
|
||||
if c <= ' ' && isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
if c == '}' {
|
||||
n := len(s.parseState)
|
||||
s.parseState[n-1] = parseObjectValue
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
return stateBeginString(s, c)
|
||||
}
|
||||
|
||||
// stateBeginString is the state after reading `{"key": value,`.
|
||||
func stateBeginString(s *scanner, c byte) int {
|
||||
if c <= ' ' && isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
if c == '"' {
|
||||
s.step = stateInString
|
||||
return scanBeginLiteral
|
||||
}
|
||||
return s.error(c, "looking for beginning of object key string")
|
||||
}
|
||||
|
||||
// stateEndValue is the state after completing a value,
|
||||
// such as after reading `{}` or `true` or `["x"`.
|
||||
func stateEndValue(s *scanner, c byte) int {
|
||||
n := len(s.parseState)
|
||||
if n == 0 {
|
||||
// Completed top-level before the current byte.
|
||||
s.step = stateEndTop
|
||||
s.endTop = true
|
||||
return stateEndTop(s, c)
|
||||
}
|
||||
if c <= ' ' && isSpace(c) {
|
||||
s.step = stateEndValue
|
||||
return scanSkipSpace
|
||||
}
|
||||
ps := s.parseState[n-1]
|
||||
switch ps {
|
||||
case parseObjectKey:
|
||||
if c == ':' {
|
||||
s.parseState[n-1] = parseObjectValue
|
||||
s.step = stateBeginValue
|
||||
return scanObjectKey
|
||||
}
|
||||
return s.error(c, "after object key")
|
||||
case parseObjectValue:
|
||||
if c == ',' {
|
||||
s.parseState[n-1] = parseObjectKey
|
||||
s.step = stateBeginString
|
||||
return scanObjectValue
|
||||
}
|
||||
if c == '}' {
|
||||
s.popParseState()
|
||||
return scanEndObject
|
||||
}
|
||||
return s.error(c, "after object key:value pair")
|
||||
case parseArrayValue:
|
||||
if c == ',' {
|
||||
s.step = stateBeginValue
|
||||
return scanArrayValue
|
||||
}
|
||||
if c == ']' {
|
||||
s.popParseState()
|
||||
return scanEndArray
|
||||
}
|
||||
return s.error(c, "after array element")
|
||||
}
|
||||
return s.error(c, "")
|
||||
}
|
||||
|
||||
// stateEndTop is the state after finishing the top-level value,
|
||||
// such as after reading `{}` or `[1,2,3]`.
|
||||
// Only space characters should be seen now.
|
||||
func stateEndTop(s *scanner, c byte) int {
|
||||
if c != ' ' && c != '\t' && c != '\r' && c != '\n' {
|
||||
// Complain about non-space byte on next call.
|
||||
s.error(c, "after top-level value")
|
||||
}
|
||||
return scanEnd
|
||||
}
|
||||
|
||||
// stateInString is the state after reading `"`.
|
||||
func stateInString(s *scanner, c byte) int {
|
||||
if c == '"' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
if c == '\\' {
|
||||
s.step = stateInStringEsc
|
||||
return scanContinue
|
||||
}
|
||||
if c < 0x20 {
|
||||
return s.error(c, "in string literal")
|
||||
}
|
||||
return scanContinue
|
||||
}
|
||||
|
||||
// stateInStringEsc is the state after reading `"\` during a quoted string.
|
||||
func stateInStringEsc(s *scanner, c byte) int {
|
||||
switch c {
|
||||
case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
|
||||
s.step = stateInString
|
||||
return scanContinue
|
||||
case 'u':
|
||||
s.step = stateInStringEscU
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in string escape code")
|
||||
}
|
||||
|
||||
// stateInStringEscU is the state after reading `"\u` during a quoted string.
|
||||
func stateInStringEscU(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInStringEscU1
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateInStringEscU1 is the state after reading `"\u1` during a quoted string.
|
||||
func stateInStringEscU1(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInStringEscU12
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateInStringEscU12 is the state after reading `"\u12` during a quoted string.
|
||||
func stateInStringEscU12(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInStringEscU123
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateInStringEscU123 is the state after reading `"\u123` during a quoted string.
|
||||
func stateInStringEscU123(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInString
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateNeg is the state after reading `-` during a number.
|
||||
func stateNeg(s *scanner, c byte) int {
|
||||
if c == '0' {
|
||||
s.step = state0
|
||||
return scanContinue
|
||||
}
|
||||
if '1' <= c && c <= '9' {
|
||||
s.step = state1
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in numeric literal")
|
||||
}
|
||||
|
||||
// state1 is the state after reading a non-zero integer during a number,
|
||||
// such as after reading `1` or `100` but not `0`.
|
||||
func state1(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
s.step = state1
|
||||
return scanContinue
|
||||
}
|
||||
return state0(s, c)
|
||||
}
|
||||
|
||||
// state0 is the state after reading `0` during a number.
|
||||
func state0(s *scanner, c byte) int {
|
||||
if c == '.' {
|
||||
s.step = stateDot
|
||||
return scanContinue
|
||||
}
|
||||
if c == 'e' || c == 'E' {
|
||||
s.step = stateE
|
||||
return scanContinue
|
||||
}
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
|
||||
// stateDot is the state after reading the integer and decimal point in a number,
|
||||
// such as after reading `1.`.
|
||||
func stateDot(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
s.step = stateDot0
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "after decimal point in numeric literal")
|
||||
}
|
||||
|
||||
// stateDot0 is the state after reading the integer, decimal point, and subsequent
|
||||
// digits of a number, such as after reading `3.14`.
|
||||
func stateDot0(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
return scanContinue
|
||||
}
|
||||
if c == 'e' || c == 'E' {
|
||||
s.step = stateE
|
||||
return scanContinue
|
||||
}
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
|
||||
// stateE is the state after reading the mantissa and e in a number,
|
||||
// such as after reading `314e` or `0.314e`.
|
||||
func stateE(s *scanner, c byte) int {
|
||||
if c == '+' || c == '-' {
|
||||
s.step = stateESign
|
||||
return scanContinue
|
||||
}
|
||||
return stateESign(s, c)
|
||||
}
|
||||
|
||||
// stateESign is the state after reading the mantissa, e, and sign in a number,
|
||||
// such as after reading `314e-` or `0.314e+`.
|
||||
func stateESign(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
s.step = stateE0
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in exponent of numeric literal")
|
||||
}
|
||||
|
||||
// stateE0 is the state after reading the mantissa, e, optional sign,
|
||||
// and at least one digit of the exponent in a number,
|
||||
// such as after reading `314e-2` or `0.314e+1` or `3.14e0`.
|
||||
func stateE0(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
return scanContinue
|
||||
}
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
|
||||
// stateT is the state after reading `t`.
|
||||
func stateT(s *scanner, c byte) int {
|
||||
if c == 'r' {
|
||||
s.step = stateTr
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal true (expecting 'r')")
|
||||
}
|
||||
|
||||
// stateTr is the state after reading `tr`.
|
||||
func stateTr(s *scanner, c byte) int {
|
||||
if c == 'u' {
|
||||
s.step = stateTru
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal true (expecting 'u')")
|
||||
}
|
||||
|
||||
// stateTru is the state after reading `tru`.
|
||||
func stateTru(s *scanner, c byte) int {
|
||||
if c == 'e' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal true (expecting 'e')")
|
||||
}
|
||||
|
||||
// stateF is the state after reading `f`.
|
||||
func stateF(s *scanner, c byte) int {
|
||||
if c == 'a' {
|
||||
s.step = stateFa
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 'a')")
|
||||
}
|
||||
|
||||
// stateFa is the state after reading `fa`.
|
||||
func stateFa(s *scanner, c byte) int {
|
||||
if c == 'l' {
|
||||
s.step = stateFal
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 'l')")
|
||||
}
|
||||
|
||||
// stateFal is the state after reading `fal`.
|
||||
func stateFal(s *scanner, c byte) int {
|
||||
if c == 's' {
|
||||
s.step = stateFals
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 's')")
|
||||
}
|
||||
|
||||
// stateFals is the state after reading `fals`.
|
||||
func stateFals(s *scanner, c byte) int {
|
||||
if c == 'e' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 'e')")
|
||||
}
|
||||
|
||||
// stateN is the state after reading `n`.
|
||||
func stateN(s *scanner, c byte) int {
|
||||
if c == 'u' {
|
||||
s.step = stateNu
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal null (expecting 'u')")
|
||||
}
|
||||
|
||||
// stateNu is the state after reading `nu`.
|
||||
func stateNu(s *scanner, c byte) int {
|
||||
if c == 'l' {
|
||||
s.step = stateNul
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal null (expecting 'l')")
|
||||
}
|
||||
|
||||
// stateNul is the state after reading `nul`.
|
||||
func stateNul(s *scanner, c byte) int {
|
||||
if c == 'l' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal null (expecting 'l')")
|
||||
}
|
||||
|
||||
// stateError is the state after reaching a syntax error,
|
||||
// such as after reading `[1}` or `5.1.2`.
|
||||
func stateError(s *scanner, c byte) int {
|
||||
return scanError
|
||||
}
|
||||
|
||||
// error records an error and switches to the error state.
|
||||
func (s *scanner) error(c byte, context string) int {
|
||||
s.step = stateError
|
||||
s.err = &SyntaxError{"invalid character " + quoteChar(c) + " " + context, s.bytes}
|
||||
return scanError
|
||||
}
|
||||
|
||||
// quoteChar formats c as a quoted character literal
|
||||
func quoteChar(c byte) string {
|
||||
// special cases - different from quoted strings
|
||||
if c == '\'' {
|
||||
return `'\''`
|
||||
}
|
||||
if c == '"' {
|
||||
return `'"'`
|
||||
}
|
||||
|
||||
// use quoted string with different quotation marks
|
||||
s := strconv.Quote(string(c))
|
||||
return "'" + s[1:len(s)-1] + "'"
|
||||
}
|
||||
|
||||
// undo causes the scanner to return scanCode from the next state transition.
|
||||
// This gives callers a simple 1-byte undo mechanism.
|
||||
func (s *scanner) undo(scanCode int) {
|
||||
if s.redo {
|
||||
panic("json: invalid use of scanner")
|
||||
}
|
||||
s.redoCode = scanCode
|
||||
s.redoState = s.step
|
||||
s.step = stateRedo
|
||||
s.redo = true
|
||||
}
|
||||
|
||||
// stateRedo helps implement the scanner's 1-byte undo.
|
||||
func stateRedo(s *scanner, c byte) int {
|
||||
s.redo = false
|
||||
s.step = s.redoState
|
||||
return s.redoCode
|
||||
}
|
29
scanner_test.go
Normal file
29
scanner_test.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonld
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var validTests = []struct {
|
||||
data string
|
||||
ok bool
|
||||
}{
|
||||
{`foo`, false},
|
||||
{`}{`, false},
|
||||
{`{]`, false},
|
||||
{`{}`, true},
|
||||
{`{"foo":"bar"}`, true},
|
||||
{`{"foo":"bar","bar":{"baz":["qux"]}}`, true},
|
||||
}
|
||||
|
||||
func TestValid(t *testing.T) {
|
||||
for _, tt := range validTests {
|
||||
if ok := Valid([]byte(tt.data)); ok != tt.ok {
|
||||
t.Errorf("Valid(%#q) = %v, want %v", tt.data, ok, tt.ok)
|
||||
}
|
||||
}
|
||||
}
|
218
tables.go
Normal file
218
tables.go
Normal file
|
@ -0,0 +1,218 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonld
|
||||
|
||||
import "unicode/utf8"
|
||||
|
||||
// safeSet holds the value true if the ASCII character with the given array
|
||||
// position can be represented inside a JSON string without any further
|
||||
// escaping.
|
||||
//
|
||||
// All values are true except for the ASCII control characters (0-31), the
|
||||
// double quote ("), and the backslash character ("\").
|
||||
var safeSet = [utf8.RuneSelf]bool{
|
||||
' ': true,
|
||||
'!': true,
|
||||
'"': false,
|
||||
'#': true,
|
||||
'$': true,
|
||||
'%': true,
|
||||
'&': true,
|
||||
'\'': true,
|
||||
'(': true,
|
||||
')': true,
|
||||
'*': true,
|
||||
'+': true,
|
||||
',': true,
|
||||
'-': true,
|
||||
'.': true,
|
||||
'/': true,
|
||||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
'3': true,
|
||||
'4': true,
|
||||
'5': true,
|
||||
'6': true,
|
||||
'7': true,
|
||||
'8': true,
|
||||
'9': true,
|
||||
':': true,
|
||||
';': true,
|
||||
'<': true,
|
||||
'=': true,
|
||||
'>': true,
|
||||
'?': true,
|
||||
'@': true,
|
||||
'A': true,
|
||||
'B': true,
|
||||
'C': true,
|
||||
'D': true,
|
||||
'E': true,
|
||||
'F': true,
|
||||
'G': true,
|
||||
'H': true,
|
||||
'I': true,
|
||||
'J': true,
|
||||
'K': true,
|
||||
'L': true,
|
||||
'M': true,
|
||||
'N': true,
|
||||
'O': true,
|
||||
'P': true,
|
||||
'Q': true,
|
||||
'R': true,
|
||||
'S': true,
|
||||
'T': true,
|
||||
'U': true,
|
||||
'V': true,
|
||||
'W': true,
|
||||
'X': true,
|
||||
'Y': true,
|
||||
'Z': true,
|
||||
'[': true,
|
||||
'\\': false,
|
||||
']': true,
|
||||
'^': true,
|
||||
'_': true,
|
||||
'`': true,
|
||||
'a': true,
|
||||
'b': true,
|
||||
'c': true,
|
||||
'd': true,
|
||||
'e': true,
|
||||
'f': true,
|
||||
'g': true,
|
||||
'h': true,
|
||||
'i': true,
|
||||
'j': true,
|
||||
'k': true,
|
||||
'l': true,
|
||||
'm': true,
|
||||
'n': true,
|
||||
'o': true,
|
||||
'p': true,
|
||||
'q': true,
|
||||
'r': true,
|
||||
's': true,
|
||||
't': true,
|
||||
'u': true,
|
||||
'v': true,
|
||||
'w': true,
|
||||
'x': true,
|
||||
'y': true,
|
||||
'z': true,
|
||||
'{': true,
|
||||
'|': true,
|
||||
'}': true,
|
||||
'~': true,
|
||||
'\u007f': true,
|
||||
}
|
||||
|
||||
// htmlSafeSet holds the value true if the ASCII character with the given
|
||||
// array position can be safely represented inside a JSON string, embedded
|
||||
// inside of HTML <script> tags, without any additional escaping.
|
||||
//
|
||||
// All values are true except for the ASCII control characters (0-31), the
|
||||
// double quote ("), the backslash character ("\"), HTML opening and closing
|
||||
// tags ("<" and ">"), and the ampersand ("&").
|
||||
var htmlSafeSet = [utf8.RuneSelf]bool{
|
||||
' ': true,
|
||||
'!': true,
|
||||
'"': false,
|
||||
'#': true,
|
||||
'$': true,
|
||||
'%': true,
|
||||
'&': false,
|
||||
'\'': true,
|
||||
'(': true,
|
||||
')': true,
|
||||
'*': true,
|
||||
'+': true,
|
||||
',': true,
|
||||
'-': true,
|
||||
'.': true,
|
||||
'/': true,
|
||||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
'3': true,
|
||||
'4': true,
|
||||
'5': true,
|
||||
'6': true,
|
||||
'7': true,
|
||||
'8': true,
|
||||
'9': true,
|
||||
':': true,
|
||||
';': true,
|
||||
'<': false,
|
||||
'=': true,
|
||||
'>': false,
|
||||
'?': true,
|
||||
'@': true,
|
||||
'A': true,
|
||||
'B': true,
|
||||
'C': true,
|
||||
'D': true,
|
||||
'E': true,
|
||||
'F': true,
|
||||
'G': true,
|
||||
'H': true,
|
||||
'I': true,
|
||||
'J': true,
|
||||
'K': true,
|
||||
'L': true,
|
||||
'M': true,
|
||||
'N': true,
|
||||
'O': true,
|
||||
'P': true,
|
||||
'Q': true,
|
||||
'R': true,
|
||||
'S': true,
|
||||
'T': true,
|
||||
'U': true,
|
||||
'V': true,
|
||||
'W': true,
|
||||
'X': true,
|
||||
'Y': true,
|
||||
'Z': true,
|
||||
'[': true,
|
||||
'\\': false,
|
||||
']': true,
|
||||
'^': true,
|
||||
'_': true,
|
||||
'`': true,
|
||||
'a': true,
|
||||
'b': true,
|
||||
'c': true,
|
||||
'd': true,
|
||||
'e': true,
|
||||
'f': true,
|
||||
'g': true,
|
||||
'h': true,
|
||||
'i': true,
|
||||
'j': true,
|
||||
'k': true,
|
||||
'l': true,
|
||||
'm': true,
|
||||
'n': true,
|
||||
'o': true,
|
||||
'p': true,
|
||||
'q': true,
|
||||
'r': true,
|
||||
's': true,
|
||||
't': true,
|
||||
'u': true,
|
||||
'v': true,
|
||||
'w': true,
|
||||
'x': true,
|
||||
'y': true,
|
||||
'z': true,
|
||||
'{': true,
|
||||
'|': true,
|
||||
'}': true,
|
||||
'~': true,
|
||||
'\u007f': true,
|
||||
}
|
44
tags.go
Normal file
44
tags.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonld
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// tagOptions is the string following a comma in a struct field's "json"
|
||||
// tag, or the empty string. It does not include the leading comma.
|
||||
type tagOptions string
|
||||
|
||||
// parseTag splits a struct field's json tag into its name and
|
||||
// comma-separated options.
|
||||
func parseTag(tag string) (string, tagOptions) {
|
||||
if idx := strings.Index(tag, ","); idx != -1 {
|
||||
return tag[:idx], tagOptions(tag[idx+1:])
|
||||
}
|
||||
return tag, tagOptions("")
|
||||
}
|
||||
|
||||
// Contains reports whether a comma-separated list of options
|
||||
// contains a particular substr flag. substr must be surrounded by a
|
||||
// string boundary or commas.
|
||||
func (o tagOptions) Contains(optionName string) bool {
|
||||
if len(o) == 0 {
|
||||
return false
|
||||
}
|
||||
s := string(o)
|
||||
for s != "" {
|
||||
var next string
|
||||
i := strings.Index(s, ",")
|
||||
if i >= 0 {
|
||||
s, next = s[:i], s[i+1:]
|
||||
}
|
||||
if s == optionName {
|
||||
return true
|
||||
}
|
||||
s = next
|
||||
}
|
||||
return false
|
||||
}
|
28
tags_test.go
Normal file
28
tags_test.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonld
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTagParsing(t *testing.T) {
|
||||
name, opts := parseTag("field,foobar,foo")
|
||||
if name != "field" {
|
||||
t.Fatalf("name = %q, want field", name)
|
||||
}
|
||||
for _, tt := range []struct {
|
||||
opt string
|
||||
want bool
|
||||
}{
|
||||
{"foobar", true},
|
||||
{"foo", true},
|
||||
{"bar", false},
|
||||
} {
|
||||
if opts.Contains(tt.opt) != tt.want {
|
||||
t.Errorf("Contains(%q) = %v", tt.opt, !tt.want)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue