diff --git a/cmd/laas/docs/docs.go b/cmd/laas/docs/docs.go index 5c4f918..57b61dd 100644 --- a/cmd/laas/docs/docs.go +++ b/cmd/laas/docs/docs.go @@ -848,7 +848,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.ObligationInput" + "$ref": "#/definitions/models.ObligationPOSTRequestJSONSchema" } } ], @@ -989,7 +989,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.UpdateObligation" + "$ref": "#/definitions/models.ObligationPATCHRequestJSONSchema" } } ], @@ -1682,29 +1682,27 @@ const docTemplate = `{ "white", "yellow", "red" - ] + ], + "example": "green" }, "comment": { - "type": "string", - "example": "This is a comment." + "type": "string" }, "id": { "type": "integer", "example": 147 }, - "md5": { - "type": "string", - "example": "deadbeef" - }, "modifications": { - "type": "boolean" + "type": "boolean", + "example": true }, "text": { "type": "string", "example": "Source code be made available when distributing the software." }, "text_updatable": { - "type": "boolean" + "type": "boolean", + "example": true }, "topic": { "type": "string", @@ -1717,17 +1715,50 @@ const docTemplate = `{ "restriction", "risk", "right" + ], + "example": "risk" + } + } + }, + "models.ObligationMapResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.ObligationMapUser" + } + }, + "paginationmeta": { + "$ref": "#/definitions/models.PaginationMeta" + }, + "status": { + "type": "integer", + "example": 200 + } + } + }, + "models.ObligationMapUser": { + "type": "object", + "properties": { + "shortnames": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "GPL-2.0-only", + "GPL-2.0-or-later" ] + }, + "topic": { + "type": "string", + "example": "copyleft" } } }, - "models.ObligationInput": { + "models.ObligationPATCHRequestJSONSchema": { "type": "object", - "required": [ - "text", - "topic", - "type" - ], "properties": { "active": { "type": "boolean", @@ -1749,16 +1780,6 @@ const docTemplate = `{ "modifications": { "type": "boolean" }, - "shortnames": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "GPL-2.0-only", - "GPL-2.0-or-later" - ] - }, "text": { "type": "string", "example": "Source code be made available when distributing the software." @@ -1766,10 +1787,6 @@ const docTemplate = `{ "text_updatable": { "type": "boolean" }, - "topic": { - "type": "string", - "example": "copyleft" - }, "type": { "type": "string", "enum": [ @@ -1781,27 +1798,38 @@ const docTemplate = `{ } } }, - "models.ObligationMapResponse": { + "models.ObligationPOSTRequestJSONSchema": { "type": "object", + "required": [ + "active", + "classification", + "comment", + "modifications", + "shortnames", + "text", + "topic", + "type" + ], "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/models.ObligationMapUser" - } + "active": { + "type": "boolean", + "example": true }, - "paginationmeta": { - "$ref": "#/definitions/models.PaginationMeta" + "classification": { + "type": "string", + "enum": [ + "green", + "white", + "yellow", + "red" + ] + }, + "comment": { + "type": "string" + }, + "modifications": { + "type": "boolean" }, - "status": { - "type": "integer", - "example": 200 - } - } - }, - "models.ObligationMapUser": { - "type": "object", - "properties": { "shortnames": { "type": "array", "items": { @@ -1812,9 +1840,22 @@ const docTemplate = `{ "GPL-2.0-or-later" ] }, + "text": { + "type": "string", + "example": "Source code be made available when distributing the software." + }, "topic": { "type": "string", "example": "copyleft" + }, + "type": { + "type": "string", + "enum": [ + "obligation", + "restriction", + "risk", + "right" + ] } } }, @@ -1889,51 +1930,6 @@ const docTemplate = `{ } } }, - "models.UpdateObligation": { - "type": "object", - "properties": { - "active": { - "type": "boolean", - "example": true - }, - "classification": { - "type": "string", - "enum": [ - "green", - "white", - "yellow", - "red" - ] - }, - "comment": { - "type": "string", - "example": "This is a comment." - }, - "modifications": { - "type": "boolean" - }, - "text": { - "type": "string", - "example": "Source code be made available when distributing the software." - }, - "text_updatable": { - "type": "boolean" - }, - "topic": { - "type": "string", - "example": "copyleft" - }, - "type": { - "type": "string", - "enum": [ - "obligation", - "restriction", - "risk", - "right" - ] - } - } - }, "models.User": { "type": "object", "required": [ diff --git a/cmd/laas/docs/swagger.json b/cmd/laas/docs/swagger.json index 5c56dd2..e884247 100644 --- a/cmd/laas/docs/swagger.json +++ b/cmd/laas/docs/swagger.json @@ -841,7 +841,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.ObligationInput" + "$ref": "#/definitions/models.ObligationPOSTRequestJSONSchema" } } ], @@ -982,7 +982,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.UpdateObligation" + "$ref": "#/definitions/models.ObligationPATCHRequestJSONSchema" } } ], @@ -1675,29 +1675,27 @@ "white", "yellow", "red" - ] + ], + "example": "green" }, "comment": { - "type": "string", - "example": "This is a comment." + "type": "string" }, "id": { "type": "integer", "example": 147 }, - "md5": { - "type": "string", - "example": "deadbeef" - }, "modifications": { - "type": "boolean" + "type": "boolean", + "example": true }, "text": { "type": "string", "example": "Source code be made available when distributing the software." }, "text_updatable": { - "type": "boolean" + "type": "boolean", + "example": true }, "topic": { "type": "string", @@ -1710,17 +1708,50 @@ "restriction", "risk", "right" + ], + "example": "risk" + } + } + }, + "models.ObligationMapResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.ObligationMapUser" + } + }, + "paginationmeta": { + "$ref": "#/definitions/models.PaginationMeta" + }, + "status": { + "type": "integer", + "example": 200 + } + } + }, + "models.ObligationMapUser": { + "type": "object", + "properties": { + "shortnames": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "GPL-2.0-only", + "GPL-2.0-or-later" ] + }, + "topic": { + "type": "string", + "example": "copyleft" } } }, - "models.ObligationInput": { + "models.ObligationPATCHRequestJSONSchema": { "type": "object", - "required": [ - "text", - "topic", - "type" - ], "properties": { "active": { "type": "boolean", @@ -1742,16 +1773,6 @@ "modifications": { "type": "boolean" }, - "shortnames": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "GPL-2.0-only", - "GPL-2.0-or-later" - ] - }, "text": { "type": "string", "example": "Source code be made available when distributing the software." @@ -1759,10 +1780,6 @@ "text_updatable": { "type": "boolean" }, - "topic": { - "type": "string", - "example": "copyleft" - }, "type": { "type": "string", "enum": [ @@ -1774,27 +1791,38 @@ } } }, - "models.ObligationMapResponse": { + "models.ObligationPOSTRequestJSONSchema": { "type": "object", + "required": [ + "active", + "classification", + "comment", + "modifications", + "shortnames", + "text", + "topic", + "type" + ], "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/models.ObligationMapUser" - } + "active": { + "type": "boolean", + "example": true }, - "paginationmeta": { - "$ref": "#/definitions/models.PaginationMeta" + "classification": { + "type": "string", + "enum": [ + "green", + "white", + "yellow", + "red" + ] + }, + "comment": { + "type": "string" + }, + "modifications": { + "type": "boolean" }, - "status": { - "type": "integer", - "example": 200 - } - } - }, - "models.ObligationMapUser": { - "type": "object", - "properties": { "shortnames": { "type": "array", "items": { @@ -1805,9 +1833,22 @@ "GPL-2.0-or-later" ] }, + "text": { + "type": "string", + "example": "Source code be made available when distributing the software." + }, "topic": { "type": "string", "example": "copyleft" + }, + "type": { + "type": "string", + "enum": [ + "obligation", + "restriction", + "risk", + "right" + ] } } }, @@ -1882,51 +1923,6 @@ } } }, - "models.UpdateObligation": { - "type": "object", - "properties": { - "active": { - "type": "boolean", - "example": true - }, - "classification": { - "type": "string", - "enum": [ - "green", - "white", - "yellow", - "red" - ] - }, - "comment": { - "type": "string", - "example": "This is a comment." - }, - "modifications": { - "type": "boolean" - }, - "text": { - "type": "string", - "example": "Source code be made available when distributing the software." - }, - "text_updatable": { - "type": "boolean" - }, - "topic": { - "type": "string", - "example": "copyleft" - }, - "type": { - "type": "string", - "enum": [ - "obligation", - "restriction", - "risk", - "right" - ] - } - } - }, "models.User": { "type": "object", "required": [ diff --git a/cmd/laas/docs/swagger.yaml b/cmd/laas/docs/swagger.yaml index 3686ac3..2561813 100644 --- a/cmd/laas/docs/swagger.yaml +++ b/cmd/laas/docs/swagger.yaml @@ -290,22 +290,21 @@ definitions: - white - yellow - red + example: green type: string comment: - example: This is a comment. type: string id: example: 147 type: integer - md5: - example: deadbeef - type: string modifications: + example: true type: boolean text: example: Source code be made available when distributing the software. type: string text_updatable: + example: true type: boolean topic: example: copyleft @@ -316,9 +315,35 @@ definitions: - restriction - risk - right + example: risk + type: string + type: object + models.ObligationMapResponse: + properties: + data: + items: + $ref: '#/definitions/models.ObligationMapUser' + type: array + paginationmeta: + $ref: '#/definitions/models.PaginationMeta' + status: + example: 200 + type: integer + type: object + models.ObligationMapUser: + properties: + shortnames: + example: + - GPL-2.0-only + - GPL-2.0-or-later + items: + type: string + type: array + topic: + example: copyleft type: string type: object - models.ObligationInput: + models.ObligationPATCHRequestJSONSchema: properties: active: example: true @@ -335,21 +360,11 @@ definitions: type: string modifications: type: boolean - shortnames: - example: - - GPL-2.0-only - - GPL-2.0-or-later - items: - type: string - type: array text: example: Source code be made available when distributing the software. type: string text_updatable: type: boolean - topic: - example: copyleft - type: string type: enum: - obligation @@ -357,25 +372,23 @@ definitions: - risk - right type: string - required: - - text - - topic - - type type: object - models.ObligationMapResponse: - properties: - data: - items: - $ref: '#/definitions/models.ObligationMapUser' - type: array - paginationmeta: - $ref: '#/definitions/models.PaginationMeta' - status: - example: 200 - type: integer - type: object - models.ObligationMapUser: + models.ObligationPOSTRequestJSONSchema: properties: + active: + example: true + type: boolean + classification: + enum: + - green + - white + - yellow + - red + type: string + comment: + type: string + modifications: + type: boolean shortnames: example: - GPL-2.0-only @@ -383,9 +396,28 @@ definitions: items: type: string type: array + text: + example: Source code be made available when distributing the software. + type: string topic: example: copyleft type: string + type: + enum: + - obligation + - restriction + - risk + - right + type: string + required: + - active + - classification + - comment + - modifications + - shortnames + - text + - topic + - type type: object models.ObligationResponse: properties: @@ -437,39 +469,6 @@ definitions: - field - search_term type: object - models.UpdateObligation: - properties: - active: - example: true - type: boolean - classification: - enum: - - green - - white - - yellow - - red - type: string - comment: - example: This is a comment. - type: string - modifications: - type: boolean - text: - example: Source code be made available when distributing the software. - type: string - text_updatable: - type: boolean - topic: - example: copyleft - type: string - type: - enum: - - obligation - - restriction - - risk - - right - type: string - type: object models.User: properties: id: @@ -1075,7 +1074,7 @@ paths: name: obligation required: true schema: - $ref: '#/definitions/models.ObligationInput' + $ref: '#/definitions/models.ObligationPOSTRequestJSONSchema' produces: - application/json responses: @@ -1167,7 +1166,7 @@ paths: name: obligation required: true schema: - $ref: '#/definitions/models.UpdateObligation' + $ref: '#/definitions/models.ObligationPATCHRequestJSONSchema' produces: - application/json responses: diff --git a/pkg/api/obligations.go b/pkg/api/obligations.go index 0645bc3..61ae38b 100644 --- a/pkg/api/obligations.go +++ b/pkg/api/obligations.go @@ -128,7 +128,7 @@ func GetObligation(c *gin.Context) { // @Tags Obligations // @Accept json // @Produce json -// @Param obligation body models.ObligationInput true "Obligation to create" +// @Param obligation body models.ObligationPOSTRequestJSONSchema true "Obligation to create" // @Success 201 {object} models.ObligationResponse // @Failure 400 {object} models.LicenseError "Bad request body" // @Failure 409 {object} models.LicenseError "Obligation with same body exists" @@ -136,7 +136,7 @@ func GetObligation(c *gin.Context) { // @Security ApiKeyAuth // @Router /obligations [post] func CreateObligation(c *gin.Context) { - var input models.ObligationInput + var input models.ObligationPOSTRequestJSONSchema if err := c.ShouldBindJSON(&input); err != nil { er := models.LicenseError{ @@ -152,9 +152,6 @@ func CreateObligation(c *gin.Context) { s := input.Text hash := md5.Sum([]byte(s)) md5hash := hex.EncodeToString(hash[:]) - input.Active = true - - input.TextUpdatable = false obligation := models.Obligation{ Md5: md5hash, @@ -164,8 +161,8 @@ func CreateObligation(c *gin.Context) { Classification: input.Classification, Comment: input.Comment, Modifications: input.Modifications, - TextUpdatable: input.TextUpdatable, Active: input.Active, + TextUpdatable: false, } result := db.DB. @@ -176,9 +173,9 @@ func CreateObligation(c *gin.Context) { if result.RowsAffected == 0 { er := models.LicenseError{ Status: http.StatusConflict, - Message: "can not create obligation with same MD5", - Error: fmt.Sprintf("Error: Obligation with topic '%s' or MD5 '%s' already exists", - obligation.Topic, obligation.Md5), + Message: "can not create obligation with same topic or text", + Error: fmt.Sprintf("Error: Obligation with topic '%s' or Text '%s'... already exists", + obligation.Topic, obligation.Text[0:10]), Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), } @@ -225,8 +222,8 @@ func CreateObligation(c *gin.Context) { // @Tags Obligations // @Accept json // @Produce json -// @Param topic path string true "Topic of the obligation to be updated" -// @Param obligation body models.UpdateObligation true "Obligation to be updated" +// @Param topic path string true "Topic of the obligation to be updated" +// @Param obligation body models.ObligationPATCHRequestJSONSchema true "Obligation to be updated" // @Success 200 {object} models.ObligationResponse // @Failure 400 {object} models.LicenseError "Invalid request" // @Failure 404 {object} models.LicenseError "No obligation with given topic found" @@ -235,14 +232,13 @@ func CreateObligation(c *gin.Context) { // @Router /obligations/{topic} [patch] func UpdateObligation(c *gin.Context) { _ = db.DB.Transaction(func(tx *gorm.DB) error { - var update models.UpdateObligation - var oldobligation models.Obligation - var obligation models.Obligation - + var updates models.ObligationPATCHRequestJSONSchema + var oldObligation models.Obligation + newObligationMap := make(map[string]interface{}) username := c.GetString("username") - query := tx.Model(&obligation) tp := c.Param("topic") - if err := query.Where(models.Obligation{Active: true, Topic: tp}).First(&obligation).Error; err != nil { + + if err := tx.Model(&oldObligation).Where(models.Obligation{Topic: tp}).First(&oldObligation).Error; err != nil { er := models.LicenseError{ Status: http.StatusNotFound, Message: fmt.Sprintf("obligation with topic '%s' not found", tp), @@ -253,9 +249,8 @@ func UpdateObligation(c *gin.Context) { c.JSON(http.StatusNotFound, er) return err } - oldobligation = obligation - if err := c.ShouldBindJSON(&update); err != nil { + if err := c.ShouldBindJSON(&updates); err != nil { er := models.LicenseError{ Status: http.StatusBadRequest, Message: "invalid json body", @@ -266,25 +261,88 @@ func UpdateObligation(c *gin.Context) { c.JSON(http.StatusBadRequest, er) return err } - if !oldobligation.TextUpdatable && update.Text != "" && update.Text != oldobligation.Text { - er := models.LicenseError{ - Status: http.StatusBadRequest, - Message: "Can not update obligation text", - Error: "invalid request", - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), + + if updates.Text.IsDefined { + if updates.Text.Value == "" { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "Text cannot be an empty string", + Error: "invalid request", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return errors.New("invalid request") } - c.JSON(http.StatusBadRequest, er) - return errors.New("invalid request") - } - if oldobligation.TextUpdatable && update.Text != "" && update.Text != oldobligation.Text { - updatedHash := md5.Sum([]byte(update.Text)) + updatedHash := md5.Sum([]byte(updates.Text.Value)) updatedMd5hash := hex.EncodeToString(updatedHash[:]) - update.Md5 = updatedMd5hash + if !oldObligation.TextUpdatable { + if updatedMd5hash != oldObligation.Md5 { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "Can not update obligation text", + Error: "invalid request", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return errors.New("invalid request") + } + } + newObligationMap["md5"] = updatedMd5hash + newObligationMap["text"] = updates.Text.Value + } + + if updates.Type.IsDefined { + if updates.Type.Value == "" { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "Type cannot be an empty string", + Error: "invalid request", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return errors.New("invalid request") + } + newObligationMap["type"] = updates.Type.Value + } + + if updates.Classification.IsDefined { + if updates.Classification.Value == "" { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "Classification cannot be an empty string", + Error: "invalid request", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return errors.New("invalid request") + } + newObligationMap["classification"] = updates.Classification.Value + } + + if updates.Modifications.IsDefined { + newObligationMap["modifications"] = updates.Modifications.Value + } + + if updates.Comment.IsDefined { + newObligationMap["comment"] = updates.Comment.Value + } + + if updates.Active.IsDefined { + newObligationMap["active"] = updates.Active.Value + } + + if updates.TextUpdatable.IsDefined { + newObligationMap["text_updatable"] = updates.TextUpdatable.Value } - if err := tx.Model(&obligation).Clauses(clause.Returning{}).Updates(update).Error; err != nil { + var newObligation models.Obligation + newObligation.Id = oldObligation.Id + if err := tx.Model(&newObligation).Clauses(clause.Returning{}).Updates(newObligationMap).Error; err != nil { er := models.LicenseError{ Status: http.StatusInternalServerError, Message: "Failed to update license", @@ -311,64 +369,64 @@ func UpdateObligation(c *gin.Context) { var changes []models.ChangeLog - if oldobligation.Topic != obligation.Topic { + if oldObligation.Topic != newObligation.Topic { changes = append(changes, models.ChangeLog{ Field: "Topic", - OldValue: &oldobligation.Topic, - UpdatedValue: &obligation.Topic, + OldValue: &oldObligation.Topic, + UpdatedValue: &newObligation.Topic, }) } - if oldobligation.Type != obligation.Type { + if oldObligation.Type != newObligation.Type { changes = append(changes, models.ChangeLog{ Field: "Type", - OldValue: &oldobligation.Type, - UpdatedValue: &obligation.Type, + OldValue: &oldObligation.Type, + UpdatedValue: &newObligation.Type, }) } - if oldobligation.Text != obligation.Text { + if oldObligation.Md5 != newObligation.Md5 { changes = append(changes, models.ChangeLog{ Field: "Text", - OldValue: &oldobligation.Text, - UpdatedValue: &obligation.Text, + OldValue: &oldObligation.Text, + UpdatedValue: &newObligation.Text, }) } - if oldobligation.Classification != obligation.Classification { - oldVal := strconv.FormatBool(oldobligation.Modifications) - newVal := strconv.FormatBool(obligation.Modifications) + if oldObligation.Classification != newObligation.Classification { + oldVal := strconv.FormatBool(oldObligation.Modifications) + newVal := strconv.FormatBool(newObligation.Modifications) changes = append(changes, models.ChangeLog{ Field: "Classification", OldValue: &oldVal, UpdatedValue: &newVal, }) } - if oldobligation.Modifications != obligation.Modifications { - oldVal := strconv.FormatBool(oldobligation.Modifications) - newVal := strconv.FormatBool(obligation.Modifications) + if oldObligation.Modifications != newObligation.Modifications { + oldVal := strconv.FormatBool(oldObligation.Modifications) + newVal := strconv.FormatBool(newObligation.Modifications) changes = append(changes, models.ChangeLog{ Field: "Modifications", OldValue: &oldVal, UpdatedValue: &newVal, }) } - if oldobligation.Comment != obligation.Comment { + if oldObligation.Comment != newObligation.Comment { changes = append(changes, models.ChangeLog{ Field: "Comment", - OldValue: &oldobligation.Comment, - UpdatedValue: &obligation.Comment, + OldValue: &oldObligation.Comment, + UpdatedValue: &newObligation.Comment, }) } - if oldobligation.Active != obligation.Active { - oldVal := strconv.FormatBool(oldobligation.Active) - newVal := strconv.FormatBool(obligation.Active) + if oldObligation.Active != newObligation.Active { + oldVal := strconv.FormatBool(oldObligation.Active) + newVal := strconv.FormatBool(newObligation.Active) changes = append(changes, models.ChangeLog{ Field: "Active", OldValue: &oldVal, UpdatedValue: &newVal, }) } - if oldobligation.TextUpdatable != obligation.TextUpdatable { - oldVal := strconv.FormatBool(oldobligation.TextUpdatable) - newVal := strconv.FormatBool(obligation.TextUpdatable) + if oldObligation.TextUpdatable != newObligation.TextUpdatable { + oldVal := strconv.FormatBool(oldObligation.TextUpdatable) + newVal := strconv.FormatBool(newObligation.TextUpdatable) changes = append(changes, models.ChangeLog{ Field: "TextUpdatable", OldValue: &oldVal, @@ -379,7 +437,7 @@ func UpdateObligation(c *gin.Context) { if len(changes) != 0 { audit := models.Audit{ UserId: user.Id, - TypeId: obligation.Id, + TypeId: newObligation.Id, Timestamp: time.Now(), Type: "Obligation", ChangeLogs: changes, @@ -399,7 +457,7 @@ func UpdateObligation(c *gin.Context) { } res := models.ObligationResponse{ - Data: []models.Obligation{obligation}, + Data: []models.Obligation{newObligation}, Status: http.StatusOK, Meta: &models.PaginationMeta{ ResourceCount: 1, diff --git a/pkg/models/optional_data_types.go b/pkg/models/optional_data_types.go new file mode 100644 index 0000000..b6bb98e --- /dev/null +++ b/pkg/models/optional_data_types.go @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2023 Siemens AG +// SPDX-FileContributor: Dearsh Oberoi +// +// SPDX-License-Identifier: GPL-2.0-only + +package models + +import ( + "encoding/json" + "errors" +) + +// When we unmarshal json, the undefined keys take zero values in structs. So, there +// is no way to differentiate between an undefined value and an actual zero value when +// it is passed. OptionalData is a generic for differentiating between undefined and +// zero valued keys in json. +type OptionalData[T any] struct { + // This is set to true if corresponding key is present in json object + IsDefined bool + rawJson json.RawMessage + Value T +} + +func (v *OptionalData[T]) UnmarshalJSON(data []byte) error { + v.rawJson = append((v.rawJson)[0:0], data...) + if len(v.rawJson) != 0 { + var x *T + if err := json.Unmarshal(data, &x); err != nil { + return err + } + if x == nil { + return errors.New("field value cannot be null") + } + v.Value = *x + v.IsDefined = true + } + return nil +} diff --git a/pkg/models/types.go b/pkg/models/types.go index 1c5cf50..41e253f 100644 --- a/pkg/models/types.go +++ b/pkg/models/types.go @@ -246,42 +246,39 @@ type AuditResponse struct { // Obligation represents an obligation record in the database. type Obligation struct { - Id int64 `json:"id" gorm:"primary_key" example:"147"` - Topic string `json:"topic" gorm:"unique" example:"copyleft"` - Type string `json:"type" enums:"obligation,restriction,risk,right"` + Id int64 `gorm:"primary_key" json:"id" example:"147"` + Topic string `gorm:"unique" json:"topic" example:"copyleft"` + Type string `json:"type" enums:"obligation,restriction,risk,right" example:"risk"` Text string `json:"text" example:"Source code be made available when distributing the software."` - Classification string `json:"classification" enums:"green,white,yellow,red"` - Modifications bool `json:"modifications"` - Comment string `json:"comment" example:"This is a comment."` - Active bool `json:"active" gorm:"column:active"` - TextUpdatable bool `json:"text_updatable"` - Md5 string `json:"md5" gorm:"unique" example:"deadbeef"` + Classification string `json:"classification" enums:"green,white,yellow,red" example:"green"` + Modifications bool `json:"modifications" example:"true"` + Comment string `json:"comment"` + Active bool `json:"active"` + TextUpdatable bool `json:"text_updatable" example:"true"` + Md5 string `gorm:"unique" json:"-"` } -// ObligationInput represents the input format for creating a new obligation. -type ObligationInput struct { +// ObligationPOSTRequestJSONSchema represents the data format of POST request for obligation +type ObligationPOSTRequestJSONSchema struct { Topic string `json:"topic" binding:"required" example:"copyleft"` - Type string `json:"type" binding:"required" enums:"obligation,restriction,risk,right"` + Type string `json:"type" enums:"obligation,restriction,risk,right" binding:"required"` Text string `json:"text" binding:"required" example:"Source code be made available when distributing the software."` - Classification string `json:"classification" enums:"green,white,yellow,red"` - Modifications bool `json:"modifications"` - Comment string `json:"comment" example:"This is a comment."` - Active bool `json:"active" example:"true"` - TextUpdatable bool `json:"text_updatable"` - Shortnames []string `json:"shortnames" example:"GPL-2.0-only,GPL-2.0-or-later"` + Classification string `json:"classification" enums:"green,white,yellow,red" binding:"required"` + Modifications bool `json:"modifications" binding:"required"` + Comment string `json:"comment" binding:"required"` + Shortnames []string `json:"shortnames" binding:"required" example:"GPL-2.0-only,GPL-2.0-or-later"` + Active bool `json:"active" binding:"required" example:"true"` } -// UpdateObligation represents the input format for updating an existing obligation. -type UpdateObligation struct { - Topic string `json:"topic" example:"copyleft"` - Type string `json:"type" enums:"obligation,restriction,risk,right"` - Text string `json:"text" example:"Source code be made available when distributing the software."` - Classification string `json:"classification" enums:"green,white,yellow,red"` - Modifications bool `json:"modifications"` - Comment string `json:"comment" example:"This is a comment."` - Active bool `json:"active" example:"true"` - TextUpdatable bool `json:"text_updatable"` - Md5 string `json:"-"` +// ObligationPATCHRequestJSONSchema represents the data format of PATCH request for obligation +type ObligationPATCHRequestJSONSchema struct { + Type OptionalData[string] `json:"type" swaggertype:"string" enums:"obligation,restriction,risk,right"` + Text OptionalData[string] `json:"text" swaggertype:"string" example:"Source code be made available when distributing the software."` + Classification OptionalData[string] `json:"classification" swaggertype:"string" enums:"green,white,yellow,red"` + Modifications OptionalData[bool] `json:"modifications" swaggertype:"boolean"` + Comment OptionalData[string] `json:"comment" swaggertype:"string" example:"This is a comment."` + Active OptionalData[bool] `json:"active" swaggertype:"boolean" example:"true"` + TextUpdatable OptionalData[bool] `json:"text_updatable" swaggertype:"boolean"` } // ObligationResponse represents the response format for obligation data.