From c8f2534112c06648dd569a1de1f1a90dfdee2865 Mon Sep 17 00:00:00 2001 From: puffaboo Date: Tue, 2 Aug 2022 10:44:50 +0100 Subject: [PATCH] rebrand to flabk, removed jsonld code to restart --- flabk.go | 44 ++++++ flabk/ap/object.go | 36 +++++ flabk/ap/object_test.go | 52 ++++++ flabk/flabweb/flabweb.go | 46 ++++++ go.mod | 2 +- pkg/asld/asld.go | 105 ------------ pkg/asld/unmarshal.go | 299 ----------------------------------- pkg/asld/unmarshal_test.go | 173 -------------------- pkg/asld/walker.go | 263 ------------------------------ pkg/asld/walker_test.go | 190 ---------------------- pkg/jsonld/jsonld.go | 4 + pkg/jsonld/unmarshal.go | 1 + pkg/jsonld/unmarshal_test.go | 239 ++++++++++++++++++++++++++++ util/uriutil/join.go | 13 ++ 14 files changed, 436 insertions(+), 1031 deletions(-) create mode 100644 flabk.go create mode 100644 flabk/ap/object.go create mode 100644 flabk/ap/object_test.go create mode 100644 flabk/flabweb/flabweb.go delete mode 100644 pkg/asld/asld.go delete mode 100644 pkg/asld/unmarshal.go delete mode 100644 pkg/asld/unmarshal_test.go delete mode 100644 pkg/asld/walker.go delete mode 100644 pkg/asld/walker_test.go create mode 100644 pkg/jsonld/jsonld.go create mode 100644 pkg/jsonld/unmarshal.go create mode 100644 pkg/jsonld/unmarshal_test.go create mode 100644 util/uriutil/join.go diff --git a/flabk.go b/flabk.go new file mode 100644 index 0000000..5268d15 --- /dev/null +++ b/flabk.go @@ -0,0 +1,44 @@ +package main + +// MUST (client and server) application/ld+json; profile="https://www.w3.org/ns/activitystreams +// SHOULD application/activity+json + +// public actor https://www.w3.org/ns/activitystreams#Public + +func main() { + panic(asfhttp.New("my.site").Run()) +} + +// { +// "@context": ["https://www.w3.org/ns/activitystreams", +// {"@language": "en"}], +// "type": "Like", +// "actor": "https://dustycloud.org/chris/", +// "name": "Chris liked 'Minimal ActivityPub update client'", +// "object": "https://rhiaro.co.uk/2016/05/minimal-activitypub", +// "to": ["https://rhiaro.co.uk/#amy", +// "https://dustycloud.org/followers", +// "https://rhiaro.co.uk/followers/"], +// "cc": "https://e14n.com/evan" +// } + +// { +// "@context": [ +// "https://www.w3.org/ns/activitystreams", +// { +// "@language": "en" +// } +// ], +// "type": "Like", +// "to": [ +// "https://rhiaro.co.uk/#amy", +// "https://dustycloud.org/followers", +// "https://rhiaro.co.uk/followers/" +// ], +// "cc": [ +// "https://e14n.com/evan" +// ], +// "object": "https://rhiaro.co.uk/2016/05/minimal-activitypub", +// "actor": "https://dustycloud.org/chris/", +// "name": "Chris liked 'Minimal ActivityPub update client'" +// } diff --git a/flabk/ap/object.go b/flabk/ap/object.go new file mode 100644 index 0000000..089137f --- /dev/null +++ b/flabk/ap/object.go @@ -0,0 +1,36 @@ +package ap + +import ( + "github.com/go-ap/jsonld" +) + +type Object struct { + Context jsonld.Context `jsonld:"@context,omitempty,collapsible"` + Type string `jsonld:"type"` + Type2 string `jsonld:"@type"` + ID jsonld.IRI `jsonld:"id,omitempty"` + Name string `jsonld:"name,omitempty"` + Source *Source `jsonld:"source,omitempty"` + To []Object `jsonld:"to,omitempty,collapsible"` + CC []Object `jsonld:"cc,omitempty,collapsible"` + Object *Object `jsonld:"object,omitempty,collapsible"` + Actor *Actor `jsonld:"actor,omitempty,collapsible"` +} + +func (o Object) Collapse() interface{} { + return o.ID +} + +type Actor struct { + ID jsonld.IRI `jsonld:"id,omitempty"` + Name string `jsonld:"name,omitempty"` +} + +func (a Actor) Collapse() interface{} { + return a.ID +} + +type Source struct { + Content string `jsonld:"content,omitempty"` + MediaType string `jsonld:"mediaType,omitempty"` +} diff --git a/flabk/ap/object_test.go b/flabk/ap/object_test.go new file mode 100644 index 0000000..a7158ac --- /dev/null +++ b/flabk/ap/object_test.go @@ -0,0 +1,52 @@ +package ap_test + +import ( + "fmt" + "testing" + + "github.com/go-ap/jsonld" + "github.com/stretchr/testify/require" + "sectorinf.com/emilis/flabk/asf/ap" +) + +func GetExample() ap.Object { + return ap.Object{ + Context: jsonld.Context{ + { + IRI: jsonld.IRI("https://www.w3.org/ns/activitystreams"), + }, + { + Term: jsonld.LanguageKw, + IRI: "en", + }, + }, + Type: "Like", + Actor: &ap.Actor{ + ID: "https://dustycloud.org/chris/", + }, + Name: "Chris liked 'Minimal ActivityPub update client'", + Object: &ap.Object{ + ID: "https://rhiaro.co.uk/2016/05/minimal-activitypub", + }, + To: []ap.Object{ + {ID: "https://rhiaro.co.uk/#amy"}, + {ID: "https://dustycloud.org/followers"}, + {ID: "https://rhiaro.co.uk/followers/"}, + }, + CC: []ap.Object{ + {ID: "https://e14n.com/evan"}, + }, + } +} + +func TestObject(t *testing.T) { + that := require.New(t) + result, err := jsonld.Marshal(GetExample()) + that.NoError(err) + fmt.Println(string(result)) + fmt.Println() + another := ap.Object{} + err = jsonld.Unmarshal(result, &another) + that.NoError(err) + fmt.Printf("%+v\n", another) +} diff --git a/flabk/flabweb/flabweb.go b/flabk/flabweb/flabweb.go new file mode 100644 index 0000000..487682b --- /dev/null +++ b/flabk/flabweb/flabweb.go @@ -0,0 +1,46 @@ +package flabweb + +import ( + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + "sectorinf.com/emilis/flabk/util/uriutil" +) + +const ( + EndpointOutbox = "outbox" +) + +var ( + tempOutboxIDCauseImNotStoringThis = 0 +) + +type Server struct { + router *gin.Engine + v1 *gin.RouterGroup + hostname string +} + +func New(hostname string) Server { + router := gin.Default() + v1 := router.Group("v1") + server := Server{ + hostname: hostname, + router: router, + v1: v1, + } + v1.POST(EndpointOutbox, server.Outbox) + return server +} + +func (s Server) Run() error { + return s.router.Run(":8081") +} + +func (s Server) Outbox(ctx *gin.Context) { + id := tempOutboxIDCauseImNotStoringThis + tempOutboxIDCauseImNotStoringThis++ + ctx.Header("Location", uriutil.JoinURIs(s.hostname, EndpointOutbox, strconv.Itoa(id))) + ctx.Status(http.StatusCreated) +} diff --git a/go.mod b/go.mod index f9fe18a..5006d16 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module git.sectorinf.com/emilis/asflab +module sectorinf.com/emilis/flabk go 1.18 diff --git a/pkg/asld/asld.go b/pkg/asld/asld.go deleted file mode 100644 index fd55477..0000000 --- a/pkg/asld/asld.go +++ /dev/null @@ -1,105 +0,0 @@ -// Package asld handles JSON-LD for asflab -// -// This will not go well -package asld - -import ( - "bytes" - "errors" -) - -const ( - tagName = "asld" - omitEmpty = "omitempty" - collapsible = "collapsible" - nullToken = "null" -) - -var ( - ErrNoMatching = errors.New("could not find matching") - ErrSyntaxError = errors.New("syntax error") - ErrEntryWithoutValue = errors.New("entry without value") - ErrMapNotStringIndexed = errors.New("map is not string indexed") -) - -// assigned by init -var ( - stoppableByByte map[byte]SymbolInfo - stoppableRaw []byte - iriPrefixes [][]byte - openSymbols []SymbolInfo - openRaw []byte - null []byte -) - -func init() { - stoppableByByte = map[byte]SymbolInfo{} - for index, sym := range stoppable { - stoppable[index].enum = symbol(index) - stoppableByByte[sym.self] = sym - } - stoppableRaw = make([]byte, len(stoppable)) - for index, symbol := range stoppable { - stoppableRaw[index] = symbol.self - } - iriPrefixesStrings := []string{ - // Currently only doing https - "https://", - } - iriPrefixes = make([][]byte, len(iriPrefixesStrings)) - for index, prefix := range iriPrefixesStrings { - iriPrefixes[index] = []byte(prefix) - } - openSymbolEnums := []symbol{ - symbolOpenParen, symbolOpenArray, symbolString, - } - openSymbols = make([]SymbolInfo, len(openSymbolEnums)) - for index, enum := range openSymbolEnums { - openSymbols[index] = stoppable[enum] - } - openRaw = _map(openSymbols, func(s SymbolInfo) byte { - return s.self - }) - null = []byte(nullToken) -} - -type SymbolInfo struct { - self byte - closer byte - enum symbol -} - -func in[T comparable](this []T, has T) bool { - for _, elem := range this { - if elem == has { - return true - } - } - return false -} - -func firstIn[T comparable](this []T, has T) (T, bool) { - for _, elem := range this { - if elem == has { - return elem, true - } - } - return has, false -} - -func _map[T any, V any](v []T, f func(T) V) []V { - output := make([]V, len(v)) - for index, elem := range v { - output[index] = f(elem) - } - return output -} - -func isIRI(v []byte) bool { - for _, prefix := range iriPrefixes { - if bytes.Equal(v, prefix) { - return true - } - } - return false -} diff --git a/pkg/asld/unmarshal.go b/pkg/asld/unmarshal.go deleted file mode 100644 index 64310df..0000000 --- a/pkg/asld/unmarshal.go +++ /dev/null @@ -1,299 +0,0 @@ -package asld - -import ( - "bytes" - "fmt" - "reflect" - "strconv" - "strings" -) - -const ( - intSize = 32 << (^uint(0) >> 63) // unexported from "math" -) - -var ( - bitSize = map[reflect.Kind]int{ - reflect.Int: intSize, - reflect.Int8: 8, - reflect.Int16: 16, - reflect.Int32: 32, - reflect.Int64: 64, - reflect.Uint: intSize, - reflect.Uint8: 8, - reflect.Uint16: 16, - reflect.Uint32: 32, - reflect.Uint64: 64, - reflect.Float32: 32, - reflect.Float64: 64, - reflect.Complex64: 64, - reflect.Complex128: 128, - } - nullWalker = newWalker([]byte{'n', 'u', 'l', 'l'}) -) - -func arrayMembers(w walker) ([]walker, error) { - var ( - members = []walker{} - final bool - ) - - elements, ok := w.SliceInner() - if !ok { - return nil, fmt.Errorf("%s %w", string(w.content[w.position]), ErrNoMatching) - } - - for !final { - elem, err := elements.CommaOrEnd() - if err != nil { - return nil, fmt.Errorf("comma or end %s: %w", string(elem.content), err) - } - final = elem.status == statusWalkerNotAffected - value := elem.walker.Until().Reset() - if !final { - // Incremenet elem.walker.position here so - // that it skips over the comma that we're - // on right now. as there's not really anything - // valid to stop on in arrays aside from them - // so we can't just call ToNext() later. - elem.walker = elem.walker.Pos(elem.walker.position + 1) - } else { - value = elem.walker - } - elements = elem.walker.Sub() - members = append(members, value) - } - - return members, nil -} - -func mapMembers(w walker) (map[string]walker, error) { - var ( - members = map[string]walker{} - lastLine bool - //debug - prev string - ) - w, sym := w.StayOrNext() - if sym == symbolEOB { - w, sym = w.StayOrNext() - panic("idk") - } - // Because this gets called recursively with all kinds of - // inputs and spacing, we should do a Stay/Walk If Space situation - elements, ok := w.SliceInner() - if !ok { - return nil, fmt.Errorf("%s %w", string(w.content[w.position]), ErrNoMatching) - } - - w.Debug() - for !lastLine { - lineInfo, err := elements.CommaOrEnd() - if err != nil { - return nil, fmt.Errorf("comma or end: %s: %w", string(elements.content), err) - } - lastLine = lineInfo.status == statusWalkerNotAffected - line := lineInfo.walker - if !lastLine { - line = line.Until() - } - - name, ok := line.Reset().ToOrStay('"') - if !ok { - continue - } - nameString, ok := name.SliceInner() - if !ok { - // TODO: maybe these should have global position - return nil, fmt.Errorf("%s %w", string(name.Current()), ErrNoMatching) - } - - // We know this is OK because the above SliceInner called it - name, _ = name.To('"') - - wNext, sym := name.Next() - if sym != symbolEOB && wNext.Current() != ':' && isIRI(nameString.content) { - panic("IRI expansion not implemented") - } else if sym != symbolEOB && wNext.Current() != ':' { - return nil, fmt.Errorf("%s at pos %d: %w", string(nameString.content), wNext.globPos, ErrEntryWithoutValue) - } - - value, sym := wNext.Next() - if sym == symbolEOB { - if value.position < value.len-1 { - value = value.Pos(value.position + 1).Sub() - } else { - return nil, fmt.Errorf("non-IRI %s: %w", string(nameString.content), ErrEntryWithoutValue) - } - } else if sym == symbolNullStart { - value = nullWalker - } - // Walk to next viable token - value = value.WalkThroughSpaces().Sub() - elements = lineInfo.walker.Sub() - n := string(nameString.content) - print(n + "\n") - fmt.Println(value) - members[n] = value - prev = n - } - - fmt.Print(prev) - return members, nil -} - -func unmarshalStruct(out reflect.Value, w walker) error { - members, err := mapMembers(w) - if err != nil { - return fmt.Errorf("getting members: %w", err) - } - - outType := out.Type() - // Deconstruct the struct fields - for index := 0; index < out.NumField(); index++ { - field := out.Field(index) - fType := outType.Field(index) - - tagInfo := fType.Tag.Get(tagName) - // TODO: support expandible/collapsible/whatever I name it - // and omitempty probably - tagParts := strings.Split(tagInfo, ",") - name := tagParts[0] - if tagInfo == "" || name == "" { - name = fType.Name - } - // mimic encoding/json behavior - if name == "-" && len(tagParts) == 1 { - continue - } - - wField, exists := members[name] - if !exists { - continue - } - fmt.Println(wField, field) - // if err := setValue(field, wField, fType); err != nil { - // return fmt.Errorf("field %s set: %w", name, err) - // } - } - - return nil -} - -func setMap(field reflect.Value, w walker) error { - if field.IsNil() { - field.Set(reflect.MakeMap(field.Type())) - } - keyType := field.Type().Key() - if keyType.Kind() != reflect.String { - return ErrMapNotStringIndexed - } - members, err := mapMembers(w) - if err != nil { - return fmt.Errorf("getting members for map: %w", err) - } - - valueType := field.Type().Elem() - for key, member := range members { - def := reflect.Indirect(reflect.New(valueType)) - err = setValue(def, member) - if err != nil { - return fmt.Errorf( - "could not set %s to %s: %w", - string(w.content), - valueType.Kind().String(), - err, - ) - } - field.SetMapIndex(reflect.ValueOf(key), def) - } - - return nil -} - -func setValue(field reflect.Value, w walker, a ...reflect.StructField) error { - var ( - err error - setter func() - ) - if w.len >= 4 && bytes.Equal(null, w.content[:4]) { - // default value - return nil - } - - k := field.Kind() - switch k { - case reflect.String: - if w.content[0] != '"' { - err = fmt.Errorf("%s is not a string", string(w.content)) - } - setter = func() { field.SetString(w.String()) } - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - var i int64 - i, err = strconv.ParseInt(string(w.content), 10, bitSize[field.Kind()]) - setter = func() { field.SetInt(i) } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - var i uint64 - i, err = strconv.ParseUint(string(w.content), 10, bitSize[field.Kind()]) - setter = func() { field.SetUint(i) } - case reflect.Float32, reflect.Float64: - var f float64 - f, err = strconv.ParseFloat(string(w.content), bitSize[field.Kind()]) - setter = func() { field.SetFloat(f) } - case reflect.Complex64, reflect.Complex128: - var c complex128 - c, err = strconv.ParseComplex(string(w.content), bitSize[field.Kind()]) - setter = func() { field.SetComplex(c) } - case reflect.Pointer: - if field.IsNil() { - field.Set(reflect.New(field.Type().Elem())) - } - // prob oops if its nil - fDeref := reflect.Indirect(field) - return setValue(fDeref, w) - case reflect.Struct, reflect.Map, reflect.Array, reflect.Slice: - if k == reflect.Array || k == reflect.Slice && len(a) > 0 { - fmt.Println("$$$$$$$$$$$", a[0].Name) - } - return unmarshal(field, w) - case reflect.Bool: - var b bool - b, err = strconv.ParseBool(string(w.content)) - setter = func() { field.SetBool(b) } - case reflect.Chan, reflect.Func, reflect.UnsafePointer: - // Ignore - return nil - default: - panic("not implemented") - } - - if err != nil { - return fmt.Errorf("could not parse %s as %s: %w", string(w.content), field.Kind().String(), err) - } - setter() - - return nil -} - -func unmarshal(out reflect.Value, w walker) error { - switch out.Kind() { - case reflect.Struct: - return unmarshalStruct(out, w) - case reflect.Map: - return setMap(out, w) - case reflect.Array, reflect.Slice: - // do array stuff here - panic("todo") - default: - panic(out.Kind().String() + " not yet supported") - } -} - -func Unmarshal[T any](data []byte) (T, error) { - tPtr := new(T) - tValue := reflect.Indirect(reflect.ValueOf(tPtr)) - w := newWalker(data) - - // Might be array/map/interface at top level, should check - return *tPtr, unmarshal(tValue, w) -} diff --git a/pkg/asld/unmarshal_test.go b/pkg/asld/unmarshal_test.go deleted file mode 100644 index 6595b86..0000000 --- a/pkg/asld/unmarshal_test.go +++ /dev/null @@ -1,173 +0,0 @@ -package asld_test - -import ( - "encoding/json" - "fmt" - "math" - "testing" - "time" - - "git.sectorinf.com/emilis/asflab/pkg/asld" - "git.sectorinf.com/emilis/asflab/pkg/epk" - "github.com/stretchr/testify/require" -) - -type testObj struct { - String string `asld:"string" json:"string"` - NoTag string - Int int - Int8 int8 - Int16 int16 - Int32 int32 - Int64 int64 - Uint uint - Uint8 uint8 - Uint16 uint16 - Uint32 uint32 - Uint64 uint64 - Float32 float32 - Float64 float64 - // Complex64 complex64 - // Complex128 complex128 - IntPtr *int - Int8Ptr *int8 - Int16Ptr *int16 - Int32Ptr *int32 - Int64Ptr *int64 - UintPtr *uint - Uint8Ptr *uint8 - Uint16Ptr *uint16 - Uint32Ptr *uint32 - Uint64Ptr *uint64 - Float32Ptr *float32 - Float64Ptr *float64 - // Complex64Ptr *complex64 - // Complex128Ptr *complex128 - TestPtr *testObj - TestArray []testObj -} - -func TestUnmarshal(t *testing.T) { - tests := map[string]struct { - obj testObj - errCheck epk.ErrorTest - }{ - "string children": { - obj: testObj{ - String: "hello", - NoTag: "no_tag", - Int: math.MaxInt, - Int8: math.MaxInt8, - Int16: math.MaxInt16, - Int32: math.MaxInt32, - Int64: math.MaxInt64, - Uint: math.MaxUint, - Uint8: math.MaxUint8, - Uint16: math.MaxUint16, - Uint32: math.MaxUint32, - Uint64: math.MaxUint64, - Float32: math.MaxFloat32, - Float64: math.MaxFloat64, - TestPtr: &testObj{ - String: "hello2", - }, - TestArray: []testObj{ - { - String: "hello3", - }, - { - String: "hello4", - TestPtr: &testObj{ - TestArray: []testObj{ - { - String: "hello5", - }, - }, - }, - }, - }, - // Complex64: complex(math.MaxFloat32, math.MaxFloat32), - // Complex128: complex(math.MaxFloat64, math.MaxFloat64), - }, - }, - } - - for name, test := range tests { - test := test - t.Run(name, func(tt *testing.T) { - that := require.New(tt) - objJSON, err := json.MarshalIndent(test.obj, "", " ") - that.NoError(err) - result, err := asld.Unmarshal[testObj](objJSON) - that.NoError(err) - that.Equal(test.obj, result) - }) - } -} - -func TestBench(t *testing.T) { - obj := testObj{ - String: "hello", - NoTag: "no_tag", - Int: math.MaxInt, - Int8: math.MaxInt8, - Int16: math.MaxInt16, - Int32: math.MaxInt32, - Int64: math.MaxInt64, - Uint: math.MaxUint, - Uint8: math.MaxUint8, - Uint16: math.MaxUint16, - Uint32: math.MaxUint32, - Uint64: math.MaxUint64, - Float32: math.MaxFloat32, - Float64: math.MaxFloat64, - // Complex64: complex(math.MaxFloat32, math.MaxFloat32), - // Complex128: complex(math.MaxFloat64, math.MaxFloat64), - } - that := require.New(t) - asldTotal := int64(0) - jsonTotal := int64(0) - - jsonMax := int64(0) - jsonMin := math.MaxInt64 - - asldMax := int64(0) - asldMin := math.MaxInt64 - - count := int64(1 << 20) - for index := int64(0); index < count; index++ { - objJSON, err := json.Marshal(obj) - that.NoError(err) - - asldStart := time.Now() - _, err = asld.Unmarshal[testObj](objJSON) - asldDur := time.Since(asldStart) - asldTotal += int64(asldDur) - if asldDur < time.Duration(asldMin) { - asldMin = int(asldDur) - } - if asldDur > time.Duration(asldMax) { - asldMax = int64(asldDur) - } - - that.NoError(err) - a := testObj{} - - jsonStart := time.Now() - err = json.Unmarshal(objJSON, &a) - jsonDur := time.Since(jsonStart) - jsonTotal += int64(jsonDur) - - if jsonDur < time.Duration(jsonMin) { - jsonMin = int(jsonDur) - } - if jsonDur > time.Duration(jsonMax) { - jsonMax = int64(jsonDur) - } - - that.NoError(err) - } - fmt.Println(count, "runs") - fmt.Printf("json avg (%s), min (%s), max (%s)\n", time.Duration(jsonTotal/count), time.Duration(jsonMin), time.Duration(jsonMax)) - fmt.Printf("asld avg (%s), min (%s), max (%s)\n", time.Duration(asldTotal/count), time.Duration(asldMin), time.Duration(asldMax)) -} diff --git a/pkg/asld/walker.go b/pkg/asld/walker.go deleted file mode 100644 index a1bb490..0000000 --- a/pkg/asld/walker.go +++ /dev/null @@ -1,263 +0,0 @@ -package asld - -import ( - "bytes" - "fmt" -) - -// Walker.... texas ranger. -// Except he's a cringe conservative. -// -// This is also cringe but not for cringe reasons. - -type symbol byte - -const ( - symbolOpenParen symbol = iota - symbolClosedParen - symbolOpenArray - symbolClosedArray - symbolString - symbolColon - symbolNullStart - symbolEOB // End-Of-Buffer -) - -const ( - statusOK status = iota - statusError - statusWalkerNotAffected -) - -var ( - stoppable = []SymbolInfo{ - symbolOpenParen: { - self: '{', - closer: '}', - }, - symbolClosedParen: { - self: '}', - }, - symbolOpenArray: { - self: '[', - closer: ']', - }, - symbolClosedArray: { - self: ']', - }, - symbolString: { - self: '"', - closer: '"', - }, - symbolColon: { - self: ':', - }, - } -) - -type status int -type walkerStatus struct { - walker - status -} - -func (w walker) Reset() walker { - w.position = 0 - return w -} - -func (w walker) Debug() { - for index, b := range w.content { - out := string(b) - if w.position == index { - out = "<[_" + out + "_]>" - } - print(out) - } - print("\n") - print("globPos", w.globPos) - print("\n") -} - -func (w walker) SliceInner() (walker, bool) { - // the !ok scenario here is only if the code is bad - s, ok := stoppableByByte[w.Current()] - // Debug - if !ok { - panic(w) - } - var height uint - for pos := w.position + 1; pos < w.len; pos++ { - curr := w.content[pos] - if curr == s.self && s.self != s.closer { - height++ - continue - } - if curr == s.closer { - if height == 0 { - return w.Between(w.position+1, pos), ok - } - height-- - } - } - return w, false -} - -type walker struct { - content []byte - len int - position int - globPos int -} - -func newWalker(data []byte) walker { - return walker{ - content: data, - len: len(data), - } -} - -func (w walker) Between(lower, upper int) walker { - // As lower, and upper, are offsets from the beginning - // of this walker's buffer, we can diff lower and w.position - // for an offset to set global position at - w.content = w.content[lower:upper] - offset := lower - w.position - w.position = 0 - w.globPos += offset - return w -} - -// Sub returns a subwalker from the current position -func (w walker) Sub() walker { - w.content = w.content[w.position:] - w.len = len(w.content) - w.position = 0 - return w -} - -// Until returns a subwalker from position 0 to the current position -func (w walker) Until() walker { - w.content = w.content[:w.position] - w.len = len(w.content) - return w -} - -func (w walker) Pos(v int) walker { - offset := v - w.position - w.position = v - w.globPos += offset - return w -} - -// ToOrStay will stay at where the walker is if b is the -// same as the current walker position. -// -// Otherwise, calls To -func (w walker) ToOrStay(b byte) (walker, bool) { - if w.Current() == b { - return w, true - } - return w.To(b) -} - -func (w walker) To(b byte) (walker, bool) { - for pos := w.position + 1; pos < w.len; pos++ { - if w.content[pos] == b { - return w.Pos(pos), true - } - } - return w, false -} - -func (w walker) WalkThroughSpaces() walker { - for pos := w.position; pos < w.len; pos++ { - b := w.content[pos] - if _, ok := stoppableByByte[b]; ok || (b >= '0' && b <= '9') || b == '-' || w.IsNullAt(pos) { - return w.Pos(pos) - } - } - return w -} - -func (w walker) StayOrNext() (walker, symbol) { - if sym, ok := stoppableByByte[w.content[w.position]]; ok { - return w, sym.enum - } - - return w.Next() -} - -func (w walker) Next() (walker, symbol) { - w, s := w.ToNext() - return w.Sub(), s -} - -func (w walker) IsNullAt(pos int) bool { - return w.content[pos] == 'n' && w.len-pos >= 4 && bytes.Equal(w.content[pos:pos+4], null) -} - -func (w walker) ToNext() (walker, symbol) { - for pos := w.position + 1; pos < w.len; pos++ { - if w.IsNullAt(pos) { - return w.Pos(pos), symbolNullStart - } - if s, ok := stoppableByByte[w.content[pos]]; ok { - return w.Pos(pos), s.enum - } - } - return w, symbolEOB -} - -// CommaOrEnd walks to the next comma, or, if none -// is present in the current walker scope, returns -// itself with status statusWalkerNotAffected. -func (w walker) CommaOrEnd() (walkerStatus, error) { - if w.Current() == ',' { - w = w.Pos(w.position + 1) - } - for pos := w.position; pos < w.len; pos++ { - if in(openRaw, w.content[pos]) { - sb, ok := stoppableByByte[w.content[pos]] - if !ok { - panic("ok someone fucked up somewhere") - } - w, ok = w.To(sb.closer) - if !ok { - return walkerStatus{ - status: statusError, - walker: w, - }, fmt.Errorf("%w %s", ErrNoMatching, string(sb.closer)) - } - pos = w.position - continue - } - if w.content[pos] == ',' { - return walkerStatus{ - status: statusOK, - walker: w.Pos(pos), - }, nil - } - } - - return walkerStatus{ - status: statusWalkerNotAffected, - walker: w, - }, nil -} - -func (w walker) Current() byte { - return w.content[w.position] -} - -func (w walker) CurrentSymbol() (SymbolInfo, bool) { - b, ok := stoppableByByte[w.Current()] - return b, ok -} - -func (w walker) String() string { - if w.Current() == '"' { - w, _ = w.SliceInner() - } - return string(w.content) -} diff --git a/pkg/asld/walker_test.go b/pkg/asld/walker_test.go deleted file mode 100644 index 11f9a63..0000000 --- a/pkg/asld/walker_test.go +++ /dev/null @@ -1,190 +0,0 @@ -package asld - -import ( - "encoding/json" - "math" - "testing" - - "git.sectorinf.com/emilis/asflab/pkg/epk" - "github.com/stretchr/testify/require" -) - -func TestWalkerSliceInner(t *testing.T) { - tests := map[string]struct { - data string - position int - expect bool - expected string - }{ - "paren simple": { - data: `{"hello":"world"}`, - expect: true, - expected: `"hello":"world"`, - }, - "paren child nested": { - data: `{"hello":"world", "sub":{"hello":"world", "sub":{"hello":"world", "sub":{"hello":"world"}}}}`, - expect: true, - expected: `"hello":"world", "sub":{"hello":"world", "sub":{"hello":"world", "sub":{"hello":"world"}}}`, - }, - "get within string": { - data: `{"hello":"world"}`, - position: 1, - expect: true, - expected: `hello`, - }, - "inverted array": { - data: `][`, - expect: false, - expected: `][`, - }, - } - - for name, test := range tests { - test := test - // if name != "get within string" { - // continue - // } - t.Run(name, func(tt *testing.T) { - that := require.New(tt) - w := newWalker([]byte(test.data)) - w.position = test.position - sub, ok := w.SliceInner() - that.Equal(test.expect, ok) - that.Equal(test.expected, string(sub.content)) - }) - } -} - -func TestNexts(t *testing.T) { - tests := map[string]struct { - contents string - expPos int - }{ - "doesn't stay at the same location if is on a next": { - contents: `"hello"`, - expPos: 6, - }, - } - - for name, test := range tests { - test := test - t.Run(name, func(tt *testing.T) { - that := require.New(t) - w := newWalker([]byte(test.contents)) - w, ok := w.To('"') - that.True(ok) - w, sym := w.ToNext() - that.Equal(symbolEOB, sym) - that.Equal(test.expPos, w.position, "positions are not the same") - }) - } -} - -func TestMapMembers(t *testing.T) { - tests := map[string]struct { - object map[string]any - errCheck epk.ErrorTest - }{ - "all types": { - object: map[string]any{ - "hello": "world", - "object_with_children": map[string]any{ - "hello": "world", - }, - "floating point": 123.456, - "integer": -50, - "uint": uint(math.MaxUint), - "array": []string{"hello", "world"}, - }, - }, - } - - for name, test := range tests { - test := test - t.Run(name, func(tt *testing.T) { - that := require.New(tt) - objJson, err := json.Marshal(test.object) - that.NoError(err) - w := newWalker(objJson) - members, err := mapMembers(w) - if test.errCheck == nil { - test.errCheck = epk.NoError - } - test.errCheck(that, err) - for key, val := range test.object { - valJson, err := json.Marshal(val) - that.NoError(err) - walker, exists := members[key] - that.True(exists) - that.Equal(valJson, walker.content, key) - } - }) - } -} - -func jsonElemList[T any](that *require.Assertions, a any) [][]byte { - that.NotNil(a) - v, ok := a.([]T) - that.True(ok) - fields := make([][]byte, len(v)) - for index, field := range v { - jsonField, err := json.Marshal(field) - that.NoError(err) - fields[index] = jsonField - } - return fields -} - -type hello struct { - Hello string -} - -func TestArrayMembers(t *testing.T) { - tests := map[string]struct { - object any - errCheck epk.ErrorTest - toJsonElementList func(*require.Assertions, any) [][]byte - }{ - "strings": { - object: []string{"a", "b", "c", "d"}, - toJsonElementList: jsonElemList[string], - }, - "ints": { - object: []int{1, 2, 3, 4, 5, 6}, - toJsonElementList: jsonElemList[int], - }, - "floats": { - object: []float64{123.456, 789.0123, 456.789}, - toJsonElementList: jsonElemList[float64], - }, - "strings with commas": { - object: []string{"this, is what I do", "what, don't like it?"}, - toJsonElementList: jsonElemList[string], - }, - "objects": { - object: []hello{{"world"}, {"mom, dad"}}, - toJsonElementList: jsonElemList[hello], - }, - } - - for name, test := range tests { - test := test - t.Run(name, func(tt *testing.T) { - that := require.New(tt) - objJson, err := json.Marshal(test.object) - that.NoError(err) - w := newWalker(objJson) - members, err := arrayMembers(w) - if test.errCheck == nil { - test.errCheck = epk.NoError - } - test.errCheck(that, err) - expected := test.toJsonElementList(that, test.object) - that.Len(members, len(expected)) - for index, elem := range expected { - equivMember := members[index] - that.Equal(elem, equivMember.content) - } - }) - } -} diff --git a/pkg/jsonld/jsonld.go b/pkg/jsonld/jsonld.go new file mode 100644 index 0000000..60dc7fc --- /dev/null +++ b/pkg/jsonld/jsonld.go @@ -0,0 +1,4 @@ +// // Package asld handles JSON-LD for asflab +// // +// // This will not go well +package asld diff --git a/pkg/jsonld/unmarshal.go b/pkg/jsonld/unmarshal.go new file mode 100644 index 0000000..b78077e --- /dev/null +++ b/pkg/jsonld/unmarshal.go @@ -0,0 +1 @@ +package asld diff --git a/pkg/jsonld/unmarshal_test.go b/pkg/jsonld/unmarshal_test.go new file mode 100644 index 0000000..3d6ac9e --- /dev/null +++ b/pkg/jsonld/unmarshal_test.go @@ -0,0 +1,239 @@ +package asld_test + +// import ( +// "encoding/json" +// "fmt" +// "math" +// "testing" +// "time" + +// "sectorinf.com/emilis/flabk/pkg/asld" +// "sectorinf.com/emilis/flabk/pkg/epk" +// "github.com/stretchr/testify/require" +// ) + +// type testObj struct { +// String string `asld:"string" json:"string"` +// NoTag string +// Int int +// Int8 int8 +// Int16 int16 +// Int32 int32 +// Int64 int64 +// Uint uint +// Uint8 uint8 +// Uint16 uint16 +// Uint32 uint32 +// Uint64 uint64 +// Float32 float32 +// Float64 float64 +// // Complex64 complex64 +// // Complex128 complex128 +// IntPtr *int +// Int8Ptr *int8 +// Int16Ptr *int16 +// Int32Ptr *int32 +// Int64Ptr *int64 +// UintPtr *uint +// Uint8Ptr *uint8 +// Uint16Ptr *uint16 +// Uint32Ptr *uint32 +// Uint64Ptr *uint64 +// Float32Ptr *float32 +// Float64Ptr *float64 +// // Complex64Ptr *complex64 +// // Complex128Ptr *complex128 +// TestPtr *testObj +// TestArray []testObj +// } + +// func TestUnmarshal(t *testing.T) { +// tests := map[string]struct { +// obj testObj +// errCheck epk.ErrorTest +// }{ +// "string children": { +// obj: testObj{ +// String: "hello", +// NoTag: "no_tag", +// Int: math.MaxInt, +// Int8: math.MaxInt8, +// Int16: math.MaxInt16, +// Int32: math.MaxInt32, +// Int64: math.MaxInt64, +// Uint: math.MaxUint, +// Uint8: math.MaxUint8, +// Uint16: math.MaxUint16, +// Uint32: math.MaxUint32, +// Uint64: math.MaxUint64, +// Float32: math.MaxFloat32, +// Float64: math.MaxFloat64, +// TestPtr: &testObj{ +// String: "hello2", +// }, +// TestArray: []testObj{ +// { +// String: "hello3", +// }, +// { +// String: "hello4", +// TestPtr: &testObj{ +// TestArray: []testObj{ +// { +// String: "hello5", +// }, +// }, +// }, +// }, +// }, +// // Complex64: complex(math.MaxFloat32, math.MaxFloat32), +// // Complex128: complex(math.MaxFloat64, math.MaxFloat64), +// }, +// }, +// } + +// for name, test := range tests { +// test := test +// t.Run(name, func(tt *testing.T) { +// that := require.New(tt) +// objJSON, err := json.MarshalIndent(test.obj, "", " ") +// that.NoError(err) +// result, err := asld.Unmarshal[testObj](objJSON) +// that.NoError(err) +// that.Equal(test.obj, result) +// }) +// } +// } + +// func TestBench(t *testing.T) { +// obj := testObj{ +// String: "hello", +// NoTag: "no_tag", +// Int: math.MaxInt, +// Int8: math.MaxInt8, +// Int16: math.MaxInt16, +// Int32: math.MaxInt32, +// Int64: math.MaxInt64, +// Uint: math.MaxUint, +// Uint8: math.MaxUint8, +// Uint16: math.MaxUint16, +// Uint32: math.MaxUint32, +// Uint64: math.MaxUint64, +// Float32: math.MaxFloat32, +// Float64: math.MaxFloat64, +// // Complex64: complex(math.MaxFloat32, math.MaxFloat32), +// // Complex128: complex(math.MaxFloat64, math.MaxFloat64), +// } +// that := require.New(t) +// asldTotal := int64(0) +// jsonTotal := int64(0) + +// jsonMax := int64(0) +// jsonMin := math.MaxInt64 + +// asldMax := int64(0) +// asldMin := math.MaxInt64 + +// count := int64(1 << 20) +// for index := int64(0); index < count; index++ { +// objJSON, err := json.Marshal(obj) +// that.NoError(err) + +// asldStart := time.Now() +// _, err = asld.Unmarshal[testObj](objJSON) +// asldDur := time.Since(asldStart) +// asldTotal += int64(asldDur) +// if asldDur < time.Duration(asldMin) { +// asldMin = int(asldDur) +// } +// if asldDur > time.Duration(asldMax) { +// asldMax = int64(asldDur) +// } + +// that.NoError(err) +// a := testObj{} + +// jsonStart := time.Now() +// err = json.Unmarshal(objJSON, &a) +// jsonDur := time.Since(jsonStart) +// jsonTotal += int64(jsonDur) + +// if jsonDur < time.Duration(jsonMin) { +// jsonMin = int(jsonDur) +// } +// if jsonDur > time.Duration(jsonMax) { +// jsonMax = int64(jsonDur) +// } + +// that.NoError(err) +// } +// fmt.Println(count, "runs") +// fmt.Printf("json avg (%s), min (%s), max (%s)\n", time.Duration(jsonTotal/count), time.Duration(jsonMin), time.Duration(jsonMax)) +// fmt.Printf("asld avg (%s), min (%s), max (%s)\n", time.Duration(asldTotal/count), time.Duration(asldMin), time.Duration(asldMax)) +// } +// func jsonElemList[T any](that *require.Assertions, a any) [][]byte { +// that.NotNil(a) +// v, ok := a.([]T) +// that.True(ok) +// fields := make([][]byte, len(v)) +// for index, field := range v { +// jsonField, err := json.Marshal(field) +// that.NoError(err) +// fields[index] = jsonField +// } +// return fields +// } + +// type hello struct { +// Hello string +// } + +// func TestArrayMembers(t *testing.T) { +// tests := map[string]struct { +// object any +// errCheck epk.ErrorTest +// toJsonElementList func(*require.Assertions, any) [][]byte +// }{ +// "strings": { +// object: []string{"a", "b", "c", "d"}, +// toJsonElementList: jsonElemList[string], +// }, +// "ints": { +// object: []int{1, 2, 3, 4, 5, 6}, +// toJsonElementList: jsonElemList[int], +// }, +// "floats": { +// object: []float64{123.456, 789.0123, 456.789}, +// toJsonElementList: jsonElemList[float64], +// }, +// "strings with commas": { +// object: []string{"this, is what I do", "what, don't like it?"}, +// toJsonElementList: jsonElemList[string], +// }, +// "objects": { +// object: []hello{{"world"}, {"mom, dad"}}, +// toJsonElementList: jsonElemList[hello], +// }, +// } + +// for name, test := range tests { +// test := test +// t.Run(name, func(tt *testing.T) { +// that := require.New(tt) +// objJson, err := json.Marshal(test.object) +// that.NoError(err) +// w := newWalker(objJson) +// members, err := arrayMembers(w) +// if test.errCheck == nil { +// test.errCheck = epk.NoError +// } +// test.errCheck(that, err) +// expected := test.toJsonElementList(that, test.object) +// that.Len(members, len(expected)) +// for index, elem := range expected { +// equivMember := members[index] +// that.Equal(elem, equivMember.content) +// } +// }) +// } +// } diff --git a/util/uriutil/join.go b/util/uriutil/join.go new file mode 100644 index 0000000..97b6dfd --- /dev/null +++ b/util/uriutil/join.go @@ -0,0 +1,13 @@ +package uriutil + +import ( + "fmt" + "strings" +) + +func JoinURIs(base string, parts ...string) string { + if len(base) < 8 || len(base) > 8 && base[:8] != "https://" { + base = "https://" + base + } + return fmt.Sprintf("%s/%s", strings.TrimRight(base, "/"), strings.Join(parts, "/")) +}