How to get the result and the status of a transaction - go

I am trying to work with the Ethereum blockchain, with the Solidity contracts.
I am currently deploying a contract and performing some actions on it, but I would like to know how to get some 'feedback/callback/returns' of a specific transaction.
Is there a way to set the status of a transaction to 0 (error) and still get events, for example ?
if (id.length <= 0) {
emit Result("KO", "1");
revert();
}
This will not work (no event) because I revert everything, but the status will be set to 0
if (id.length <= 0) {
emit Result("KO", "1");
return;
}
I will get some event, but the status will stay 1
if (id.length <= 0) {
revert("KO_1");
}
The status will be 0, but I will not have any event
Here is my go code to perform the action :
func testFunction(id []byte) {
//...
//...
tx, err := instance.Action(opt, id)
if (errors.HasError(err)) {
return
}
callbackValue := subscribeToContract(tx.Hash().Hex())
logs.Pretty(tx, callbackValue)
//...
//...
}
func subscribeToContract(hashToRead string) myStruct {
query := ethereum.FilterQuery{
Addresses: []common.Address{address},
}
soc := make(chan types.Log)
sub, err := WssClient.SubscribeFilterLogs(context.Background(), query, soc)
if err != nil {
logs.Error(err)
}
for {
select {
case err := <-sub.Err():
logs.Info(`SOMETHING ERROR`)
logs.Error(err)
case vLog := <-soc:
logs.Info(`SOMETHING`)
contractAbi, _ := abi.JSON(strings.NewReader(string(SignABI)))
event := myStruct{}
contractAbi.Unpack(&event, "Result", vLog.Data)
logs.Info(`New Event from [` + vLog.TxHash.Hex() + `] : ` + event.Message)
}
}
}
If id.length > 0, all good.
But if id.length <= 0, I have no callback from the subscribeToContract function.
Is there a way to directly have the result status, or should loop with tx, err := client.TransactionReceipt(context.Background(), txHash) until I get a Status?

I didn't found any way to subscribe to a specific status change for a transaction, but a workaround :
The go-ethereum package provides 2 functions SubscribeFilterLogs and SubscribeNewHead. We can use the first one to get the logs (if relevant) and the second one to get a block information :
SubscribeNewHead subscribes to notifications about the current
blockchain head on the given channel.
A transaction can be validated or rejected/reverted when a block is mined, so we can use this 'trick'
func checkTransactionReceipt(_txHash string) int {
client, _ := getClient("https://ropsten.infura.io/v3/XXXXXX")
txHash := common.HexToHash(_txHash)
tx, err := client.TransactionReceipt(context.Background(), txHash)
if (Error.HasError(err)) {
return (-1)
}
return (int(tx.Status))
}
func WaitForBlockCompletation(data EthData, hashToRead string) int {
soc := make(chan *types.Header)
sub, err := data.WssClient.SubscribeNewHead(context.Background(), soc)
if (err != nil) {
return -1
}
for {
select {
case err := <-sub.Err():
_ = err
return -1
case header := <-soc:
logs.Info(header.TxHash.Hex())
transactionStatus := checkTransactionReceipt(hashToRead)
if (transactionStatus == 0) {
//FAILURE
sub.Unsubscribe()
return 0
} else if (transactionStatus == 1) {
//SUCCESS
sub.Unsubscribe()
return 1
}
}
}
}
Basically we are waiting for the block to be mined, then we check the TransactionReceipt which fail with an error (not found) if the transaction is not yet validated/rejected. Then, if the transaction is, we can unsubscribe the subscription and return the transaction status (0 fail, 1 success).
Not sure if it's the worst, best, only method, but it's working ! Be free to improve this solution !

An easier/newer solution:
I think the function waitMined is the function you are looking for.
bind.WaitMined(context.Background(), client, signedTx)
Originally posted in here.

Related

Problems with map, goroutine and mutex

This project is made to receive POST routes that will finally count as access to later write to a database. The intuition is to save interaction with the database of another project in production. I decided to do it in go, but I'm new to the language and I'm struggling to understand. I'm trying to make it so that there is no loss or that there are more accesses.
The project basically consists of a controller, a service and two models, just enough to meet the need for which it was created. In my controller I have the function that will be responsible for receiving the POST.
controllers/views.go:
func StoreViews(c *fiber.Ctx) error {
var songview models.SongView
err := c.BodyParser(&songview)
if err != nil {
return c.Status(403).JSON(fiber.Map{
"errors": fiber.Map{"request": err.Error()},
})
}
songview.Date = time.Now()
errs := utils.ValidateStruct(songview)
if len(errs) > 0 {
return c.Status(403).JSON(map[string]interface{}{"errors": errs})
}
go services.StoreViews(songview)
return c.SendStatus(fiber.StatusOK)
}
To handle the received data I made these three functions in my service:
services/views.go
var (
StoreViewsMap = make(map[string]*models.SongView)
StoreControl sync.RWMutex
)
func StoreViews(sview models.SongView) bool {
nameKey := strconv.Itoa(int(sview.SongId)) + sview.Lang + sview.Date.Format("2006-01-02")
songview := getSongView(nameKey)
initSongView(nameKey, songview, sview)
return true
}
func getSongView(name string) *models.SongView {
StoreControl.RLock()
defer StoreControl.RUnlock()
return StoreViewsMap[name]
}
func initSongView(name string, songview *models.SongView, sview models.SongView) bool {
StoreControl.Lock()
defer StoreControl.Unlock()
if songview == nil {
insert := models.SongView{
SongId: sview.SongId,
Lang: sview.Lang,
Date: sview.Date,
Views: 0,
}
songViewNew := &insert // see if & is needed
StoreViewsMap[name] = songViewNew
} else {
songview.Views = songview.Views + 1
}
return true
}
I tried to implement RWMutex to get it to do everything without overlapping anything, but it's not working as it should, sometimes it disappears with views, other times it rescues "songview" in the getSongView function wrongly, among several other problems that I found modifying and reviewing my code. The current code is not in the version that I managed to get closer to the expected result, but I didn't save this version so I decided to bring the current code to exemplify what I'm facing.
I would like you to help me understand how I can deal with several concurrent requests disputing, how I can interact with the data in the best possible way and if there is an error in the use of a pointer I am open to understand. To simulate a POST "attack" to my code I'm using this code in another main.go I made for this test.
var limit int = 10
func main() {
channel := make(chan string)
for i := 0; i < limit; i++ {
go func(i int) {
post("http://localhost:3000/views/store", "lang=pt&song_id=296", i)
channel <- "ok"
}(i)
go func(i int) {
post("http://localhost:3000/views/store", "lang=en&song_id=3016", i)
channel <- "ok"
}(i)
go func(i int) {
post("http://localhost:3000/views/store", "lang=pt&song_id=3016", i)
channel <- "ok"
}(i)
}
for i := 0; i < limit*3; i++ {
<-channel
}
}
func post(url string, json string, index int) {
payload := strings.NewReader(json)
client := &http.Client{}
req, err := http.NewRequest("POST", url, payload)
if err != nil {
fmt.Println(err)
return
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer res.Body.Close()
_, err = ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return
}
if res.StatusCode != 200 {
fmt.Println(res.StatusCode)
}
}
My song-view model is this: (I'm just using it to sort the data, although the project is connected to the bank of the project in production, it is read-only)
type SongView struct {
Id int64 `json:"id"`
SongId int64 `json:"song_id" form:"song_id" gorm:"notNull" validate:"required,number"`
ArtistId int64 `json:"artist_id"`
Lang string `json:"lang" validate:"required,oneof=pt en es de fr"`
Date time.Time `json:"date" gorm:"column:created_at" validate:"required"`
Views int64 `json:"views"`
}
I believe that this code can be written a little more easily in Go, but that is not the question. From your description, it appears that the data is lost somewhere. Have you tried the Go data race detector tool? Below is a link
https://go.dev/doc/articles/race_detector
Can you provide examples of input data where errors/missing items appear?
It happens because your code has a race condition in between read and write to map.
Example:
G1 - goroutine 1
G2 - goroutine 2
G1: ReadLock and Read songview named "MySong"
G1: MySong doesn't exists. Nil will be inserted and returned.
G1: Unlock
G2: ReadLock and Read songview named "MySong"
G2: MySong exist Nil. Nil will be returned.
G1: WriteLock.
G1: songView = nil, so create a new one. Set counter to 1.
G1: set counter to 1. Insert to map on key "MySong"
G1: Unlock
G2: WriteLock: songView = nil(because you read it on step 2). Create new SongView. Set counter to 1.
G2: unlock
As a result you have 1 "MySong" with counter 1 because you rewrite a previous value.
The idea of locking - Atomicity. So, all your operation should be atomic.
func initSongView(name string, sview models.SongView) bool {
StoreControl.Lock()
defer StoreControl.Unlock()
songview := StoreViewsMap[name]
if songview == nil {
insert := models.SongView{
SongId: sview.SongId,
Lang: sview.Lang,
Date: sview.Date,
Views: 1, // counter should be 1 because it's a first view
}
StoreViewsMap[name] = &insert
} else {
songview.Views = songview.Views + 1
}
return true
}

GoLang: fatal error: too many callback functions

I am trying to write a go app that will monitor the status of a server application I run in windows. The application will run for roughly 16 hours before throwing out the following error: (Small snippet)
fatal error: too many callback functions
goroutine 137 [running]:
runtime.throw(0xc4c0a1, 0x1b)
H:/Program Files/Go/src/runtime/panic.go:1117 +0x79 fp=0xc000639d30 sp=0xc000639d00 pc=0x899379
syscall.compileCallback(0xbd18a0, 0xc00041fce0, 0x1, 0x0)
H:/Program Files/Go/src/runtime/syscall_windows.go:201 +0x5e5 fp=0xc000639e28 sp=0xc000639d30 pc=0x8c90e5
syscall.NewCallback(...)
H:/Program Files/Go/src/syscall/syscall_windows.go:177
main.FindWindow(0xc47278, 0x13, 0xc000639f50, 0x2, 0x2)
I have two files. One is the file that is calling a bunch of Windows API stuff, and one is a goroutine that is being performed every 30 seconds to get an update.
I am fairly new to go, especially in windows related development, so I am struggling to find the issue and how to prevent it.
Here is the main file (test example).
func main() {
go updateServerStats()
select {}
}
func ServerStats() {
serverStatsTicker := time.NewTicker(30 * time.Second)
for range serverStatsTicker.C {
serverRunning, serverHung, err := ServerHangCheck()
if err != nil {
ErrorLogger.Println("Server Check Error: ", err)
}
if serverHung {
fmt.Println("Server is hung")
}
}
}
Here is the primary callback/windows file. Its very much still a very rough, very work in progress. Something I found and modified from go playground.
package main
import (
"fmt"
"syscall"
"unsafe"
)
var (
user32 = syscall.MustLoadDLL("user32.dll")
procEnumWindows = user32.MustFindProc("EnumWindows")
procGetWindowTextW = user32.MustFindProc("GetWindowTextW")
procIsHungAppWindow = user32.MustFindProc("IsHungAppWindow")
)
//EnumWindows iterates over each window to be used for callbacks
func EnumWindows(enumFunc uintptr, lparam uintptr) (err error) {
r1, _, e1 := syscall.Syscall(procEnumWindows.Addr(), 2, uintptr(enumFunc), uintptr(lparam), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
//GetWindowText gets the description of the Window, which is the "Text" of the window.
func GetWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (len int32, err error) {
r0, _, e1 := syscall.Syscall(procGetWindowTextW.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(str)), uintptr(maxCount))
len = int32(r0)
if len == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
//IsHungAppWindow uses the IsHungAppWindow to see if Windows has been getting responses.
func IsHungAppWindow(hwnd syscall.Handle) (ishung bool, err error) {
r2, _, err := syscall.Syscall(procIsHungAppWindow.Addr(), 2, uintptr(hwnd), 0, 0)
if r2 == 1{
return true, err
}
return false, err
}
//FindWindow uses EnumWindows with a callback to GetWindowText, and if matches given Title, checks if its hung, and returns state.
func FindWindow(title string) (bool ,bool, error) {
var hwnd syscall.Handle
var isHung bool = false
cb := syscall.NewCallback(func(h syscall.Handle, p uintptr) uintptr {
b := make([]uint16, 200)
_, err := GetWindowText(h, &b[0], int32(len(b)))
if err != nil {
// ignore the error
return 1 // continue enumeration
}
if syscall.UTF16ToString(b) == title {
// note the window
isHung, _ = IsHungAppWindow(h)
hwnd = h
return 0 // stop enumeration
}
return 1 // continue enumeration
})
EnumWindows(cb, 0)
if hwnd == 0 {
return false, false, fmt.Errorf("DCS Not Found")
}
if isHung == true {
return true, isHung, fmt.Errorf("DCS Is Running But Hung")
}
return true, isHung, nil
}
//ServerHangCheck checks the server to see if the window is hung or process is running.
func ServerHangCheck() (bool, bool, error) {
const title = "server_application"
running, hung, err := FindWindow(title)
return running, hung, err
}
Looking at syscall.NewCallback, we find that it's actually implemented via runtime/syscall_windows.go as the function compileCallback:
func NewCallback(fn interface{}) uintptr {
return compileCallback(fn, true)
}
Looking at the runtime/syscall_windows.go code we find that it has a fixed size table of all registered Go callbacks. This code varies a lot between Go releases so it's not too productive to delve any further here. However, there's one thing that is clear: the code checks to see if the callback function is already registered, and if so, re-uses it. So a single callback function occupies one table slot, but adding multiple functions will eventually use up all the table slots and result in the fatal error that you encountered.
You asked in a comment (the comments popped up while I was writing this):
Would I need to reuse it, or can I "close" the original? – Mallachar 7 mins ago
You cannot close out the original. There's an actual table of functions elsewhere in memory; a registered callback uses up a slot in this table, and slots are never released.
Per the advice of torek I got a work around.
I created a global variable
var callbacker uintptr
Then then I created a function which is called by init
func init() {
callbacker = syscall.NewCallback(CallBackCreator)
}
func CallBackCreator(h syscall.Handle, p uintptr) uintptr {
hwnd = 0
b := make([]uint16, 200)
_, err := GetWindowText(h, &b[0], int32(len(b)))
if err != nil {
// ignore the error
return 1 // continue enumeration
}
if syscall.UTF16ToString(b) == title {
// note the window
isHung, _ = IsHungAppWindow(h)
hwnd = h
return 0 // stop enumeration
}
return 1 // continue enumeration
}
which is now called by the function
EnumWindows(callbacker, 0)
Likely not the most "Go" appropriate way, but I am now making better progress.

Handle user answer

The task is that user input a sum of deposit and I could handle it in this context, but isn't like a simple command. Example:
My code:
func main () {
testing()
NewBot, BotError = tgBotApi.NewBotAPI(configuration.BOT_TOKEN)
if BotError != nil {
fmt.Println(BotError.Error())
}
NewBot.Debug = true
fmt.Println("OK", time.Now().Unix(), time.Now(), time.Now().Weekday())
setWebhook(NewBot)
updates := NewBot.ListenForWebhook("/" + configuration.BOT_TOKEN)
//go successfulPaymentListen()
go http.ListenAndServeTLS(fmt.Sprintf("%s:%s", configuration.BOT_HOST, configuration.BOT_PORT), configuration.CERT_FILE, configuration.CERT_KEY, nil)
for update := range updates {
if update.Message != nil {
recognizeCommand(update)
} else if update.CallbackQuery != nil {
if update.CallbackQuery.Data == "/addFunds crypto" {
get_data.AddFundsChooseCurrencyCrypto(update, NewBot)
} else if update.CallbackQuery.Data == "/addFunds qiwi" {
get_data.AddFundsChooseCurrencyQiwi(update, NewBot)
} else if strings.Split(update.CallbackQuery.Data, " ")[2] != "" {
get_data.AddFundsChooseCurrencyCurrentCrypto(update, NewBot, strings.Split(update.CallbackQuery.Data, " ")[2])
//This function is below
}
}
}
}
get_data.AddFundsChooseCurrencyCurrentCrypto:
func AddFundsChooseCurrencyCurrentCrypto(update tgBotApi.Update, NewBot *tgBotApi.BotAPI, currency string) {
chatUser := int64(update.CallbackQuery.From.ID)
msg := tgBotApi.NewMessage(chatUser, "Input a sum of deposit:")
NewBot.Send(msg)
//There is I have to handle user answer, but I can't override ListenWebHook
}
The problem is that I need ListenWebHook localy( in the function AddFundsChooseCurrencyCurrentCrypto) instead of main function
------------------------ UPDATE ------------------------
I have tried this code:
func AddFundsChooseCurrencyCurrentCrypto(update tgBotApi.Update, NewBot *tgBotApi.BotAPI, currency string) {
chatUser := int64(update.CallbackQuery.From.ID)
msg := tgBotApi.NewMessage(chatUser, "Input a sum of deposit:")
NewBot.Send(msg)
NewBotContext, BotError := tgBotApi.NewBotAPI(configuration.BOT_TOKEN)
if BotError != nil {
log.Panic(BotError.Error())
}
updates := NewBotContext.ListenForWebhook("/" + configuration.BOT_TOKEN)
for update := range updates {
fmt.Println(update)
}
}
But error:
panic: http: multiple registrations for /mytokenbot
goroutine 1 [running]:
net/http.(*ServeMux).Handle(0xe38620, 0xc25304, 0x2f, 0xc7dbe0, 0xc00018bec0)
You've tried to register the same url '/mytokenbot' twice with your router. You can find the error in net/http:
https://golang.org/src/net/http/server.go#L2433
In the mux Handle function.
So just look through your code for the register function with servemux, and check how you might be calling it twice.

Golang: pass boolean flag from function in file/sub-directory A, to function in file/sub-directory B

The following function lives in the folder go-ethereum/core/vm/instructions.go:
func opAdd(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
// begin execution time tracking
var startTime = time.Now().UnixNano();
x, y := stack.pop(), stack.pop()
stack.push(math.U256(x.Add(x, y)))
evm.interpreter.intPool.put(y)
// log ellapsed execution time
fmt.Println("execute opAdd consume = ",(time.Now().UnixNano() - startTime))
return nil, nil
}
it's meant to output the execution time of the opcode opAdd in the execution of the Ethereum virtual machine, which looks like this:
However, what I'd like to do is have this information only display when it's running a programme that I've initiated locally, on my own network node.
Here is a function in the file go-ethereum/internal/ethapi/api.go:
// SendRawTransaction will add the signed transaction to the transaction pool.
// The sender is responsible for signing the transaction and using the correct nonce.
func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (string, error) {
tx := new(types.Transaction)
if err := rlp.DecodeBytes(encodedTx, tx); err != nil {
return "", err
}
if err := s.b.SendTx(ctx, tx); err != nil {
return "", err
}
signer := types.MakeSigner(s.b.ChainConfig(), s.b.CurrentBlock().Number())
if tx.To() == nil {
from, err := types.Sender(signer, tx)
if err != nil {
return "", err
}
addr := crypto.CreateAddress(from, tx.Nonce())
log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex())
} else {
log.Info("Submitted transaction", "fullhash", tx.Hash().Hex(), "recipient", tx.To())
}
return tx.Hash().Hex(), nil
}
This function only executes when one submits a transaction to the network, and outputs Submitted transaction in the terminal window's console.
Is there a way that I could pass a boolean flag from go-ethereum/internal/ethapi/api.go to go-ethereum/core/vm/instructions.go, right before it outputs Submitted transaction so that I could execute the opcode execution time catalouging functionality?
EDIT
From the file go-ethereum/internal/ethapi/api.go:
// submitTransaction is a helper function that submits tx to txPool and logs a message.
func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) {
if err := b.SendTx(ctx, tx); err != nil {
return common.Hash{}, err
}
if tx.To() == nil {
signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number())
from, _ := types.Sender(signer, tx)
addr := crypto.CreateAddress(from, tx.Nonce())
log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex())
} else {
// flag for opcode execution time tracking
vm.OpcodeTrigger = true
log.Info("Submitted transaction", "fullhash", tx.Hash().Hex(), "recipient", tx.To())
}
return tx.Hash(), nil
}
and from go-ethereum/core/vm/instructions.go:
var (
OpcodeTrigger bool
bigZero = new(big.Int)
)
func opAdd(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
// wrap the logging in a check
if OpcodeTrigger {
// begin execution time tracking
var startTime = time.Now().UnixNano();
}
x, y := stack.pop(), stack.pop()
stack.push(math.U256(x.Add(x, y)))
evm.interpreter.intPool.put(y)
// wrap the logging in a check
if OpcodeTrigger {
// now set the boolean flag back to false
OpcodeTrigger = false
// log ellapsed execution time
fmt.Println("execute opAdd consume = ",(time.Now().UnixNano() - startTime))
}
return nil, nil
}
To control whether timing information is output in instructions.go, yes you could add a boolean variable to the file instructions.go (perhaps something like):
var Debug bool
and wrap the logging in a check on it:
if Debug {
// log ellapsed execution time
fmt.Println("execute opAdd consume = ",(time.Now().UnixNano() - startTime))
}
and then set it somewhere (for example in your api pkg) with
import ".../core/vm"
vm.Debug = true
this is not goroutine safe so would have to be set once on startup depending on conditions or protected with a mutex. It's also pretty horrible but as a quick hack to see it work you could do this (sorry not familiar with this particular code base) .
I'm not clear on how opAdd is triggered by your other code if it was directly called you could of course just add a parameter to the function to control the output from the calling site, this would be preferable.
Normally this sort of thing you'd either:
Pass in whether to debug or not to the function call opAdd
Set a global debugging level on startup once to control whether logging takes place or at what level globally and this would affect all such tracing.
They do have a log package, I don't know why it's not used here:
https://github.com/ethereum/go-ethereum/blob/master/log/logger.go
EDIT
I would just always allocate startTime , but if you wish to avoid that, you need to declare it at the top level, compare this to your code above:
// wrap the logging in a check
var startTime time.Time
if OpcodeTrigger {
// begin execution time tracking
startTime = time.Now().UnixNano();
}
NB if this is concurrent at all you should be adding a mutex to the vm pkg and only mutating OpCodeTrigger with a function which wraps the access with a mu.Lock - at that point you might start to ask yourself if there are better ways to do this :)
Better if you get stuck again to ask on a forum like forum.golangbridge.org as stackoverflow is not designed for extensive back and forth.

Understand this code (golang), double parantheses ()()

I was wondering if someone can explain this syntax to me. In the google maps go api, they have
type Client struct {
httpClient *http.Client
apiKey string
baseURL string
clientID string
signature []byte
requestsPerSecond int
rateLimiter chan int
}
// NewClient constructs a new Client which can make requests to the Google Maps WebService APIs.
func NewClient(options ...ClientOption) (*Client, error) {
c := &Client{requestsPerSecond: defaultRequestsPerSecond}
WithHTTPClient(&http.Client{})(c) //???????????
for _, option := range options {
err := option(c)
if err != nil {
return nil, err
}
}
if c.apiKey == "" && (c.clientID == "" || len(c.signature) == 0) {
return nil, errors.New("maps: API Key or Maps for Work credentials missing")
}
// Implement a bursty rate limiter.
// Allow up to 1 second worth of requests to be made at once.
c.rateLimiter = make(chan int, c.requestsPerSecond)
// Prefill rateLimiter with 1 seconds worth of requests.
for i := 0; i < c.requestsPerSecond; i++ {
c.rateLimiter <- 1
}
go func() {
// Wait a second for pre-filled quota to drain
time.Sleep(time.Second)
// Then, refill rateLimiter continuously
for _ = range time.Tick(time.Second / time.Duration(c.requestsPerSecond)) {
c.rateLimiter <- 1
}
}()
return c, nil
}
// WithHTTPClient configures a Maps API client with a http.Client to make requests over.
func WithHTTPClient(c *http.Client) ClientOption {
return func(client *Client) error {
if _, ok := c.Transport.(*transport); !ok {
t := c.Transport
if t != nil {
c.Transport = &transport{Base: t}
} else {
c.Transport = &transport{Base: http.DefaultTransport}
}
}
client.httpClient = c
return nil
}
}
And this is the line I don't understand in NewClient
WithHTTPClient(&http.Client{})(c)
Why are there two ()()?
I see that WithHTTPClient takes in a *http.Client which that line does, but then it also passes in a pointer to the client struct declared above it?
WithHTTPClient returns a function, ie:
func WithHTTPClient(c *http.Client) ClientOption {
return func(client *Client) error {
....
return nil
}
}
WithHTTPClient(&http.Client{})(c) is just calling that function with c (a pointer to a Client) as parameter. It could be written as:
f := WithHTTPClient(&http.Client{})
f(c)

Resources