Compare commits
No commits in common. "0d8ad2a356e9edcd142cbead748cb36fdf4e800d" and "cd20d2c9eb12a715c05a4e106ec5692273d618ec" have entirely different histories.
0d8ad2a356
...
cd20d2c9eb
20
errors.go
20
errors.go
@ -1,20 +0,0 @@
|
|||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNotASlice = errors.New("Current object or field is not a slice!")
|
|
||||||
ErrNotAStruct = errors.New("Current object or field is not a struct!")
|
|
||||||
ErrOutOfBounds = errors.New("Index(es) out of bounds!")
|
|
||||||
ErrAppendMultipleDocuments = errors.New("Cannot append to multiple documents!")
|
|
||||||
ErrNotSliceOrStruct = errors.New("Current object or field is not a slice nor a struct!")
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
errFmtMalformedField = "Malformed field name passed: '%s'"
|
|
||||||
errFmtNotAModel = "Type '%s' is not a model"
|
|
||||||
errFmtNotHasID = "Type '%s' does not implement HasID"
|
|
||||||
errFmtModelNotRegistered = "Model not registered for type: '%s'"
|
|
||||||
)
|
|
197
gridfs.go
197
gridfs.go
@ -1,197 +0,0 @@
|
|||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/fatih/structtag"
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo/gridfs"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
|
||||||
"html/template"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GridFSFile struct {
|
|
||||||
ID primitive.ObjectID `bson:"_id"`
|
|
||||||
Name string `bson:"filename"`
|
|
||||||
Length int `bson:"length"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseFmt(format string, value any) string {
|
|
||||||
tmpl, err := template.New("filename").Parse(format)
|
|
||||||
panik(err)
|
|
||||||
w := new(strings.Builder)
|
|
||||||
err = tmpl.Execute(w, value)
|
|
||||||
panik(err)
|
|
||||||
return w.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func bucket(gfsRef GridFSReference) *gridfs.Bucket {
|
|
||||||
b, _ := gridfs.NewBucket(DB, options.GridFSBucket().SetName(gfsRef.BucketName))
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func gridFsLoad(val any, g GridFSReference, field string) any {
|
|
||||||
doc := reflect.ValueOf(val)
|
|
||||||
rdoc := reflect.ValueOf(val)
|
|
||||||
if doc.Kind() != reflect.Pointer {
|
|
||||||
doc = reflect.New(reflect.TypeOf(val))
|
|
||||||
doc.Elem().Set(reflect.ValueOf(val))
|
|
||||||
}
|
|
||||||
var next string
|
|
||||||
if len(strings.Split(field, ".")) > 1 {
|
|
||||||
next = strings.Join(strings.Split(field, ".")[1:], ".")
|
|
||||||
field = strings.Split(field, ".")[0]
|
|
||||||
} else {
|
|
||||||
next = field
|
|
||||||
}
|
|
||||||
_, rfield, ferr := getNested(field, rdoc)
|
|
||||||
if ferr != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch rfield.Kind() {
|
|
||||||
case reflect.Slice:
|
|
||||||
for i := 0; i < rfield.Len(); i++ {
|
|
||||||
cur := rfield.Index(i)
|
|
||||||
if cur.Kind() != reflect.Pointer {
|
|
||||||
tmp := reflect.New(cur.Type())
|
|
||||||
tmp.Elem().Set(cur)
|
|
||||||
cur = tmp
|
|
||||||
}
|
|
||||||
intermediate := gridFsLoad(cur.Interface(), g, next)
|
|
||||||
if intermediate == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ival := reflect.ValueOf(intermediate)
|
|
||||||
if ival.Kind() == reflect.Pointer {
|
|
||||||
ival = ival.Elem()
|
|
||||||
}
|
|
||||||
rfield.Index(i).Set(ival)
|
|
||||||
}
|
|
||||||
case reflect.Struct:
|
|
||||||
|
|
||||||
intermediate := gridFsLoad(rfield.Interface(), g, next)
|
|
||||||
if intermediate != nil {
|
|
||||||
rfield.Set(reflect.ValueOf(intermediate))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
b := bucket(g)
|
|
||||||
var found GridFSFile
|
|
||||||
cursor, err := b.Find(bson.M{"filename": parseFmt(g.FilenameFmt, val)})
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cursor.Next(context.TODO())
|
|
||||||
_ = cursor.Decode(&found)
|
|
||||||
bb := bytes.NewBuffer(nil)
|
|
||||||
_, err = b.DownloadToStream(found.ID, bb)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if rfield.Type().AssignableTo(reflect.TypeFor[[]byte]()) {
|
|
||||||
rfield.Set(reflect.ValueOf(bb.Bytes()))
|
|
||||||
} else if rfield.Type().AssignableTo(reflect.TypeFor[string]()) {
|
|
||||||
rfield.Set(reflect.ValueOf(bb.String()))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if rdoc.Kind() != reflect.Pointer {
|
|
||||||
return doc.Elem().Interface()
|
|
||||||
}
|
|
||||||
return doc.Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
func gridFsSave(val any, imodel InternalModel) error {
|
|
||||||
var rerr error
|
|
||||||
v := reflect.ValueOf(val)
|
|
||||||
el := v
|
|
||||||
if v.Kind() == reflect.Pointer {
|
|
||||||
el = el.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch el.Kind() {
|
|
||||||
case reflect.Struct:
|
|
||||||
for i := 0; i < el.NumField(); i++ {
|
|
||||||
ft := el.Type().Field(i)
|
|
||||||
fv := el.Field(i)
|
|
||||||
if !ft.IsExported() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, err := structtag.Parse(string(ft.Tag))
|
|
||||||
panik(err)
|
|
||||||
var gfsRef *GridFSReference
|
|
||||||
for kk, vv := range imodel.GridFSReferences {
|
|
||||||
if strings.HasPrefix(kk, ft.Name) {
|
|
||||||
gfsRef = &vv
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var inner = func(b *gridfs.Bucket, it reflect.Value) error {
|
|
||||||
filename := parseFmt(gfsRef.FilenameFmt, it.Interface())
|
|
||||||
contents := GridFSFile{}
|
|
||||||
curs, err2 := b.Find(bson.M{"filename": filename})
|
|
||||||
|
|
||||||
if !errors.Is(err2, mongo.ErrNoDocuments) {
|
|
||||||
_ = curs.Decode(&contents)
|
|
||||||
if !reflect.ValueOf(contents).IsZero() {
|
|
||||||
_ = b.Delete(contents.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c := it.Field(gfsRef.Idx)
|
|
||||||
var rdr io.Reader
|
|
||||||
|
|
||||||
if c.Type().AssignableTo(reflect.TypeOf([]byte{})) {
|
|
||||||
rdr = bytes.NewReader(c.Interface().([]byte))
|
|
||||||
} else if c.Type().AssignableTo(reflect.TypeOf("")) {
|
|
||||||
rdr = strings.NewReader(c.Interface().(string))
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("gridfs loader type '%s' not supported", c.Type().String())
|
|
||||||
}
|
|
||||||
_, err = b.UploadFromStream(filename, rdr)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if gfsRef != nil {
|
|
||||||
b := bucket(*gfsRef)
|
|
||||||
if fv.Kind() == reflect.Slice {
|
|
||||||
for j := 0; j < fv.Len(); j++ {
|
|
||||||
lerr := inner(b, fv.Index(j))
|
|
||||||
if lerr != nil {
|
|
||||||
return lerr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if fv.Kind() == reflect.Struct {
|
|
||||||
lerr := inner(b, fv)
|
|
||||||
if lerr != nil {
|
|
||||||
return lerr
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lerr := inner(b, el)
|
|
||||||
if lerr != nil {
|
|
||||||
return lerr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = gridFsSave(fv.Interface(), imodel)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case reflect.Slice:
|
|
||||||
for i := 0; i < el.Len(); i++ {
|
|
||||||
rerr = gridFsSave(el.Index(i).Interface(), imodel)
|
|
||||||
if rerr != nil {
|
|
||||||
return rerr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return rerr
|
|
||||||
}
|
|
25
model.go
25
model.go
@ -66,7 +66,6 @@ type IModel interface {
|
|||||||
getIdxs() []*mongo.IndexModel
|
getIdxs() []*mongo.IndexModel
|
||||||
getParsedIdxs() map[string][]InternalIndex
|
getParsedIdxs() map[string][]InternalIndex
|
||||||
serializeToStore() any
|
serializeToStore() any
|
||||||
getTypeName() string
|
|
||||||
setTypeName(str string)
|
setTypeName(str string)
|
||||||
getExists() bool
|
getExists() bool
|
||||||
setExists(n bool)
|
setExists(n bool)
|
||||||
@ -77,10 +76,6 @@ type IModel interface {
|
|||||||
setSelf(arg interface{})
|
setSelf(arg interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) getTypeName() string {
|
|
||||||
return m.typeName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) setTypeName(str string) {
|
func (m *Model) setTypeName(str string) {
|
||||||
m.typeName = str
|
m.typeName = str
|
||||||
}
|
}
|
||||||
@ -140,10 +135,10 @@ func (m *Model) Find(query interface{}, opts ...*options.FindOptions) (*Query, e
|
|||||||
qqv := reflect.New(qqt)
|
qqv := reflect.New(qqt)
|
||||||
qqv.Elem().Set(reflect.MakeSlice(qqt, 0, 0))
|
qqv.Elem().Set(reflect.MakeSlice(qqt, 0, 0))
|
||||||
qq := &Query{
|
qq := &Query{
|
||||||
model: m,
|
Model: m,
|
||||||
collection: m.getColl(),
|
Collection: m.getColl(),
|
||||||
doc: qqv.Interface(),
|
doc: qqv.Interface(),
|
||||||
op: OP_FIND_ALL,
|
Op: OP_FIND_ALL,
|
||||||
}
|
}
|
||||||
q, err := m.FindRaw(query, opts...)
|
q, err := m.FindRaw(query, opts...)
|
||||||
|
|
||||||
@ -177,7 +172,7 @@ func (m *Model) FindPaged(query interface{}, page int64, perPage int64, opts ...
|
|||||||
opts = append(opts, options.Find().SetSkip(skipAmt).SetLimit(perPage))
|
opts = append(opts, options.Find().SetSkip(skipAmt).SetLimit(perPage))
|
||||||
}
|
}
|
||||||
q, err := m.Find(query, opts...)
|
q, err := m.Find(query, opts...)
|
||||||
q.op = OP_FIND_PAGED
|
q.Op = OP_FIND_PAGED
|
||||||
return q, err
|
return q, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,11 +195,11 @@ func (m *Model) FindOne(query interface{}, options ...*options.FindOneOptions) (
|
|||||||
v := reflect.New(reflect.TypeOf(qqn))
|
v := reflect.New(reflect.TypeOf(qqn))
|
||||||
v.Elem().Set(reflect.ValueOf(qqn))
|
v.Elem().Set(reflect.ValueOf(qqn))
|
||||||
qq := &Query{
|
qq := &Query{
|
||||||
collection: m.getColl(),
|
Collection: m.getColl(),
|
||||||
rawDoc: raw,
|
rawDoc: raw,
|
||||||
doc: v.Elem().Interface(),
|
doc: v.Elem().Interface(),
|
||||||
op: OP_FIND_ONE,
|
Op: OP_FIND_ONE,
|
||||||
model: m,
|
Model: m,
|
||||||
}
|
}
|
||||||
qq.rawDoc = raw
|
qq.rawDoc = raw
|
||||||
err = rip.Decode(qq.doc)
|
err = rip.Decode(qq.doc)
|
||||||
@ -264,7 +259,7 @@ func (m *Model) Append(field string, a ...interface{}) error {
|
|||||||
fv = fv.Elem()
|
fv = fv.Elem()
|
||||||
}
|
}
|
||||||
if fv.Kind() != reflect.Slice {
|
if fv.Kind() != reflect.Slice {
|
||||||
return ErrNotASlice
|
return fmt.Errorf("Current object is not a slice!")
|
||||||
}
|
}
|
||||||
for _, b := range a {
|
for _, b := range a {
|
||||||
val := reflect.ValueOf(incrementTagged(b))
|
val := reflect.ValueOf(incrementTagged(b))
|
||||||
@ -296,7 +291,7 @@ func (m *Model) Pull(field string, a ...any) error {
|
|||||||
fv = fv.Elem()
|
fv = fv.Elem()
|
||||||
}
|
}
|
||||||
if fv.Kind() != reflect.Slice {
|
if fv.Kind() != reflect.Slice {
|
||||||
return ErrNotASlice
|
return fmt.Errorf("Current object is not a slice!")
|
||||||
}
|
}
|
||||||
outer:
|
outer:
|
||||||
for _, b := range a {
|
for _, b := range a {
|
||||||
@ -337,7 +332,7 @@ func (m *Model) Swap(field string, i, j int) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if i >= fv.Len() || j >= fv.Len() {
|
if i >= fv.Len() || j >= fv.Len() {
|
||||||
return ErrOutOfBounds
|
return fmt.Errorf("index(es) out of bounds")
|
||||||
}
|
}
|
||||||
oi := fv.Index(i).Interface()
|
oi := fv.Index(i).Interface()
|
||||||
oj := fv.Index(j).Interface()
|
oj := fv.Index(j).Interface()
|
||||||
|
@ -127,7 +127,7 @@ func doSave(c *mongo.Collection, isNew bool, arg interface{}) error {
|
|||||||
var err error
|
var err error
|
||||||
m, ok := arg.(IModel)
|
m, ok := arg.(IModel)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf(errFmtNotAModel, nameOf(arg))
|
return fmt.Errorf("type '%s' is not a model", nameOf(arg))
|
||||||
}
|
}
|
||||||
m.setSelf(m)
|
m.setSelf(m)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
@ -138,7 +138,6 @@ func doSave(c *mongo.Collection, isNew bool, arg interface{}) error {
|
|||||||
vp.Elem().Set(selfo)
|
vp.Elem().Set(selfo)
|
||||||
}
|
}
|
||||||
var asHasId = vp.Interface().(HasID)
|
var asHasId = vp.Interface().(HasID)
|
||||||
var asModel = vp.Interface().(IModel)
|
|
||||||
if isNew {
|
if isNew {
|
||||||
m.setCreated(now)
|
m.setCreated(now)
|
||||||
}
|
}
|
||||||
@ -157,8 +156,6 @@ func doSave(c *mongo.Collection, isNew bool, arg interface{}) error {
|
|||||||
(asHasId).SetId(pnid)
|
(asHasId).SetId(pnid)
|
||||||
}
|
}
|
||||||
incrementAll(asHasId)
|
incrementAll(asHasId)
|
||||||
_, im, _ := ModelRegistry.HasByName(asModel.getTypeName())
|
|
||||||
_ = gridFsSave(asHasId, *im)
|
|
||||||
|
|
||||||
_, err = c.InsertOne(context.TODO(), m.serializeToStore())
|
_, err = c.InsertOne(context.TODO(), m.serializeToStore())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -173,7 +170,7 @@ func doDelete(m *Model, arg interface{}) error {
|
|||||||
self, ok := arg.(HasID)
|
self, ok := arg.(HasID)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf(errFmtNotHasID, nameOf(arg))
|
return fmt.Errorf("Object '%s' does not implement HasID", nameOf(arg))
|
||||||
}
|
}
|
||||||
c := m.getColl()
|
c := m.getColl()
|
||||||
_, err := c.DeleteOne(context.TODO(), bson.M{"_id": self.Id()})
|
_, err := c.DeleteOne(context.TODO(), bson.M{"_id": self.Id()})
|
||||||
|
143
model_test.go
143
model_test.go
@ -11,14 +11,14 @@ import (
|
|||||||
|
|
||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
initTest()
|
initTest()
|
||||||
doc := Create(iti_single()).(*story)
|
doc := Create(&iti_single).(*story)
|
||||||
assert.Equal(t, iti_single().Title, doc.Title)
|
assert.Equal(t, iti_single.Title, doc.Title)
|
||||||
assert.Equal(t, iti_single().Chapters[0].Summary, doc.Chapters[0].Summary)
|
assert.Equal(t, iti_single.Chapters[0].Summary, doc.Chapters[0].Summary)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSave(t *testing.T) {
|
func TestSave(t *testing.T) {
|
||||||
initTest()
|
initTest()
|
||||||
storyDoc := Create(iti_multi()).(*story)
|
storyDoc := Create(iti_multi).(*story)
|
||||||
lauthor := Create(author).(*user)
|
lauthor := Create(author).(*user)
|
||||||
storyDoc.Author = lauthor
|
storyDoc.Author = lauthor
|
||||||
assert.Equal(t, storyDoc.Id(), int64(0))
|
assert.Equal(t, storyDoc.Id(), int64(0))
|
||||||
@ -37,8 +37,8 @@ func TestSave(t *testing.T) {
|
|||||||
func TestPopulate(t *testing.T) {
|
func TestPopulate(t *testing.T) {
|
||||||
initTest()
|
initTest()
|
||||||
|
|
||||||
bandDoc := Create(iti_single().Chapters[0].Bands[0]).(*band)
|
bandDoc := Create(iti_single.Chapters[0].Bands[0]).(*band)
|
||||||
storyDoc := Create(iti_single()).(*story)
|
storyDoc := Create(iti_single).(*story)
|
||||||
mauthor := Create(author).(*user)
|
mauthor := Create(author).(*user)
|
||||||
saveDoc(t, mauthor)
|
saveDoc(t, mauthor)
|
||||||
saveDoc(t, bandDoc)
|
saveDoc(t, bandDoc)
|
||||||
@ -79,8 +79,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
|
|
||||||
func TestModel_FindAll(t *testing.T) {
|
func TestModel_FindAll(t *testing.T) {
|
||||||
initTest()
|
initTest()
|
||||||
im := iti_multi()
|
createAndSave(t, &iti_multi)
|
||||||
createAndSave(t, &im)
|
|
||||||
smodel := Create(story{}).(*story)
|
smodel := Create(story{}).(*story)
|
||||||
query, err := smodel.Find(bson.M{}, options.Find())
|
query, err := smodel.Find(bson.M{}, options.Find())
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
@ -91,13 +90,12 @@ func TestModel_FindAll(t *testing.T) {
|
|||||||
|
|
||||||
func TestModel_PopulateMulti(t *testing.T) {
|
func TestModel_PopulateMulti(t *testing.T) {
|
||||||
initTest()
|
initTest()
|
||||||
bandDoc := Create(iti_single().Chapters[0].Bands[0]).(*band)
|
bandDoc := Create(iti_single.Chapters[0].Bands[0]).(*band)
|
||||||
saveDoc(t, bandDoc)
|
saveDoc(t, bandDoc)
|
||||||
mauthor := Create(author).(*user)
|
mauthor := Create(author).(*user)
|
||||||
saveDoc(t, mauthor)
|
saveDoc(t, mauthor)
|
||||||
im := iti_multi()
|
iti_multi.Author = mauthor
|
||||||
im.Author = mauthor
|
createAndSave(t, &iti_multi)
|
||||||
createAndSave(t, &im)
|
|
||||||
smodel := Create(story{}).(*story)
|
smodel := Create(story{}).(*story)
|
||||||
query, err := smodel.Find(bson.M{}, options.Find())
|
query, err := smodel.Find(bson.M{}, options.Find())
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
@ -109,52 +107,6 @@ func TestModel_PopulateMulti(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestModel_PopulateChained_Multi(t *testing.T) {
|
|
||||||
initTest()
|
|
||||||
im := iti_multi()
|
|
||||||
bandDoc := Create(iti_single().Chapters[0].Bands[0]).(*band)
|
|
||||||
saveDoc(t, bandDoc)
|
|
||||||
mauthor := Create(author).(*user)
|
|
||||||
saveDoc(t, mauthor)
|
|
||||||
im.Author = mauthor
|
|
||||||
createAndSave(t, &im)
|
|
||||||
smodel := Create(story{}).(*story)
|
|
||||||
query, err := smodel.Find(bson.M{}, options.Find())
|
|
||||||
assert.Equal(t, nil, err)
|
|
||||||
final := CreateSlice(story{})
|
|
||||||
query.Populate("Author").Populate("Chapters.Bands").Exec(&final)
|
|
||||||
assert.Greater(t, len(final), 0)
|
|
||||||
for _, s := range final {
|
|
||||||
assert.NotZero(t, s.Chapters[0].Bands[0].Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPopulate_Chained(t *testing.T) {
|
|
||||||
initTest()
|
|
||||||
|
|
||||||
bandDoc := Create(iti_single().Chapters[0].Bands[0]).(*band)
|
|
||||||
storyDoc := Create(iti_single()).(*story)
|
|
||||||
mauthor := Create(author).(*user)
|
|
||||||
saveDoc(t, mauthor)
|
|
||||||
saveDoc(t, bandDoc)
|
|
||||||
storyDoc.Author = mauthor
|
|
||||||
saveDoc(t, storyDoc)
|
|
||||||
assert.Greater(t, storyDoc.ID, int64(0))
|
|
||||||
|
|
||||||
smodel := Create(story{}).(*story)
|
|
||||||
q, err := smodel.FindByID(storyDoc.ID)
|
|
||||||
assert.Equal(t, nil, err)
|
|
||||||
assert.NotPanics(t, func() {
|
|
||||||
foundDoc := &story{}
|
|
||||||
q.Populate("Author").Populate("Chapters.Bands").Exec(foundDoc)
|
|
||||||
j, _ := q.JSON()
|
|
||||||
fmt.Printf("%s\n", j)
|
|
||||||
})
|
|
||||||
for _, c := range storyDoc.Chapters {
|
|
||||||
assert.NotZero(t, c.Bands[0].Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestModel_Append(t *testing.T) {
|
func TestModel_Append(t *testing.T) {
|
||||||
initTest()
|
initTest()
|
||||||
bandDoc := Create(metallica).(*band)
|
bandDoc := Create(metallica).(*band)
|
||||||
@ -184,7 +136,7 @@ func TestModel_Delete(t *testing.T) {
|
|||||||
|
|
||||||
func TestModel_Pull(t *testing.T) {
|
func TestModel_Pull(t *testing.T) {
|
||||||
initTest()
|
initTest()
|
||||||
storyDoc := Create(iti_multi()).(*story)
|
storyDoc := Create(iti_multi).(*story)
|
||||||
smodel := Create(story{}).(*story)
|
smodel := Create(story{}).(*story)
|
||||||
saveDoc(t, storyDoc)
|
saveDoc(t, storyDoc)
|
||||||
err := storyDoc.Pull("Chapters", storyDoc.Chapters[4])
|
err := storyDoc.Pull("Chapters", storyDoc.Chapters[4])
|
||||||
@ -200,78 +152,15 @@ func TestModel_Pull(t *testing.T) {
|
|||||||
|
|
||||||
func TestModel_Swap(t *testing.T) {
|
func TestModel_Swap(t *testing.T) {
|
||||||
initTest()
|
initTest()
|
||||||
is := iti_single()
|
iti_single.Author = &author
|
||||||
is.Author = &author
|
storyDoc := Create(iti_single).(*story)
|
||||||
storyDoc := Create(iti_single()).(*story)
|
|
||||||
saveDoc(t, storyDoc)
|
saveDoc(t, storyDoc)
|
||||||
storyDoc.Chapters[0].Bands = append(storyDoc.Chapters[0].Bands, bodom)
|
storyDoc.Chapters[0].Bands = append(storyDoc.Chapters[0].Bands, metallica)
|
||||||
assert.Equal(t, 2, len(storyDoc.Chapters[0].Bands))
|
assert.Equal(t, 2, len(storyDoc.Chapters[0].Bands))
|
||||||
err := storyDoc.Swap("Chapters[0].Bands", 0, 1)
|
err := storyDoc.Swap("Chapters[0].Bands", 0, 1)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
c := storyDoc.Chapters[0].Bands
|
c := storyDoc.Chapters[0].Bands
|
||||||
assert.Equal(t, bodom.ID, c[0].ID)
|
assert.Equal(t, metallica.ID, c[0].ID)
|
||||||
assert.Equal(t, diamondHead.ID, c[1].ID)
|
assert.Equal(t, dh.ID, c[1].ID)
|
||||||
saveDoc(t, storyDoc)
|
saveDoc(t, storyDoc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestModel_GridFSLoad(t *testing.T) {
|
|
||||||
initTest()
|
|
||||||
ModelRegistry.Model(somethingWithNestedChapters{})
|
|
||||||
model := Create(somethingWithNestedChapters{}).(*somethingWithNestedChapters)
|
|
||||||
thingDoc := Create(doSomethingWithNested()).(*somethingWithNestedChapters)
|
|
||||||
found := &somethingWithNestedChapters{}
|
|
||||||
|
|
||||||
saveDoc(t, thingDoc)
|
|
||||||
assert.NotZero(t, thingDoc.ID)
|
|
||||||
fq, err := model.FindByID(thingDoc.ID)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
fq.LoadFile("NestedText", "Chapters.Text").Exec(found)
|
|
||||||
assert.NotZero(t, found.NestedText)
|
|
||||||
assert.NotZero(t, len(found.Chapters))
|
|
||||||
for _, c := range found.Chapters {
|
|
||||||
assert.NotZero(t, c.Text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestModel_GridFSLoad_Chained(t *testing.T) {
|
|
||||||
initTest()
|
|
||||||
ModelRegistry.Model(somethingWithNestedChapters{})
|
|
||||||
model := Create(somethingWithNestedChapters{}).(*somethingWithNestedChapters)
|
|
||||||
thingDoc := Create(doSomethingWithNested()).(*somethingWithNestedChapters)
|
|
||||||
found := &somethingWithNestedChapters{}
|
|
||||||
|
|
||||||
saveDoc(t, thingDoc)
|
|
||||||
assert.NotZero(t, thingDoc.ID)
|
|
||||||
fq, err := model.FindByID(thingDoc.ID)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
fq.LoadFile("NestedText").LoadFile("Chapters.Text").Exec(found)
|
|
||||||
assert.NotZero(t, found.NestedText)
|
|
||||||
assert.NotZero(t, len(found.Chapters))
|
|
||||||
for _, c := range found.Chapters {
|
|
||||||
assert.NotZero(t, c.Text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestModel_GridFSLoad_Complex(t *testing.T) {
|
|
||||||
initTest()
|
|
||||||
model := Create(story{}).(*story)
|
|
||||||
bandDoc := Create(iti_single().Chapters[0].Bands[0]).(*band)
|
|
||||||
thingDoc := Create(iti_multi()).(*story)
|
|
||||||
mauthor := Create(author).(*user)
|
|
||||||
found := &story{}
|
|
||||||
saveDoc(t, bandDoc)
|
|
||||||
saveDoc(t, mauthor)
|
|
||||||
thingDoc.Author = mauthor
|
|
||||||
saveDoc(t, thingDoc)
|
|
||||||
assert.NotZero(t, thingDoc.ID)
|
|
||||||
fq, err := model.FindByID(thingDoc.ID)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
fq.Populate("Author", "Chapters.Bands").LoadFile("Chapters.Text").Exec(found)
|
|
||||||
assert.NotZero(t, len(found.Chapters))
|
|
||||||
for _, c := range found.Chapters {
|
|
||||||
assert.NotZero(t, c.Text)
|
|
||||||
assert.NotZero(t, c.Bands[0].Name)
|
|
||||||
}
|
|
||||||
j, _ := fq.JSON()
|
|
||||||
fmt.Printf("%s\n", j)
|
|
||||||
}
|
|
||||||
|
49
query.go
49
query.go
@ -15,9 +15,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
collection *mongo.Collection
|
// the handle of the collection associated with this query
|
||||||
op string
|
Collection *mongo.Collection
|
||||||
model *Model
|
// the operation from which this query stems
|
||||||
|
Op string
|
||||||
|
// the model instance associated with this query
|
||||||
|
Model *Model
|
||||||
done bool
|
done bool
|
||||||
rawDoc any
|
rawDoc any
|
||||||
doc any
|
doc any
|
||||||
@ -184,41 +187,9 @@ func populate(r Reference, rcoll string, rawDoc interface{}, d string, src inter
|
|||||||
return src
|
return src
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadFile - loads the contents of one or more files
|
|
||||||
// stored in gridFS into the fields named by `fields`.
|
|
||||||
//
|
|
||||||
// gridFS fields can be either a `string` or `[]byte`, and are
|
|
||||||
// tagged with `gridfs:"BUCKET,FILE_FORMAT`
|
|
||||||
// where:
|
|
||||||
// - `BUCKET` is the name of the bucket where the files are stored
|
|
||||||
// - `FILE_FORMAT` is any valid go template string that resolves to
|
|
||||||
// the unique file name.
|
|
||||||
// all exported values and methods present in the surrounding
|
|
||||||
// struct can be used in this template.
|
|
||||||
func (q *Query) LoadFile(fields ...string) *Query {
|
|
||||||
_, cm, _ := ModelRegistry.HasByName(q.model.typeName)
|
|
||||||
if cm != nil {
|
|
||||||
for _, field := range fields {
|
|
||||||
var r GridFSReference
|
|
||||||
hasAnnotated := false
|
|
||||||
for k2, v := range cm.GridFSReferences {
|
|
||||||
if strings.HasPrefix(k2, field) {
|
|
||||||
r = v
|
|
||||||
hasAnnotated = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hasAnnotated {
|
|
||||||
q.doc = gridFsLoad(q.doc, r, field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return q
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate populates document references via reflection
|
// Populate populates document references via reflection
|
||||||
func (q *Query) Populate(fields ...string) *Query {
|
func (q *Query) Populate(fields ...string) *Query {
|
||||||
_, cm, _ := ModelRegistry.HasByName(q.model.typeName)
|
_, cm, _ := ModelRegistry.HasByName(q.Model.typeName)
|
||||||
|
|
||||||
if cm != nil {
|
if cm != nil {
|
||||||
rawDoc := q.rawDoc
|
rawDoc := q.rawDoc
|
||||||
@ -283,7 +254,7 @@ func (q *Query) Populate(fields ...string) *Query {
|
|||||||
func (q *Query) reOrganize() {
|
func (q *Query) reOrganize() {
|
||||||
var trvo reflect.Value
|
var trvo reflect.Value
|
||||||
if arr, ok := q.rawDoc.(bson.A); ok {
|
if arr, ok := q.rawDoc.(bson.A); ok {
|
||||||
typ := ModelRegistry[q.model.typeName].Type
|
typ := ModelRegistry[q.Model.typeName].Type
|
||||||
if typ.Kind() != reflect.Pointer {
|
if typ.Kind() != reflect.Pointer {
|
||||||
typ = reflect.PointerTo(typ)
|
typ = reflect.PointerTo(typ)
|
||||||
}
|
}
|
||||||
@ -497,7 +468,7 @@ func handleAnon(raw interface{}, rtype reflect.Type, rval reflect.Value) reflect
|
|||||||
|
|
||||||
// JSON - marshals this Query's results into json format
|
// JSON - marshals this Query's results into json format
|
||||||
func (q *Query) JSON() (string, error) {
|
func (q *Query) JSON() (string, error) {
|
||||||
res, err := json.MarshalIndent(q.doc, "", "\t")
|
res, err := json.MarshalIndent(q.doc, "\n", "\t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -524,6 +495,6 @@ func (q *Query) Exec(result interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
reflect.ValueOf(result).Elem().Set(reflect.ValueOf(q.doc).Elem())
|
reflect.ValueOf(result).Elem().Set(reflect.ValueOf(q.doc).Elem())
|
||||||
q.model.self = q.doc
|
q.Model.self = q.doc
|
||||||
q.done = true
|
q.done = true
|
||||||
}
|
}
|
||||||
|
76
registry.go
76
registry.go
@ -24,7 +24,6 @@ type InternalModel struct {
|
|||||||
Collection string
|
Collection string
|
||||||
References map[string]Reference
|
References map[string]Reference
|
||||||
Indexes map[string][]InternalIndex
|
Indexes map[string][]InternalIndex
|
||||||
GridFSReferences map[string]GridFSReference
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reference stores a typed document reference
|
// Reference stores a typed document reference
|
||||||
@ -44,13 +43,6 @@ type Reference struct {
|
|||||||
Exists bool
|
Exists bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type GridFSReference struct {
|
|
||||||
BucketName string
|
|
||||||
FilenameFmt string
|
|
||||||
LoadType reflect.Type
|
|
||||||
Idx int
|
|
||||||
}
|
|
||||||
|
|
||||||
type TModelRegistry map[string]*InternalModel
|
type TModelRegistry map[string]*InternalModel
|
||||||
|
|
||||||
// ModelRegistry - the ModelRegistry stores a map containing
|
// ModelRegistry - the ModelRegistry stores a map containing
|
||||||
@ -90,36 +82,6 @@ func getRawTypeFromTag(tagOpt string, slice bool) reflect.Type {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeGfsRef(tag *structtag.Tag, idx int) GridFSReference {
|
|
||||||
opts := tag.Options
|
|
||||||
var ffmt string
|
|
||||||
if len(opts) < 1 {
|
|
||||||
ffmt = "%s"
|
|
||||||
} else {
|
|
||||||
ffmt = opts[0]
|
|
||||||
}
|
|
||||||
var typ reflect.Type
|
|
||||||
if len(opts) < 2 {
|
|
||||||
typ = reflect.TypeOf("")
|
|
||||||
} else {
|
|
||||||
switch opts[1] {
|
|
||||||
case "bytes":
|
|
||||||
typ = reflect.TypeOf([]byte{})
|
|
||||||
case "string":
|
|
||||||
typ = reflect.TypeOf("")
|
|
||||||
default:
|
|
||||||
typ = reflect.TypeOf("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return GridFSReference{
|
|
||||||
FilenameFmt: ffmt,
|
|
||||||
BucketName: tag.Name,
|
|
||||||
LoadType: typ,
|
|
||||||
Idx: idx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeRef(idx int, modelName string, fieldName string, ht reflect.Type) Reference {
|
func makeRef(idx int, modelName string, fieldName string, ht reflect.Type) Reference {
|
||||||
if modelName != "" {
|
if modelName != "" {
|
||||||
if ModelRegistry.Index(modelName) != -1 {
|
if ModelRegistry.Index(modelName) != -1 {
|
||||||
@ -144,11 +106,10 @@ func makeRef(idx int, modelName string, fieldName string, ht reflect.Type) Refer
|
|||||||
panic("model name was empty")
|
panic("model name was empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTags(t reflect.Type, v reflect.Value) (map[string][]InternalIndex, map[string]Reference, map[string]GridFSReference, string) {
|
func parseTags(t reflect.Type, v reflect.Value) (map[string][]InternalIndex, map[string]Reference, string) {
|
||||||
coll := ""
|
coll := ""
|
||||||
refs := make(map[string]Reference)
|
refs := make(map[string]Reference, 0)
|
||||||
idcs := make(map[string][]InternalIndex)
|
idcs := make(map[string][]InternalIndex, 0)
|
||||||
gfsRefs := make(map[string]GridFSReference)
|
|
||||||
|
|
||||||
for i := 0; i < v.NumField(); i++ {
|
for i := 0; i < v.NumField(); i++ {
|
||||||
sft := t.Field(i)
|
sft := t.Field(i)
|
||||||
@ -165,17 +126,15 @@ func parseTags(t reflect.Type, v reflect.Value) (map[string][]InternalIndex, map
|
|||||||
ft = ft.Elem()
|
ft = ft.Elem()
|
||||||
if _, ok := tags.Get("ref"); ok != nil {
|
if _, ok := tags.Get("ref"); ok != nil {
|
||||||
if ft.Kind() == reflect.Struct {
|
if ft.Kind() == reflect.Struct {
|
||||||
ii2, rr2, gg2, _ := parseTags(ft, reflect.New(ft).Elem())
|
ii2, rr2, _ := parseTags(ft, reflect.New(ft).Elem())
|
||||||
for k, vv := range ii2 {
|
for k, v := range ii2 {
|
||||||
idcs[sft.Name+"."+k] = vv
|
idcs[sft.Name+"."+k] = v
|
||||||
}
|
}
|
||||||
for k, vv := range rr2 {
|
for k, v := range rr2 {
|
||||||
refs[sft.Name+"."+k] = vv
|
refs[sft.Name+"."+k] = v
|
||||||
}
|
|
||||||
for k, vv := range gg2 {
|
|
||||||
gfsRefs[sft.Name+"."+k] = vv
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case reflect.Pointer:
|
case reflect.Pointer:
|
||||||
@ -201,26 +160,18 @@ func parseTags(t reflect.Type, v reflect.Value) (map[string][]InternalIndex, map
|
|||||||
sname := sft.Name + "@" + refTag.Name
|
sname := sft.Name + "@" + refTag.Name
|
||||||
refs[sname] = makeRef(i, refTag.Name, sft.Name, sft.Type)
|
refs[sname] = makeRef(i, refTag.Name, sft.Name, sft.Type)
|
||||||
}
|
}
|
||||||
if gtag, ok := tags.Get("gridfs"); ok == nil {
|
|
||||||
sname := sft.Name + "@" + gtag.Name
|
|
||||||
gfsRefs[sname] = makeGfsRef(gtag, i)
|
|
||||||
}
|
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
idxTag, err := tags.Get("idx")
|
idxTag, err := tags.Get("idx")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
idcs[sft.Name] = scanIndex(idxTag.Value())
|
idcs[sft.Name] = scanIndex(idxTag.Value())
|
||||||
}
|
}
|
||||||
if gtag, ok := tags.Get("gridfs"); ok == nil {
|
|
||||||
sname := sft.Name + "@" + gtag.Name
|
|
||||||
gfsRefs[sname] = makeGfsRef(gtag, i)
|
|
||||||
}
|
|
||||||
shouldContinue = false
|
shouldContinue = false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return idcs, refs, gfsRefs, coll
|
return idcs, refs, coll
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has returns the model typename and InternalModel instance corresponding
|
// Has returns the model typename and InternalModel instance corresponding
|
||||||
@ -240,7 +191,7 @@ func (r TModelRegistry) Has(i interface{}) (string, *InternalModel, bool) {
|
|||||||
|
|
||||||
// HasByName functions almost identically to Has,
|
// HasByName functions almost identically to Has,
|
||||||
// except that it takes a string as its argument.
|
// except that it takes a string as its argument.
|
||||||
func (r TModelRegistry) HasByName(n string) (string, *InternalModel, bool) {
|
func (t TModelRegistry) HasByName(n string) (string, *InternalModel, bool) {
|
||||||
if t, ok := ModelRegistry[n]; ok {
|
if t, ok := ModelRegistry[n]; ok {
|
||||||
return n, t, true
|
return n, t, true
|
||||||
}
|
}
|
||||||
@ -255,7 +206,7 @@ func (r TModelRegistry) Index(n string) int {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r TModelRegistry) new_(n string) interface{} {
|
func (t TModelRegistry) new_(n string) interface{} {
|
||||||
if name, m, ok := ModelRegistry.HasByName(n); ok {
|
if name, m, ok := ModelRegistry.HasByName(n); ok {
|
||||||
v := reflect.New(m.Type)
|
v := reflect.New(m.Type)
|
||||||
df := v.Elem().Field(m.Idx)
|
df := v.Elem().Field(m.Idx)
|
||||||
@ -311,7 +262,7 @@ func (r TModelRegistry) Model(mdl ...any) {
|
|||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
panic("A model must embed the Model struct!")
|
panic("A model must embed the Model struct!")
|
||||||
}
|
}
|
||||||
inds, refs, gfs, coll := parseTags(t, v)
|
inds, refs, coll := parseTags(t, v)
|
||||||
if coll == "" {
|
if coll == "" {
|
||||||
panic(fmt.Sprintf("a model needs to be given a collection name! (passed type: %s)", n))
|
panic(fmt.Sprintf("a model needs to be given a collection name! (passed type: %s)", n))
|
||||||
}
|
}
|
||||||
@ -321,7 +272,6 @@ func (r TModelRegistry) Model(mdl ...any) {
|
|||||||
Collection: coll,
|
Collection: coll,
|
||||||
Indexes: inds,
|
Indexes: inds,
|
||||||
References: refs,
|
References: refs,
|
||||||
GridFSReferences: gfs,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for k, v := range ModelRegistry {
|
for k, v := range ModelRegistry {
|
||||||
|
64
testing.go
64
testing.go
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -32,7 +31,7 @@ type chapter struct {
|
|||||||
LoggedInOnly bool `bson:"loggedInOnly" json:"loggedInOnly" form:"loggedInOnly"`
|
LoggedInOnly bool `bson:"loggedInOnly" json:"loggedInOnly" form:"loggedInOnly"`
|
||||||
Posted time.Time `bson:"datePosted,omitempty" json:"datePosted"`
|
Posted time.Time `bson:"datePosted,omitempty" json:"datePosted"`
|
||||||
FileName string `json:"fileName" bson:"-"`
|
FileName string `json:"fileName" bson:"-"`
|
||||||
Text string `json:"text" bson:"-" gridfs:"story_text,/stories/{{.ChapterID}}.txt"`
|
Text string `json:"text" bson:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type band struct {
|
type band struct {
|
||||||
@ -60,20 +59,6 @@ type story struct {
|
|||||||
Completed bool `bson:"completed" json:"completed" form:"completed"`
|
Completed bool `bson:"completed" json:"completed" form:"completed"`
|
||||||
Downloads int `bson:"downloads" json:"downloads"`
|
Downloads int `bson:"downloads" json:"downloads"`
|
||||||
}
|
}
|
||||||
type somethingWithNestedChapters struct {
|
|
||||||
ID int64 `bson:"_id" json:"_id"`
|
|
||||||
Model `bson:",inline" json:",inline" coll:"nested_stuff"`
|
|
||||||
Chapters []chapter `bson:"chapters" json:"chapters"`
|
|
||||||
NestedText string `json:"text" bson:"-" gridfs:"nested_text,/nested/{{.ID}}.txt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *somethingWithNestedChapters) Id() any {
|
|
||||||
return s.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *somethingWithNestedChapters) SetId(id any) {
|
|
||||||
s.ID = id.(int64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *story) Id() any {
|
func (s *story) Id() any {
|
||||||
return s.ID
|
return s.ID
|
||||||
@ -129,7 +114,6 @@ func genChaps(single bool) []chapter {
|
|||||||
{"Sean Harris", "Colin Kimberley", "Brian Tatler"},
|
{"Sean Harris", "Colin Kimberley", "Brian Tatler"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
l := loremipsum.New()
|
|
||||||
|
|
||||||
for i := 0; i < ceil; i++ {
|
for i := 0; i < ceil; i++ {
|
||||||
spf := fmt.Sprintf("%d.md", i+1)
|
spf := fmt.Sprintf("%d.md", i+1)
|
||||||
@ -140,47 +124,34 @@ func genChaps(single bool) []chapter {
|
|||||||
Words: 50,
|
Words: 50,
|
||||||
Notes: "notenotenote !!!",
|
Notes: "notenotenote !!!",
|
||||||
Genre: []string{"Slash"},
|
Genre: []string{"Slash"},
|
||||||
Bands: []band{diamondHead},
|
Bands: []band{dh},
|
||||||
Characters: []string{"Sean Harris", "Brian Tatler", "Duncan Scott", "Colin Kimberley"},
|
Characters: []string{"Sean Harris", "Brian Tatler", "Duncan Scott", "Colin Kimberley"},
|
||||||
Relationships: relMap[i],
|
Relationships: relMap[i],
|
||||||
Adult: true,
|
Adult: true,
|
||||||
Summary: l.Paragraph(),
|
Summary: loremipsum.New().Paragraph(),
|
||||||
Hidden: false,
|
Hidden: false,
|
||||||
LoggedInOnly: true,
|
LoggedInOnly: true,
|
||||||
FileName: spf,
|
FileName: spf,
|
||||||
Text: strings.Join(l.ParagraphList(10), "\n\n"),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func doSomethingWithNested() somethingWithNestedChapters {
|
var iti_single story = story{
|
||||||
l := loremipsum.New()
|
|
||||||
swnc := somethingWithNestedChapters{
|
|
||||||
Chapters: genChaps(false),
|
|
||||||
NestedText: strings.Join(l.ParagraphList(15), "\n\n"),
|
|
||||||
}
|
|
||||||
return swnc
|
|
||||||
}
|
|
||||||
func iti_single() story {
|
|
||||||
return story{
|
|
||||||
Title: "title",
|
Title: "title",
|
||||||
Completed: true,
|
Completed: true,
|
||||||
Chapters: genChaps(true),
|
Chapters: genChaps(true),
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func iti_multi() story {
|
var iti_multi story = story{
|
||||||
return story{
|
|
||||||
Title: "Brian Tatler Fucked and Abused Sean Harris",
|
Title: "Brian Tatler Fucked and Abused Sean Harris",
|
||||||
Completed: false,
|
Completed: false,
|
||||||
Chapters: genChaps(false),
|
Chapters: genChaps(false),
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func iti_blank() story {
|
func iti_blank() story {
|
||||||
t := iti_single()
|
t := iti_single
|
||||||
t.Chapters = make([]chapter, 0)
|
t.Chapters = make([]chapter, 0)
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
@ -219,30 +190,11 @@ var metallica = band{
|
|||||||
Locked: false,
|
Locked: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
var diamondHead = band{
|
var dh = band{
|
||||||
ID: 503,
|
ID: 503,
|
||||||
Name: "Diamond Head",
|
Name: "Diamond Head",
|
||||||
Locked: false,
|
Locked: false,
|
||||||
Characters: []string{
|
Characters: []string{"Brian Tatler", "Sean Harris", "Duncan Scott", "Colin Kimberley"},
|
||||||
"Brian Tatler",
|
|
||||||
"Sean Harris",
|
|
||||||
"Duncan Scott",
|
|
||||||
"Colin Kimberley",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
var bodom = band{
|
|
||||||
ID: 74,
|
|
||||||
Name: "Children of Bodom",
|
|
||||||
Locked: false,
|
|
||||||
Characters: []string{
|
|
||||||
"Janne Wirman",
|
|
||||||
"Alexi Laiho",
|
|
||||||
"Jaska Raatikainen",
|
|
||||||
"Henkka T. Blacksmith",
|
|
||||||
"Roope Latvala",
|
|
||||||
"Daniel Freyberg",
|
|
||||||
"Alexander Kuoppala",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveDoc(t *testing.T, doc IModel) {
|
func saveDoc(t *testing.T, doc IModel) {
|
||||||
|
44
util.go
44
util.go
@ -54,45 +54,19 @@ func coerceInt(input reflect.Value, dst reflect.Value) interface{} {
|
|||||||
|
|
||||||
var arrRegex, _ = regexp.Compile(`\[(?P<index>\d+)]$`)
|
var arrRegex, _ = regexp.Compile(`\[(?P<index>\d+)]$`)
|
||||||
|
|
||||||
func getNested(field string, aValue reflect.Value) (*reflect.Type, *reflect.Value, error) {
|
func getNested(field string, value reflect.Value) (*reflect.StructField, *reflect.Value, error) {
|
||||||
if strings.HasPrefix(field, ".") || strings.HasSuffix(field, ".") {
|
if strings.HasPrefix(field, ".") || strings.HasSuffix(field, ".") {
|
||||||
return nil, nil, fmt.Errorf(errFmtMalformedField, field)
|
return nil, nil, fmt.Errorf("Malformed field name %s passed", field)
|
||||||
}
|
}
|
||||||
value := aValue
|
|
||||||
if value.Kind() == reflect.Pointer {
|
|
||||||
value = value.Elem()
|
|
||||||
}
|
|
||||||
aft := value.Type()
|
|
||||||
dots := strings.Split(field, ".")
|
dots := strings.Split(field, ".")
|
||||||
if value.Kind() != reflect.Struct /*&& arrRegex.FindString(dots[0]) == ""*/ {
|
if value.Kind() != reflect.Struct && arrRegex.FindString(dots[0]) == "" {
|
||||||
if value.Kind() == reflect.Slice {
|
return nil, nil, fmt.Errorf("This value is not a struct!")
|
||||||
st := reflect.MakeSlice(value.Type().Elem(), 0, 0)
|
|
||||||
for i := 0; i < value.Len(); i++ {
|
|
||||||
cur := value.Index(i)
|
|
||||||
if len(dots) > 1 {
|
|
||||||
_, cv, _ := getNested(strings.Join(dots[1:], "."), cur.FieldByName(dots[0]))
|
|
||||||
reflect.Append(st, *cv)
|
|
||||||
//return getNested(, "."), fv)
|
|
||||||
} else {
|
|
||||||
reflect.Append(st, cur)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
typ := st.Type().Elem()
|
|
||||||
return &typ, &st, nil
|
|
||||||
}
|
|
||||||
if len(dots) > 1 {
|
|
||||||
return nil, nil, ErrNotSliceOrStruct
|
|
||||||
} else {
|
|
||||||
return &aft, &value, nil
|
|
||||||
}
|
|
||||||
/*ft := value.Type()
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
ref := value
|
ref := value
|
||||||
if ref.Kind() == reflect.Pointer {
|
if ref.Kind() == reflect.Pointer {
|
||||||
ref = ref.Elem()
|
ref = ref.Elem()
|
||||||
}
|
}
|
||||||
var fv = ref.FieldByName(arrRegex.ReplaceAllString(dots[0], ""))
|
var fv reflect.Value = ref.FieldByName(arrRegex.ReplaceAllString(dots[0], ""))
|
||||||
if arrRegex.FindString(dots[0]) != "" && fv.Kind() == reflect.Slice {
|
if arrRegex.FindString(dots[0]) != "" && fv.Kind() == reflect.Slice {
|
||||||
matches := arrRegex.FindStringSubmatch(dots[0])
|
matches := arrRegex.FindStringSubmatch(dots[0])
|
||||||
ridx, _ := strconv.Atoi(matches[0])
|
ridx, _ := strconv.Atoi(matches[0])
|
||||||
@ -104,7 +78,7 @@ func getNested(field string, aValue reflect.Value) (*reflect.Type, *reflect.Valu
|
|||||||
if len(dots) > 1 {
|
if len(dots) > 1 {
|
||||||
return getNested(strings.Join(dots[1:], "."), fv)
|
return getNested(strings.Join(dots[1:], "."), fv)
|
||||||
} else {
|
} else {
|
||||||
return &ft.Type, &fv, nil
|
return &ft, &fv, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func makeSettable(rval reflect.Value, value interface{}) reflect.Value {
|
func makeSettable(rval reflect.Value, value interface{}) reflect.Value {
|
||||||
@ -153,17 +127,17 @@ func pull(s reflect.Value, idx int, typ reflect.Type) reflect.Value {
|
|||||||
|
|
||||||
func checkStruct(ref reflect.Value) error {
|
func checkStruct(ref reflect.Value) error {
|
||||||
if ref.Kind() == reflect.Slice {
|
if ref.Kind() == reflect.Slice {
|
||||||
return ErrAppendMultipleDocuments
|
return fmt.Errorf("Cannot append to multiple documents!")
|
||||||
}
|
}
|
||||||
if ref.Kind() != reflect.Struct {
|
if ref.Kind() != reflect.Struct {
|
||||||
return ErrNotAStruct
|
return fmt.Errorf("Current object is not a struct!")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSlice(ref reflect.Value) error {
|
func checkSlice(ref reflect.Value) error {
|
||||||
if ref.Kind() != reflect.Slice {
|
if ref.Kind() != reflect.Slice {
|
||||||
return ErrNotASlice
|
return fmt.Errorf("Current field is not a slice!")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user