@@ -8,13 +8,11 @@ package gclient
88
99import (
1010 "context"
11- "fmt"
1211 "net/http"
1312 "reflect"
1413
1514 "github.com/gogf/gf/v2/errors/gcode"
1615 "github.com/gogf/gf/v2/errors/gerror"
17- "github.com/gogf/gf/v2/internal/json"
1816 "github.com/gogf/gf/v2/net/goai"
1917 "github.com/gogf/gf/v2/os/gstructs"
2018 "github.com/gogf/gf/v2/text/gregex"
@@ -29,7 +27,7 @@ import (
2927// The request object `req` is defined like:
3028//
3129// type UserCreateReq struct {
32- // g.Meta `path:"/user/{id}" method:"post"`
30+ // g.Meta `path:"/user/{id}" method:"post" mime:"application/json" `
3331// Id int `in:"path"` // Path parameter
3432// Token string `in:"header"` // Header parameter
3533// Page int `in:"query"` // Query parameter
@@ -41,6 +39,11 @@ import (
4139// The response object `res` should be a pointer type. It automatically converts result
4240// to given object `res` if success.
4341//
42+ // Supported g.Meta tags:
43+ // - "path": Request path (required)
44+ // - "method": HTTP method (required)
45+ // - "mime": Content-Type header (optional, e.g., "application/json")
46+ //
4447// Supported `in` tag values:
4548// - "path": URL path parameters (e.g., /user/{id})
4649// - "query": URL query parameters (e.g., ?page=1)
@@ -63,13 +66,14 @@ import (
6366// )
6467// err := client.DoRequestObj(ctx, req, &res)
6568// // Actual request: POST /user/123?page=1
66- // // Headers: Token: Bearer xxx
69+ // // Headers: Token: Bearer xxx, Content-Type: application/json
6770// // Cookies: Session=session-id
6871// // Body: {"name":"John","age":25}
6972func (c * Client ) DoRequestObj (ctx context.Context , req , res any ) error {
7073 var (
71- method = gmeta .Get (req , gtag .Method ).String ()
72- path = gmeta .Get (req , gtag .Path ).String ()
74+ method = gmeta .Get (req , gtag .Method ).String ()
75+ path = gmeta .Get (req , gtag .Path ).String ()
76+ contentType = gmeta .Get (req , gtag .Mime ).String ()
7377 )
7478 if method == "" {
7579 return gerror .NewCodef (
@@ -115,6 +119,10 @@ func (c *Client) DoRequestObj(ctx context.Context, req, res any) error {
115119 client = client .SetCookie (k , v )
116120 }
117121 }
122+ // Set Content-Type from mime tag if specified
123+ if contentType != "" {
124+ client = client .ContentType (contentType )
125+ }
118126
119127 // Prepare body data
120128 var data any
@@ -176,16 +184,20 @@ type requestParams struct {
176184// It returns parameters categorized into path, query, header, cookie, and body.
177185//
178186// Supported `in` tag values:
179- // - "path": URL path parameters
180- // - "query": URL query parameters (supports slice/array/map types )
187+ // - "path": URL path parameters (primitive types only)
188+ // - "query": URL query parameters (supports primitive types and slice/array)
181189// - "header": HTTP request headers (string values only)
182190// - "cookie": HTTP cookies (string values only)
183- // - (empty): Request body parameters (default)
191+ // - (empty): Request body parameters (default, supports all types)
192+ //
193+ // Type restrictions:
194+ // - Struct and Map types are NOT supported for path/query/header/cookie parameters
195+ // - Only primitive types, slices, and arrays are allowed for query parameters
196+ // - Struct fields without `in` tag will be placed in the request body
184197//
185- // For embedded structs:
186- // - Anonymous embedded structs are automatically flattened
187- // - Named struct fields with `in:"query"` are flattened to query parameters
188- // - Named struct fields without `in` tag are placed in body as-is
198+ // Embedded struct handling:
199+ // - Anonymous embedded structs without tags: fields are flattened into body
200+ // - Named struct fields: kept as nested structure in body
189201func (c * Client ) classifyRequestParams (req any ) (* requestParams , error ) {
190202 params := & requestParams {
191203 path : make (map [string ]any ),
@@ -195,10 +207,10 @@ func (c *Client) classifyRequestParams(req any) (*requestParams, error) {
195207 body : make (map [string ]any ),
196208 }
197209
198- // Use RecursiveOptionEmbedded to automatically flatten anonymous embedded structs
210+ // Process direct fields first, then handle embedded structs for body parameters
199211 fields , err := gstructs .Fields (gstructs.FieldsInput {
200212 Pointer : req ,
201- RecursiveOption : gstructs .RecursiveOptionEmbedded ,
213+ RecursiveOption : gstructs .RecursiveOptionEmbeddedNoTag ,
202214 })
203215 if err != nil {
204216 return nil , err
@@ -219,29 +231,12 @@ func (c *Client) classifyRequestParams(req any) (*requestParams, error) {
219231
220232 // Handle named struct fields (non-embedded)
221233 if ! field .IsEmbedded () && reflectValue .IsValid () && reflectValue .Kind () == reflect .Struct {
222- // If struct field has `in` tag, special handling is required
234+ // Struct fields with `in` tag are not supported
223235 if inTag != "" {
224- switch inTag {
225- case goai .ParameterInQuery :
226- // Flatten struct fields to query parameters
227- if err := flattenStructToMap (params .query , fieldValue ); err != nil {
228- return nil , err
229- }
230- continue
231-
232- case goai .ParameterInHeader :
233- // Header doesn't support struct, serialize to JSON
234- jsonBytes , _ := json .Marshal (fieldValue )
235- params .header [fieldName ] = string (jsonBytes )
236- continue
237-
238- case goai .ParameterInPath , goai .ParameterInCookie :
239- // Path and Cookie don't support struct type
240- return nil , gerror .Newf (
241- `field "%s" with in:"%s" cannot be a struct type` ,
242- fieldName , inTag ,
243- )
244- }
236+ return nil , gerror .Newf (
237+ `field "%s" with in:"%s" cannot be a struct type` ,
238+ fieldName , inTag ,
239+ )
245240 }
246241 // Struct field without `in` tag goes to body
247242 params .body [fieldName ] = fieldValue
@@ -254,16 +249,15 @@ func (c *Client) classifyRequestParams(req any) (*requestParams, error) {
254249 params .path [fieldName ] = fieldValue
255250
256251 case goai .ParameterInQuery :
257- // Handle map type (flatten to key[subkey] format)
252+ // Map type is not supported for query parameters
258253 if reflectValue .IsValid () && reflectValue .Kind () == reflect .Map {
259- for _ , key := range reflectValue .MapKeys () {
260- mapKey := fmt .Sprintf ("%s[%s]" , fieldName , key .String ())
261- params .query [mapKey ] = reflectValue .MapIndex (key ).Interface ()
262- }
263- } else {
264- // Slice/array/primitive types are handled by SetQueryMap
265- params .query [fieldName ] = fieldValue
254+ return nil , gerror .Newf (
255+ `field "%s" with in:"query" cannot be a map type, please use struct fields instead` ,
256+ fieldName ,
257+ )
266258 }
259+ // Slice/array/primitive types are handled by SetQueryMap
260+ params .query [fieldName ] = fieldValue
267261
268262 case goai .ParameterInHeader :
269263 params .header [fieldName ] = gconv .String (fieldValue )
@@ -279,29 +273,3 @@ func (c *Client) classifyRequestParams(req any) (*requestParams, error) {
279273
280274 return params , nil
281275}
282-
283- // flattenStructToMap flattens struct fields to target map.
284- // It's used for flattening named struct fields with `in:"query"` tag.
285- func flattenStructToMap (targetMap map [string ]any , structValue any ) error {
286- fields , err := gstructs .Fields (gstructs.FieldsInput {
287- Pointer : structValue ,
288- RecursiveOption : gstructs .RecursiveOptionEmbedded ,
289- })
290- if err != nil {
291- return err
292- }
293-
294- for _ , field := range fields {
295- if ! field .IsExported () {
296- continue
297- }
298-
299- fieldName := field .TagPriorityName ()
300- fieldValue := field .Value .Interface ()
301-
302- // Use field name directly (consistent with anonymous embedded behavior)
303- targetMap [fieldName ] = fieldValue
304- }
305-
306- return nil
307- }
0 commit comments