ValidateCreate function not called on ValidateAndCreate - validation

I'm usign buffalo to build a simple backend to store values.
I have a Target struct defined as follow:
type Target struct {
ID uuid.UUID `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Description string `json:"description" db:"description"`
Observations []Observation `json:"values,omitempty" has_many:"observations" order_by:"updated_at desc"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// ...
func (t *Target) Validate(tx *pop.Connection) (*validate.Errors, error) {
return validate.Validate(
&validators.StringIsPresent{Field: t.Name, Name: "Name"},
&validators.StringLengthInRange{Field: t.Name, Name: "Name", Min: 2, Max: 15},
), nil
}
func (t *Target) ValidateCreate(tx *pop.Connection) (*validate.Errors, error) {
return t.Validate(tx)
}
func (t *Target) ValidateUpdate(tx *pop.Connection) (*validate.Errors, error) {
return t.Validate(tx)
}
A new Target is created using the following action:
func TargetAdd(c buffalo.Context) error {
body, err := io.ReadAll(c.Request().Body)
if err != nil {
log.Get().Error("Error reading body: %v", err)
return err
}
target := &models.Target{}
json.Unmarshal([]byte(body), target)
vErr, err := models.DB.ValidateAndCreate(target)
if err != nil {
log.Get().Error("entity not valid: %s", err)
return response.SendGeneralError(c, err)
}
if vErr.HasAny() {
log.Get().Error("entity not valid: %s", vErr)
return response.SendGeneralError(c, err)
}
return response.SendOKResponse(c, target)
}
The problem is that the ValidateAndCreate function does not call any of the validation functions defined for the Target model, even if it should be so (link to documentation).
Debugging the issue, I found that in the validation.go file, in this function
func (m *Model) validateCreate(c *Connection) (*validate.Errors, error) {
return m.iterateAndValidate(func(model *Model) (*validate.Errors, error) {
verrs, err := model.validate(c)
if err != nil {
return verrs, err
}
if x, ok := model.Value.(validateCreateable); ok {
vs, err := x.ValidateCreate(c)
if vs != nil {
verrs.Append(vs)
}
if err != nil {
return verrs, err
}
}
return verrs, err
})
}
the call to model.Value.(validateCreateable) seems to return ok to false.
Can someone please explain me where's the problem and how it's possibile to validate a model? Thanks.
EDIT:
Changing the import from
"github.com/gobuffalo/validate"
"github.com/gobuffalo/validate/validators"
to
"github.com/gobuffalo/validate/v3"
"github.com/gobuffalo/validate/v3/validators"
seems to fix the problem I have

Related

I want to create Access Rights by using MSPID for different organisations

My idea to do this was to use the MSPID. So I would get the MSPID of the organisation, if it is the wrong MSPID then it will return an error message and if it is the right MSPID then continue. In my code I only did it in the DeleteAsset function however I would like to do it in the CreateAsset, UpdateAsset, TransferAsset and DeleteAsset functions.
The problem is that when I go to use the CreateAsset function on the command line it gives me this error:
peer chaincode invoke "${TARGET_TLS_OPTIONS[#]}" -C mychannel -n basic -c '{"function":"CreateAsset","Args":["Asset1","Andrew Faure","20","None","O+", "Penicilin", "None", "Family Doctor", "Fractured Leg", "10/05/2022"]}'
Error: endorsement failure during invoke. response: status:500 message:"error in simulation: failed to execute transaction 9446e0c320832b92f06ea56f577a29e9a5ec94c276329cb1d31bb8a85581138d: could not launch chaincode basic_1.0:605aa3a69a8107c9c7b5fc22072554235d3ea827b0eefbffae29f0c9998bf0e6: chaincode registration failed: container exited with 2"
Without the MSPID the system works fine. However, I need the MSPID in order to create access rights to organisations.
I'm new to Hyperledger and Go lang. Would really appreciate it if anyone can help
package chaincode
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-chaincode-go/pkg/cid"
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
// SmartContract provides functions for managing an Asset
type SmartContract struct {
contractapi.Contract
}
type Asset struct {
ID string `json:"ID"`
Owner string `json:"Owner"`
Age int `json:"Age"`
MedicalFamilyHistory string `json:"MedicalFamilyHistory"`
BloodType string `json:"BloodType"`
Allergies string `json:"Allergies"`
Medication string `json:"Medication"`
DataSharedWith string `json:"DataSharedWith"`
CurrentIssue string `json:"CurrentIssue"`
Date string `json:"Date"`
}
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id
string, owner string, age int, medicalFamilyHistory string, bloodType string,
allergies string, medication string, dataSharedWith string, currentIssue
string, date string) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if exists {
return fmt.Errorf("the asset %s already exists", id)
}
asset := Asset{
ID: id,
Owner: owner,
Age: age,
MedicalFamilyHistory: medicalFamilyHistory,
BloodType: bloodType,
Allergies: allergies,
Medication: medication,
DataSharedWith: dataSharedWith,
CurrentIssue: currentIssue,
Date: date,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id
string) (*Asset, error) {
assetJSON, err := ctx.GetStub().GetState(id)
if err != nil {
return nil, fmt.Errorf("failed to read from world state: %v", err)
}
if assetJSON == nil {
return nil, fmt.Errorf("the asset %s does not exist", id)
}
var asset Asset
err = json.Unmarshal(assetJSON, &asset)
if err != nil {
return nil, err
}
return &asset, nil
}
func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id
string, owner string, age int, medicalFamilyHistory string, bloodType string,
allergies string, medication string, dataSharedWith string, currentIssue string, date
string) error {
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("the asset %s does not exist", id)
}
// overwriting original asset with new asset
asset := Asset{
ID: id,
Owner: owner,
Age: age,
MedicalFamilyHistory: medicalFamilyHistory,
BloodType: bloodType,
Allergies: allergies,
Medication: medication,
DataSharedWith: dataSharedWith,
CurrentIssue: currentIssue,
Date: date,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, stub
shim.ChaincodeStubInterface, id string) error {
msp, err := s.GetMSPID(stub)
if err != nil {
return err
}
// The error is with "Org1MSP" becuase I found out that mspid is Org1MSP
if msp != "org1MSP" {
return fmt.Errorf("Wrong Organisation, this organisation does not have access
to this function")
}
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("the asset %s does not exist", id)
}
return ctx.GetStub().DelState(id)
}
func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id
string) (bool, error) {
assetJSON, err := ctx.GetStub().GetState(id)
if err != nil {
return false, fmt.Errorf("failed to read from world state: %v", err)
}
return assetJSON != nil, nil
}
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id
string, newOwner string) (string, error) {
asset, err := s.ReadAsset(ctx, id)
if err != nil {
return "", err
}
oldOwner := asset.Owner
asset.Owner = newOwner
assetJSON, err := json.Marshal(asset)
if err != nil {
return "", err
}
err = ctx.GetStub().PutState(id, assetJSON)
if err != nil {
return "", err
}
return oldOwner, nil
}
func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface)
([]*Asset, error) {
// range query with empty string for startKey and endKey does an
// open-ended query of all assets in the chaincode namespace.
resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
if err != nil {
return nil, err
}
defer resultsIterator.Close()
var assets []*Asset
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, err
}
var asset Asset
err = json.Unmarshal(queryResponse.Value, &asset)
if err != nil {
return nil, err
}
assets = append(assets, &asset)
}
return assets, nil
}
func (s *SmartContract) GetMSPID(stub shim.ChaincodeStubInterface) (string, error) {
// Get the Client ID object
clientId, err := cid.New(stub)
if err != nil {
// Handle error
}
mspid, err := clientId.GetMSPID()
if err != nil {
// Handle error
}
// mspId, err := cid.GetMSPID(stub)
// if err != nil {
// return err
// }
// The error is with "Org1MSP" becuase I found out that mspid is Org1MSP
// if mspId != "Org1MSP" {
// return fmt.Errorf(mspId)
// }
return mspid, nil
}
Please look into below fabric sample chaincode. In this smart contract , they are validating MSPID.
https://github.com/hyperledger/fabric-samples/blob/main/asset-transfer-private-data/chaincode-go/chaincode/asset_transfer.go[![enter image description here]1]1

How to set slice interface values with reflection

I would like to build a function that takes a generic pointer array and fill that list based on mongo results.
I don't know how to set the value I got from mongo into my pointer array. In the below attempt, program panics with following error : reflect.Set: value of type []interface {} is not assignable to type []Person
When I print total / documents found, it corresponds to what I am expecting. So I think question is about reflection.
func getListWithCount(ctx context.Context, receiver interface{}) (int, error) {
//my mongo query here
var mongoResp struct {
Total int `bson:"total"`
Documents interface{} `bson:"documents"`
}
if err := cursor.Decode(&mongoResp); err != nil {
return 0, err
}
receiverValue := reflect.ValueOf(receiver)
docs := []interface{}(mongoResp.Documents.(primitive.A))
receiverValue.Elem().Set(reflect.ValueOf(docs))
return mongoResp.Total, nil
}
type Person struct {
Name string `bson:"name"`
}
func main() {
var persons []Person
count, err := getListWithCount(context.Background(), &persons)
if err != nil {
log.Fatal(err)
}
fmt.Println(count)
fmt.Println(persons)
}
You should be able to decode first into bson.RawValue and then Unmarshal it into the receiver.
func getListWithCount(ctx context.Context, receiver interface{}) (int, error) {
//my mongo query here
var mongoResp struct {
Total int `bson:"total"`
Documents bson.RawValue `bson:"documents"`
}
if err := cursor.Decode(&mongoResp); err != nil {
return 0, err
}
if err := mongoResp.Documents.Unmarshal(receiver); err != nil {
return 0, err
}
return mongoResp.Total, nil
}
You can also implement it as a custom bson.Unmarshaler.
type MongoResp struct {
Total int `bson:"total"`
Documents interface{} `bson:"documents"`
}
func (r *MongoResp) UnmarshalBSON(data []byte) error {
var temp struct {
Total int `bson:"total"`
Documents bson.RawValue `bson:"documents"`
}
if err := bson.Unmarshal(data, &temp); err != nil {
return err
}
r.Total = temp.Total
return temp.Documents.Unmarshal(r.Documents)
}
With that you would use it in the function like so:
func getListWithCount(ctx context.Context, receiver interface{}) (int, error) {
//my mongo query here
mongoResp := MongoResp{Documents: receiver}
if err := cursor.Decode(&mongoResp); err != nil {
return 0, err
}
return mongoResp.Total, nil
}
Dynamically create a struct type that matches the queried document. See commentary below for details.
func getListWithCount(receiver interface{}) (int, error) {
dst := reflect.ValueOf(receiver).Elem()
// Your mongo query here
// Create a struct type that matches the document.
doct := reflect.StructOf([]reflect.StructField{
reflect.StructField{Name: "Total", Type: reflect.TypeOf(0), Tag: `bson:"total"`},
reflect.StructField{Name: "Documents", Type: dst.Type(), Tag: `bson:"documents"`},
})
// Decode to a value of the type.
docp := reflect.New(doct)
if err := cursor.Decode(docp.Interface()); err != nil {
return 0, err
}
docv := docp.Elem()
// Copy the Documents field to *receiver.
dst.Set(docv.Field(1))
// Return the total
return docv.Field(0).Interface().(int), nil
}
there is no need to use reflect here, you can decode it directly to your Person slices
func getPersons(ctx context.Context, coll *mongo.Collection, results interface{}) error {
cur, err := coll.Find(ctx, bson.D{})
if err != nil {
return err
}
err = cur.All(ctx, results)
if err != nil {
return err
}
return nil
}
and the len is the count of the results.
err = getPersons(ctx, coll, &persons)
require.NoError(t, err)
t.Logf("Got %d persons: %v", len(persons), persons)
see https://gist.github.com/xingyongtao/459f92490bdcbf7d5afe9f5d1ae6c04a

Difference in func with receiver and param golang

I try to create marshal func for struct Item.
So the question is, why the first example gives stackoverflow for goroutine and second works correctly?
Call method with receiver
type Item struct{
a int
}
func some(items []Item){
for _,i:=range items{
buf,err:=i.MarhsalBinary()
fmt.Println(buf.Bytes(), err)
}
func (i Item) MarshalBinary() ([]byte, error) {
var (
buf bytes.Buffer
err error
)
if err = gob.NewEncoder(&buf).Encode(i); err != nil { // endless loop here
return nil, err
}
return buf.Bytes(), err
}
This is pass by value.
type Item struct{
a int
}
func some(items []Item){
for _,i:=range items{
buf,err:=MarhsalBinary(i)
fmt.Println(buf.Bytes(), err)
}
func MarshalBinary(i Item) ([]byte, error) {
var (
buf bytes.Buffer
err error
)
if err = gob.NewEncoder(&buf).Encode(i); err != nil { // works correctly
return nil, err
}
return buf.Bytes(), err
}

Unmarshalling YAML to ordered maps

I am trying to unmarshal the following YAML with Go YAML v3.
model:
name: mymodel
default-children:
- payment
pipeline:
accumulator_v1:
by-type:
type: static
value: false
result-type:
type: static
value: 3
item_v1:
amount:
type: schema-path
value: amount
start-date:
type: schema-path
value: start-date
Under pipeline is an arbitrary number of ordered items. The struct to which this should be unmarshalled looks like this:
type PipelineItemOption struct {
Type string
Value interface{}
}
type PipelineItem struct {
Options map[string]PipelineItemOption
}
type Model struct {
Name string
DefaultChildren []string `yaml:"default-children"`
Pipeline orderedmap[string]PipelineItem // "pseudo code"
}
How does this work with Golang YAML v3? In v2 there was MapSlice, but that is gone in v3.
You claim that marshaling to an intermediate yaml.Node is highly non-generic, but I don't really see why. It looks like this:
package main
import (
"fmt"
"gopkg.in/yaml.v3"
)
type PipelineItemOption struct {
Type string
Value interface{}
}
type PipelineItem struct {
Name string
Options map[string]PipelineItemOption
}
type Pipeline []PipelineItem
type Model struct {
Name string
DefaultChildren []string `yaml:"default-children"`
Pipeline Pipeline
}
func (p *Pipeline) UnmarshalYAML(value *yaml.Node) error {
if value.Kind != yaml.MappingNode {
return fmt.Errorf("pipeline must contain YAML mapping, has %v", value.Kind)
}
*p = make([]PipelineItem, len(value.Content)/2)
for i := 0; i < len(value.Content); i += 2 {
var res = &(*p)[i/2]
if err := value.Content[i].Decode(&res.Name); err != nil {
return err
}
if err := value.Content[i+1].Decode(&res.Options); err != nil {
return err
}
}
return nil
}
var input []byte = []byte(`
model:
name: mymodel
default-children:
- payment
pipeline:
accumulator_v1:
by-type:
type: static
value: false
result-type:
type: static
value: 3
item_v1:
amount:
type: schema-path
value: amount
start-date:
type: schema-path
value: start-date`)
func main() {
var f struct {
Model Model
}
var err error
if err = yaml.Unmarshal(input, &f); err != nil {
panic(err)
}
fmt.Printf("%v", f)
}
For me it was a bit of a learning curve to figure out what v3 expects instead of MapSlice. Similar to answer from #flyx, the yaml.Node tree needs to be walked, particularly its []Content.
Here is a utility to provide an ordered map[string]interface{} that is a little more reusable and tidy. (Though it is not as constrained as the question specified.)
Per structure above, redefine Pipeline generically:
type Model struct {
Name string
DefaultChildren []string `yaml:"default-children"`
Pipeline *yaml.Node
}
Use a utility fn to traverse yaml.Node content:
// fragment
var model Model
if err := yaml.Unmarshal(&model) ; err != nil {
return err
}
om, err := getOrderedMap(model.Pipeline)
if err != nil {
return err
}
for _,k := range om.Order {
v := om.Map[k]
fmt.Printf("%s=%v\n", k, v)
}
The utility fn:
type OrderedMap struct {
Map map[string]interface{}
Order []string
}
func getOrderedMap(node *yaml.Node) (om *OrderedMap, err error) {
content := node.Content
end := len(content)
count := end / 2
om = &OrderedMap{
Map: make(map[string]interface{}, count),
Order: make([]string, 0, count),
}
for pos := 0 ; pos < end ; pos += 2 {
keyNode := content[pos]
valueNode := content[pos + 1]
if keyNode.Tag != "!!str" {
err = fmt.Errorf("expected a string key but got %s on line %d", keyNode.Tag, keyNode.Line)
return
}
var k string
if err = keyNode.Decode(&k) ; err != nil {
return
}
var v interface{}
if err = valueNode.Decode(&v) ; err != nil {
return
}
om.Map[k] = v
om.Order = append(om.Order, k)
}
return
}
Building from #jws's solution and adding recursion:
func Encode(obj any) (string, error) {
var buffer bytes.Buffer
yamlEncoder := yaml.NewEncoder(&buffer)
yamlEncoder.SetIndent(2)
encodeErr := yamlEncoder.Encode(obj)
if encodeErr != nil {
return "", encodeErr
}
return buffer.String(), nil
}
type OrderedMap struct {
Map map[string]interface{}
Order []string
}
func (om *OrderedMap) MarshalYAML() (interface{}, error) {
node, err := EncodeDocumentNode(om)
if err != nil {
return nil, err
}
return node.Content[0], nil
}
// DecodeDocumentNode decodes a root yaml node into an OrderedMap
func DecodeDocumentNode(node *yaml.Node) (*OrderedMap, error) {
if node.Kind != yaml.DocumentNode {
return nil, fmt.Errorf("node %v is not a document node", node)
}
om, err := decodeMap(node.Content[0])
if err != nil {
return nil, err
}
return om, err
}
func decode(node *yaml.Node) (any, error) {
switch node.Tag {
case "!!null":
return decodeNull(node)
case "!!str":
return decodeStr(node)
case "!!map":
return decodeMap(node)
case "!!seq":
return decodeSeq(node)
default:
return nil, fmt.Errorf("unknown node tag %s", node.Tag)
}
}
func decodeNull(_ *yaml.Node) (any, error) {
return nil, nil
}
func decodeStr(node *yaml.Node) (string, error) {
var s string
if err := node.Decode(&s); err != nil {
return "", fmt.Errorf("decode error for %v: %v", node, err)
}
return s, nil
}
func decodeMap(node *yaml.Node) (*OrderedMap, error) {
keyValuePairs := lo.Map(lo.Chunk(node.Content, 2), func(c []*yaml.Node, _ int) mo.Result[lo.Entry[string, any]] {
if len(c) != 2 {
return mo.Err[lo.Entry[string, any]](fmt.Errorf("invalid yaml; expected key/value pair"))
}
keyNode := c[0]
valueNode := c[1]
if keyNode.Tag != "!!str" {
return mo.Err[lo.Entry[string, any]](fmt.Errorf("expected a string key but got %s on line %d", keyNode.Tag, keyNode.Line))
}
key, err := decodeStr(keyNode)
if err != nil {
return mo.Err[lo.Entry[string, any]](fmt.Errorf("key decode error: %v", err))
}
value, err := decode(valueNode)
if err != nil {
return mo.Err[lo.Entry[string, any]](fmt.Errorf("value decode error: %v", err))
}
return mo.Ok(lo.Entry[string, any]{
Key: key,
Value: value,
})
})
validErrGroups := lo.GroupBy(keyValuePairs, func(kvp mo.Result[lo.Entry[string, any]]) bool {
return kvp.IsOk()
})
errs := validErrGroups[false]
if len(errs) != 0 {
return nil, fmt.Errorf("%v", lo.Map(errs, func(e mo.Result[lo.Entry[string, any]], _ int) error {
return e.Error()
}))
}
kvps := lo.Map(validErrGroups[true], func(kvp mo.Result[lo.Entry[string, any]], _ int) lo.Entry[string, any] {
return kvp.MustGet()
})
return &OrderedMap{
Map: lo.FromEntries(kvps),
Order: lo.Map(kvps, func(kvp lo.Entry[string, any], _ int) string {
return kvp.Key
}),
}, nil
}
func decodeSeq(node *yaml.Node) ([]*OrderedMap, error) {
seq := lo.Map(node.Content, func(n *yaml.Node, _ int) mo.Result[*OrderedMap] {
return mo.Try(func() (*OrderedMap, error) {
return decodeMap(n)
})
})
validErrGroups := lo.GroupBy(seq, func(kvp mo.Result[*OrderedMap]) bool {
return kvp.IsOk()
})
errs := validErrGroups[false]
if len(errs) != 0 {
return nil, fmt.Errorf("%v", lo.Map(errs, func(e mo.Result[*OrderedMap], _ int) error {
return e.Error()
}))
}
oms := validErrGroups[true]
return lo.Map(oms, func(om mo.Result[*OrderedMap], _ int) *OrderedMap {
return om.MustGet()
}), nil
}
// EncodeDocumentNode encodes an OrderedMap into a root yaml node
func EncodeDocumentNode(om *OrderedMap) (*yaml.Node, error) {
node, err := encodeMap(om)
if err != nil {
return nil, err
}
return &yaml.Node{
Kind: yaml.DocumentNode,
Content: []*yaml.Node{node},
Line: 1,
Column: 1,
}, nil
}
func encode(x any) (*yaml.Node, error) {
if x == nil {
return encodeNull()
}
switch reflect.ValueOf(x).Kind() {
case reflect.String:
return encodeStr(x.(string))
case reflect.Ptr:
return encodeMap(x.(*OrderedMap))
case reflect.Slice:
return encodeSeq(x.([]*OrderedMap))
default:
return nil, fmt.Errorf("unable to encode %v with kind %v", x, reflect.ValueOf(x).Kind())
}
}
func encodeNull() (*yaml.Node, error) {
return &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!null",
}, nil
}
func encodeStr(s string) (*yaml.Node, error) {
return &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!str",
Value: s,
}, nil
}
func encodeMap(om *OrderedMap) (*yaml.Node, error) {
content := lo.FlatMap(om.Order, func(key string, _ int) []mo.Result[*yaml.Node] {
return []mo.Result[*yaml.Node]{
mo.Try(func() (*yaml.Node, error) {
return encodeStr(key)
}),
mo.Try(func() (*yaml.Node, error) {
return encode(om.Map[key])
}),
}
})
validErrGroups := lo.GroupBy(content, func(kvp mo.Result[*yaml.Node]) bool {
return kvp.IsOk()
})
errs := validErrGroups[false]
if len(errs) != 0 {
return nil, fmt.Errorf("%v", lo.Map(errs, func(e mo.Result[*yaml.Node], _ int) error {
return e.Error()
}))
}
nodes := validErrGroups[true]
return &yaml.Node{
Kind: yaml.MappingNode,
Tag: "!!map",
Content: lo.Map(nodes, func(c mo.Result[*yaml.Node], _ int) *yaml.Node {
return c.MustGet()
}),
}, nil
}
func encodeSeq(oms []*OrderedMap) (*yaml.Node, error) {
content := lo.Map(oms, func(om *OrderedMap, _ int) mo.Result[*yaml.Node] {
return mo.Try(func() (*yaml.Node, error) {
return encodeMap(om)
})
})
validErrGroups := lo.GroupBy(content, func(kvp mo.Result[*yaml.Node]) bool {
return kvp.IsOk()
})
errs := validErrGroups[false]
if len(errs) != 0 {
return nil, fmt.Errorf("%v", lo.Map(errs, func(e mo.Result[*yaml.Node], _ int) error {
return e.Error()
}))
}
nodes := validErrGroups[true]
return &yaml.Node{
Kind: yaml.SequenceNode,
Tag: "!!seq",
Content: lo.Map(nodes, func(c mo.Result[*yaml.Node], _ int) *yaml.Node {
return c.MustGet()
}),
}, nil
}
End-to-end test:
func TestDecodeEncodeE2E(t *testing.T) {
y := heredoc.Doc(`
root:
outer-key-4:
- inner-key-7:
key-8: value-8
key-9: value-9
- inner-key-10:
key-11: value-11
key-12: value-12
outer-key-3:
- inner-key-5: inner-value-5
- inner-key-6: inner-value-6
outer-key-1:
inner-key-1: inner-value-1
inner-key-2: inner-value-2
outer-key-2:
inner-key-3: inner-value-3
inner-key-4: inner-value-4
key-1: value-1
key-2: value-2
`)
var documentNode yaml.Node
err := yaml.Unmarshal([]byte(y), &documentNode)
require.NoError(t, err)
decodeActual, decodeErr := DecodeDocumentNode(&documentNode)
require.NoError(t, decodeErr)
stringifiedOrderedMap, stringifiedOrderedMapErr := Encode(decodeActual)
assert.NoError(t, stringifiedOrderedMapErr)
assert.Equal(t, y, stringifiedOrderedMap)
encodeActual, encodeErr := EncodeDocumentNode(decodeActual)
require.NoError(t, encodeErr)
// for troubleshooting purposes; commented out because lines and columns don't match
// assert.Equal(t, &documentNode, encodeActual)
stringifiedNode, stringifiedNodeErr := Encode(encodeActual)
assert.NoError(t, stringifiedNodeErr)
assert.Equal(t, y, stringifiedNode)
}

Golang Gorilla Websocket stops receiving information at 120 seconds

I'm currently trying to connect to the CEX.IO bitcoin exchange's websocket, but have been having issues not only with CEX.IO but with others too. All of my connections drop around the 120-second mark which makes me think there is some TTL problem going on. The Process() goroutine in the main package ends up just hanging and waiting for data from the readLoop which just stops receiving data. I've included some read-only API keys in the code so you can test if you'd like.
package main
import (
"fmt"
"bitbucket.org/tradedefender/cryptocurrency/exchange-connector/cexio"
"github.com/shopspring/decimal"
"encoding/json"
"time"
)
type OrderBook struct {
Asks []Ask
Bids []Bid
}
type Ask struct {
Rate decimal.Decimal
Amount decimal.Decimal
}
type Bid struct {
Rate decimal.Decimal
Amount decimal.Decimal
}
func main() {
cexioConn := new(cexio.Connection)
err := cexioConn.Connect()
if err != nil {
fmt.Errorf("error: %s", err.Error())
}
err = cexioConn.Authenticate("TLwYkktLf7Im6nqSKt6UO1IrU", "9ImOJcR7Qj3LMIyPCzky0D7WE")
if err != nil {
fmt.Errorf("error: %s", err.Error())
}
readChannel := make(chan cexio.IntraAppMessage, 25)
go cexioConn.ReadLoop(readChannel)
processor := Processor{
WatchPairs: [][2]string{
[2]string{
"BTC", "USD",
},
},
conn: cexioConn,
}
go processor.Process(readChannel)
// LOL
for {
continue
}
}
type Processor struct {
WatchPairs [][2]string
conn *cexio.Connection
}
func (p *Processor) Process(ch <-chan cexio.IntraAppMessage) {
p.conn.SubscribeToOrderBook(p.WatchPairs[0])
pingTimer := time.Now().Unix()
for {
fmt.Printf("(%v)\n", time.Now().Unix())
if (time.Now().Unix() - pingTimer) >= 10 {
fmt.Println("sending ping")
p.conn.SendPing()
pingTimer = time.Now().Unix()
}
readMsg := <- ch
output, _ := json.Marshal(readMsg.SocketMessage)
fmt.Println(string(output))
if readMsg.SocketMessage.Event == "ping" {
fmt.Println("sending pong")
p.conn.SendPong()
pingTimer = time.Now().Unix()
}
}
}
Below is the connector to the cexio websocket. Here is a link to their API: https://cex.io/websocket-api
package cexio
import (
"github.com/gorilla/websocket"
//"github.com/shopspring/decimal"
"github.com/satori/go.uuid"
"encoding/hex"
"encoding/json"
"crypto/hmac"
"crypto/sha256"
"bytes"
"strconv"
"time"
"fmt"
)
const Url = "wss://ws.cex.io/ws/"
type Connection struct {
conn *websocket.Conn
}
type IntraAppMessage struct {
SocketMessage GenericMessage
ProgramMessage ProgramMessage
}
type GenericMessage struct {
Event string `json:"e"`
Data interface{} `json:"data"`
Auth AuthData `json:"auth,omitempty"`
Ok string `json:"ok,omitempty"`
Oid string `json:"oid,omitempty"`
Time int64 `json:"time,omitempty"`
}
type ProgramMessage struct {
Error string
}
type AuthData struct {
Key string `json:"key"`
Signature string `json:"signature"`
Timestamp int64 `json:"timestamp"`
}
type OrderBookSubscribeData struct {
Pair [2]string `json:"pair"`
Subscribe bool `json:"subscribe"`
Depth int `json:"depth"`
}
func (c *Connection) SendPong() error {
pongMsg := GenericMessage{
Event: "pong",
}
err := c.conn.WriteJSON(pongMsg)
if err != nil {
return nil
}
deadline := time.Now().Add(15*time.Second)
err = c.conn.WriteControl(websocket.PongMessage, nil, deadline)
if err != nil {
return err
}
return nil
}
func (c *Connection) SendPing() error {
pingMsg := GenericMessage{
Event: "get-balance",
Oid: uuid.NewV4().String(),
}
err := c.conn.WriteJSON(pingMsg)
if err != nil {
return err
}
deadline := time.Now().Add(15*time.Second)
err = c.conn.WriteControl(websocket.PingMessage, nil, deadline)
if err != nil {
return err
}
return nil
}
func (c *Connection) Connect() error {
dialer := *websocket.DefaultDialer
wsConn, _, err := dialer.Dial(Url, nil)
if err != nil {
return err
}
c.conn = wsConn
//c.conn.SetPingHandler(c.HandlePing)
for {
_, msgBytes, err := c.conn.ReadMessage()
if err != nil {
c.Disconnect()
return err
}
fmt.Println(string(msgBytes))
var m GenericMessage
err = json.Unmarshal(msgBytes, &m)
if err != nil {
c.Disconnect()
return err
}
if m.Event != "connected" {
c.Disconnect()
return err
} else {
break
}
}
return nil
}
func (c *Connection) Disconnect() error {
return c.conn.Close()
}
func (c *Connection) ReadLoop(ch chan<- IntraAppMessage) {
for {
fmt.Println("starting new read")
_, msgBytes, err := c.conn.ReadMessage()
if err != nil {
ch <- IntraAppMessage{
ProgramMessage: ProgramMessage{
Error: err.Error(),
},
}
continue
}
var m GenericMessage
err = json.Unmarshal(msgBytes, &m)
if err != nil {
ch <- IntraAppMessage{
ProgramMessage: ProgramMessage{
Error: err.Error(),
},
}
continue
}
ch <- IntraAppMessage{
SocketMessage: m,
}
}
}
func CreateSignature(timestamp int64, key, secret string) string {
secretBytes := []byte(secret)
h := hmac.New(sha256.New, secretBytes)
var buffer bytes.Buffer
buffer.WriteString(strconv.FormatInt(timestamp, 10))
buffer.WriteString(key)
h.Write(buffer.Bytes())
return hex.EncodeToString(h.Sum(nil))
}
func (c *Connection) Authenticate(key, secret string) error {
timestamp := time.Now().Unix()
signature := CreateSignature(timestamp, key, secret)
var authMsg GenericMessage
authMsg.Event = "auth"
authMsg.Auth = AuthData{
Key: key,
Signature: signature,
Timestamp: timestamp,
}
err := c.conn.WriteJSON(authMsg)
if err != nil {
return err
}
for {
_, msgBytes, err := c.conn.ReadMessage()
if err != nil {
c.Disconnect()
return err
}
fmt.Println(string(msgBytes))
var m GenericMessage
err = json.Unmarshal(msgBytes, &m)
if err != nil {
c.Disconnect()
return err
}
if m.Event != "auth" && m.Ok != "ok" {
c.Disconnect()
return err
} else {
break
}
}
return nil
}
func (c *Connection) SubscribeToOrderBook(pair [2]string) error {
sendMsg := GenericMessage{
Event: "order-book-subscribe",
Data: OrderBookSubscribeData{
Pair: pair,
Subscribe: true,
Depth: 0,
},
Oid: uuid.NewV4().String(),
}
err := c.conn.WriteJSON(sendMsg)
if err != nil {
return err
}
return nil
}
func (c *Connection) GetBalance() error {
sendMsg := GenericMessage{
Event: "get-balance",
Oid: uuid.NewV4().String(),
}
err := c.conn.WriteJSON(sendMsg)
if err != nil {
return err
}
return nil
}
Solution was to remove the
for {
continue
}
at the end of the main function

Resources