How to update array element in firestore with golang? - go

Following code:
Lobby := map[string]interface{}{
"table_id" :"new table id",
"Status" : true,
"name" : "shivam",
"array":[]interface{}{0,1,3},// this replace existing array with new values
}
result, err := client.Collection("lobbies").Doc("12").Set(ctx,Lobby,firestore.MergeAll)
I just want to update 2nd element in array with the new value

There is no way to inform a database to replace a specific element in a slice or array.
Your array field stores a slice and is not a map, so you need to implement your own code that will regenerate that slice in the way you need and replace it in the document, something like:
Lobby := map[string]interface{} {
"table_id" : "new table id",
"Status" : true,
"name" : "shivam",
"array" : []interface{}{0,1,3},
}
new_slice, err := change_my_slice(Lobby["array"])
if err != nil {
log.Errorf("Error message goes here")
return nil, err
}
Lobby["array"] = new_slice
result, err := client.Collection("lobbies").Doc("12").Set(ctx,Lobby,firestore.MergeAll)

This is currently possible, although advertised as "Not yet supported" at firebase.
Here's a slightly modified example.
type User struct {
Address string `firestore:"address"`
Hobbies []Stickers `firestore:"hobbies"`
}
package main
import (
"cloud.google.com/go/firestore"
"context"
"fmt"
)
func main() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
log.Fatalln("Client Error", err)
}
defer client.Close()
co := client.Doc("User/Frank")
_, err := co.Update(ctx, []firestore.Update{
{Path: "hobbies", Value: firestore.ArrayUnion("Simping")},
})
if err != nil {
log.Println("Update error", err)
}
}
You can check the documentation of ArrayUnion and ArrayRemove at pkg

Related

How to test nested input in Go

I have function for getting user input using os.Stdin
func (i input) GetInput(stdin io.Reader) (string, error) {
reader := bufio.NewReader(stdin)
data, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("get input error: %w", err)
}
return strings.ReplaceAll(data, "\n", ""), nil
}
In my programm I need to have 2 inputs:
First for getting base info for example user name
Second for getting aditional info that depends on first input
name,err := GetInput(os.Stdin)
if err != nil {
// error handling.....
}
switch name {
case "test":
//do something...
age, err := GetInput(os.Stdin)
if err != nil {
// error handling.....
}
fmt.Println(age)
case "another":
// Here another input
}
It it possible to write unit tests for that case?
For testing one user input I use this snippet and it works:
var stdin bytes.Buffer
stdin.Write([]byte(fmt.Sprintf("%s\n", tt.input)))
GetInput(stdin)
But it didn't work with 2 nested inputs
Maybe consider having a function that returns a specific type as a result and put it into a separate package.
Since I see name and age mentioned, perhaps we can assume a concrete type like Person for illustration.
It is important to note that we want to include the actual reader as a parameter and not have a hard coded reference to os.Stdin. This makes the mocking of nested inputs possible in the first place.
With this, the signature of the method could look something like the following:
func NestedInput(input io.Reader) (*Person, error)
The corresponding type could be:
type Person struct {
Name string
Age int
}
If one now combines your code snippets to a complete GO file with the name input.go in a separate directory, it might look something like this:
package input
import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
)
func getInput(reader *bufio.Reader) (string, error) {
data, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("get input error: %w", err)
}
return strings.ReplaceAll(data, "\n", ""), nil
}
type Person struct {
Name string
Age int
}
func NestedInput(input io.Reader) (*Person, error) {
reader := bufio.NewReader(input)
name, err := getInput(reader)
if err != nil {
return nil, err
}
switch name {
case "q":
return nil, nil
default:
ageStr, err := getInput(reader)
if err != nil {
return nil, err
}
age, err := strconv.Atoi(ageStr)
if err != nil {
return nil, err
}
return &Person{Name: name, Age: age}, nil
}
}
An input of q returns nil, nil and could be used to terminate the input, e.g. if the query was made in a loop.
Unit Test
The unit test
func Test_nestedInput(t *testing.T)
in a file named input_test.go should now provide the input data.
Since the NestedInput function now expects an io.Reader as a parameter, we can simply generate the desired input with, for example,
input := strings.NewReader("George\n26\n")
So the test could look something like this:
package input
import (
"strings"
"testing"
)
func Test_nestedInput(t *testing.T) {
input := strings.NewReader("George\n26\n")
person, err := NestedInput(input)
if err != nil {
t.Error("nested input failed")
}
if person == nil {
t.Errorf("expected person, but got nil")
return
}
if person.Name != "George" {
t.Errorf("wrong name %s, expected 'George'", person.Name)
}
if person.Age != 26 {
t.Errorf("wrong age %d, expected 26", person.Age)
}
}
Of course, the tests can be extended with further details. But this, as you can see, mocks a nested input.

How to unmarshal/parse a request using Go correctly?

How to parse a curl request response in Golang correctly? I have tried the following where I send a request to an api and its response is:
{
"Certificates": [
{
"Name": "some-name.here",
.......
}
],
"DataRange": "Certificates 1 - 1",
"TotalCount": 1
}
Now I want to use the Name in the Certificates in a string variable. i.e match. Before even I get to the looping through of the response, I get the error: json: cannot unmarshal object into Go value of type []program.Item. This error is coming from json.Unmarshal function where I pass my []bytes and the struct to use the bytes. Am I doing this correctly?
type Item struct {
Certificates []CertificatesInfo
}
type CertificatesInfo struct {
Name string
}
func main() {
url := .....
req, err := http.NewRequest("GET", url, nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
// handle err
continue
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
continue
}
var ItemInfo []Item
if err := json.Unmarshal(bodyBytes, &ItemInfo); err != nil {
return nil, fmt.Errorf("failed to parse %v", err)
}
for _, infos := range ItemInfo {
for _, names := range infos.Certificates {
infraId:= names.Name
match:= display_name
}
}
}
There's a mismatch between your sample JSON response data (a single object containing a list of certificates), and your code, which is expecting a list of objects, each of which contain a list of certificates).
Assuming that your JSON example is correct, this bit:
var ItemInfo []Item
if err := json.Unmarshal(bodyBytes, &ItemInfo); err != nil {
. . .
should probably be
var itemInfo Item
if err := json.Unmarshal(bodyBytes, &ItemInfo); err != nil {
. . .
Works on my machine:
https://go.dev/play/p/8FqIif1zzsQ
package main
import (
"encoding/json"
"fmt"
)
func main() {
resp := Item{}
err := json.Unmarshal([]byte(data), &resp)
if err != nil {
fmt.Print(err)
panic(err)
}
fmt.Printf("resp.DataRange: %s\n", resp.DataRange)
fmt.Printf("resp.TotalCount: %d\n", resp.TotalCount)
fmt.Printf("len(resp.Certificates: %d\n", len(resp.Certificates))
for i, c := range resp.Certificates {
fmt.Printf("resp.Certificates[%d].Name: %s\n", i, c.Name)
}
}
type Item struct {
Certificates []CertificatesInfo
DataRange string
TotalCount int
}
type CertificatesInfo struct {
Name string
}
const data = `
{
"Certificates": [
{ "Name": "alpha" },
{ "Name": "bravo" },
{ "Name": "charlie" }
],
"DataRange": "Certificates 1 - 1",
"TotalCount": 3
}
`

How to bulk remove objects in minio with golang

I'm trying to bulk remove objects in minio as described here:
objectsCh := make(chan minio.ObjectInfo)
// Send object names that are needed to be removed to objectsCh
go func() {
defer close(objectsCh)
// List all objects from a bucket-name with a matching prefix.
for object := range minioClient.ListObjects(context.Background(), "my-bucketname", "my-prefixname", true, nil) {
if object.Err != nil {
log.Fatalln(object.Err)
}
objectsCh <- object
}
}()
opts := minio.RemoveObjectsOptions{
GovernanceBypass: true,
}
for rErr := range minioClient.RemoveObjects(context.Background(), "my-bucketname", objectsCh, opts) {
fmt.Println("Error detected during deletion: ", rErr)
}
Where I can ListObjects by bucketname and prefixname. However I'm struggling to find an approach where I can ListObjects by for example a slice of object names which I want to remove or any other way. So my question is: how can I properly generate a ListObjects for arbitrary objectNames in a given bucket? Or is there any other way to do remove objects by their names? Thanks.
func DeleteItemInMinio(ctx context.Context, item []string) (string, error) {
minioClient, err := minio.New("test.com", os.Getenv("MINIO_ACCESS_KEY"), os.Getenv("MINIO_SECRET_KEY"), true)
if err != nil {
log.Println(err)
}
for _, val := range item {
err = minioClient.RemoveObject("my-bucketname", val)
if err != nil {
panic(err)
}
}
return "success", nil
}
and call it with :
r.POST("/test/delete", func(c *gin.Context) {
item := []string{"golang.png", "phplogo.jpg"}
execute.DeleteItemInMinio(context.Background(), item)
})
i tried it and it works, in case you still need it

Go elasticsearch bulk insert

I have been unable to solve the problem into elasticsearch Bulk method for several days, since I am not strong in Go and started learning it not so long ago, while executing the code :
package main
import (
"bytes"
json "encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
type BulkInsertMetaData struct {
Index []BulkInsertIndex `json:"index"`
}
type BulkInsertIndex struct {
Index string `json:"_index"`
ID string `json:"_id"`
}
type BulInsertData struct {
Url string `json:"url"`
}
func main() {
dataMeta := BulkInsertMetaData{
Index: []BulkInsertIndex{
{
Index: "Test",
ID: "1234567890",
},
},
}
data := BulInsertData{
Url: "http://XXXX.XX",
}
TojsBulInsertData, _ := json.Marshal(data)
TojsBulkInsertMetaData, _ := json.Marshal(dataMeta)
BulkMetaData := bytes.NewBuffer(append(TojsBulkInsertMetaData, []byte("\n")...))
BulkData := bytes.NewBuffer(append(TojsBulInsertData, []byte("\n")...))
log.Println(BulkMetaData)
log.Println(BulkData)
respMetaData, err := http.Post("http://127.0.0.1:9200/_bulk", "application/json", BulkMetaData)
if err != nil {
log.Println(err)
}
body, err := ioutil.ReadAll(respMetaData.Body)
if err != nil {
log.Println(err)
}
fmt.Println(string(body))
respBulkData, err := http.Post("http://127.0.0.1:9200/_bulk", "application/json", BulkData)
if err != nil {
log.Println(err)
}
body2, err := ioutil.ReadAll(respBulkData.Body)
if err != nil {
log.Println(err)
}
fmt.Println(string(body2))
}
but i get an error:
2022/02/09 14:37:02 {"index":[{"_index":"Test","_id":"1234567890"}]}
2022/02/09 14:37:02 {"url":"http://XXXX.XX"}
{"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"Malformed action/metadata line [1], expected START_OBJECT or END_OBJECT but found [START_ARRAY]"}],"type":"illegal_argument_exception","reason":"Malformed action/metadata line [1], expected START_OBJECT or END_OBJECT but found [START_ARRAY]"},"status":400}
please help and explain what I'm doing wrong, I searched the Internet for the answer to my question but did not find
I test insert when using REST client passes without problems
BulkMetaData should be {"index":{"_index":"Test","_id":"1234567890"}} (without []) and it should be sent to /_bulk together with BulkData, as a single payload:
{"index":{"_index":"Test","_id":"1234567890"}}
{"url":"http://XXXX.XX"}
Sorry for kinda necroing but I also recently needed to design a Bulk connector in our codebase and the fact that there are NO NDJSON encoder/decoders out on the web is appalling. Here is my implementation:
func ParseToNDJson(data []map[string]interface{}, dst *bytes.Buffer) error {
enc := json.NewEncoder(dst)
for _, element := range data {
if err := enc.Encode(element); err != nil {
if err != io.EOF {
return fmt.Errorf("failed to parse NDJSON: %v", err)
}
break
}
}
return nil
}
Driver code to test:
func main() {
var b bytes.Buffer
var data []map[string]interface{}
// pointless data generation...
for i, name := range []string{"greg", "sergey", "alex"} {
data = append(data, map[string]interface{}{name: i})
}
if err := ParseToNDJson(query, &body); err != nil {
return nil, fmt.Errorf("error encoding request: %s", err)
}
res, err := esapi.BulkRequest{
Index: "tasks",
Body: strings.NewReader(body.String()),
}.Do(ctx, q.es)
Hope this helps someone

Converting a complex http json response array to a simple struct slice without creating several structs to match the response using Go

If an http response comes in a format that is not directly a list of objects the only way I could figure out how to convert them to structs is by creating two structs to match the exact format of the response. Is there anyway to do this cleaner where I can just create a Product struct and don't need to create the ProductRes wrapper struct?
Below is an example of what the response from the api I am calling looks like:
{
"items": [
{
"name": "Product 1",
"price": 20.45
},
{
"name": "Product 2",
"price": 31.24
}
]
}
Here are the two structs I create to convert the api response to a slice of Product:
type Product struct {
Name string `json:"name"`
Price float64 `json:"price"`
}
type ProductRes struct {
Items []Product `json:"items"`
}
Here is part of the code to make the api request and convert the response to a slice of Product:
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
log.Fatalln(err)
}
resp, err := c.client.Do(req)
if err != nil {
log.Fatalln(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
}
products := ProductRes{}
// This line makes me think I actually do need multiple structs, unless I can modify body somehow prior to sending it in here
json.Unmarshal(body, &products)
You can eliminate the declared type ProductRes by using an anonymous type:
var wrapper struct { Items []Product }
err := json.Unmarshal(body, &wrapper)
if err != nil {
// TODO: handle error
}
products := wrapper.Items
You can also use a map:
var m map[string][]Product
err := json.Unmarshal(body, &m)
if err != nil {
// TODO: handle error
}
products := m["items"]

Resources