Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(metadata): implement GET Units of Measure API #4127

Merged
merged 2 commits into from
Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions cmd/core-metadata/res/uom.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
[Uom]
Source="reference to source for all UoM if not specified below"
[Uom.Units]
[Uom.Units.temperature]
Source="www.weather.com"
Values=["C","F","K"]
[Uom.Units.weights]
Source="www.usa.gov/federal-agencies/weights-and-measures-division"
Values=["lbs","ounces","kilos","grams"]
[Units]
[Units.temperature]
Source="www.weather.com"
Values=["C","F","K"]
[Units.weights]
Source="www.usa.gov/federal-agencies/weights-and-measures-division"
Values=["lbs","ounces","kilos","grams"]
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/edgexfoundry/edgex-go
require (
bitbucket.org/bertimus9/systemstat v0.0.0-20180207000608-0eeff89b0690
github.com/edgexfoundry/go-mod-bootstrap/v2 v2.3.0-dev.12
github.com/edgexfoundry/go-mod-core-contracts/v2 v2.3.0-dev.14
github.com/edgexfoundry/go-mod-core-contracts/v2 v2.3.0-dev.15
github.com/edgexfoundry/go-mod-messaging/v2 v2.3.0-dev.13
github.com/edgexfoundry/go-mod-registry/v2 v2.2.0
github.com/edgexfoundry/go-mod-secrets/v2 v2.3.0-dev.5
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ github.com/edgexfoundry/go-mod-bootstrap/v2 v2.3.0-dev.12 h1:bVTjv56lGmMpr6eECOA
github.com/edgexfoundry/go-mod-bootstrap/v2 v2.3.0-dev.12/go.mod h1:h+vCqw+fw9GN+c/ZMDKW6Ik08uL/4mMJjqVIGepmoSU=
github.com/edgexfoundry/go-mod-configuration/v2 v2.2.0 h1:AZeaAPJM5X93ITFgwbwluYDtYEJ7tkCMSlj35GwfLLU=
github.com/edgexfoundry/go-mod-configuration/v2 v2.2.0/go.mod h1:YP17JhMnXTitowXE13QJwFaKo0oc03iyoKLjWAYl4FE=
github.com/edgexfoundry/go-mod-core-contracts/v2 v2.3.0-dev.14 h1:dA5YAbVHBzzD3Qi7qc9Hyqnp174zyIEhv6PtDiSJHsA=
github.com/edgexfoundry/go-mod-core-contracts/v2 v2.3.0-dev.14/go.mod h1:YdJ0iBWad86sgOs6am01mE3IAX6d22H08f/enVho4TU=
github.com/edgexfoundry/go-mod-core-contracts/v2 v2.3.0-dev.15 h1:nDEOf1TXpnU0fx/aKWnJE8z49OQ2AzjnOdMaoZRLzh4=
github.com/edgexfoundry/go-mod-core-contracts/v2 v2.3.0-dev.15/go.mod h1:YdJ0iBWad86sgOs6am01mE3IAX6d22H08f/enVho4TU=
github.com/edgexfoundry/go-mod-messaging/v2 v2.3.0-dev.13 h1:Z7bpS0HK9fdKdAb4r264Ds1nnl1yfRQk7cJMTeh6tl4=
github.com/edgexfoundry/go-mod-messaging/v2 v2.3.0-dev.13/go.mod h1:yLJ9EK4Feg409FDr0oP87LbaRLyOSGJk/ikaIfEDKcI=
github.com/edgexfoundry/go-mod-registry/v2 v2.2.0 h1:dk9ul1t7INAiyZXeu/GrpinFE3qOekdy8uZOqEGgIiE=
Expand Down
46 changes: 46 additions & 0 deletions internal/core/metadata/controller/http/uom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// Copyright (C) 2022 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

package http

import (
"net/http"

bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/container"
"github.com/edgexfoundry/go-mod-bootstrap/v2/di"
"github.com/edgexfoundry/go-mod-core-contracts/v2/common"
"github.com/edgexfoundry/go-mod-core-contracts/v2/dtos/responses"

"github.com/edgexfoundry/edgex-go/internal/core/metadata/container"
"github.com/edgexfoundry/edgex-go/internal/pkg"
"github.com/edgexfoundry/edgex-go/internal/pkg/utils"
)

type UnitOfMeasureController struct {
dic *di.Container
}

func NewUnitOfMeasureController(dic *di.Container) *UnitOfMeasureController {
return &UnitOfMeasureController{
dic: dic,
}
}

func (uc *UnitOfMeasureController) UnitsOfMeasure(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
u := container.UnitsOfMeasureFrom(uc.dic.Get)
lc := bootstrapContainer.LoggingClientFrom(uc.dic.Get)

response := responses.NewUnitsOfMeasureResponse("", "", http.StatusOK, u)

utils.WriteHttpHeader(w, ctx, http.StatusOK)

switch r.Header.Get(common.Accept) {
case common.ContentTypeTOML:
pkg.EncodeAndWriteTomlResponse(u, w, lc)
default:
pkg.EncodeAndWriteResponse(response, w, lc)
}
}
86 changes: 86 additions & 0 deletions internal/core/metadata/controller/http/uom_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// Copyright (C) 2022 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

package http

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/edgexfoundry/go-mod-bootstrap/v2/di"
"github.com/edgexfoundry/go-mod-core-contracts/v2/common"
"github.com/edgexfoundry/go-mod-core-contracts/v2/dtos/responses"
"github.com/pelletier/go-toml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/edgexfoundry/edgex-go/internal/core/metadata/container"
"github.com/edgexfoundry/edgex-go/internal/core/metadata/uom"
)

func TestUnitOfMeasureController_UnitsOfMeasure(t *testing.T) {
testUoM := uom.UnitsOfMeasureImpl{
Source: "test global source",
Units: map[string]uom.Unit{
"unit1": uom.Unit{
Source: "test unit source",
Values: []string{"v1", "v2", "v3"},
},
},
}
dic := mockDic()
dic.Update(di.ServiceConstructorMap{
container.UnitsOfMeasureInterfaceName: func(get di.Get) interface{} {
return &testUoM
},
})

controller := NewUnitOfMeasureController(dic)
assert.NotNil(t, controller)

tests := []struct {
name string
accept string
expectedResponse any
}{
{"valid - json response", common.ContentTypeJSON, responses.NewUnitsOfMeasureResponse("", "", http.StatusOK, testUoM)},
{"valid - toml response", common.ContentTypeTOML, testUoM},
}

for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, common.ApiUnitsOfMeasureRoute, http.NoBody)
req.Header.Set(common.Accept, testCase.accept)
require.NoError(t, err)

recorder := httptest.NewRecorder()
handler := http.HandlerFunc(controller.UnitsOfMeasure)
handler.ServeHTTP(recorder, req)

assert.Equal(t, http.StatusOK, recorder.Result().StatusCode, "HTTP status code not as expected")
if testCase.accept == common.ContentTypeJSON {
expectedBytes, err := json.Marshal(testCase.expectedResponse)
require.NoError(t, err)
assert.JSONEq(t, recorder.Body.String(), string(expectedBytes), "JSON response not as expected")

actualResponse := responses.UnitsOfMeasureResponse{}
err = json.Unmarshal(recorder.Body.Bytes(), &actualResponse)
require.NoError(t, err)

assert.Equal(t, common.ApiVersion, actualResponse.ApiVersion, "Api Version not as expected")
lenny-goodell marked this conversation as resolved.
Show resolved Hide resolved
assert.Equal(t, http.StatusOK, actualResponse.StatusCode, "BaseResponse status code not as expected")
assert.Empty(t, actualResponse.Message, "Message should be empty when it is successful")
} else {
actualResponse := uom.UnitsOfMeasureImpl{}
err = toml.Unmarshal(recorder.Body.Bytes(), &actualResponse)
require.NoError(t, err)

lenny-goodell marked this conversation as resolved.
Show resolved Hide resolved
assert.Equal(t, actualResponse, testCase.expectedResponse, "TOML response not as expected")
}
})
}
}
4 changes: 4 additions & 0 deletions internal/core/metadata/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ func LoadRestRoutes(r *mux.Router, dic *di.Container, serviceName string) {
r.HandleFunc(common.ApiConfigRoute, cc.Config).Methods(http.MethodGet)
r.HandleFunc(common.ApiMetricsRoute, cc.Metrics).Methods(http.MethodGet)

// Units of Measure
uc := metadataController.NewUnitOfMeasureController(dic)
r.HandleFunc(common.ApiUnitsOfMeasureRoute, uc.UnitsOfMeasure).Methods(http.MethodGet)

// Device Profile
dc := metadataController.NewDeviceProfileController(dic)
r.HandleFunc(common.ApiDeviceProfileRoute, dc.AddDeviceProfile).Methods(http.MethodPost)
Expand Down
16 changes: 6 additions & 10 deletions internal/core/metadata/uom/uom.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,21 @@
package uom

type UnitsOfMeasureImpl struct {
Uom ConfigurationStruct
}

type ConfigurationStruct struct {
Source string
Units map[string]Unit
Source string `json:"source,omitempty"`
Units map[string]Unit `json:"units,omitempty"`
}

type Unit struct {
Source string
Values []string
Source string `json:"source,omitempty"`
Values []string `json:"values,omitempty"`
}

func (u *UnitsOfMeasureImpl) Validate(unit string) bool {
if unit == "" || len(u.Uom.Units) == 0 {
if unit == "" || len(u.Units) == 0 {
return true
}

for _, units := range u.Uom.Units {
for _, units := range u.Units {
for _, v := range units.Values {
if unit == v {
return true
Expand Down
16 changes: 15 additions & 1 deletion internal/pkg/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

"github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger"
"github.com/edgexfoundry/go-mod-core-contracts/v2/common"
"github.com/pelletier/go-toml"
)

func EncodeAndWriteResponse(i interface{}, w http.ResponseWriter, LoggingClient logger.LoggingClient) {
Expand All @@ -29,7 +30,20 @@ func EncodeAndWriteResponse(i interface{}, w http.ResponseWriter, LoggingClient
// Problems encoding
if err != nil {
LoggingClient.Error("Error encoding the data: " + err.Error())
http.Error(w, err.Error(), http.StatusBadRequest)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

func EncodeAndWriteTomlResponse(i interface{}, w http.ResponseWriter, lc logger.LoggingClient) {
w.Header().Set(common.ContentType, common.ContentTypeTOML)

enc := toml.NewEncoder(w)
err := enc.Encode(i)
// Problems encoding
if err != nil {
lc.Error("Error encoding the data: " + err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
92 changes: 92 additions & 0 deletions openapi/v2/core-metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,36 @@ components:
serviceName:
description: "Outputs the name of the service the response is from"
type: string
Unit:
description: "express magnitudes of a physical quantity"
type: object
properties:
source:
type: string
description: "helps provide the location of documentation about the origins and details of the units specified"
values:
type: array
items:
type: string
description: "a list of arbitrary unit representation to be interpreted by the EdgeX data provider/consumer"
UnitsOfMeasure:
description: "Units of Measure definition"
type: object
properties:
source:
type: string
description: "top level location of documentation about the origins and details of the units specified"
units:
type: object
additionalProperties:
$ref: '#/components/schemas/Unit'
UnitsOfMeasureResponse:
allOf:
- $ref: '#/components/schemas/BaseResponse'
type: object
properties:
uom:
$ref: '#/components/schemas/UnitsOfMeasure'
parameters:
offsetParam:
in: query
Expand Down Expand Up @@ -1103,6 +1133,15 @@ components:
type: string
format: uuid
example: "14a42ea6-c394-41c3-8bcd-a29b9f5e6835"
acceptHeader:
in: header
name: Accept
description: "indicates which content types, expressed as MIME types, the client is able to understand"
schema:
type: string
enum:
- application/json
- application/toml
labelsParam:
in: query
name: labels
Expand Down Expand Up @@ -1481,6 +1520,26 @@ components:
profileName: "device-simple"
serviceName: "device-simple"
adminState: "UNLOCKED"
UnitsOfMeasureResponseExample:
value:
apiVersion: "v2"
statusCode: 200
uom:
source: "reference to source for all UoM if not specified below"
units:
"temperature":
source: "www.weather.com"
value:
- "C"
- "F"
- "K"
"weights":
source: "www.usa.gov/federal-agencies/weights-and-measures-division"
value:
- "lbs"
- "ounces"
- "kilos"
- "grams"
paths:
/device:
parameters:
Expand Down Expand Up @@ -3767,6 +3826,39 @@ paths:
examples:
500Example:
$ref: '#/components/examples/500Example'
/uom:
get:
summary: "Returns the Units of Measure definition"
parameters:
- $ref: '#/components/parameters/acceptHeader'
responses:
'200':
description: "OK"
headers:
X-Correlation-ID:
$ref: '#/components/headers/correlatedResponseHeader'
content:
application/json:
schema:
$ref: '#/components/schemas/UnitsOfMeasureResponse'
examples:
UnitsOfMeasureResponse:
$ref: '#/components/examples/UnitsOfMeasureResponseExample'
application/toml:
schema:
$ref: '#/components/schemas/UnitsOfMeasure'
'500':
description: "Internal Server Error"
headers:
X-Correlation-ID:
$ref: '#/components/headers/correlatedResponseHeader'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
500Example:
$ref: '#/components/examples/500Example'
/config:
get:
summary: "Returns the current configuration of the service."
Expand Down