github.com/wangyougui/gf/v2@v2.6.5/net/ghttp/ghttp_z_unit_feature_request_struct_test.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/wangyougui/gf.
     6  
     7  package ghttp_test
     8  
     9  import (
    10  	"fmt"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/wangyougui/gf/v2/frame/g"
    15  	"github.com/wangyougui/gf/v2/net/ghttp"
    16  	"github.com/wangyougui/gf/v2/test/gtest"
    17  	"github.com/wangyougui/gf/v2/util/guid"
    18  	"github.com/wangyougui/gf/v2/util/gvalid"
    19  )
    20  
    21  func Test_Params_Parse(t *testing.T) {
    22  	type User struct {
    23  		Id   int
    24  		Name string
    25  		Map  map[string]interface{}
    26  	}
    27  	s := g.Server(guid.S())
    28  	s.BindHandler("/parse", func(r *ghttp.Request) {
    29  		var user *User
    30  		if err := r.Parse(&user); err != nil {
    31  			r.Response.WriteExit(err)
    32  		}
    33  		r.Response.WriteExit(user.Map["id"], user.Map["score"])
    34  	})
    35  	s.BindHandler("/parseErr", func(r *ghttp.Request) {
    36  		var user User
    37  		err := r.Parse(user)
    38  		r.Response.WriteExit(err != nil)
    39  	})
    40  	s.SetDumpRouterMap(false)
    41  	s.Start()
    42  	defer s.Shutdown()
    43  
    44  	time.Sleep(100 * time.Millisecond)
    45  	gtest.C(t, func(t *gtest.T) {
    46  		client := g.Client()
    47  		client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
    48  		t.Assert(client.PostContent(ctx, "/parse", `{"id":1,"name":"john","map":{"id":1,"score":100}}`), `1100`)
    49  		t.Assert(client.PostContent(ctx, "/parseErr", `{"id":1,"name":"john","map":{"id":1,"score":100}}`), true)
    50  	})
    51  }
    52  
    53  func Test_Params_ParseQuery(t *testing.T) {
    54  	type User struct {
    55  		Id   int
    56  		Name string
    57  	}
    58  	s := g.Server(guid.S())
    59  	s.BindHandler("/parse-query", func(r *ghttp.Request) {
    60  		var user *User
    61  		if err := r.ParseQuery(&user); err != nil {
    62  			r.Response.WriteExit(err)
    63  		}
    64  		r.Response.WriteExit(user.Id, user.Name)
    65  	})
    66  	s.SetDumpRouterMap(false)
    67  	s.Start()
    68  	defer s.Shutdown()
    69  
    70  	time.Sleep(100 * time.Millisecond)
    71  	gtest.C(t, func(t *gtest.T) {
    72  		c := g.Client()
    73  		c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
    74  		t.Assert(c.GetContent(ctx, "/parse-query"), `0`)
    75  		t.Assert(c.GetContent(ctx, "/parse-query?id=1&name=john"), `1john`)
    76  		t.Assert(c.PostContent(ctx, "/parse-query"), `0`)
    77  		t.Assert(c.PostContent(ctx, "/parse-query", g.Map{
    78  			"id":   1,
    79  			"name": "john",
    80  		}), `0`)
    81  	})
    82  }
    83  
    84  func Test_Params_ParseForm(t *testing.T) {
    85  	type User struct {
    86  		Id   int
    87  		Name string
    88  	}
    89  	s := g.Server(guid.S())
    90  	s.BindHandler("/parse-form", func(r *ghttp.Request) {
    91  		var user *User
    92  		if err := r.ParseForm(&user); err != nil {
    93  			r.Response.WriteExit(err)
    94  		}
    95  		r.Response.WriteExit(user.Id, user.Name)
    96  	})
    97  	s.SetDumpRouterMap(false)
    98  	s.Start()
    99  	defer s.Shutdown()
   100  
   101  	time.Sleep(100 * time.Millisecond)
   102  	gtest.C(t, func(t *gtest.T) {
   103  		c := g.Client()
   104  		c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
   105  		t.Assert(c.GetContent(ctx, "/parse-form"), `0`)
   106  		t.Assert(c.GetContent(ctx, "/parse-form", g.Map{
   107  			"id":   1,
   108  			"name": "john",
   109  		}), 0)
   110  		t.Assert(c.PostContent(ctx, "/parse-form"), `0`)
   111  		t.Assert(c.PostContent(ctx, "/parse-form", g.Map{
   112  			"id":   1,
   113  			"name": "john",
   114  		}), `1john`)
   115  	})
   116  }
   117  
   118  func Test_Params_ComplexJsonStruct(t *testing.T) {
   119  	type ItemEnv struct {
   120  		Type  string
   121  		Key   string
   122  		Value string
   123  		Brief string
   124  	}
   125  
   126  	type ItemProbe struct {
   127  		Type           string
   128  		Port           int
   129  		Path           string
   130  		Brief          string
   131  		Period         int
   132  		InitialDelay   int
   133  		TimeoutSeconds int
   134  	}
   135  
   136  	type ItemKV struct {
   137  		Key   string
   138  		Value string
   139  	}
   140  
   141  	type ItemPort struct {
   142  		Port  int
   143  		Type  string
   144  		Alias string
   145  		Brief string
   146  	}
   147  
   148  	type ItemMount struct {
   149  		Type    string
   150  		DstPath string
   151  		Src     string
   152  		SrcPath string
   153  		Brief   string
   154  	}
   155  
   156  	type SaveRequest struct {
   157  		AppId          uint
   158  		Name           string
   159  		Type           string
   160  		Cluster        string
   161  		Replicas       uint
   162  		ContainerName  string
   163  		ContainerImage string
   164  		VersionTag     string
   165  		Namespace      string
   166  		Id             uint
   167  		Status         uint
   168  		Metrics        string
   169  		InitImage      string
   170  		CpuRequest     uint
   171  		CpuLimit       uint
   172  		MemRequest     uint
   173  		MemLimit       uint
   174  		MeshEnabled    uint
   175  		ContainerPorts []ItemPort
   176  		Labels         []ItemKV
   177  		NodeSelector   []ItemKV
   178  		EnvReserve     []ItemKV
   179  		EnvGlobal      []ItemEnv
   180  		EnvContainer   []ItemEnv
   181  		Mounts         []ItemMount
   182  		LivenessProbe  ItemProbe
   183  		ReadinessProbe ItemProbe
   184  	}
   185  
   186  	s := g.Server(guid.S())
   187  	s.BindHandler("/parse", func(r *ghttp.Request) {
   188  		if m := r.GetMap(); len(m) > 0 {
   189  			var data *SaveRequest
   190  			if err := r.Parse(&data); err != nil {
   191  				r.Response.WriteExit(err)
   192  			}
   193  			r.Response.WriteExit(data)
   194  		}
   195  	})
   196  	s.SetDumpRouterMap(false)
   197  	s.Start()
   198  	defer s.Shutdown()
   199  
   200  	time.Sleep(100 * time.Millisecond)
   201  	gtest.C(t, func(t *gtest.T) {
   202  		client := g.Client()
   203  		client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
   204  		content := `
   205  {
   206      "app_id": 5,
   207      "cluster": "test",
   208      "container_image": "nginx",
   209      "container_name": "test",
   210      "container_ports": [
   211          {
   212              "alias": "别名",
   213              "brief": "描述",
   214              "port": 80,
   215              "type": "tcp"
   216          }
   217      ],
   218      "cpu_limit": 100,
   219      "cpu_request": 10,
   220      "create_at": "2020-10-10 12:00:00",
   221      "creator": 1,
   222      "env_container": [
   223          {
   224              "brief": "用户环境变量",
   225              "key": "NAME",
   226              "type": "string",
   227              "value": "john"
   228          }
   229      ],
   230      "env_global": [
   231          {
   232              "brief": "数据数量",
   233              "key": "NUMBER",
   234              "type": "string",
   235              "value": "1"
   236          }
   237      ],
   238      "env_reserve": [
   239          {
   240              "key": "NODE_IP",
   241              "value": "status.hostIP"
   242          }
   243      ],
   244      "liveness_probe": {
   245          "brief": "存活探针",
   246          "initial_delay": 10,
   247          "path": "",
   248          "period": 5,
   249          "port": 80,
   250          "type": "tcpSocket"
   251      },
   252      "readiness_probe": {
   253          "brief": "就绪探针",
   254          "initial_delay": 10,
   255          "path": "",
   256          "period": 5,
   257          "port": 80,
   258          "type": "tcpSocket"
   259      },
   260      "id": 0,
   261      "init_image": "",
   262      "labels": [
   263          {
   264              "key": "app",
   265              "value": "test"
   266          }
   267      ],
   268      "mem_limit": 1000,
   269      "mem_request": 100,
   270      "mesh_enabled": 0,
   271      "metrics": "",
   272      "mounts": [],
   273      "name": "test",
   274      "namespace": "test",
   275      "node_selector": [
   276          {
   277              "key": "group",
   278              "value": "app"
   279          }
   280      ],
   281      "replicas": 1,
   282      "type": "test",
   283      "update_at": "2020-10-10 12:00:00",
   284      "version_tag": "test"
   285  }
   286  `
   287  		t.Assert(client.PostContent(ctx, "/parse", content), `{"AppId":5,"Name":"test","Type":"test","Cluster":"test","Replicas":1,"ContainerName":"test","ContainerImage":"nginx","VersionTag":"test","Namespace":"test","Id":0,"Status":0,"Metrics":"","InitImage":"","CpuRequest":10,"CpuLimit":100,"MemRequest":100,"MemLimit":1000,"MeshEnabled":0,"ContainerPorts":[{"Port":80,"Type":"tcp","Alias":"别名","Brief":"描述"}],"Labels":[{"Key":"app","Value":"test"}],"NodeSelector":[{"Key":"group","Value":"app"}],"EnvReserve":[{"Key":"NODE_IP","Value":"status.hostIP"}],"EnvGlobal":[{"Type":"string","Key":"NUMBER","Value":"1","Brief":"数据数量"}],"EnvContainer":[{"Type":"string","Key":"NAME","Value":"john","Brief":"用户环境变量"}],"Mounts":[],"LivenessProbe":{"Type":"tcpSocket","Port":80,"Path":"","Brief":"存活探针","Period":5,"InitialDelay":10,"TimeoutSeconds":0},"ReadinessProbe":{"Type":"tcpSocket","Port":80,"Path":"","Brief":"就绪探针","Period":5,"InitialDelay":10,"TimeoutSeconds":0}}`)
   288  	})
   289  }
   290  
   291  func Test_Params_Parse_Attr_Pointer1(t *testing.T) {
   292  	type User struct {
   293  		Id   *int
   294  		Name *string
   295  	}
   296  	s := g.Server(guid.S())
   297  	s.BindHandler("/parse1", func(r *ghttp.Request) {
   298  		if m := r.GetMap(); len(m) > 0 {
   299  			var user *User
   300  			if err := r.Parse(&user); err != nil {
   301  				r.Response.WriteExit(err)
   302  			}
   303  			r.Response.WriteExit(user.Id, user.Name)
   304  		}
   305  	})
   306  	s.BindHandler("/parse2", func(r *ghttp.Request) {
   307  		if m := r.GetMap(); len(m) > 0 {
   308  			var user = new(User)
   309  			if err := r.Parse(user); err != nil {
   310  				r.Response.WriteExit(err)
   311  			}
   312  			r.Response.WriteExit(user.Id, user.Name)
   313  		}
   314  	})
   315  	s.SetDumpRouterMap(false)
   316  	s.Start()
   317  	defer s.Shutdown()
   318  
   319  	time.Sleep(100 * time.Millisecond)
   320  	gtest.C(t, func(t *gtest.T) {
   321  		client := g.Client()
   322  		client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
   323  		t.Assert(client.PostContent(ctx, "/parse1", `{"id":1,"name":"john"}`), `1john`)
   324  		t.Assert(client.PostContent(ctx, "/parse2", `{"id":1,"name":"john"}`), `1john`)
   325  		t.Assert(client.PostContent(ctx, "/parse2?id=1&name=john"), `1john`)
   326  		t.Assert(client.PostContent(ctx, "/parse2", `id=1&name=john`), `1john`)
   327  	})
   328  }
   329  
   330  func Test_Params_Parse_Attr_Pointer2(t *testing.T) {
   331  	type User struct {
   332  		Id *int `v:"required"`
   333  	}
   334  	s := g.Server(guid.S())
   335  	s.BindHandler("/parse", func(r *ghttp.Request) {
   336  		var user *User
   337  		if err := r.Parse(&user); err != nil {
   338  			r.Response.WriteExit(err.Error())
   339  		}
   340  		r.Response.WriteExit(user.Id)
   341  	})
   342  	s.SetDumpRouterMap(false)
   343  	s.Start()
   344  	defer s.Shutdown()
   345  
   346  	time.Sleep(100 * time.Millisecond)
   347  	gtest.C(t, func(t *gtest.T) {
   348  		client := g.Client()
   349  		client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
   350  		t.Assert(client.PostContent(ctx, "/parse"), `The Id field is required`)
   351  		t.Assert(client.PostContent(ctx, "/parse?id=1"), `1`)
   352  	})
   353  }
   354  
   355  // It does not support this kind of converting yet.
   356  // func Test_Params_Parse_Attr_SliceSlice(t *testing.T) {
   357  //	type User struct {
   358  //		Id     int
   359  //		Name   string
   360  //		Scores [][]int
   361  //	}
   362  //	//	s := g.Server(guid.S())
   363  //	s.BindHandler("/parse", func(r *ghttp.Request) {
   364  //		if m := r.GetMap(); len(m) > 0 {
   365  //			var user *User
   366  //			if err := r.Parse(&user); err != nil {
   367  //				r.Response.WriteExit(err)
   368  //			}
   369  //			r.Response.WriteExit(user.Scores)
   370  //		}
   371  //	})
   372  //	//	s.SetDumpRouterMap(false)
   373  //	s.Start()
   374  //	defer s.Shutdown()
   375  //
   376  //	time.Sleep(100 * time.Millisecond)
   377  //	gtest.C(t, func(t *gtest.T) {
   378  //		client := g.Client()
   379  //		client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
   380  //		t.Assert(client.PostContent(ctx, "/parse", `{"id":1,"name":"john","scores":[[1,2,3]]}`), `1100`)
   381  //	})
   382  // }
   383  
   384  func Test_Params_Struct(t *testing.T) {
   385  	type User struct {
   386  		Id    int
   387  		Name  string
   388  		Time  *time.Time
   389  		Pass1 string `p:"password1"`
   390  		Pass2 string `p:"password2" v:"password2 @required|length:2,20|password3#||密码强度不足"`
   391  	}
   392  	s := g.Server(guid.S())
   393  	s.BindHandler("/struct1", func(r *ghttp.Request) {
   394  		if m := r.GetMap(); len(m) > 0 {
   395  			user := new(User)
   396  			if err := r.GetStruct(user); err != nil {
   397  				r.Response.WriteExit(err)
   398  			}
   399  			r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
   400  		}
   401  	})
   402  	s.BindHandler("/struct2", func(r *ghttp.Request) {
   403  		if m := r.GetMap(); len(m) > 0 {
   404  			user := (*User)(nil)
   405  			if err := r.GetStruct(&user); err != nil {
   406  				r.Response.WriteExit(err)
   407  			}
   408  			if user != nil {
   409  				r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
   410  			}
   411  		}
   412  	})
   413  	s.BindHandler("/struct-valid", func(r *ghttp.Request) {
   414  		if m := r.GetMap(); len(m) > 0 {
   415  			user := new(User)
   416  			if err := r.GetStruct(user); err != nil {
   417  				r.Response.WriteExit(err)
   418  			}
   419  			if err := gvalid.New().Data(user).Run(r.Context()); err != nil {
   420  				r.Response.WriteExit(err)
   421  			}
   422  		}
   423  	})
   424  	s.BindHandler("/parse", func(r *ghttp.Request) {
   425  		if m := r.GetMap(); len(m) > 0 {
   426  			var user *User
   427  			if err := r.Parse(&user); err != nil {
   428  				r.Response.WriteExit(err)
   429  			}
   430  			r.Response.WriteExit(user.Id, user.Name, user.Pass1, user.Pass2)
   431  		}
   432  	})
   433  	s.SetDumpRouterMap(false)
   434  	s.Start()
   435  	defer s.Shutdown()
   436  
   437  	time.Sleep(100 * time.Millisecond)
   438  	gtest.C(t, func(t *gtest.T) {
   439  		client := g.Client()
   440  		client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
   441  		t.Assert(client.GetContent(ctx, "/struct1", `id=1&name=john&password1=123&password2=456`), `1john123456`)
   442  		t.Assert(client.PostContent(ctx, "/struct1", `id=1&name=john&password1=123&password2=456`), `1john123456`)
   443  		t.Assert(client.PostContent(ctx, "/struct2", `id=1&name=john&password1=123&password2=456`), `1john123456`)
   444  		t.Assert(client.PostContent(ctx, "/struct2", ``), ``)
   445  		t.Assert(client.PostContent(ctx, "/struct-valid", `id=1&name=john&password1=123&password2=0`), "The password2 value `0` length must be between 2 and 20; 密码强度不足")
   446  		t.Assert(client.PostContent(ctx, "/parse", `id=1&name=john&password1=123&password2=0`), "The password2 value `0` length must be between 2 and 20")
   447  		t.Assert(client.PostContent(ctx, "/parse", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john123Abc!@#123Abc!@#`)
   448  	})
   449  }
   450  
   451  func Test_Params_Structs(t *testing.T) {
   452  	type User struct {
   453  		Id    int
   454  		Name  string
   455  		Time  *time.Time
   456  		Pass1 string `p:"password1"`
   457  		Pass2 string `p:"password2" v:"password2 @required|length:2,20|password3#||密码强度不足"`
   458  	}
   459  	s := g.Server(guid.S())
   460  	s.BindHandler("/parse1", func(r *ghttp.Request) {
   461  		var users []*User
   462  		if err := r.Parse(&users); err != nil {
   463  			r.Response.WriteExit(err)
   464  		}
   465  		r.Response.WriteExit(users[0].Id, users[1].Id)
   466  	})
   467  	s.SetDumpRouterMap(false)
   468  	s.Start()
   469  	defer s.Shutdown()
   470  
   471  	time.Sleep(100 * time.Millisecond)
   472  	gtest.C(t, func(t *gtest.T) {
   473  		client := g.Client()
   474  		client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
   475  		t.Assert(client.PostContent(ctx,
   476  			"/parse1",
   477  			`[{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}, {"id":2,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}]`),
   478  			`12`,
   479  		)
   480  	})
   481  }
   482  
   483  func Test_Params_Struct_Validation(t *testing.T) {
   484  	type User struct {
   485  		Id   int    `v:"required"`
   486  		Name string `v:"name@required-with:id"`
   487  	}
   488  	s := g.Server(guid.S())
   489  	s.Group("/", func(group *ghttp.RouterGroup) {
   490  		group.ALL("/", func(r *ghttp.Request) {
   491  			var (
   492  				err  error
   493  				user *User
   494  			)
   495  			err = r.Parse(&user)
   496  			if err != nil {
   497  				r.Response.WriteExit(err)
   498  			}
   499  			r.Response.WriteExit(user.Id, user.Name)
   500  		})
   501  	})
   502  	s.SetDumpRouterMap(false)
   503  	s.Start()
   504  	defer s.Shutdown()
   505  
   506  	time.Sleep(100 * time.Millisecond)
   507  	gtest.C(t, func(t *gtest.T) {
   508  		c := g.Client()
   509  		c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
   510  		t.Assert(c.GetContent(ctx, "/", ``), `The Id field is required`)
   511  		t.Assert(c.GetContent(ctx, "/", `id=1&name=john`), `1john`)
   512  		t.Assert(c.PostContent(ctx, "/", `id=1&name=john&password1=123&password2=456`), `1john`)
   513  		t.Assert(c.PostContent(ctx, "/", `id=1`), `The name field is required`)
   514  	})
   515  }
   516  
   517  // https://github.com/wangyougui/gf/issues/1488
   518  func Test_Params_Parse_Issue1488(t *testing.T) {
   519  	s := g.Server(guid.S())
   520  	s.Group("/", func(group *ghttp.RouterGroup) {
   521  		group.ALL("/", func(r *ghttp.Request) {
   522  			type Request struct {
   523  				Type         []int  `p:"type"`
   524  				Keyword      string `p:"keyword"`
   525  				Limit        int    `p:"per_page" d:"10"`
   526  				Page         int    `p:"page" d:"1"`
   527  				Order        string
   528  				CreatedAtLte string
   529  				CreatedAtGte string
   530  				CreatorID    []int
   531  			}
   532  			for i := 0; i < 10; i++ {
   533  				r.SetParamMap(g.Map{
   534  					"type[]":           0,
   535  					"keyword":          "",
   536  					"t_start":          "",
   537  					"t_end":            "",
   538  					"reserve_at_start": "",
   539  					"reserve_at_end":   "",
   540  					"user_name":        "",
   541  					"flag":             "",
   542  					"per_page":         6,
   543  				})
   544  				var parsed Request
   545  				_ = r.Parse(&parsed)
   546  				r.Response.Write(parsed.Page, parsed.Limit)
   547  			}
   548  		})
   549  	})
   550  	s.SetDumpRouterMap(false)
   551  	s.Start()
   552  	defer s.Shutdown()
   553  
   554  	time.Sleep(100 * time.Millisecond)
   555  	gtest.C(t, func(t *gtest.T) {
   556  		c := g.Client()
   557  		c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
   558  		t.Assert(c.GetContent(ctx, "/", ``), `16161616161616161616`)
   559  	})
   560  }