From b359f0df9a44853277652d150b42111a355e3e3e Mon Sep 17 00:00:00 2001 From: rajdeep11 Date: Thu, 13 Mar 2025 16:20:53 +0530 Subject: [PATCH] adding ProblemDetail model for error message Issue-Id: AIMLFW-181 1) Added the Problem detail model. 2) changes to the register model info. Change-Id: I0c107003676e95a31f17a64501d8d489fd5ca4e1 Signed-off-by: rajdeep11 --- apis/mmes_apis.go | 69 +++++++++++++++----------------- apis_test/mmes_apis_test.go | 96 +++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 + models/ProblemDetail.go | 26 ++++++++++++ 4 files changed, 155 insertions(+), 38 deletions(-) create mode 100644 models/ProblemDetail.go diff --git a/apis/mmes_apis.go b/apis/mmes_apis.go index 183d5ad..2807bcc 100644 --- a/apis/mmes_apis.go +++ b/apis/mmes_apis.go @@ -22,6 +22,7 @@ import ( "io" "net/http" "os" + "errors" "gerrit.o-ran-sc.org/r/aiml-fw/awmf/modelmgmtservice/core" "gerrit.o-ran-sc.org/r/aiml-fw/awmf/modelmgmtservice/db" @@ -30,6 +31,8 @@ import ( "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" "github.com/google/uuid" + "github.com/jackc/pgerrcode" + "github.com/lib/pq" ) const ( @@ -51,12 +54,15 @@ func NewMmeApiHandler(dbMgr core.DBMgr, iDB db.IDB) *MmeApiHandler { } func (m *MmeApiHandler) RegisterModel(cont *gin.Context) { + logging.INFO("registering model info") var modelInfo models.ModelRelatedInformation if err := cont.ShouldBindJSON(&modelInfo); err != nil { - cont.JSON(http.StatusBadRequest, gin.H{ - "error": err.Error(), + cont.JSON(http.StatusBadRequest, models.ProblemDetail{ + Status: http.StatusBadRequest, + Title: "Bad Request", + Detail: fmt.Sprintf("The request json is not correct, %s", err.Error()), }) return } @@ -66,8 +72,10 @@ func (m *MmeApiHandler) RegisterModel(cont *gin.Context) { validate := validator.New() if err := validate.Struct(modelInfo); err != nil { - cont.JSON(http.StatusBadRequest, gin.H{ - "error": err.Error(), + cont.JSON(http.StatusBadRequest, models.ProblemDetail{ + Status: http.StatusBadRequest, + Title: "Bad Request", + Detail: fmt.Sprintf("The request json is not correct as it can't be validated, %s", err.Error()), }) return } @@ -76,10 +84,24 @@ func (m *MmeApiHandler) RegisterModel(cont *gin.Context) { if err := m.iDB.Create(modelInfo); err != nil { logging.ERROR("error", err) - cont.JSON(http.StatusBadRequest, gin.H{ - "Error": err.Error(), - }) - return + var pqErr *pq.Error + if errors.As(err, &pqErr) { + if pqErr.Code == pgerrcode.UniqueViolation { + cont.JSON(http.StatusConflict, models.ProblemDetail{ + Status: http.StatusConflict, + Title: "model name and version combination already present", + Detail: "The request json is not correct as", + }) + return + } else { + cont.JSON(http.StatusInternalServerError, models.ProblemDetail{ + Status: http.StatusInternalServerError, + Title: "Bad Request", + Detail: "The request json is not correct as", + }) + return + } + } } logging.INFO("model is saved.") @@ -88,36 +110,7 @@ func (m *MmeApiHandler) RegisterModel(cont *gin.Context) { "modelInfo": modelInfo, }) - // logging.INFO("Creating model...") - // bodyBytes, _ := io.ReadAll(cont.Request.Body) - - // var modelInfo models.ModelInfo - // //Need to unmarshal JSON to Struct, to access request - // //data such as model name, rapp id etc - // err := json.Unmarshal(bodyBytes, &modelInfo) - // if err != nil || modelInfo.ModelId.ModelName == "" { - // logging.ERROR("Error in unmarshalling") - // cont.JSON(http.StatusBadRequest, gin.H{ - // "code": http.StatusBadRequest, - // "message": string("Can not parse input data, provide mandatory details"), - // }) - // } else { - // id := uuid.New() - // modelInfo.Id = id.String() - // modelInfoBytes, _ := json.Marshal(modelInfo) - // err := m.dbmgr.CreateBucket(modelInfo.ModelId.ModelName) - // if err == nil { - // m.dbmgr.UploadFile(modelInfoBytes, modelInfo.ModelId.ModelName+os.Getenv("INFO_FILE_POSTFIX"), modelInfo.ModelId.ModelName) - // } else { - // cont.JSON(http.StatusInternalServerError, gin.H{ - // "code": http.StatusInternalServerError, - // "message": err.Error(), - // }) - // } - // cont.JSON(http.StatusCreated, gin.H{ - // "modelinfo": modelInfoBytes, - // }) - // } + } /* diff --git a/apis_test/mmes_apis_test.go b/apis_test/mmes_apis_test.go index 25a643a..2c6a268 100644 --- a/apis_test/mmes_apis_test.go +++ b/apis_test/mmes_apis_test.go @@ -35,6 +35,8 @@ import ( "gerrit.o-ran-sc.org/r/aiml-fw/awmf/modelmgmtservice/routers" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/jackc/pgerrcode" + "github.com/lib/pq" ) var registerModelBody = `{ @@ -53,6 +55,38 @@ var registerModelBody = `{ } }` +var invalidRegisterModelBody = `{ + "id" : "id", + "modelId": { + "modelName": "model3", + "modelVersion" : "2" + }, + "description": "hello world2", + "modelInformation": { + "metadata": { + "author": "someone" + }, + "inputDataType": "pdcpBytesDl,pdcpBytesUl,kpi", + "outputDataType": "c, d" + } +` + +var invalidRegisterModelBody2 = `{ + "id" : "id", + "modelId": { + "modelName": "model3", + "modelsion" : "2" + }, + "description": "hello world2", + "modelInformation": { + "metadata": { + "author": "someone" + }, + "inputDataType": "pdcpBytesDl,pdcpBytesUl,kpi", + "outputDataType": "c, d" + } +}` + type dbMgrMock struct { mock.Mock core.DBMgr @@ -111,6 +145,68 @@ func TestRegisterModel(t *testing.T) { assert.Equal(t, 201, w.Code) } +func TestRegisterModelFailInvalidJson(t *testing.T) { + os.Setenv("LOG_FILE_NAME", "testing") + iDBMockInst := new(iDBMock) + handler := apis.NewMmeApiHandler(nil, iDBMockInst) + router := routers.InitRouter(handler) + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/ai-ml-model-registration/v1/model-registrations", strings.NewReader(invalidRegisterModelBody)) + router.ServeHTTP(w, req) + + assert.Equal(t, 400, w.Code) + body, _ := io.ReadAll(w.Body) + + assert.Equal(t, `{"status":400,"title":"Bad Request","detail":"The request json is not correct, unexpected EOF"}`, string(body)) +} + +func TestRegisterModelFailInvalidRequest(t *testing.T) { + os.Setenv("LOG_FILE_NAME", "testing") + iDBMockInst := new(iDBMock) + handler := apis.NewMmeApiHandler(nil, iDBMockInst) + router := routers.InitRouter(handler) + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/ai-ml-model-registration/v1/model-registrations", strings.NewReader(invalidRegisterModelBody2)) + router.ServeHTTP(w, req) + + assert.Equal(t, 400, w.Code) + body, _ := io.ReadAll(w.Body) + + assert.Equal(t, `{"status":400,"title":"Bad Request","detail":"The request json is not correct as it can't be validated, Key: 'ModelRelatedInformation.ModelId.ModelVersion' Error:Field validation for 'ModelVersion' failed on the 'required' tag"}`, string(body)) +} + +func TestRegisterModelFailCreateDuplicateModel(t *testing.T) { + os.Setenv("LOG_FILE_NAME", "testing") + iDBMockInst := new(iDBMock) + iDBMockInst.On("Create", mock.Anything).Return(&pq.Error{Code: pgerrcode.UniqueViolation}) + handler := apis.NewMmeApiHandler(nil, iDBMockInst) + router := routers.InitRouter(handler) + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/ai-ml-model-registration/v1/model-registrations", strings.NewReader(registerModelBody)) + router.ServeHTTP(w, req) + + assert.Equal(t, 409, w.Code) + body, _ := io.ReadAll(w.Body) + + assert.Equal(t, "{\"status\":409,\"title\":\"model name and version combination already present\",\"detail\":\"The request json is not correct as\"}", string(body)) +} + +func TestRegisterModelFailCreate(t *testing.T) { + os.Setenv("LOG_FILE_NAME", "testing") + iDBMockInst := new(iDBMock) + iDBMockInst.On("Create", mock.Anything).Return(&pq.Error{Code: pgerrcode.SQLClientUnableToEstablishSQLConnection}) + handler := apis.NewMmeApiHandler(nil, iDBMockInst) + router := routers.InitRouter(handler) + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/ai-ml-model-registration/v1/model-registrations", strings.NewReader(registerModelBody)) + router.ServeHTTP(w, req) + + assert.Equal(t, 500, w.Code) + body, _ := io.ReadAll(w.Body) + + assert.Equal(t, "{\"status\":500,\"title\":\"Bad Request\",\"detail\":\"The request json is not correct as\"}", string(body)) +} + func TestWhenSuccessGetModelInfoList(t *testing.T) { // Setting ENV os.Setenv("LOG_FILE_NAME", "testing") diff --git a/go.mod b/go.mod index 17fd0a6..787197a 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,8 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.2.1 gorm.io/driver/postgres v1.5.9 gorm.io/gorm v1.25.12 + github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 + github.com/lib/pq v1.10.7 ) require ( diff --git a/models/ProblemDetail.go b/models/ProblemDetail.go new file mode 100644 index 0000000..2655531 --- /dev/null +++ b/models/ProblemDetail.go @@ -0,0 +1,26 @@ +/* +================================================================================== +Copyright (c) 2025 Samsung Electronics Co., Ltd. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +================================================================================== +*/ + +package models + +type ProblemDetail struct{ + Status int `json:"status"` + Title string `json:"title"` + Detail string `json:"detail"` +} + -- 2.16.6