Categorygithub.com/davidwartell/go-mongo-apicursor
modulepackage
0.0.0-20220929202716-627a6df03dec
Repository: https://github.com/davidwartell/go-mongo-apicursor.git
Documentation: pkg.go.dev

# README

go-mongo-apicursor

A golang implementation of the Relay GraphQL Cursor Connections Specification (https://relay.dev/graphql/connections.htm) for MongoDB. Apache License. Will also work with REST apis to solve the same problem of sharing a cursor to MongoDB query results over a web service request/response.

Prerequisites

If you want to run the linter you will need golangci installed.

HomeBrew: https://brew.sh/

brew install golangci/tap/golangci-lint

Building

make setup
make all

Building for Release

make setup
make release all

Unit Tests

make test
  • Linter
make golint
  • Security Linter
make gosec

Example

type Person struct {
    Id bson.ObjectId `bson:"_id"`
    Name string `bson:"name"`
    CreatedTime time.Time `bson:"createdTime"`
}

type PersonFactory struct {
}

func (df *PersonFactory) New() interface{} {
	return NewPerson()
}

func (df *PersonFactory) NewConnection() apicursor.Connection {
	doc := PersonConnection{}
	return &doc
}

func (df *PersonFactory) NewEdge() apicursor.Edge {
	doc := PersonEdge{}
	return &doc
}

type PersonConnection struct {
	// A list of edges.
	Edges []*PersonEdge
	// A list of nodes.
	Nodes []*Person
	// Information to aid in pagination.
	PageInfo *apicursor.PageInfo
	// Identifies the total count of items in the connection.
	TotalCount uint64
}

func (dc *PersonConnection) GetEdges() []*PersonEdge {
	return dc.Edges
}

func (dc *PersonConnection) SetEdges(edges []apicursor.Edge) {
	dc.Edges = make([]*PersonEdge, len(edges))
	for i, d := range edges {
		de := d.(*PersonEdge)
		dc.Edges[i] = de
	}
}

func (dc *PersonConnection) GetNodes() []*Person {
	return dc.Nodes
}

func (dc *PersonConnection) SetNodes(nodes []interface{}) {
	dc.Nodes = make([]*Person, len(nodes))
	for i, d := range nodes {
		person := d.(*Person)
		dc.Nodes[i] = person
	}
}

func (dc *PersonConnection) GetPageInfo() *apicursor.PageInfo {
	return dc.PageInfo
}

func (dc *PersonConnection) SetPageInfo(pginfo *apicursor.PageInfo) {
	dc.PageInfo = pginfo
}

func (dc *PersonConnection) GetTotalCount() uint64 {
	return dc.TotalCount
}

func (dc *PersonConnection) SetTotalCount(count uint64) {
	dc.TotalCount = count
}

type PersonEdge struct {
	// A cursor for use in pagination.
	Cursor string
	// The item at the end of the edge.
	Node *Person
}

func (de *PersonEdge) GetCursor() string {
	return de.Cursor
}

func (de *PersonEdge) SetCursor(c string) {
	de.Cursor = c
}

func (de *PersonEdge) GetNode() interface{} {
	return de.Node
}

func (de *PersonEdge) SetNode(node interface{}) {
	de.Node = node.(*Person)
}

// PersonOrder Ordering options for Person connections
type PersonOrder struct {
	// The ordering direction.
	Direction apicursor.OrderDirection `json:"direction"`
}

func (r *queryResolver) Persons(ctx context.Context, after *string, before *string, first *int, last *int, orderBy *PersonOrder) (*PersonConnection, error) {
    cursor := apicursor.NewCursor()
	err = cursor.LoadFromAPIRequest(after, before, first, last, &PersonFactory{})
	if err != nil {
		return err
	}
	return person.Instance().Find(
		ctx,
		cursor,
		orderBy,
	)
}

type PersonCursorMarshaler struct{}

func (dcm PersonCursorMarshaler) UnmarshalMongo(c apicursor.APICursor, findFilter bson.M, naturalSortDirection int) (err error) {
	return c.AddCursorFilters(findFilter, naturalSortDirection,
		apicursor.CursorFilterField{
			FieldName: "createdTime",
			FieldType: apicursor.CursorFieldTypeTime,
		},
		apicursor.CursorFilterField{
			FieldName: "_id",
			FieldType: apicursor.CursorFieldTypeMongoOid,
		},
	)
}

func (dcm PersonCursorMarshaler) Marshal(obj interface{}) (cursorFields map[string]string, err error) {
	person := obj.(*Person)
	cursorFields = make(map[string]string)
	cursorFields["createdTime"], err = apicursor.MarshalTimeField(person.CreatedTime)
	cursorFields["_id"] = apicursor.MarshalMongoOidField(person.Id)
	return
}

func (s *personService) Find(ctx context.Context, queryCursor apicursor.APICursor, orderBy *PersonOrder) (personConnection *PersonConnection, err error) {
	if queryCursor == nil {
		queryCursor = apicursor.NewCursor()
	}
	queryCursor.SetMarshaler(&PersonCursorMarshaler{})
	
	filter := bson.M{}
	
	findOptions := &options.FindOptions{}
	findOptions.SetLimit(queryCursor.FindLimit())
	sortDirection := apicursor.DESC.Int()
	if orderBy != nil {
		sortDirection = orderBy.Direction.Int()
	}
	findOptions.SetSort(bson.D{
		{"createdTime", queryCursor.CursorFilterSortDirection(sortDirection)},
		{"_id", queryCursor.CursorFilterSortDirection(sortDirection)},
	})
	
	var countDocsResult int64
	countDocsResult, err = collection.CountDocuments(ctx, filter)
	if err != nil {
		return
	}
	
	err = queryCursor.UnmarshalMongo(filter, sortDirection)
	if err != nil {
		return
	}
	
	var mongoCursor *mongo.Cursor
	mongoCursor, err = collection.Find(ctx, filter, findOptions)
	if err != nil {
		return
	}
	defer func() {
		if mongoCursor != nil {
			_ = mongoCursor.Close(ctx)
		}
	}()

	var connectionResult apicursor.Connection
	connectionResult, err = queryCursor.ConnectionFromMongoCursor(ctx, mongoCursor, countDocsResult)
	if err != nil {
		return
	}
	personConnection = connectionResult.(*PersonConnection)
	return
}

Contributing

Happy to accept PRs.

Author

davidwartell

License

Released under the Apache License.

# Functions

No description provided by the author
No description provided by the author
goland:noinspection GoUnusedExportedFunction.

# Constants

goland:noinspection ALL.
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
goland:noinspection ALL.
No description provided by the author
No description provided by the author
No description provided by the author

# Structs

No description provided by the author
No description provided by the author

# Interfaces

No description provided by the author
Connection implements paging according to spec: https://relay.dev/graphql/connections.htm.
CursorMarshaler is used to marshal and unmarshal cursors to and from data store query filters.
No description provided by the author
ModelFactory is used to construct new objects when loading results.

# Type aliases

No description provided by the author
No description provided by the author
No description provided by the author