How to avoid repeating myself in Go? - go

I just started with Go, and my background includes generics. Since Go still does not support generics, I'm wondering how do I keep my code DRY?
Take a look at the example below, the request argument has a dynamic type, which returns a dynamic response (PaymentMethodResponse). If I want to create another request, I copy and paste the whole code inside the method, changing only the type of the request and response and the localVarPath variable.
/*
PaymentMethods Returns available payment methods.
Queries the available payment methods for a transaction based on the transaction context (like amount, country, and currency). Besides giving back a list of the available payment methods, the response also returns which input details you need to collect from the shopper (to be submitted to `/payments`). Although we highly recommend using this endpoint to ensure you are always offering the most up-to-date list of payment methods, its usage is optional. You can, for example, also cache the `/paymentMethods` response and update it once a week.
* #param request PaymentMethodsRequest - reference of PaymentMethodsRequest).
* #param ctxs ..._context.Context - optional, for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
#return PaymentMethodsResponse
*/
func (a *Checkout) PaymentMethods(request *PaymentMethodsRequest, ctxs ..._context.Context) (PaymentMethodsResponse, *_nethttp.Response, error) {
var (
localVarHTTPMethod = _nethttp.MethodPost
localVarPostBody interface{}
localVarReturnValue PaymentMethodsResponse
)
// create path and map variables
localVarPath := a.BasePath() + "/paymentMethods"
localVarHeaderParams := make(map[string]string)
localVarQueryParams := _neturl.Values{}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{"application/json"}
// set Content-Type header
localVarHTTPContentType := common.SelectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := common.SelectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
// body params
if request != nil {
localVarPostBody = request
}
var ctx _context.Context
if len(ctxs) == 1 {
ctx = ctxs[0]
}
r, err := a.Client.PrepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.Client.CallAPI(r)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := common.NewAPIError(localVarBody, localVarHTTPResponse.Status)
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.Client.Decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := common.NewAPIError(localVarBody, err.Error())
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}
Example of usage: (request is a json struct)
res, httpRes, err := client.Checkout.PaymentMethods(&checkout.PaymentMethodsRequest{})

You can use the same approach as the one used by json.Unmarshal and other decoders/unmarshalers that accept an argument of type interface{} and instead of returning a value of an unknown type they store the result of their operation into the provided interface{} argument.
Here's example pseudo code:
func apicall(req, res interface{}) error {
inputbody, err := jsonencode(req)
if err != nil {
return err
}
response, err := httpclient.postrequest(inputbody)
if err != nil {
return err
}
return jsondecode(res, response.body)
}
func main() {
req := new(PaymentMethodsRequest)
res := new(PaymentMethodsResponse)
if err := apicall(req, res); err != nil {
return err
}
// do something with res
}

Related

Best behavior on error during multipart stream

I have implemented a CSV export endpoint that is grabbing data from a database, row by row, and writing each line to BodyWriter. This must happen line by line, because the implementation needs to be conscious of memory consumption...
Here is a naive pseudo implementation:
rows, err := db.Query(q)
if err != nil {
w.WriteHeader(http.StatusInternalServerError) // <-- location 0
return
}
for rows.Next() {
if err := rows.Err(); err != nil {
// <-- location 1
return
}
r := result{}
if err := rows.Scan(&r); err != nil {
// <-- location 2
return
}
stringSlice := convertToStringSlice(r)
err := w.Write([]byte(line))
if err != nil {
// <-- location 3
return
}
}
return // <-- location 4
In location 0 - there is only one call to BodyWriter.WriteHeader so no problem.
In location 4 - I've already implicitly called BodyWriter.WriteHeader by calling BodyWriter.Write. Once I return, the BodyWriter is probably released and that is how (I assume) how the connection is closed (client gets EOF?).
But what if an error occurs after I've already written a few lines, in position 1/2/3? How do I differentiate this from the situation when we return in location 4?
I want to somehow notify the client that something went wrong, but the 200 status was already sent...
It also seems that Golang standard http library manages the connection internally and does not expose an easy way to manually close the TCP connection.
What is the best way to handle this situation?
use the context package
so if the frontend close the request you are not endup reading data and processing data from your database for no use
func test3Handler(w http.ResponseWriter, r *http.Request) {
rows, err := db.QueryContext(r.Context(), q)
}
the *http.Request have a context that is active while the request is active so if it gets an error that 'the context is closed' which means that the frontend dropped the request in the middle of the request
you can't update the `w.WriteHeader(200)` or call `w.WriteHeader(200)` after writing to the body it needs to be called before writing to the body,
you will need to send the error in that row, also even it didn'
for rows.Next() {
res := result{}
if err := rows.Scan(&res); err != nil {
// <-- location 2
fmt.Fprintf(w, "cant read row from the db error %v\n", err)
return
}
stringSlice, err := convertToStringSlice(r)
if err != nil {
fmt.Fprintf(w, "cant process row %+v, error %v\n", res, stringSlice)
return
}
fmt.Fprintln(w, stringSlice)
}
if you do want to return a 500 on a scan error, scan all rows to memory and if there is an error return an 500 else return all rows
func csvHandler(w http.ResponseWriter, r *http.Request) {
rows, err := db.QueryContext(r.Context(), q)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
var list []*result
for rows.Next() {
res := &result{}
if err := rows.Scan(res); err != nil {
// <-- location 2
w.WriteHeader(http.StatusInternalServerError)
return
}
list = append(list, res)
}
csvEnc := csv.NewWriter(w)
for _, res := range list {
err = csvEnc.Write(convertToStringSlice(res)...)
}
if err != nil {
// <-- location 3
// handle response writing error
return
}
return
}
this solution is using more memory than the previous example

affected/package: database/sql "Deficiency in the code"

go version: go 1.19
my problem is with the database/sql package. I have plsql code available in service. values are sent to the service via map. but the number is not limited,
I need to send to db.exec() via sql.Named, I checked with loop and with interface, it didn't work.
Please help me.
my codes are:
in the controller
const begin = "
jsonData.Put('stateid', :stateid);
"
array_for_param := map[string]string{
"stateid": "a",
}
temp, err := services.Perform(c, begin, array_for_param, i_User_Id)
if err != nil {
log.Fatal(err)
}
service code:
var params []interface{}
for key, value := range array_for_param {
params = append(params, sql.Named(key, value))
}
if _, err := db.Exec(declare+begin+end,
sql.Named("i_User_Id", i_User_Id),
params...,
); err != nil {
log.Fatal(err)
}
The main problem I have is that I need to send the sql.Named code using a for, which is an unknown number
I did that too
if _, err := db.Exec(declare+begin+end,
sql.Named("i_User_Id", i_User_Id),
for key, value := range array_for_param {
return sql.Named(key, value)
}
); err != nil {
log.Fatal(err)
}

Go Struct Fields Undefined

Learning Go as I work through creating a REST API. Getting the error undefined: authDetails when I use it the two times at the bottom to access the fields. I understand that it's set in a branch that may not be executed. I've tried moving it outside of the branch, or declaring it above with var authDetails auth.AccessDetails and then setting the values afterwards. I've also tried moving the assignment outside of the braces, but no matter what I do it doesn't seem to carry the values from within the conditional block out to where I try to use them.
I think I'm misunderstanding something regarding scope, if someone could assist me in understanding what the problem is it would be a huge help.
The Struct definition is:
type AccessDetails struct {
AccessUuid string
UserId uint64
}
and the code is:
func (server *Server) GetTokenUserIDFromRequest(r *http.Request) (uint64, error) {
var authDetails auth.AccessDetails
tokenString := auth.ExtractToken(r)
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return 0, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return []byte(os.Getenv("API_SECRET")), nil
})
if err != nil {
return 0, err
}
claims, ok := token.Claims.(jwt.MapClaims)
if ok && token.Valid {
accessUuid, ok := claims["access_uuid"].(string)
if !ok {
return 0, errors.New("token not valid, cannot retrieve access_uuid")
}
userid, err := strconv.ParseUint(fmt.Sprintf("%.0f", claims["user_id"]), 10, 64)
if err != nil {
return 0, err
}
authDetails := &auth.AccessDetails{
AccessUuid: accessUuid,
UserId: userid,
}
}
redisuserid, err := server.RedisClient.Get(authDetails.AccessUuid).Result()
if err != nil {
return 0, err
}
userID, _ := strconv.ParseUint(redisuserid, 10, 64)
if userID != authDetails.UserId {
return 0, errors.New("userid from token does not match userid from database")
}
return userID, nil
}
You are redeclaring authDetails in the if-block instead of setting the value declared at the outer scope. The relevant section in the language specification is:
https://golang.org/ref/spec#Short_variable_declarations
Instead of authDetails:=... use authDetails=..., so it becomes an assignment, not a declaration.

Map response to a struct using Golang

I am attempting to map a response from an API to a struct using Golang.
The JSON that comes back when I view the link in the browser is below:
{
"GBP": 657.54
}
And I just want to map it to a simple struct like so:
type Price struct {
Name string
Value float64
}
Here is my current code.
func FetchCoinPrice(fsym string, tsyms string) Price {
url := fmt.Sprintf("https://min-api.cryptocompare.com/data/price?fsym=" + fsym + "&tsyms=" + tsyms)
fmt.Println("Requesting data from " + url)
price := Price{}
// getting the data using http
request, err := http.Get(url)
if err != nil {
log.Fatal(err.Error())
}
// Read the response body using ioutil
body, err := ioutil.ReadAll(request.Body)
if err != nil {
log.Fatal(err.Error())
}
defer request.Body.Close()
if request.StatusCode == http.StatusOK {
json.Unmarshal(body, &price)
}
return price
}
At the moment all I receive is an empty struct, I know the link is bringing back the correct data and I've tested it in my browser.
The mapping doesn't work that way. Instead, you should use a map:
data := []byte(`{
"GBP": 657.54
}`)
priceMap := map[string]float64{}
err := json.Unmarshal(data, &priceMap)
// Check your errors!
if err != nil {
log.Fatal(err.Error())
}
fmt.Println(priceMap)
This will print:
map[GBP:657.54]
You can then iterate over the map and build the struct you mentioned above, or just access the entry directly if you know the currency. eg: priceMap["GBP"]
You should really check your errors, especially if you're not getting the output you expect from Unmarshal.
The problem is that the unmarshaler cannot guess that keys in a JSON object should correspond to some value in a struct. Golang JSON mapping simply doesn't work that way.
However, you can make your "Price" type implement json.Unmarshaler to deserialize a message into a map of floats (map[string]float64) then ensure the shape is right and populate the struct accordingly:
func (p *Price) UnmarshalJSON(bs []byte) error {
kvs := map[string]float64{}
err := json.Unmarshal(bs, &kvs)
if err != nil {
return err
}
if len(kvs) != 1 {
return fmt.Errorf("expected 1 key, got %d", len(kvs))
}
for name, value := range kvs {
p.Name, p.Value = name, value
}
return nil
}
func main() {
jsonstr := `[{"GBP":657.54},{"USD":123.45}]`
ps := []Price{}
err := json.Unmarshal([]byte(jsonstr), &ps)
if err != nil {
panic(err)
}
// ps=[]main.Price{
// main.Price{Name:"GBP", Value:657.54},
// main.Price{Name:"USD", Value:123.45}
// }
}

Calling SOAP with Golang

I am new to golang and trying make a soap call with gowsdl .
I have generated the wsdl code and installed it as a package. I am however struggling to understand the syntax for call the method from it.
When I examine the package, this is what I want in the soap body:
type AccountUser struct {
XMLName xml.Name `xml:"http://exacttarget.com/wsdl/partnerAPI AccountUser"`
*APIObject
AccountUserID int32 `xml:"AccountUserID,omitempty"`
UserID string `xml:"UserID,omitempty"`
Password string `xml:"Password,omitempty"`
Name string `xml:"Name,omitempty"`
Email string `xml:"Email,omitempty"`
MustChangePassword bool `xml:"MustChangePassword,omitempty"`
ActiveFlag bool `xml:"ActiveFlag,omitempty"`
ChallengePhrase string `xml:"ChallengePhrase,omitempty"`
ChallengeAnswer string `xml:"ChallengeAnswer,omitempty"`
UserPermissions []*UserAccess `xml:"UserPermissions,omitempty"`
Delete int32 `xml:"Delete,omitempty"`
LastSuccessfulLogin time.Time `xml:"LastSuccessfulLogin,omitempty"`
IsAPIUser bool `xml:"IsAPIUser,omitempty"`
NotificationEmailAddress string `xml:"NotificationEmailAddress,omitempty"`
IsLocked bool `xml:"IsLocked,omitempty"`
Unlock bool `xml:"Unlock,omitempty"`
BusinessUnit int32 `xml:"BusinessUnit,omitempty"`
DefaultBusinessUnit int32 `xml:"DefaultBusinessUnit,omitempty"`
DefaultApplication string `xml:"DefaultApplication,omitempty"`
Locale *Locale `xml:"Locale,omitempty"`
TimeZone *TimeZone `xml:"TimeZone,omitempty"`
DefaultBusinessUnitObject *BusinessUnit `xml:"DefaultBusinessUnitObject,omitempty"`
AssociatedBusinessUnits struct {
BusinessUnit []*BusinessUnit `xml:"BusinessUnit,omitempty"`
} `xml:"AssociatedBusinessUnits,omitempty"`
Roles struct {
Role []*Role `xml:"Role,omitempty"`
} `xml:"Roles,omitempty"`
LanguageLocale *Locale `xml:"LanguageLocale,omitempty"`
SsoIdentities struct {
SsoIdentity []*SsoIdentity `xml:"SsoIdentity,omitempty"`
} `xml:"SsoIdentities,omitempty"`
}
And the method to call the SOAP is :
func (s *SOAPClient) Call(soapAction string, request, response interface{}) error {
envelope := SOAPEnvelope{
//Header: SoapHeader{},
}
envelope.Body.Content = request
buffer := new(bytes.Buffer)
encoder := xml.NewEncoder(buffer)
//encoder.Indent(" ", " ")
if err := encoder.Encode(envelope); err != nil {
return err
}
if err := encoder.Flush(); err != nil {
return err
}
log.Println(buffer.String())
req, err := http.NewRequest("POST", s.url, buffer)
if err != nil {
return err
}
if s.auth != nil {
req.SetBasicAuth(s.auth.Login, s.auth.Password)
}
req.Header.Add("Content-Type", "text/xml; charset=\"utf-8\"")
if soapAction != "" {
req.Header.Add("SOAPAction", soapAction)
}
req.Header.Set("User-Agent", "gowsdl/0.1")
req.Close = true
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: s.tls,
},
Dial: dialTimeout,
}
client := &http.Client{Transport: tr}
res, err := client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
rawbody, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
if len(rawbody) == 0 {
log.Println("empty response")
return nil
}
log.Println(string(rawbody))
respEnvelope := new(SOAPEnvelope)
respEnvelope.Body = SOAPBody{Content: response}
err = xml.Unmarshal(rawbody, respEnvelope)
if err != nil {
return err
}
fault := respEnvelope.Body.Fault
if fault != nil {
return fault
}
return nil
}
I have imported the package into my go file , and would love pointers on how to call this.
To use the generated code you obviously will have to first initialize the soap client with one of the generated "constructor" functions NewSOAPClient or NewSOAPClientWithTLSConfig.
After that you'll need to prepare two values that you can use as the request and response arguments to the Call method, they represent the body content of the soap request/response payloads.
The types of those two values will depend on what kind of call you want to make, for example the hypothetical calls create_account, update_account, and delete_account would usually require different types. Basically, the type of the request value should be marshalable into an xml that matches the xml expected by the soap service for the specified action, and the type of the response should be unmarshalable from an xml that matches the soap service's documented response for the specified action.
Consider this contrived example:
There is a SOAP service that allows you to create users. For you to be able to create a user with the service it requires you to send an email and a password, and if everyting's ok it will return an id. In such a case your two request/response types would look like this:
type CreateUserRequest struct {
Email string `xml:"Email,omitempty"`
Password string `xml:"Password,omitempty"`
}
type CreateUserResponse struct {
ID string `xml:"ID"`
}
Then the client code would look like this:
client := NewSOAPClient("https://soap.example.com/call", true, nil)
req := &CreateUserRequest{
Email: "jdoe#example.com",
Password: "1234567890",
}
res := &CreateUserResponse{}
if err := client.Call("create_user", req, res); err != nil {
panic(err)
}
// if everything went well res.ID should have its
// value set with the one returned by the service.
fmt.Println(res.ID)

Resources