Last active
May 24, 2024 19:47
-
-
Save dlisboa/1dc648912282fb4f911e0546963cb9db to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import "net/url" | |
import "regexp" | |
import "fmt" | |
import "os" | |
import "encoding/json" | |
type Username struct { | |
String string | |
Valid bool | |
} | |
type User struct { | |
Username Username `json:"username"` | |
} | |
func (u *User) Valid() bool { | |
return u.Username.Valid | |
} | |
func (u *User) UnmarshalFormData(values url.Values) error { | |
name, err := NewUsername(values.Get("username")) | |
if err != nil { | |
return err | |
} | |
u.Username = name | |
return nil | |
} | |
var re = regexp.MustCompile(`^\w{3,}$`) | |
func NewUsername(s string) (Username, error) { | |
if !re.MatchString(s) { | |
return Username{}, fmt.Errorf("Username invalid: %#v", s) | |
} | |
return Username{s, true}, nil | |
} | |
func (u Username) MarshalJSON() ([]byte, error) { | |
if u.Valid { | |
return json.Marshal(u.String) | |
} | |
return json.Marshal("!INVALID!") | |
} | |
type FormDataUnmarshaler interface { | |
UnmarshalFormData(url.Values) error | |
} | |
func decode[T FormDataUnmarshaler](into T, values url.Values) error { | |
err := into.UnmarshalFormData(values) | |
if err != nil { | |
return fmt.Errorf("decode: %w", err) | |
} | |
return nil | |
} | |
func main() { | |
var u User | |
values := url.Values{} | |
fmt.Println("with invalid form data username") | |
values.Set("username", "<inv@lid>**") | |
doDecode(u, values) | |
fmt.Println("with valid form data username") | |
values.Set("username", "dlisboa") | |
doDecode(u, values) | |
fmt.Println("with empty form data username") | |
values = url.Values{} | |
doDecode(u, values) | |
} | |
func doDecode(u User, vals url.Values) { | |
err := decode(&u, vals) | |
if err != nil { | |
fmt.Printf("%s, user: %+v\n", err, u) | |
} | |
json.NewEncoder(os.Stdout).Encode(u) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This kind of validation has a few properties:
UnmarshalText/UnmarshalJSON
. If you look at theUnmarshalJSON
implementation, it validates the input inside of itdecode(T, url.Values)
function, or callT.UnmarshalFormData(url.Values)
explicitlyUnmarshalFormData
method using unreflected codeUser
doesn't have astring
as username, but aUsername
type. Not all strings are valid usernames. IfUsernames
are only created usingNewUsername
we have assurance that when you pass a username to a functionfunc f(u Username)
it is already valid. The invalid strings stop at the barrier (the handler that calledUnmarshalFormData
)