Skip to content
This repository was archived by the owner on Jun 23, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions docs/http/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package main

import (
"encoding/json"
"fmt"
"io"
"net/http"
)


type UserResponse struct {
Attributes User `json:"attributes"`
}

// here its used by json := exported
type User struct {
ID int `json:"id"`
Name string `json:"name"`
AccountIDs []int `json:"account_ids"`
}
// array_of_ints = {array_of_strings}.map(:to_s)
// convert an array into something else
// 1. create a second array variable
// arrayOfInts := make([]int, len(arrayOfStrings))
// 2. iterate and convert each element
// for i, v := range arrayOfStrings {
// arrayOfInts[i] = ....
// }

func main() {
url := "https://sample-accounts-api.herokuapp.com/users/1"
// no underscore in golang
resp, err := http.Get(url)
// best practice to check and write defer code nearby to original code
defer resp.Body.Close() // defer here vs derer after error handling is same in this case

if err != nil {
fmt.Println("error found %v", err) // shadowing error variable... [ from interface error{}] -> avoid naming conflicts with packages
return
}
// return fmt.Errorf("http get %w", err)
//error handlling missing

// defer resp.Body.Close()
// defer will be skipped here, but it's ok because an error occurred, so there is no body to close

body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("error found %v", err)
return
}

// var result map[string]any
// err = json.Unmarshal(body, &result)

// if err != nil {
// fmt.Println("error found")
// }
// const attr = "attributes"
// optimization + safety [ type safety ]
// attributes, _ := result["attributes"].(map[string]any)
// attributesJSON, _ := json.Marshal(attributes)
var user UserResponse // dont use same var name as struct type name
err = json.Unmarshal(body, &user)

if err != nil {
fmt.Println("error found")
}

fmt.Println("User: ", user.Attributes.AccountIDs)
// fmt.Printf("ID: %d, Account IDs: %v\n", user.ID, user.AccountIDs)

// user.Attributes.AccountIDs is slice of ints
// convert to slice of strings by using strconv.Itoa
// use strings.Join() to join the elements with ","

}
112 changes: 112 additions & 0 deletions docs/http/main_1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package main

import (
"encoding/json"
"fmt"
"io"
"net/http"
)

type UserResponse struct {
Attributes User `json:"attributes"`
}

type User struct {
ID int `json:"id"`
Name string `json:"name"`
AccountIDs []int `json:"account_ids"`
}

type AccountResponse struct {
Attributes Account `json:"attributes"`
}

type Account struct {
ID int `json:"id"`
UserID int `json:"user_id"`
Name string `json:"name"`
Balance int `json:"balance"`
}

func getUser(userID int) (User, error) {
url := fmt.Sprintf("https://sample-accounts-api.herokuapp.com/users/%d", userID)
resp, err := http.Get(url)
if err != nil {
return User{}, err
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function has great error handling! But it is generally recommended to use error wrapping (nested errors) when you propagate errors to parent functions. This way, you can immediately identify where an error comes from, just by looking at the error message. Error wrapping is provided by the errors package, you can check the official documentation for more information.

In this case, instead of returning the err object, you can use fmt.Errorf and the verb %w to create a new error:

returnUser{}, fmt.Errorf("fetching user %v: %w", userID, err)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ohhh i see , the error stack will have 2 similar logs this way..gotcha

}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return User{}, err
}

var userResponse UserResponse
err = json.Unmarshal(body, &userResponse)
if err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comes down to personal preferences, so other people may have different suggestions on this. But a more idiomatic way of retrieving an error and checking if it's nil, is like this:

if err = json.Unmarshal(body, &userResponse); err != nil {
	return User{}, err
}

The main benefit of this approach is that the scope of the err variable is limited to the if statement. This style is used whenever the function that you want to check (in this case, json.Unmarshal()) only returns an error object, and you want to immediately check the error.


Note: your solution is also perfectly valid, since the err variable has already been declared in a previous statement. The code that I recommended does not actually limit the scope of the outer err variable, but it's still considered more "go idiomatic", aka it's more common.

return User{}, err
}

return userResponse.Attributes, nil
}

func getAccounts(userID int) ([]Account, error) {
url := fmt.Sprintf("https://sample-accounts-api.herokuapp.com/users/%d/accounts", userID)
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

var accountResponses []AccountResponse
err = json.Unmarshal(body, &accountResponses)
if err != nil {
return nil, err
}

accounts := make([]Account, len(accountResponses))
Copy link
Collaborator

@mavrikis-kostas mavrikis-kostas Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great use of the make function here, this will create a new slice that has preconfigured capacity 👍

for i, accountResponse := range accountResponses {
accounts[i] = accountResponse.Attributes
Copy link
Collaborator

@mavrikis-kostas mavrikis-kostas Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick; your solution works great, but there is a safer solution that I would recommend.

In this file, you have already created a slice accounts with preconfigured capacity, and inside this loop, you are assigning a value to each element:

accounts := make([]Account, len(accountResponses))
for i, accountResponse := range accountResponses {
	accounts[i] = accountResponse.Attributes
}

This will work great if you are 100% sure that the capacity is correct, and the slice does not need to be resized. However, the code will cause panics if the capacity is not big enough.


A more common approach, is to use append for each new element. If the capacity is correct, then append will have exactly the same behavior as your code. If the capacity is not enough, then append will resize the slice, preventing panics.

accounts := make([]Account, len(accountResponses))
for i, accountResponse := range accountResponses {
	accounts = append(accounts, accountResponse.Attributes)
}

There are some online articles that compare the performance between assignments accounts[i] = ... and appends accounts = append(accounts, ...), and they show that the performance is almost the same. For this exercise, we know that the capacity is definitely correct, so both approaches are 100% acceptable. So it is up to personal preference.

}

return accounts, nil
}

func displayUser(user User, accounts []Account) {
fmt.Printf("User: %s\n", user.Name)
fmt.Println("Accounts:")
totalBalance := 0
for _, account := range accounts {
fmt.Printf(" - %s: %d\n", account.Name, account.Balance)
totalBalance += account.Balance
}
fmt.Printf("Total Balance: %d\n", totalBalance)
}

func main() {
var userID int
_, err := fmt.Scanln(&userID)
if err != nil {
fmt.Println("Invalid input")
return
}

user, err := getUser(userID)
if err != nil {
fmt.Println("error found")
return
}

accounts, err := getAccounts(userID)
if err != nil {
fmt.Println("error found")
return
}

displayUser(user, accounts)
}
33 changes: 33 additions & 0 deletions docs/testing/palindrome_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package stringutils

import (
"testing"
)

func TestReverse(t *testing.T) {
testCases := []struct {
name string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file has a mix of whitespaces and tabs, if you auto-format the file in your IDE, it will be fixed 👌 (Go only uses tab characters for indentation)

input string
expected string
}{
{"empty string", "", ""},
{"single character", "x", "x"},
{"palindrome", "aba", "aba"},
{"simple string", "qwe", "ewq"},
{"with spaces", "qwe rt", "tr ewq"},
{"with numbers", "12345", "54321"},
{"with special characters", "!@#$%^&*()", ")(*&^%$#@!"},
{"with mixed case", "Hello World", "dlroW olleH"},
{"with unicode", "こんにちは", "はちにんこ"},
{"with emojis", "😀😃😄", "😄😃😀"},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual := Reverse(tc.input)
if actual != tc.expected {
t.Errorf("Reverse(%q) = %q; expected %q", tc.input, actual, tc.expected)
}
})
}
}