I'm trying to write a "Binder" middleware that will validate any request query using a struct type with gin bindings/validators
So for example, let's say I have an endpoint group called /api/subject which requires the query string to have a subject code and an ID that will be validated using the following struct (called entity.Subject):
type Subject struct {
Code string `binding:"required,alphanum"`
ID string `binding:"required,alphanum,len=4"`
}
That's just one example, but I'd like to be able to pass any struct type to this middleware, because I'd like to access the query data on future handlers without worrying about query validation.
So I tried something like this:
func Binder(t reflect.Type) gin.HandlerFunc {
return func(c *gin.Context) {
obj := reflect.New(t).Elem().Interface()
if err := c.BindQuery(&obj); err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}
c.Set(t.Name(), obj)
}
}
And added this middleware like so:
apiGroup := router.Group("/api")
{
// other subgroups/endpoints
// ...
subjectGroup := apiGroup.Group("/subject", middleware.Binder(reflect.TypeOf(entity.Subject{})))
}
And later on, in another handler function, let's say GetSubject, I want to access the subject data passed by doing c.MustGet("Subject").(entity.Subject)
But this isn't working =(, when I print obj, it's just an empty interface, how would I do this?
I managed to do something similar!
I created the following middleware
var allowedTypes = []binding.Binding{
binding.Query,
binding.Form,
binding.FormPost,
binding.FormMultipart,
}
func Bind(name string, data interface{}, bindingType binding.Binding) gin.HandlerFunc {
return func(ctx *gin.Context) {
ok := false
for _, b := range allowedTypes {
if b == bindingType {
ok = true
}
}
if !ok {
ctx.AbortWithError(
http.StatusInternalServerError,
fmt.Errorf("Bind function only allows %v\n", allowedTypes),
)
}
_ = ctx.MustBindWith(data, bindingType)
ctx.Set(name, data)
}
}
Remember to pass a pointer to your desired type in the call, like so:
router.GET("/something", Bind("Object", &myObject, binding.Query))
I restricted only to a few binding types because they allow ShouldBind to be called multiple times, whereas JSON, XML and others consume the Request body.
This way you can pass multiple Bind middlewares and if the validation fails it automatically aborts with http.StatusBadRequest
Related
I am having an issue with the default binding in Gin. I have an incoming request where the body is multiple Entity objects like so:
<Entity>
<Name>Name One here</Name>
...
</Entity>
<Entity>
<Name>Name Two here</Name>
...
</Entity>
My goal is to map it to a corresponding slice. So the struct for the desired object is like so:
type Entity struct {
XMLName xml.Name `bson:"-" json:"-" xml:"Entity"`
Name string `bson:"name,omitempty" json:",omitempty" xml:",omitempty"`
...
}
The problem I'm experiencing is that only one of the supplied objects is ever mapped into the slice, regardless of how many are passed in the request body. Note that the JSON version of the request parses correctly.
[
{
Name: "Name One",
...
},
{
Name: "Name Two",
...
}
]
I have a struct to model request structure
type ApplicationRequest struct {
XMLName xml.Name `bson:"-" xml:"Entities"`
Entities []Entity `binding:"required" xml:"Entity"`
ParameterOne bool
...
}
So now within the controller function, I handle the binding like this:
func RequestHandler() gin.HandlerFunc {
return func(c *gin.Context) {
var request ApplicationRequest
if err := c.Bind(&request.Entities); err != nil {
responseFunction(http.StatusBadRequest, ..., Message: err.Error()})
return
}
// At this point, the request.Entities slice has ONE element, never more than one
}
}
Note I'm using the gin context.Bind(...) function because it handles the parsing of JSON or XML implicitly, and works for all other scenarios that I need.
Hopefully this provides enough context, any help would be greatly appreciated! Thanks!
it is not a gin problem:
unmarshal-xml-array-in-golang-only-getting-the-first-element
follow is two way to deal it:
add a root node just like #zangw
change the bind method by 'for'
github.com\gin-gonic\gin#v1.8.1\binding\xml.go line 28 func decodeXML
from
func decodeXML(r io.Reader, obj any) error {
decoder := xml.NewDecoder(r)
if err := decoder.Decode(obj); err != nil {
return err
}
return validate(obj)
}
to
func decodeXML(r io.Reader, obj any) error {
decoder := xml.NewDecoder(r)
for {
if err := decoder.Decode(obj); err != nil {
if err == io.EOF{
break
}
return err
}
}
return validate(obj)
}
I am writing a library in Go for using the Strava API. It's a simple API to expose the various objects (athlete, activity, and so on) that make up Strava's data. I am struggling to come up with a way that separates the mechanics of making a request so it can be reused to fetch the various different objects in the API. What I have so far:
type Model interface {
Url() *url.URL
Data() interface{} // pointer to location of unmarshaled response
}
// an activity (run, bike ride, etc)
type Activity struct {
Id int64 `json:"id"`
Name string `json:"name"`
Distance float64 `json:"distance"`
// ...
}
func (a *Activity) Url() *url.URL {
return url.Parse(fmt.Sprintf("https://www.strava.com/api/v3/activities/%d", a.Id))
}
func (a *Activity) Data() interface{} {
return a
}
// gear (shoes, bike, etc)
type Gear struct {
Id string `json:"id"`
Name string `json:"name"`
}
func (g *Gear) Url() *url.URL {
return url.Parse(fmt.Sprintf("https://www.strava.com/api/v3/gear/%s", g.Id))
}
func (g *Gear) Data() interface{} {
return g
}
// a page of activities
type ActivityPage struct {
AthleteId int64
PageNum int
Activities []Activity
}
func (p *ActivityPage) Url() *url.URL {
return url.Parse(fmt.Sprintf("https://www.strava.com/api/v3/athletes/%d/activities?page=%d&per_page=%d", p.AthleteId, p.PageNum, perPage))
}
func (p *ActivityPage) Data() interface{} {
return &p.Activities
}
type Client struct {
hc *http.Client
}
// error handling omitted
func (c *Client) fetch(m Model) error {
data, _ := c.fetchUrl(m.Url())
json.Unmarshal(data, m.Data())
return nil
}
func (c *Client) fetchUrl(u *url.URL) ([]byte, error) {
req := &http.Request{
// omit access token
Method: "GET",
URL: u,
}
resp, _ := c.hc.Do(req)
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
The Data() pointer is needed because the GET /athlete/activities endpoint returns a list of Activitys, rather than a specific model. ActivityPage is therefore a bit of a hack — an object that carries the data needed to build the URL along with a place to put the results. For cases where a GET returns a model the Data() pointer is just the object.
With this code, I can do:
client := Client{}
activity := Activity{Id: 1234}
client.fetch(activity)
fmt.Print(activity.Name)
page := ActivityPage{AthleteId: 1, PageNum: 1}
client.fetch(page)
fmt.Print(len(page.Activities))
But this feels.. icky. I don't like partially constructing the object and passing it to fetch() to be finished off, or that fetch doesn't actually return anything except an error on failure. The Data() pointer is a hack.
AIUI, interfaces are a way to write code that can work with objects of different types, but I feel like I want the inverse — to have some code (a Fetch() method or something) that is inherited by all objects with a certain trait.
How can I make this cleaner? I realise this is kind of open-ended so I'm more than happy to refine what the exact question is as appropriate. Are there canonical examples of building a REST client in Go? (I haven't found anything compelling so far)
this is a standard workaround typical for go, you pass a pointer to data you want to be modified by reflection, standard lib is built like that, you can at most make method to accept pointer to data and url directly to be more verbose and not interface. It will make it at least match cleaner what will get modified just from looking at api calls for user like:
func (c *Client) fetch(url string, responceBuffer interface{}) error {
data, err := c.fetchUrl(url)
if err != nil {
return err
}
return json.Unmarshal(data, responceBuffer)
}
What I'd like to do is overwrite some boolean values on a given object, such as:
func main() {
manager := dshardmanager.New("Bot " + token)
manager.bareSession.State.TrackRoles = false;
manager.bareSession.State.TrackPresences = false;
// more stuff happens down here
}
However bareSession is an unexported field, so I need to do this differently from what I'm gathering. I've come across some approaches using reflection but I'd like to learn the best practices approach to doing this.
In my specific case, it looks like the library I'm using offers a method to accomplish this. I've been tinkering with how to overwrite/define such a method but can't seem to figure out how to go about this.
What's the ideal approach to defining this SessionFunc() to customize the session the way I'm trying to?
I don't know the context of that library, so I'm not sure if what I'll write here makes sense for you :) But by looking at the API, SessionFunc is a func(token string) (*discordgo.Session, error), i.e., a function which receives a string and returns a Session and an error. So you can make something like this to override it:
func main() {
manager := dshardmanager.New("Bot " + token)
manager.SessionFunc = func(token string) (*discordgo.Session, error) {
// use "token"
// if invalid:
if token == "" {
// return an error
return nil, fmt.Errorf("invalid token")
}
// otherwise, return a valid session
return &discordgo.Session{}
}
// more stuff happens down here
}
The code is obviously very generic, but the main idea is that you need to define that function with that exact header, and implement it. I don't know how you can, for example, evaluate the token parameter or create a discordgo.Session. Or how you can configure the TrackRoles or TrackPresences values by using SessionFunc. That's very specific for that library only, but I guess it makes more sense to you than to me :)
You could also define a regular function elsewhere with that exact header:
func createNewSession(token string) (*discordgo.Session, error) {
// use "token"
// if invalid:
if token == "" {
// return an error
return nil, fmt.Errorf("invalid token")
}
// otherwise, return a valid session
return &discordgo.Session{}
}
And set it with:
func main() {
manager := dshardmanager.New("Bot " + token)
manager.SessionFunc = createNewSession
// more stuff happens down here
}
Both approaches work the same way.
Reflection is [almost] never the recommended way to do those things, if the library creators made those properties private, they shouldn't be changed/accessed from outside.
This will not allow you to change bareSession. But if you need to derive a new *discordgo.Session with custom parameters you can do something similar to as follows.
func MySessionFunc(m *dshardmanager.Manager) dshardmanager.SessionFunc {
return func(token string) (*discordgo.Session, error) {
//Call default Session allocator
s, err := m.StdSessionFunc(token)
if err != nil {
return nil, err
}
//Then, change its exported fields
s.State.TrackRoles = false
s.TrackPresences = false
return s, nil
}
}
func main() {
manager := dshardmanager.New("Bot " + token)
manager.SessionFunc = MySessionFunc(manager)
}
I've got some REST API with my models defined as Go structs.
type User struct {
FirstName string
LastName string
}
Then I've got my database methods for getting data.
GetUserByID(id int) (*User, error)
Now I'd like to replace my REST API with https://github.com/twitchtv/twirp .
Therefore I started defining my models inside .proto files.
message User {
string first_name = 2;
string last_name = 3;
}
Now I've got two User types. Let's call them the native and the proto type.
I've also got a service defined in my .proto file which returns a user to the frontend.
service Users {
rpc GetUser(Id) returns (User);
}
This generates an interface that I have to fill in.
func (s *Server) GetUser(context.Context, id) (*User, error) {
// i'd like to reuse my existing database methods
u, err := db.GetUserByID(id)
// handle error
// do more stuff
return u, nil
}
Unfortunately this does not work. My database returns a native User but the interface requires a proto user.
Is there an easy way to make it work? Maybe using type aliases?
Thanks a lot!
One way you can solve your problem is by doing the conversion manually.
type User struct {
FirstName string
LastName string
}
type protoUser struct {
firstName string
lastName string
}
func main() {
u := db() // Retrieve a user from a mocked db
fmt.Println("Before:")
fmt.Printf("%#v\n", *u) // What db returns (*protoUser)
fmt.Println("After:")
fmt.Printf("%#v\n", u.AsUser()) // What conversion returns (User)
}
// Mocked db that returns pointer to protoUser
func db() *protoUser {
pu := protoUser{"John", "Dough"}
return &pu
}
// Conversion method (converts protoUser into a User)
func (pu *protoUser) AsUser() User {
return User{pu.firstName, pu.lastName}
}
The key part is the AsUser method on the protoUser struct.
There we simply write our custom logic for converting a protoUser into a User type we want to be working with.
Working Example
As #Peter mentioned in the comment section.
I've seen a project which made it with a custom Convert function. It converts the Protobuf to local struct via json.Unmarshal, not sure how's the performance but it's a way to go.
Preview Code PLAYGROUND
// Convert converts the in struct to out struct via `json.Unmarshal`
func Convert(in interface{}, out interface{}) error {
j, err := json.Marshal(in)
if err != nil {
return err
}
err = json.Unmarshal(j, &out)
if err != nil {
return err
}
return nil
}
func main() {
// Converts the protobuf struct to local struct via json.Unmarshal
var localUser User
if err := convert(protoUser, &localUser); err != nil {
panic(err)
}
}
Output
Before:
main.ProtoUser{FirstName:"John", LastName:"Dough"}
After:
main.User{FirstName:"John", LastName:"Dough"}
Program exited.
I'm just picking up go, so apologies if my terminology isn't precise. My end goal is to add the name of a cachebusted CSS file to the layout template of my Go application. The CSS file built on the fly when the application starts up so can't be hardcoded. In my template file I have this:
//More html here
<link href="{{.CSSFile}}" rel="stylesheet">
//more html here
I have a Render method on a View type like shown below. It takes data interface{} as an argument and then runs ExecuteTemplate. It is called by every controller in one way or another that sends the data argument and exposes information. I know how to add it as data from the controller that then calls the Render method, but I obviously don't want to add the CSS file in every single controller action, so it makes the most sense to add it in the Render function one time and have it added to the data that gets passed to ExecuteTemplate. My issue is how do I add this information to the data already being passed to Render and then pass that whole of information to ExecuteTemplate. What I have below works for the CSS file, but it obviously doesn't send along the data that was passed to the original Render argument.
type View struct {
Template *template.Template
Layout string
}
func (v *View) Render(w http.ResponseWriter, data interface{}) error {
d := Data{}
d.AddCSSFile()
w.Header().Set("Content-Type", "text/html")
err := v.Template.ExecuteTemplate(w, v.Layout, d)
if err != nil {
log.Println(err)
fmt.Fprintln(w, "<h1>Something went wrong. Please contact us at support")
}
return nil
}
type Data struct {
Alerts []Alert
Yield interface{}
CSSFile interface{}
}
func (d *Data) AddCSSFile() {
ss, _ := filepath.Glob("./assets/site-*.css")
fp := strings.Join(ss, "")
_, d.CSSFile = filepath.Split(fp)
}
I've created a gist which, not entirely complete, is a little more fleshed out of what I'm trying to do:
https://gist.github.com/codelitt/549a68149add0482c6dc2514a46aa580
Not sure I understand exactly what you're asking but if what you want is to combine the data interface{} argument with the d := Data{} value inside Render, then you could do something like this...
// ...
func (v *View) Render(w http.ResponseWriter, data interface{}) error {
p := Page{Data:data}
p.AddCSSFile()
w.Header().Set("Content-Type", "text/html")
err := v.Template.ExecuteTemplate(w, v.Layout, p)
if err != nil {
log.Println(err)
fmt.Fprintln(w, "<h1>Something went wrong. Please contact us at support")
}
return nil
}
type Page struct {
Alerts []Alert
Yield interface{}
CSSFile interface{}
Data interface{}
}
func (p *Page) AddCSSFile() {
// ...
}
Edit: Or you could also just initialize an anonymous struct value and pass it to ExecuteTemplate without having to change you existing Data type.
// ...
err := v.Template.ExecuteTemplate(w, v.Layout, struct{
Data
Args interface{}
}{Data:d, Args:data})
// ...
Edit2: So if I understand your comment correctly the data interface{} argument passed to the Render method could in some or all instances be or contain a value of a type that matches one of the Data fields' types, in which case you would like to set that value to that field so as to pass it together to the ExecuteTemplate method. At least one solution to that, as you've already found out, is to use type assertion. Below is a slightly modified version of your example from the comment in the context of your original example from the question.
func (v *View) Render(w http.ResponseWriter, data interface{}) error {
d := Data{}
d.AddCSSFile()
if alerts, ok := data.([]Alert); ok {
d.Alerts = alerts
}
w.Header().Set("Content-Type", "text/html")
err := v.Template.ExecuteTemplate(w, v.Layout, d)
if err != nil {
log.Println(err)
fmt.Fprintln(w, "<h1>Something went wrong. Please contact us at support")
}
return nil
}