Go Notes
TOC
- Printing in Go Lang
- Data Types
- 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 :
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 :
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 _, 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")
}