From ba24d8f7a2f3980d597c38365c9ef9d62fa334db Mon Sep 17 00:00:00 2001 From: ergz Date: Fri, 18 Aug 2023 13:33:56 -0700 Subject: [PATCH] adds validation to the json --- cmd/api/helpers.go | 42 +- cmd/api/movies.go | 12 +- go.mod | 2 +- greenlight.sublime-workspace | 1303 +++++++++++++++++++++++++++++++ internal/data/runtime.go | 26 + internal/validator/validator.go | 55 ++ 6 files changed, 1409 insertions(+), 31 deletions(-) create mode 100644 greenlight.sublime-workspace create mode 100644 internal/validator/validator.go diff --git a/cmd/api/helpers.go b/cmd/api/helpers.go index 087ec39..cc31da2 100644 --- a/cmd/api/helpers.go +++ b/cmd/api/helpers.go @@ -7,7 +7,6 @@ import ( "io" "net/http" "strconv" - "strings" "github.com/julienschmidt/httprouter" ) @@ -15,56 +14,51 @@ import ( type envelope map[string]any func (app *application) readJSON(w http.ResponseWriter, r *http.Request, dest any) error { + maxBytes := 1_048_576 r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes)) - decoder := json.NewDecoder(r.Body) - decoder.DisallowUnknownFields() - - err := decoder.Decode(dest) + dec := json.NewDecoder(r.Body) + dec.DisallowUnknownFields() + err := dec.Decode(dest) if err != nil { var syntaxError *json.SyntaxError - var unmarshallError *json.UnmarshalTypeError - var invalidUnmarshall *json.InvalidUnmarshalError - var maxBytesError *http.MaxBytesError + var unmarshalTypeError *json.UnmarshalTypeError + var invalidUnmarshalError *json.InvalidUnmarshalError switch { case errors.As(err, &syntaxError): - return fmt.Errorf("body contains badly-formed JSON (at character %d)", syntaxError.Offset) + return fmt.Errorf("body contains badly-formatted JSON (at character %d)", syntaxError.Offset) case errors.Is(err, io.ErrUnexpectedEOF): return errors.New("body contains badly-formed JSON") - case errors.As(err, &unmarshallError): - if unmarshallError.Field != "" { - return fmt.Errorf("the body contains incorrect json type for field %q", unmarshallError.Field) + case errors.As(err, &unmarshalTypeError): + if unmarshalTypeError.Field != "" { + return fmt.Errorf("body contains incorrect JOSN type for field %q", unmarshalTypeError.Field) } - return fmt.Errorf("body contains incorrect JSON type (at character %d)", unmarshallError.Offset) + + return fmt.Errorf("body contains incorrect JOSN type (at character %d)", unmarshalTypeError.Offset) case errors.Is(err, io.EOF): return errors.New("body must not be empty") - case strings.HasPrefix(err.Error(), "json: unknown field "): - fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ") - return fmt.Errorf("body contains uknown key %s", fieldName) - - case errors.As(err, &maxBytesError): - return fmt.Errorf("body must not be larger than %d bytes", maxBytesError.Limit) - - case errors.As(err, &invalidUnmarshall): + case errors.As(err, &invalidUnmarshalError): panic(err) default: return err + } } - err = decoder.Decode(&struct{}{}) + + err = dec.Decode(&struct{}{}) if err != io.EOF { - return errors.New("body must only contain a single JSON value") + return errors.New("body must only contain a single json value") } - return nil // no errors + return nil } func (app *application) readIDParam(r *http.Request) (int64, error) { diff --git a/cmd/api/movies.go b/cmd/api/movies.go index 433a783..5682c6b 100644 --- a/cmd/api/movies.go +++ b/cmd/api/movies.go @@ -10,19 +10,19 @@ import ( func (app *application) createMovieHandler(w http.ResponseWriter, r *http.Request) { var input struct { - Title string `json:"title"` - Year int32 `json:"year"` - Runtime int32 `json:"runtime"` - Genres []string `json:"genres"` + Title string `json:"title"` + Year int32 `json:"year"` + Runtime data.Runtime `json:"runtime"` + Genres []string `json:"genres"` } err := app.readJSON(w, r, &input) if err != nil { - app.badRequestResponse(w, r, err) + app.errorResponse(w, r, http.StatusBadRequest, err.Error()) return } - fmt.Fprintf(w, "%v+\n", input) + fmt.Fprintf(w, "%+v\n", input) } func (app *application) showMovieHandler(w http.ResponseWriter, r *http.Request) { diff --git a/go.mod b/go.mod index cc268e2..af83fd7 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module greenlight.ergz -go 1.20 +go 1.21 require github.com/julienschmidt/httprouter v1.3.0 // indirect diff --git a/greenlight.sublime-workspace b/greenlight.sublime-workspace new file mode 100644 index 0000000..49dd3ae --- /dev/null +++ b/greenlight.sublime-workspace @@ -0,0 +1,1303 @@ +{ + "auto_complete": + { + "selected_items": + [ + [ + "unmar", + "unmarshallError" + ], + [ + "max", + "maxBytesError" + ], + [ + "Max", + "MaxBytesError" + ], + [ + "StatusBad", + "StatusBadRequest" + ], + [ + "Unmar", + "InvalidUnmarshalError" + ], + [ + "Un", + "UnmarshalTypeError" + ], + [ + "error", + "serverErrorResponse" + ], + [ + "Sprit", + "Sprintf" + ], + [ + "Response", + "ResponseWriter" + ], + [ + "Res", + "ResponseWriter" + ], + [ + "quoted", + "quotedJSONValue" + ], + [ + "json", + "jsonValue" + ], + [ + "Sprint", + "Sprintf" + ], + [ + "S", + "SnippetModel" + ], + [ + "Sn", + "SnippetModel" + ], + [ + "s", + "snippetCreate" + ], + [ + "sn", + "snippetView" + ], + [ + "GLOBAL", + "GLOBAL_SECONDARY_BUFFER" + ], + [ + "regi", + "region_2" + ], + [ + "byte", + "byte_to_lock" + ], + [ + "tone", + "tone_hz" + ], + [ + "HR", + "HRESULT" + ], + [ + "HRES", + "HRESULT" + ], + [ + "buf", + "buffer_description" + ], + [ + "LPDIRECTSOU", + "LPDIRECTSOUNDBUFFER" + ], + [ + "direc", + "direct_sound_create" + ], + [ + "HM", + "HMODULE" + ], + [ + "Xst", + "x_input_set_state_stub" + ], + [ + "X_INP", + "X_INPUT_GET_STATE" + ], + [ + "X", + "XInputGetState_" + ], + [ + "XInputGetSA", + "XInputGetState" + ], + [ + "x_", + "x_input_get_state" + ], + [ + "uin", + "uint16_t" + ], + [ + "stio", + "stdio.h" + ], + [ + "W32_", + "W32_window_dimensions" + ], + [ + "window", + "window_dims" + ], + [ + "wind", + "window_width" + ], + [ + "w32_", + "w32_update_window" + ], + [ + "BITMAP_", + "BITMAP_HEIGHT" + ], + [ + "BITMA", + "BITMAP_WIDTH" + ], + [ + "bitmap_me", + "BITMAP_MEMORY" + ], + [ + "uint_", + "uint8_t" + ], + [ + "BITM", + "BITMAP_WIDTH" + ], + [ + "BITMAP", + "BITMAP_HEIGHT" + ], + [ + "bitmap_mem", + "bitmap_mem_size" + ], + [ + "bitma_", + "bitmap_memory" + ], + [ + "bit", + "bitmap_memory" + ], + [ + "bitma", + "bitmap_handle" + ], + [ + "w32", + "w32_update_window" + ], + [ + "HWN", + "HWND" + ], + [ + "LREST", + "LRESULT" + ], + [ + "CALLBACK", + "CF_CALLBACK" + ], + [ + "WINDOW", + "WINDOW_HEIGHT" + ], + [ + "rota", + "rotation_matrix_X" + ], + [ + "mat", + "mat4_create_translation" + ] + ] + }, + "buffers": + [ + { + "file": "cmd/api/routes.go", + "settings": + { + "buffer_size": 541, + "encoding": "UTF-8", + "line_ending": "Windows" + }, + "undo_stack": + [ + [ + 9, + 1, + "insert", + { + "characters": "\nrouter." + }, + "CQAAACMBAAAAAAAAJAEAAAAAAAAAAAAAJAEAAAAAAAAlAQAAAAAAAAAAAAAlAQAAAAAAACYBAAAAAAAAAAAAACYBAAAAAAAAJwEAAAAAAAAAAAAAJwEAAAAAAAAoAQAAAAAAAAAAAAAoAQAAAAAAACkBAAAAAAAAAAAAACkBAAAAAAAAKgEAAAAAAAAAAAAAKgEAAAAAAAArAQAAAAAAAAAAAAArAQAAAAAAACwBAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAAIwEAAAAAAAAjAQAAAAAAAAAAAAAAAPC/" + ], + [ + 10, + 1, + "insert", + { + "characters": "Meh" + }, + "AwAAACwBAAAAAAAALQEAAAAAAAAAAAAALQEAAAAAAAAuAQAAAAAAAAAAAAAuAQAAAAAAAC8BAAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAALAEAAAAAAAAsAQAAAAAAAAAAAAAAAPC/" + ], + [ + 11, + 1, + "insert_completion", + { + "completion": "lsp_select_completion {\"index\":1,\"session_name\":\"gopls\"}", + "format": "command", + "keep_prefix": true, + "must_insert": false, + "query_change_id": + [ + 8, + 10, + 3 + ], + "trigger": "MethodNotAllowed.ServeHTTP" + }, + "AgAAACwBAAAAAAAALAEAAAAAAAADAAAATWVoLAEAAAAAAABIAQAAAAAAAAAAAAA", + "AQAAAAAAAAABAAAALwEAAAAAAAAvAQAAAAAAAAAAAAAAAPC/" + ], + [ + 12, + 1, + "run_macro_file", + { + "file": "res://Packages/Default/Delete Line.sublime-macro" + }, + "AQAAACQBAAAAAAAAJAEAAAAAAAAlAAAACXJvdXRlci5NZXRob2ROb3RBbGxvd2VkLlNlcnZlSFRUUCgpCg", + "AQAAAAAAAAABAAAARwEAAAAAAABHAQAAAAAAAAAAAAAAAPC/" + ] + ] + } + ], + "build_system": "", + "build_system_choices": + [ + [ + [ + [ + "Packages/Golang Build/Go.sublime-build", + "" + ], + [ + "Packages/Golang Build/Go.sublime-build", + "Run" + ], + [ + "Packages/Golang Build/Go.sublime-build", + "Test" + ], + [ + "Packages/Golang Build/Go.sublime-build", + "Install" + ], + [ + "Packages/Golang Build/Go.sublime-build", + "Cross-Compile (Interactive)" + ], + [ + "Packages/Golang Build/Go.sublime-build", + "Clean" + ] + ], + [ + "Packages/Golang Build/Go.sublime-build", + "Clean" + ] + ], + [ + [ + [ + "cl - build", + "" + ], + [ + "cl - build", + "& run" + ], + [ + "cl - build", + "& debug" + ], + [ + "Packages/C++/C++ Single File.sublime-build", + "" + ], + [ + "Packages/C++/C++ Single File.sublime-build", + "Run" + ] + ], + [ + "cl - build", + "" + ] + ], + [ + [ + [ + "cl_cpp", + "" + ], + [ + "Packages/C++/C++ Single File.sublime-build", + "" + ], + [ + "Packages/C++/C++ Single File.sublime-build", + "Run" + ] + ], + [ + "cl_cpp", + "" + ] + ], + [ + [ + [ + "cl_cpp", + "" + ], + [ + "cl_cpp", + "run" + ], + [ + "Packages/C++/C++ Single File.sublime-build", + "" + ], + [ + "Packages/C++/C++ Single File.sublime-build", + "Run" + ] + ], + [ + "cl_cpp", + "" + ] + ], + [ + [ + [ + "cl_cpp", + "" + ], + [ + "cl_cpp", + "run" + ], + [ + "cl_cpp", + "debug" + ], + [ + "Packages/C++/C++ Single File.sublime-build", + "" + ], + [ + "Packages/C++/C++ Single File.sublime-build", + "Run" + ] + ], + [ + "cl_cpp", + "" + ] + ], + [ + [ + [ + "cl_cpp", + "" + ], + [ + "cl_cpp", + "run_debugger" + ], + [ + "Packages/C++/C++ Single File.sublime-build", + "" + ], + [ + "Packages/C++/C++ Single File.sublime-build", + "Run" + ] + ], + [ + "cl_cpp", + "" + ] + ] + ], + "build_varint": "", + "command_palette": + { + "height": 0.0, + "last_filter": "", + "selected_items": + [ + [ + "selcet color", + "UI: Select Color Scheme" + ], + [ + "select color", + "UI: Select Color Scheme" + ], + [ + "LSP settings", + "Preferences: LSP Settings" + ], + [ + "lsp enal", + "LSP: Enable Language Server in Project" + ], + [ + "terminus cl", + "Terminus: Close All" + ], + [ + "rename", + "Terminus: Rename Title" + ], + [ + "terminus them", + "Terminus Utilities: Select Theme" + ], + [ + "settings", + "Preferences: Settings" + ], + [ + "terminus view", + "Terminus: Open Default Shell in Tab (View)" + ], + [ + "terminus", + "Terminus: Close All" + ], + [ + "lsp:ena", + "LSP: Enable Language Server in Project" + ], + [ + "projcet", + "Project: Save As" + ], + [ + "lsp: ena", + "LSP: Enable Language Server Globally" + ], + [ + "remove", + "Package Control: Remove Package" + ], + [ + "install", + "Package Control: Install Package" + ], + [ + "lsp:", + "LSP: Enable Language Server Globally" + ], + [ + "edit", + "PackageDev: Edit Current Color Scheme" + ], + [ + "gopls", + "Preferences: LSP-gopls Settings" + ], + [ + "reinde", + "Indentation: Reindent Lines" + ], + [ + "set syntax: ht", + "Set Syntax: HTML" + ], + [ + "install pac", + "Package Control: Install Package" + ], + [ + "edit curre", + "PackageDev: Edit Current Color Scheme" + ], + [ + "reinder", + "Preferences: Settings – Distraction Free" + ], + [ + "color sc", + "UI: Select Color Scheme" + ], + [ + "select colo", + "UI: Select Color Scheme" + ], + [ + "select color", + "UI: Select Color Scheme" + ], + [ + "browse", + "Preferences: Browse Packages" + ], + [ + "select ", + "UI: Select Color Scheme" + ], + [ + "set syntax: color", + "Set Syntax: Sublime Text Color Scheme (JSON)" + ], + [ + "colored", + "Colored Comments: Settings" + ], + [ + "upper", + "Convert Case: Upper Case" + ], + [ + "tmtheme", + "Plugin Development: Convert Color Scheme to .sublime-color-scheme" + ], + [ + "packagedev: conver", + "PackageDev: Convert (YAML, JSON, PList) to…" + ], + [ + "set syntax: pyt", + "Set Syntax: Python" + ], + [ + "edit curr", + "PackageDev: Edit Current Color Scheme" + ], + [ + "theme", + "UI: Select Theme" + ], + [ + "edit co", + "PackageDev: Edit Current Color Scheme" + ], + [ + "color", + "UI: Select Color Scheme" + ], + [ + "select co", + "UI: Select Color Scheme" + ], + [ + "colored comments: ", + "Colored Comments: Override Current Color Scheme" + ], + [ + "edit c", + "PackageDev: Edit Current Color Scheme" + ], + [ + "edit color", + "PackageDev: Edit Current Color Scheme" + ], + [ + "settiings", + "Set Syntax: Sublime Text Settings (JSON)" + ], + [ + "edit cur", + "PackageDev: Edit Current Color Scheme" + ], + [ + "lowerc", + "Convert Case: Lower Case" + ], + [ + "ke", + "Preferences: Key Bindings" + ], + [ + "brose", + "Preferences: Browse Packages" + ], + [ + "upper case", + "Convert Case: Upper Case" + ], + [ + "uppee", + "Convert Case: Upper Case" + ], + [ + "key", + "Preferences: Key Bindings" + ], + [ + "brows", + "Preferences: Browse Packages" + ], + [ + "index", + "Help: Indexing Status" + ], + [ + "rei", + "Indentation: Reindent Lines" + ], + [ + "project", + "Project: Save As" + ], + [ + "edit cur", + "PackageDev: Edit Current Color Scheme" + ], + [ + "eidt", + "Project: Edit Project" + ], + [ + "edit ", + "PackageDev: Edit Current Color Scheme" + ], + [ + "set syntax c++", + "Set Syntax: C++11" + ], + [ + "reind", + "Indentation: Reindent Lines" + ], + [ + "keyb", + "Preferences: Key Bindings" + ] + ], + "width": 0.0 + }, + "console": + { + "height": 160.0, + "history": + [ + "view.run_command(\"annotated_comments\")" + ] + }, + "distraction_free": + { + "menu_visible": true, + "show_minimap": false, + "show_open_files": false, + "show_tabs": false, + "side_bar_visible": false, + "status_bar_visible": false + }, + "expanded_folders": + [ + "/D/dev/gocode/src/ergz/greenlight", + "/D/dev/gocode/src/ergz/greenlight/cmd", + "/D/dev/gocode/src/ergz/greenlight/cmd/api", + "/D/dev/gocode/src/ergz/greenlight/internal", + "/D/dev/gocode/src/ergz/greenlight/internal/data", + "/D/dev/gocode/src/ergz/greenlight/internal/validator" + ], + "file_history": + [ + "/D/dev/gocode/src/ergz/greenlight/cmd/api/healthcheck.go", + "/D/dev/gocode/src/ergz/greenlight/greenlight.sublime-project", + "/D/dev/gocode/src/ergz/greenlight/internal/data/movies.go", + "/D/dev/gocode/src/ergz/greenlight/cmd/api/routes.go", + "/D/dev/gocode/src/ergz/greenlight/cmd/api/helpers.go", + "/D/dev/gocode/src/ergz/greenlight/cmd/api/errors.go", + "/D/dev/gocode/src/ergz/greenlight/internal/validator/validator.go", + "/D/dev/gocode/src/ergz/greenlight/internal/data/runtime.go", + "/D/dev/gocode/src/ergz/greenlight/cmd/api/main.go", + "/D/dev/gocode/src/ergz/greenlight/cmd/api/movies.go", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/Theme - Monokai Pro.sublime-settings", + "/C/Users/emanuel/AppData/Local/nvim/README.md", + "/C/Users/emanuel/AppData/Local/nvim/lua/custom/plugins/autopairs.lua", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/LSP-gopls.sublime-settings", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/LSP-gopls/LSP-gopls.sublime-settings", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/Tomorrow Night.sublime-color-scheme", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/Tomorrow Color Schemes/Tomorrow Night.sublime-color-scheme", + "/D/dev/gocode/src/ergz/greenlight/internal/data.go", + "/D/dev/snippetbox/cmd/web/routes.go", + "/D/dev/snippetbox/cmd/web/main.go", + "/D/dev/snippetbox/cmd/web/handlers.go", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/Tomorrow Night - Blue.sublime-color-scheme", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/Tomorrow Color Schemes/Tomorrow Night - Blue.sublime-color-scheme", + "/D/dev/snippetbox/internal/models/errors.go", + "/D/dev/snippetbox/internal/models/snippets.go", + "/D/dev/snippetbox/go.mod", + "/D/dev/snippetbox/go.sum", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/Gofmt.sublime-settings", + "/D/dev/snippetbox/cmd/web/helpers.go", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/golang.sublime-settings", + "/D/dev/snippetbox/ui/html/partials/nav.tmpl.html", + "/D/dev/snippetbox/ui/html/base.tmpl.html", + "/D/dev/snippetbox/ui/html/pages/home.html", + "/D/dev/snippetbox/main.go", + "/D/learning/performance-aware/sim_8086.cpp", + "/D/learning/performance-aware/c_asm.asm", + "/D/learning/performance-aware/sim_8086.py", + "/C/Users/emanuel/AppData/Local/clink/flexprompt_config.lua", + "/D/learning/performance-aware/listing_0037_single_register_mov.asm", + "/C/Users/emanuel/.wslconfig", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/Monokai Pro (Filter Spectrum).sublime-color-scheme", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/Theme - Monokai Pro/Monokai Pro (Filter Spectrum).sublime-color-scheme", + "/D/learning/handmade-hero/build.bat", + "/C/Users/emanuel/AppData/Local/clink/clink_settings", + "/D/learning/handmade-hero/code/win32_handmade.cpp", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/the-witness.sublime-color-scheme", + "/C/Users/emanuel/Documents/rstudio-themes/Solarized (Dark high contrast).tmTheme", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/Solarized (Dark high contrast).JSON-tmTheme", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/Solarized (Dark high contrast).tmTheme", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/Solarized Dark (emanuel mods).sublime-color-scheme", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/Solarized (Dark high contrast).sublime-color-scheme", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/Solarized (Dark Emanuel Mods).sublime-color-scheme", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/handmade-hero-theme.tmTheme", + "/C/Users/emanuel/AppData/Local/Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState/settings.json", + "/C/Users/emanuel/AppData/Roaming/RStudio/themes/base16-atelierestuary.dark.rstheme", + "/D/learning/handmade-hero/.gitignore", + "/D/learning/handmade-hero/handmade-hero.sublime-project", + "/D/tools-configurations/windows-terminal-settings.json", + "/D/learning/handmade-hero/123", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/AnnotatedComments/annotated_comment.py", + "/D/learning/handmade-hero/build/app.ilk", + "/C/Program Files (x86)/Windows Kits/10/Include/10.0.19041.0/um/wingdi.h", + "/C/Program Files (x86)/Windows Kits/10/Include/10.0.19041.0/um/WinUser.h", + "/D/dev/C++/C++.sublime-syntax", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/c-better.sublime-syntax", + "/D/dev/C++.sublime-package", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/base16-ateliersavanna.dark.sublime-color-scheme", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/C++.sublime-settings", + "/C/Users/emanuel/AppData/Roaming/Sublime Text/Packages/User/c-better.sublime-settings", + "/D/learning/intro-to-c-hmh/code/test.cpp", + "/C/Users/emanuel/AppData/Roaming/RStudio/themes/base16-ateliersavanna.dark.rstheme", + "/C/Users/emanuel/Documents/rstudio-themes/base16-atelierdune.dark.tmTheme", + "/D/dev/3d-graphics/3drenderer/3drenderer.sublime-project", + "/D/dev/3d-graphics/3drenderer/src/mesh.c", + "/D/dev/3d-graphics/3drenderer/src/matrix.c", + "/D/dev/3d-graphics/3drenderer/src/matrix.h", + "/D/dev/3d-graphics/3drenderer/src/display.c", + "/D/dev/3d-graphics/3drenderer/src/display.h", + "/D/dev/libraries/SDL2-devel-2.24.2-VC/SDL2-2.24.2/include/SDL_render.h", + "/D/dev/3d-graphics/3drenderer/src/main.c", + "/D/dev/libraries/SDL2-devel-2.24.2-VC/SDL2-2.24.2/include/SDL_events.h", + "/D/dev/3d-graphics/3drenderer/src/array.c", + "/D/dev/3d-graphics/3drenderer/src/array.h", + "/D/dev/3d-graphics/3drenderer/src/vector.c", + "/D/dev/3d-graphics/3drenderer/src/vector.h", + "/D/dev/3d-graphics/3drenderer/build.bat", + "/D/dev/3d-graphics/3drenderer/src/mesh.h", + "/D/dev/3d-graphics/3drenderer/src/triangle.h", + "/D/dev/libraries/SDL2-devel-2.24.2-VC/SDL2-2.24.2/include/SDL.h", + "/D/dev/libraries/SDL2-devel-2.24.2-VC/SDL2-2.24.2/include/SDL_video.h", + "/D/dev/libraries/SDL2-devel-2.24.2-VC/SDL2-2.24.2/include/SDL_timer.h", + "/D/dev/3d-graphics/3drenderer/Makefile", + "/C/Users/emanuel/Downloads/SDL2-devel-2.24.0-VC/SDL2-2.24.0/docs/README-windows.md", + "/C/Users/emanuel/Downloads/SDL2-devel-2.24.0-VC/SDL2-2.24.0/docs/README.md" + ], + "find": + { + "height": 44.0 + }, + "find_in_files": + { + "height": 116.0, + "where_history": + [ + ] + }, + "find_state": + { + "case_sensitive": false, + "find_history": + [ + "req", + "Req", + "req" + ], + "highlight": true, + "in_selection": false, + "preserve_case": false, + "regex": false, + "replace_history": + [ + ], + "reverse": false, + "scrollbar_highlights": true, + "show_context": true, + "use_buffer2": true, + "use_gitignore": true, + "whole_word": false, + "wrap": true + }, + "groups": + [ + { + "sheets": + [ + { + "buffer": 0, + "file": "cmd/api/routes.go", + "selected": true, + "semi_transient": false, + "settings": + { + "buffer_size": 541, + "regions": + { + }, + "selection": + [ + [ + 292, + 292 + ] + ], + "settings": + { + "auto_complete_triggers": + [ + { + "characters": "<", + "selector": "text.html, text.xml" + }, + { + "rhs_empty": true, + "selector": "punctuation.accessor" + }, + { + "characters": ".", + "selector": "meta.tag, source - comment - string.quoted.double.block - string.quoted.single.block - string.unquoted.heredoc", + "server": "gopls" + } + ], + "lsp_active": true, + "lsp_hover_provider_count": 1, + "lsp_uri": "file:///D:/dev/gocode/src/ergz/greenlight/cmd/api/routes.go", + "show_definitions": false, + "syntax": "Packages/Go/Go.sublime-syntax", + "tab_size": 4, + "translate_tabs_to_spaces": false + }, + "translation.x": 0.0, + "translation.y": 0.0, + "zoom_level": 1.0 + }, + "stack_index": 0, + "stack_multiselect": false, + "type": "text" + } + ] + } + ], + "incremental_find": + { + "height": 32.0 + }, + "input": + { + "height": 44.0 + }, + "layout": + { + "cells": + [ + [ + 0, + 0, + 1, + 1 + ] + ], + "cols": + [ + 0.0, + 1.0 + ], + "rows": + [ + 0.0, + 1.0 + ] + }, + "menu_visible": true, + "output.LSP Log Panel": + { + "height": 0.0 + }, + "output.diagnostics": + { + "height": 0.0 + }, + "output.exec": + { + "height": 242.0 + }, + "output.find_results": + { + "height": 0.0 + }, + "output.gofmt": + { + "height": 0.0 + }, + "output.golang_build": + { + "height": 156.0 + }, + "output.mdpopups": + { + "height": 0.0 + }, + "output.package_dev": + { + "height": 156.0 + }, + "pinned_build_system": "", + "project": "greenlight.sublime-project", + "replace": + { + "height": 60.0 + }, + "save_all_on_build": true, + "select_file": + { + "height": 0.0, + "last_filter": "", + "selected_items": + [ + [ + "movies", + "cmd\\api\\movies.go" + ], + [ + "mvoes", + "cmd\\api\\movies.go" + ], + [ + "errors", + "cmd\\api\\errors.go" + ], + [ + "health", + "cmd\\api\\healthcheck.go" + ], + [ + "main", + "cmd\\api\\main.go" + ], + [ + "mai", + "cmd\\api\\main.go" + ], + [ + "mov", + "cmd\\api\\movies.go" + ], + [ + "", + "cmd\\api\\helpers.go" + ], + [ + "heak", + "cmd\\api\\healthcheck.go" + ], + [ + "helper", + "cmd\\api\\helpers.go" + ], + [ + "routes", + "cmd\\web\\routes.go" + ], + [ + "hand", + "cmd\\web\\handlers.go" + ], + [ + "mod", + "go.mod" + ], + [ + "go.s", + "go.sum" + ], + [ + "go.mo", + "go.mod" + ], + [ + "helpers", + "cmd\\web\\helpers.go" + ], + [ + "he", + "cmd\\web\\helpers.go" + ], + [ + "handler", + "cmd\\web\\handlers.go" + ], + [ + "hme", + "ui\\html\\pages\\home.html" + ], + [ + "cpp", + "sim_8086.cpp" + ], + [ + "py", + "sim_8086.py" + ], + [ + "sim", + "sim_8086.py" + ], + [ + "win32", + "handmade-hero\\code\\win32_handmade.cpp" + ], + [ + "bui", + "handmade-hero\\build.bat" + ], + [ + "win32_", + "handmade-hero\\code\\win32_handmade.cpp" + ], + [ + "build", + "handmade-hero\\build.bat" + ], + [ + "buil", + "handmade-hero\\build.bat" + ], + [ + ".giti", + "handmade-hero\\.gitignore" + ], + [ + "builk", + "handmade-hero\\build\\app.ilk" + ], + [ + "giti", + "handmade-hero\\.gitignore" + ], + [ + "test", + "code\\test.cpp" + ], + [ + "buid", + "build.bat" + ], + [ + "buld", + "build.bat" + ], + [ + "buikd", + "beej-guide-networking.sublime-project" + ], + [ + "b u", + "build.bat" + ], + [ + "hello", + "hello.lua" + ], + [ + "dis", + "3drenderer\\src\\display.c" + ], + [ + "vect", + "3drenderer\\src\\vector.h" + ], + [ + "pro", + "3drenderer\\3drenderer.sublime-project" + ], + [ + "mes", + "3drenderer\\src\\mesh.c" + ], + [ + "mesh", + "3drenderer\\src\\mesh.c" + ], + [ + "displa", + "3drenderer\\src\\display.c" + ], + [ + "dspla", + "3drenderer\\src\\display.c" + ], + [ + "project", + "3drenderer\\3drenderer.sublime-project" + ], + [ + "matrix", + "3drenderer\\src\\matrix.c" + ], + [ + "man", + "3drenderer\\src\\main.c" + ], + [ + "proj", + "3drenderer\\3drenderer.sublime-project" + ], + [ + "arra", + "3drenderer\\src\\array.h" + ], + [ + "mesh.", + "3drenderer\\src\\mesh.h" + ], + [ + "matrix.h", + "3drenderer\\src\\matrix.h" + ], + [ + "vec", + "3drenderer\\src\\vector.c" + ], + [ + "matr", + "3drenderer\\src\\matrix.h" + ], + [ + "display", + "3drenderer\\src\\display.h" + ], + [ + "make", + "Makefile" + ] + ], + "width": 0.0 + }, + "select_project": + { + "height": 499.666666667, + "last_filter": "", + "selected_items": + [ + [ + "", + "D:\\learning\\handmade-hero\\handmade-hero.sublime-project" + ], + [ + "art", + "D:\\dev\\art-of-asm\\art-of-masm.sublime-project" + ] + ], + "width": 378.666666667 + }, + "select_symbol": + { + "height": 59.3333333333, + "last_filter": "routes", + "selected_items": + [ + [ + "routes", + "routes" + ], + [ + "read", + "readJSON" + ], + [ + "create", + "createMovieHandler" + ], + [ + "show", + "showMovieHandler" + ], + [ + "Insert", + "Insert" + ], + [ + "fill_sound", + "w32_fill_sound_buffer" + ], + [ + "direct_sound", + "w32_init_direct_sound" + ], + [ + "sound_b", + "w32_fill_sound_buffer" + ], + [ + "w32_fi", + "w32_fill_sound_buffer" + ], + [ + "w32_get_", + "w32_get_window_dimensions" + ], + [ + "renderWeird", + "render_weird_gradient" + ], + [ + "update", + "update" + ] + ], + "width": 592.0 + }, + "selected_group": 0, + "settings": + { + }, + "show_minimap": false, + "show_open_files": true, + "show_tabs": true, + "side_bar_visible": true, + "side_bar_width": 196.0, + "status_bar_visible": true, + "template_settings": + { + } +} diff --git a/internal/data/runtime.go b/internal/data/runtime.go index 1476f85..f31b5b4 100644 --- a/internal/data/runtime.go +++ b/internal/data/runtime.go @@ -1,10 +1,14 @@ package data import ( + "errors" "fmt" "strconv" + "strings" ) +var ErrInvalidRuntimeFormat = errors.New("invalid runtime format, should be something like '102 mins'") + type Runtime int32 func (r Runtime) MarshalJSON() ([]byte, error) { @@ -14,3 +18,25 @@ func (r Runtime) MarshalJSON() ([]byte, error) { return []byte(quotedJSONValue), nil } + +func (r *Runtime) UnmarshalJSON(jsonValue []byte) error { + unquoJSONValue, err := strconv.Unquote(string(jsonValue)) + if err != nil { + return ErrInvalidRuntimeFormat + } + + jsonParts := strings.Split(unquoJSONValue, " ") + + if len(jsonParts) != 2 || (jsonParts[1] != "mins" && jsonParts[1] != "minutes") { + return ErrInvalidRuntimeFormat + } + + i, err := strconv.ParseInt(jsonParts[0], 10, 32) + if err != nil { + return ErrInvalidRuntimeFormat + } + + *r = Runtime(i) + + return nil +} diff --git a/internal/validator/validator.go b/internal/validator/validator.go new file mode 100644 index 0000000..830e97f --- /dev/null +++ b/internal/validator/validator.go @@ -0,0 +1,55 @@ +package validator + +import "regexp" + +var ( + EmailRX = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}") +) + +type Validator struct { + Errors map[string]string +} + +func New() *Validator { + return &Validator{Errors: make(map[string]string)} +} + +func (v *Validator) Valid() bool { + return len(v.Errors) == 0 +} + +func (v *Validator) AddError(key, message string) { + if _, exists := v.Errors[key]; !exists { + v.Errors[key] = message + } +} + +func (v *Validator) Check(ok bool, key string, message string) { + if !ok { + v.AddError(key, message) + } +} + +func PermittedValue[T comparable](value T, permittedValues ...T) bool { + for i := range permittedValues { + if value == permittedValues[i] { + return true + } + } + + return false +} + +func Matches(value string, rx *regexp.Regexp) bool { + return rx.MatchString(value) +} + +func Unique[T comparable](values []T) bool { + uniqueValues := make(map[T]bool) + + for _, value := range values { + uniqueValues[value] = true + } + + return len(values) == len(uniqueValues) +}