github.com/tcotav/boggle@v0.0.0-20231023231124-a86497006536/cmd/bogglesvc/service.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "unicode" 6 7 "github.com/gin-gonic/gin" 8 "github.com/rs/zerolog/log" 9 "github.com/tcotav/boggle/middleware" 10 "github.com/tcotav/boggle/solver" 11 "github.com/tcotav/boggle/wordlookup" 12 ) 13 14 type Server struct { 15 Lookup *wordlookup.Dictionary 16 } 17 18 func main() { 19 // TODO - set up config, currently everything is hard coded 20 lookup, err := wordlookup.NewDictionaryFromFile("data/words_alpha.txt") 21 if err != nil { 22 // no dictionary means we want to stop the service 23 // log.Panic vs. log.Fatal - panic will allow any defered to finish, fatal will not. it will os.Exit(1) 24 log.Panic().Str("App", "bogglesvc").Str("Call", "main").Err(err).Msg("") 25 } 26 // set up our server 27 s := Server{Lookup: lookup} 28 29 ginMode := "release" 30 gin.SetMode(ginMode) // need to set this to turn off default DEBUG noise from gin logging 31 router := gin.New() 32 // set our custom request logger here w/gin 33 router.Use(middleware.RequestLogger()) 34 // lose this when we switched from gin.Default() to gin.New() 35 // so add back, recovers from panics and 500s if there is one 36 router.Use(gin.Recovery()) 37 38 // hack in some dummy handler to test basics 39 router.GET("/ping", func(c *gin.Context) { 40 c.JSON(200, gin.H{ 41 "message": "pong", 42 }) 43 }) 44 45 // here's our boggle 46 router.POST("/boggle", s.PostBoggle) 47 log.Info().Str("App", "bogglesvc").Str("Call", "main").Msg("Starting server on port 8080") 48 router.Run(":8080") 49 } 50 51 // BoggleRequest is the request body for the POST /boggle endpoint 52 type BoggleRequest struct { 53 Matrix [][]rune `json:"matrix"` 54 } 55 56 // utility function to make sure our 4x4 matrix has valid characters 57 func validateInput(matrix [][]rune) bool { 58 // make sure we have a 4x4 matrix 59 if len(matrix) != 4 { 60 return false 61 } 62 63 // all the rows are length 4 64 for i := 0; i < len(matrix); i++ { 65 if len(matrix[i]) != 4 { 66 return false 67 } 68 } 69 70 // then test the runes to ensure they're valid 71 for i := 0; i < len(matrix); i++ { 72 for j := 0; j < len(matrix[i]); j++ { 73 // check for valid characters - matches upper and lower A-Z 74 if !unicode.IsLetter(matrix[i][j]) { 75 return false 76 } 77 } 78 } 79 return true 80 } 81 82 func (s *Server) PostBoggle(c *gin.Context) { 83 // get the request body 84 var boggleRequest BoggleRequest 85 // Decode the JSON request 86 // TODO -- this unmarshal fails w/ "cannot unmarshal string into Go struct field BoggleRequest.matrix of type int32" 87 decoder := json.NewDecoder(c.Request.Body) 88 if err := decoder.Decode(&boggleRequest); err != nil { 89 log.Error().Str("App", "bogglesvc").Str("Call", "postBoggle").Err(err).Msg("Error parsing request body") 90 c.JSON(400, gin.H{"message": "Error parsing request body"}) 91 return 92 } 93 /* variation tried -- same error as above but uses a gin helper method 94 95 //err := c.BindJSON(&boggleRequest) 96 if err != nil { 97 log.Error().Str("App", "bogglesvc").Str("Call", "postBoggle").Err(err).Msg("Error parsing request body") 98 c.JSON(400, gin.H{"message": "Error parsing request body"}) 99 return 100 }*/ 101 if !validateInput(boggleRequest.Matrix) { 102 log.Error().Str("App", "bogglesvc").Str("Call", "postBoggle").Msg("Invalid input") 103 c.JSON(400, gin.H{"message": "Invalid input"}) 104 return 105 } 106 107 // get the matrix 108 matrix := boggleRequest.Matrix 109 110 // process the matrix 111 b := solver.NewBoggleSolver(matrix, s.Lookup) 112 words := b.FindWords() 113 114 // return the words 115 c.JSON(200, gin.H{"words": words}) 116 }