refactor: use query parameters for data-object attribute APIs

- read token and field from query parameters
- default missing or empty fields to value
- expose attribute query and update through GET and PATCH routes
- return true only for measurement mode 1
- add query parsing and measurement mode test coverage
This commit is contained in:
douxu 2026-07-17 16:40:34 +08:00
parent 38f21f3cda
commit ab029e292b
3 changed files with 114 additions and 56 deletions

View File

@ -24,16 +24,9 @@ func DataObjectAttributeQueryHandler(c *gin.Context) {
ctx := c.Request.Context()
pgClient := database.GetPostgresDBClient()
token := c.Param("token")
field := c.Param("field")
if token == "" {
err := fmt.Errorf("tokens is missing from the path")
logger.Error(ctx, "query tokens from path failed", "error", err, "url", c.Request.RequestURI)
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
return
} else if field == "" {
err := fmt.Errorf("field is missing from the path")
logger.Error(ctx, "query field from path failed", "error", err, "url", c.Request.RequestURI)
token, field, err := parseDataObjectAttributeQuery(c)
if err != nil {
logger.Error(ctx, "query token from query parameters failed", "error", err, "url", c.Request.RequestURI)
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
return
}
@ -45,8 +38,7 @@ func DataObjectAttributeQueryHandler(c *gin.Context) {
return
}
normalizedField := strings.ToLower(field)
if err := validateDataObjectField(token, dataObjectType, normalizedField); err != nil {
if err := validateDataObjectField(token, dataObjectType, field); err != nil {
logger.Warn(ctx, "validate data object field failed", "token", token, "field", field, "error", err)
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
return
@ -94,7 +86,7 @@ func DataObjectAttributeQueryHandler(c *gin.Context) {
case constants.DataObjectTypeParameter:
value, err := buildParameterAttributeValue(
ctx,
normalizedField,
field,
parameter,
func(ctx context.Context, parameter *database.ParameterDataObject) (any, error) {
return database.QueryParameterDataObjectValue(ctx, pgClient, parameter)
@ -117,7 +109,7 @@ func DataObjectAttributeQueryHandler(c *gin.Context) {
result := dataObjectAttributeQueryResult{
Token: token,
Field: normalizedField,
Field: field,
Code: errcode.ErrProcessSuccess.Code(),
Msg: errcode.ErrProcessSuccess.Msg(),
Value: value,
@ -128,7 +120,7 @@ func DataObjectAttributeQueryHandler(c *gin.Context) {
case constants.DataObjectTypeMeasurement:
value, err := buildMeasurementAttributeValue(
ctx,
normalizedField,
field,
measurement,
measurementComponent,
queryMeasurementRealtimeValue,
@ -147,7 +139,7 @@ func DataObjectAttributeQueryHandler(c *gin.Context) {
result := dataObjectAttributeQueryResult{
Token: token,
Field: normalizedField,
Field: field,
Code: errcode.ErrProcessSuccess.Code(),
Msg: errcode.ErrProcessSuccess.Msg(),
Value: value,
@ -160,6 +152,19 @@ func DataObjectAttributeQueryHandler(c *gin.Context) {
}
}
func parseDataObjectAttributeQuery(c *gin.Context) (string, string, error) {
token := c.Query("token")
if token == "" {
return "", "", fmt.Errorf("token is missing from query parameters")
}
field := strings.ToLower(c.Query("field"))
if field == "" {
field = "value"
}
return token, field, nil
}
type measurementValueLoader func(context.Context, orm.JSONMap) (any, error)
type parameterValueLoader func(context.Context, *database.ParameterDataObject) (any, error)
@ -285,15 +290,7 @@ func buildMeasurementAttributeValue(
}
return loadValue(ctx, measurement.DataSource)
case "mode":
fmt.Println("measurement.Mode:", measurement.Mode)
switch measurement.Mode {
case 0:
return false, nil
case 1:
return true, nil
default:
return nil, fmt.Errorf("invalid measurement mode %d: expected 0 or 1", measurement.Mode)
}
return measurement.Mode == 1, nil
case "meta":
return "MEASUREMENT", nil
case "type":

View File

@ -2,6 +2,8 @@ package handler
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"modelRT/common"
@ -9,10 +11,75 @@ import (
"modelRT/database"
"modelRT/orm"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParseDataObjectAttributeQuery(t *testing.T) {
const token = "nspath.component.bay.measurement"
tests := []struct {
name string
target string
wantToken string
wantField string
wantErr string
}{
{
name: "reads token and field from query parameters",
target: "/data-object/attribute?token=" + token + "&field=NAME",
wantToken: token,
wantField: "name",
},
{
name: "defaults missing field to value",
target: "/data-object/attribute?token=" + token,
wantToken: token,
wantField: "value",
},
{
name: "defaults empty field to value",
target: "/data-object/attribute?token=" + token + "&field=",
wantToken: token,
wantField: "value",
},
{
name: "rejects missing token",
target: "/data-object/attribute?field=value",
wantErr: "token is missing from query parameters",
},
{
name: "rejects empty token",
target: "/data-object/attribute?token=&field=value",
wantErr: "token is missing from query parameters",
},
{
name: "does not read legacy path parameters",
target: "/data-object/attribute/" + token + "/value",
wantErr: "token is missing from query parameters",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
ctx.Request = httptest.NewRequest(http.MethodGet, tt.target, nil)
token, field, err := parseDataObjectAttributeQuery(ctx)
if tt.wantErr != "" {
require.EqualError(t, err, tt.wantErr)
assert.Empty(t, token)
assert.Empty(t, field)
return
}
require.NoError(t, err)
assert.Equal(t, tt.wantToken, token)
assert.Equal(t, tt.wantField, field)
})
}
}
func TestValidateDataObjectField(t *testing.T) {
tests := []struct {
name string
@ -182,34 +249,28 @@ func TestBuildMeasurementAttributeValueRejectsUnsupportedField(t *testing.T) {
func TestBuildMeasurementAttributeValueMode(t *testing.T) {
component := &orm.Component{}
tests := []struct {
name string
mode int16
expected bool
}{
{name: "automatic mode", mode: 1, expected: true},
{name: "manual mode", mode: 0, expected: false},
{name: "other positive mode", mode: 2, expected: false},
{name: "negative mode", mode: -1, expected: false},
}
manualMode, err := buildMeasurementAttributeValue(
context.Background(),
"mode",
&orm.Measurement{Mode: 0},
component,
nil,
)
require.NoError(t, err)
assert.Equal(t, false, manualMode)
automaticMode, err := buildMeasurementAttributeValue(
context.Background(),
"mode",
&orm.Measurement{Mode: 1},
component,
nil,
)
require.NoError(t, err)
assert.Equal(t, true, automaticMode)
_, err = buildMeasurementAttributeValue(
context.Background(),
"mode",
&orm.Measurement{Mode: 2},
component,
nil,
)
require.Error(t, err)
assert.Contains(t, err.Error(), "expected 0 or 1")
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual, err := buildMeasurementAttributeValue(
context.Background(),
"mode",
&orm.Measurement{Mode: tt.mode},
component,
nil,
)
require.NoError(t, err)
assert.Equal(t, tt.expected, actual)
})
}
}

View File

@ -11,6 +11,6 @@ import (
func registerDataObjectRoutes(rg *gin.RouterGroup, middlewares ...gin.HandlerFunc) {
g := rg.Group("/data-object/")
g.Use(middlewares...)
g.GET("attribute/:token/:field", handler.DataObjectAttributeQueryHandler)
g.POST("update", handler.DataObjectAttributeUpdateHandler)
g.GET("attribute", handler.DataObjectAttributeQueryHandler)
g.PATCH("attribute", handler.DataObjectAttributeUpdateHandler)
}