Skip to content

Go Notes

TOC

  1. Printing in Go Lang
  2. Data Types
  3. File Handling

Printing in Go Lang

There are 3 Functions for writing to stdout

import (
    "fmt"

)

hello := "hello"

fmt.Print(hello)
fmt.Prinln(hello) // adds a whitespace between args and adds a '\n'
fmt.Printf("%v, %T", hello, hello) // %v returns the value and %T the type.

Data Types

int // with variants like uint and ...
float // with variants 
bool // true / false
string 
nil
byte
complex 

go lang error handling

error vs panics

  • error -> programmatic error
  • panic -> programmer error

define custom error types :

type error interface { 
    Error() string
}

Creating a file parser error :

type ParseError struct { 
    Message string
    Line, Char int 
}

func (p *ParseError) Error() string { 
    // stdout the error
}

Deferred Functions

defer func() { 
if err := recover(); err != nil { 
        fmt.Printf("trapped panic")
}()

func panicF() { 

    panic(errors.New("unknown exception"))

    }
}

Interfaces

Creating an interface :

var t interface { 
    talk () string
}
type martian struct{}


func (m martian) talk() string { 
    return "nack nacK"
}


type laser int 


func (l laser) talk() string { 

    return strings.Repeat("pew", int(l))
}

Pointers

  • Declare and use pointers
  • Pointers and RAM

Anything capital is made available to the external users.

Those starting with underscore and small case are kept away from external users.

Parsing Command line arguments using Flag package

package main 

import (
    "fmt"
    "flag"
)

func main() { 
    var inFile string
    var outFile string

    flag.StringVar(&inFile, "input", "", "Usage -> -input=input.txt")
    flag.StringVar(&outFile, "output", "", "Usage ->  -output=output.txt")

    flag.Parse()
    fmt.Println("inFile -> ", inFile)
    fmt.Println("outFile -> ", outFile)

}

Note : When a function has multiple defer statements, it gets executed like a Stack.

Reading from a file and parsing it :

package main 

import (
    "os"
    "log"
    "fmt"
    "encoding/json"
)

type TBook struct { 
    Title string `json:"title"`
    Author string `json:"author"`
}


type TUser struct { 
    Name string  `json:"name"`
    Books []TBook  `json:"books"`
}


func LoadJson(FPath string) ([]TUser, error) { 

    f, err := os.Open(FPath)
    if err !=  nil { 
        log.Fatal("Error opening file")
        return nil, err
    }

    var Users []TUser

    err = json.NewDecoder(f).Decode(&Users)

    if err != nil { 
        log.Fatal("Error Decoding json")
    }

    defer f.Close()

    return Users, nil


}


func main() { 
    fmt.Println("")
    LoadJson("data.json")

}

Loops in go lang :

for i := 0; i < 5; i++ { 

}


for iterator.Next() {
}
for line != lastLine {

}
for !gotResponse || response.invalid() {

}

Looping over slices / maps

for _, bw := range SliceList
for i, _ := range SliceList
for i := range SliceList
    for _, user := range Users { 
        for _, book := range user.Books { 
            count[book]++

        }

    }

    commonBooks := []TBook{}
    for book, count := range count { 
        if count > 1 { 
            commonBooks = append(commonBooks, book)

        }
    }

Creating a Sorting Interface

  • Define the len method
  • Define the swap method
  • Define the less ( comparison ) method
type byAuthor T[]Book

func (b byAuthor) Len() int { return len(b) }

func (b byAuthor) Swap(i, j int) { 
    b[i], b[j] = b[j], b[i]
}

func (b byAuthor) Less(i, j int) bool {
    if b[i].Author != b[j].Author {
        return b[i].Author < b[j].Author
    }
    return b[i].Title < b[j].Title
}

Go Packages

Convention -> use dir name as the package name for each source file.

GIN

gin create router

router := gin.Default()

router.GET("/", func(c *gin.Context) { 
    c.JSON(200, gin.H { 
        "message": "hello world",
    })

})

router.Run()

setting up swagger docs with gin.

 go get -u github.com/swaggo/swag/cmd/swag
 go get -u github.com/swaggo/gin-swagger
 go get -u github.com/swaggo/files

QUIZ APP WITH GIN

package main 

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "encoding/json"
    "os"
    "net/http"
    "math/rand"
    "path/filepath"
    "log"
    "strings"
    "time"
    "github.com/google/uuid"

)

// types 

type UserIP string 

type UserLimit struct { 

        LastCall        time.Time       `json:"last_call"`
        RequestCount    int             `json:"request_count"`

}




type LogRecord struct { 
    RequestEndpoint        string         `json:"request_endpoint"` 
    CorrelationId          string         `json:"correlation_id"`
    ProcessingTime         time.Duration  `json:"processing_time"`
    ResponseCode           int            `json:"response_code"`



}





type APIResponse[T any] struct { 
    Ok       bool `json:"ok"`
    Result   []T `json:"result"`
}


type TQuestion struct { 
    Question string `json:"question"`
    Option1 string  `json:"option1"`
    Option2 string  `json:"option2"`
    Option3 string  `json:"option3"`
    Option4 string  `json:"option4"`
    CorrectOption int `json:"correctOption"`
    Explanation string `json:"explanation"`

}

type TCategory string 

func GetFileName(category string) string { 
    return strings.ToUpper(category[:len(category) - 5])
}

// middlewares 
func JsonLogger() gin.HandlerFunc { 
    return func(context *gin.Context) { 

        clientIP := context.ClientIP()
        userLimit, ok := rateLimitter[clientIP]
        if ok {
            fmt.Println("Found apple:", value)
        } else {
            fmt.Println("Apple not found")
        }

        RequestEndpoint := context.FullPath()
        startTime := time.Now()
        corrId := context.GetHeader("X-Correlation-Id")
        if corrId != "" {
        } else {
            corrId = uuid.NewString()
        }

        context.Header("X-Correlation-Id", corrId)
        context.Next()
        endTime := time.Now()
        ResponseCode := context.Writer.Status()

        ProcessingTime := endTime.Sub(startTime)
        l := LogRecord{CorrelationId: corrId, ProcessingTime: ProcessingTime, RequestEndpoint: RequestEndpoint, ResponseCode :ResponseCode}
        lJson, err := json.Marshal(l)
        if err != nil { 
            log.Fatal("Error Parsing JSON")
        }
        log.Println(string(lJson))
    }
}


// Helper Methods
func GetQuestion(category string) (TQuestion, error) { 

    catWords := strings.Fields(category)
    joinedCategory := strings.ToLower(strings.Join(catWords, "_"))
    safeName := filepath.Base(joinedCategory)
    fileName := filepath.Join("data", safeName+".json")

    questions := make([]TQuestion, 0)
    f, err := os.ReadFile(fileName)
    if err != nil { 
        fmt.Println("error reading file")
    }
    parseError := json.Unmarshal(f, &questions)
    if parseError != nil { 
        fmt.Println("unmarshing error")
    }

    qIndex := rand.Intn(len(questions))
    qObject := questions[qIndex]
    return qObject, nil

}

// Handlers
func HomeHandler(context *gin.Context) { 
    context.HTML(http.StatusOK, "home.html", gin.H{})
}

func QuizHandler(context *gin.Context) { 
    context.HTML(http.StatusOK, "quiz.html", gin.H{})
}


func CategoryAPIHandler(context *gin.Context) { 
    var categoryBuffer string
    categories := []string{}

    files, err := os.ReadDir("./data")
    if err != nil { 
        log.Fatal("Error Reading Data Dir")
        context.JSON(http.StatusInternalServerError, gin.H{})
    }
    for _, file := range files { 
        if filepath.Ext(file.Name()) != ".json" { 
            continue
        }
        categoryBuffer = GetFileName(file.Name())

        categories = append(categories, categoryBuffer)

    }
    rv := APIResponse[string] { Ok : true,Result : categories}
    context.JSON(http.StatusOK, rv)
}


func QuestionAPIHandler(context *gin.Context) { 

    category := context.Query("category")


    qObject, err := GetQuestion(category)
    if err != nil {
        // handle error here
    }
    context.JSON(http.StatusOK, qObject)
}

func DemoAPIHandler(context *gin.Context) {
     panic("test panic")

}

func init() { 
    rateLimitter := make(map[UserIP]UserLimit)
    gin.SetMode(gin.ReleaseMode)
}


func main() {
    fmt.Println("Starting the Server on port 8005...")
    router := gin.New()
    router.Use(JsonLogger())
    router.Use(gin.CustomRecovery(func(c *gin.Context, err interface{}) {
        c.AbortWithStatusJSON(500, gin.H{"error": "Internal Server Error"})
    }))
    router.LoadHTMLGlob("templates/*")

    // web routes
    router.GET("/", HomeHandler)
    router.GET("/quiz", QuizHandler)

    // api routes
    router.GET("/api/demo", DemoAPIHandler)
    router.GET("/api/category", CategoryAPIHandler)
    router.GET("/api/q", QuestionAPIHandler)

    router.Run(":8005")

}

Structuring a Project with Go lang using the Repository Pattern