rebrand to flabk, removed jsonld code to restart
This commit is contained in:
parent
02df70bb82
commit
c8f2534112
|
@ -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'"
|
||||
// }
|
|
@ -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"`
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
2
go.mod
2
go.mod
|
@ -1,4 +1,4 @@
|
|||
module git.sectorinf.com/emilis/asflab
|
||||
module sectorinf.com/emilis/flabk
|
||||
|
||||
go 1.18
|
||||
|
||||
|
|
105
pkg/asld/asld.go
105
pkg/asld/asld.go
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
// // Package asld handles JSON-LD for asflab
|
||||
// //
|
||||
// // This will not go well
|
||||
package asld
|
|
@ -0,0 +1 @@
|
|||
package asld
|
|
@ -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)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
|
@ -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, "/"))
|
||||
}
|
Loading…
Reference in New Issue