Skip to content

Commit

Permalink
Merge pull request #10 from phoops/feature/structured-value-support
Browse files Browse the repository at this point in the history
Add support for structured value type
  • Loading branch information
m4ng0 authored Nov 23, 2021
2 parents 57a317d + 2bf6db8 commit f5170d4
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 14 deletions.
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ module github.com/phoops/ngsiv2

go 1.13

require github.com/paulmach/go.geojson v1.4.0
require (
github.com/mitchellh/mapstructure v1.4.2
github.com/paulmach/go.geojson v1.4.0
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/paulmach/go.geojson v1.4.0 h1:5x5moCkCtDo5x8af62P9IOAYGQcYHtxz2QJ3x1DoCgY=
github.com/paulmach/go.geojson v1.4.0/go.mod h1:YaKx1hKpWF+T2oj2lFJPsW/t1Q5e1jQI61eoQSTwpIs=
62 changes: 49 additions & 13 deletions model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"
"unicode"

"github.com/mitchellh/mapstructure"
geojson "github.com/paulmach/go.geojson"
)

Expand Down Expand Up @@ -41,20 +42,22 @@ type Metadata struct {

type AttributeType string

// Constants representing NGSIv2 special data types
const (
StringType AttributeType = "String"
TextType AttributeType = "Text"
NumberType AttributeType = "Number"
FloatType AttributeType = "Float"
IntegerType AttributeType = "Integer"
BooleanType AttributeType = "Boolean"
PercentageType AttributeType = "Percentage"
DateTimeType AttributeType = "DateTime"
GeoPointType AttributeType = "geo:point"
GeoLineType AttributeType = "geo:line"
GeoPolygonType AttributeType = "geo:polygon"
GeoBoxType AttributeType = "geo:box"
GeoJSONType AttributeType = "geo:json"
StringType AttributeType = "String"
TextType AttributeType = "Text"
NumberType AttributeType = "Number"
FloatType AttributeType = "Float"
IntegerType AttributeType = "Integer"
BooleanType AttributeType = "Boolean"
PercentageType AttributeType = "Percentage"
DateTimeType AttributeType = "DateTime"
GeoPointType AttributeType = "geo:point"
GeoLineType AttributeType = "geo:line"
GeoPolygonType AttributeType = "geo:polygon"
GeoBoxType AttributeType = "geo:box"
GeoJSONType AttributeType = "geo:json"
StructuredValueType AttributeType = "StructuredValue"
)

const (
Expand Down Expand Up @@ -683,6 +686,19 @@ func (e *Entity) SetAttributeAsGeoJSON(name string, value *geojson.Geometry) err
return nil
}

func (e *Entity) SetAttributeAsStructuredValue(name string, value interface{}) error {
if err := validateAttributeName(name); err != nil {
return err
}
e.Attributes[name] = &Attribute{
typeValue: typeValue{
Type: StructuredValueType,
Value: value,
},
}
return nil
}

func (a *Attribute) GetAsString() (string, error) {
if a.Type != StringType && a.Type != TextType {
return "", fmt.Errorf("Attribute is nor String or Text, but %s", a.Type)
Expand Down Expand Up @@ -765,6 +781,16 @@ func (a *Attribute) GetAsGeoJSON() (*geojson.Geometry, error) {
return g, nil
}

// DecodeStructuredValue decodes the attribute into output if attribute type is StructuredValue.
// output must be a pointer to a map or struct.
func (a *Attribute) DecodeStructuredValue(output interface{}) error {
if a.Type != StructuredValueType {
return fmt.Errorf("Attribute is not %s, but '%s'", StructuredValueType, a.Type)
}

return mapstructure.Decode(a.Value, output)
}

func (e *Entity) GetAttributeAsString(attributeName string) (string, error) {
if a, err := e.GetAttribute(attributeName); err != nil {
return "", err
Expand Down Expand Up @@ -845,6 +871,16 @@ func (e *Entity) GetAttributeAsGeoJSON(attributeName string) (*geojson.Geometry,
return a.GetAsGeoJSON()
}

// DecodeStructuredValueAttribute decodes the attribute named attributeName into output if
// attribute type is StructuredValue. output must be a pointer to a map or struct.
func (e *Entity) DecodeStructuredValueAttribute(attributeName string, output interface{}) error {
a, err := e.GetAttribute(attributeName)
if err != nil {
return err
}
return a.DecodeStructuredValue(output)
}

func NewBatchUpdate(action ActionType) *BatchUpdate {
b := &BatchUpdate{ActionType: action}
return b
Expand Down
94 changes: 94 additions & 0 deletions model/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,75 @@ func TestEntityUnmarshal(t *testing.T) {
} else {
t.Fatal("Expected error on getting a nasty boolean, got nil")
}

structuredVal := `
{
"id": "Structured1",
"manufacturers": {
"metadata": {},
"type": "StructuredValue",
"value": [
"Audi",
"Alfa Romeo",
"BMW"
]
},
"seller": {
"metadata": {},
"type": "StructuredValue",
"value": {
"name": "Jane",
"age": 25
}
}
}
`

structuredEntity := &model.Entity{}
if err := json.Unmarshal([]byte(structuredVal), structuredEntity); err != nil {
t.Fatalf("Error unmarshaling entity: %v", err)
}

manufacturersAttr, err := structuredEntity.GetAttribute("manufacturers")
if err != nil {
t.Fatalf("Unexpected error: '%v'", err)
}
if manufacturersAttr.Type != model.StructuredValueType {
t.Fatalf("Expected '%s' for manufacturers attribute type, got '%s'", model.StructuredValueType, manufacturersAttr.Type)
}

type intarray []int
type stringarray []string
manufacturersInt := make(intarray, 0)
manufacturersString := make(stringarray, 0)
if err := manufacturersAttr.DecodeStructuredValue(&manufacturersInt); err == nil {
t.Fatal("Expected a failure on non integers 'manufacturers'")
}
err = manufacturersAttr.DecodeStructuredValue(&manufacturersString)
if err != nil {
t.Fatalf("Unexpected error: '%v'", err)
}
if len(manufacturersString) != 3 {
t.Fatalf("Expected '%d' manufacturers, got '%d'", 3, len(manufacturersString))
}
if manufacturersString[2] != "BMW" {
t.Fatalf("Expected 'BMW' as third manufacturer, got '%s'", manufacturersString[2])
}

type Person struct {
Name string
Age int
}

person := new(Person)
err = structuredEntity.DecodeStructuredValueAttribute("seller", person)
if err != nil {
t.Fatalf("Unexpected error: '%v'", err)
}

if person.Name != "Jane" || person.Age != 25 {
t.Fatalf("Expected Jane, got '%v'", *person)
}
}

func TestEntityMarshal(t *testing.T) {
Expand Down Expand Up @@ -352,6 +421,16 @@ func TestEntityMarshal(t *testing.T) {
geoJsonPoint := geojson.NewPointGeometry([]float64{4.1, 2.3})
office.SetAttributeAsGeoJSON("position", geoJsonPoint)

type Dog struct {
Name string
Age int
}

ambra := &Dog{"Ambra", 4}
if err := office.SetAttributeAsStructuredValue("mascot", ambra); err != nil {
t.Fatalf("Unexpected error: '%v'", err)
}

bytes, err := json.Marshal(office)
if err != nil {
t.Fatalf("Unexpected error: '%v'", err)
Expand Down Expand Up @@ -481,6 +560,21 @@ func TestEntityMarshal(t *testing.T) {
}
}
}

mascotAttr, err := unmarshaled.GetAttribute("mascot")
if err != nil {
t.Fatalf("Unexpected error: '%v'", err)
}
if mascotAttr.Type != model.StructuredValueType {
t.Fatalf("Expected %s type, got '%s'", model.StructuredValueType, mascotAttr.Type)
}
mascot := new(Dog)
if err := mascotAttr.DecodeStructuredValue(mascot); err != nil {
t.Fatalf("Unexpected error: '%v'", err)
}
if mascot.Name != "Ambra" || mascot.Age != 4 {
t.Fatalf("Expected Ambra as mascot, got '%v'", mascot)
}
}

func TestDirectEntityAttributeAccess(t *testing.T) {
Expand Down

0 comments on commit f5170d4

Please sign in to comment.