Skip to content

Commit f5170d4

Browse files
authored
Merge pull request #10 from phoops/feature/structured-value-support
Add support for structured value type
2 parents 57a317d + 2bf6db8 commit f5170d4

File tree

4 files changed

+149
-14
lines changed

4 files changed

+149
-14
lines changed

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ module github.com/phoops/ngsiv2
22

33
go 1.13
44

5-
require github.com/paulmach/go.geojson v1.4.0
5+
require (
6+
github.com/mitchellh/mapstructure v1.4.2
7+
github.com/paulmach/go.geojson v1.4.0
8+
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
2+
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
13
github.com/paulmach/go.geojson v1.4.0 h1:5x5moCkCtDo5x8af62P9IOAYGQcYHtxz2QJ3x1DoCgY=
24
github.com/paulmach/go.geojson v1.4.0/go.mod h1:YaKx1hKpWF+T2oj2lFJPsW/t1Q5e1jQI61eoQSTwpIs=

model/model.go

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"time"
1111
"unicode"
1212

13+
"github.com/mitchellh/mapstructure"
1314
geojson "github.com/paulmach/go.geojson"
1415
)
1516

@@ -41,20 +42,22 @@ type Metadata struct {
4142

4243
type AttributeType string
4344

45+
// Constants representing NGSIv2 special data types
4446
const (
45-
StringType AttributeType = "String"
46-
TextType AttributeType = "Text"
47-
NumberType AttributeType = "Number"
48-
FloatType AttributeType = "Float"
49-
IntegerType AttributeType = "Integer"
50-
BooleanType AttributeType = "Boolean"
51-
PercentageType AttributeType = "Percentage"
52-
DateTimeType AttributeType = "DateTime"
53-
GeoPointType AttributeType = "geo:point"
54-
GeoLineType AttributeType = "geo:line"
55-
GeoPolygonType AttributeType = "geo:polygon"
56-
GeoBoxType AttributeType = "geo:box"
57-
GeoJSONType AttributeType = "geo:json"
47+
StringType AttributeType = "String"
48+
TextType AttributeType = "Text"
49+
NumberType AttributeType = "Number"
50+
FloatType AttributeType = "Float"
51+
IntegerType AttributeType = "Integer"
52+
BooleanType AttributeType = "Boolean"
53+
PercentageType AttributeType = "Percentage"
54+
DateTimeType AttributeType = "DateTime"
55+
GeoPointType AttributeType = "geo:point"
56+
GeoLineType AttributeType = "geo:line"
57+
GeoPolygonType AttributeType = "geo:polygon"
58+
GeoBoxType AttributeType = "geo:box"
59+
GeoJSONType AttributeType = "geo:json"
60+
StructuredValueType AttributeType = "StructuredValue"
5861
)
5962

6063
const (
@@ -683,6 +686,19 @@ func (e *Entity) SetAttributeAsGeoJSON(name string, value *geojson.Geometry) err
683686
return nil
684687
}
685688

689+
func (e *Entity) SetAttributeAsStructuredValue(name string, value interface{}) error {
690+
if err := validateAttributeName(name); err != nil {
691+
return err
692+
}
693+
e.Attributes[name] = &Attribute{
694+
typeValue: typeValue{
695+
Type: StructuredValueType,
696+
Value: value,
697+
},
698+
}
699+
return nil
700+
}
701+
686702
func (a *Attribute) GetAsString() (string, error) {
687703
if a.Type != StringType && a.Type != TextType {
688704
return "", fmt.Errorf("Attribute is nor String or Text, but %s", a.Type)
@@ -765,6 +781,16 @@ func (a *Attribute) GetAsGeoJSON() (*geojson.Geometry, error) {
765781
return g, nil
766782
}
767783

784+
// DecodeStructuredValue decodes the attribute into output if attribute type is StructuredValue.
785+
// output must be a pointer to a map or struct.
786+
func (a *Attribute) DecodeStructuredValue(output interface{}) error {
787+
if a.Type != StructuredValueType {
788+
return fmt.Errorf("Attribute is not %s, but '%s'", StructuredValueType, a.Type)
789+
}
790+
791+
return mapstructure.Decode(a.Value, output)
792+
}
793+
768794
func (e *Entity) GetAttributeAsString(attributeName string) (string, error) {
769795
if a, err := e.GetAttribute(attributeName); err != nil {
770796
return "", err
@@ -845,6 +871,16 @@ func (e *Entity) GetAttributeAsGeoJSON(attributeName string) (*geojson.Geometry,
845871
return a.GetAsGeoJSON()
846872
}
847873

874+
// DecodeStructuredValueAttribute decodes the attribute named attributeName into output if
875+
// attribute type is StructuredValue. output must be a pointer to a map or struct.
876+
func (e *Entity) DecodeStructuredValueAttribute(attributeName string, output interface{}) error {
877+
a, err := e.GetAttribute(attributeName)
878+
if err != nil {
879+
return err
880+
}
881+
return a.DecodeStructuredValue(output)
882+
}
883+
848884
func NewBatchUpdate(action ActionType) *BatchUpdate {
849885
b := &BatchUpdate{ActionType: action}
850886
return b

model/model_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,75 @@ func TestEntityUnmarshal(t *testing.T) {
321321
} else {
322322
t.Fatal("Expected error on getting a nasty boolean, got nil")
323323
}
324+
325+
structuredVal := `
326+
{
327+
"id": "Structured1",
328+
"manufacturers": {
329+
"metadata": {},
330+
"type": "StructuredValue",
331+
"value": [
332+
"Audi",
333+
"Alfa Romeo",
334+
"BMW"
335+
]
336+
},
337+
"seller": {
338+
"metadata": {},
339+
"type": "StructuredValue",
340+
"value": {
341+
"name": "Jane",
342+
"age": 25
343+
}
344+
}
345+
}
346+
`
347+
348+
structuredEntity := &model.Entity{}
349+
if err := json.Unmarshal([]byte(structuredVal), structuredEntity); err != nil {
350+
t.Fatalf("Error unmarshaling entity: %v", err)
351+
}
352+
353+
manufacturersAttr, err := structuredEntity.GetAttribute("manufacturers")
354+
if err != nil {
355+
t.Fatalf("Unexpected error: '%v'", err)
356+
}
357+
if manufacturersAttr.Type != model.StructuredValueType {
358+
t.Fatalf("Expected '%s' for manufacturers attribute type, got '%s'", model.StructuredValueType, manufacturersAttr.Type)
359+
}
360+
361+
type intarray []int
362+
type stringarray []string
363+
manufacturersInt := make(intarray, 0)
364+
manufacturersString := make(stringarray, 0)
365+
if err := manufacturersAttr.DecodeStructuredValue(&manufacturersInt); err == nil {
366+
t.Fatal("Expected a failure on non integers 'manufacturers'")
367+
}
368+
err = manufacturersAttr.DecodeStructuredValue(&manufacturersString)
369+
if err != nil {
370+
t.Fatalf("Unexpected error: '%v'", err)
371+
}
372+
if len(manufacturersString) != 3 {
373+
t.Fatalf("Expected '%d' manufacturers, got '%d'", 3, len(manufacturersString))
374+
}
375+
if manufacturersString[2] != "BMW" {
376+
t.Fatalf("Expected 'BMW' as third manufacturer, got '%s'", manufacturersString[2])
377+
}
378+
379+
type Person struct {
380+
Name string
381+
Age int
382+
}
383+
384+
person := new(Person)
385+
err = structuredEntity.DecodeStructuredValueAttribute("seller", person)
386+
if err != nil {
387+
t.Fatalf("Unexpected error: '%v'", err)
388+
}
389+
390+
if person.Name != "Jane" || person.Age != 25 {
391+
t.Fatalf("Expected Jane, got '%v'", *person)
392+
}
324393
}
325394

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

424+
type Dog struct {
425+
Name string
426+
Age int
427+
}
428+
429+
ambra := &Dog{"Ambra", 4}
430+
if err := office.SetAttributeAsStructuredValue("mascot", ambra); err != nil {
431+
t.Fatalf("Unexpected error: '%v'", err)
432+
}
433+
355434
bytes, err := json.Marshal(office)
356435
if err != nil {
357436
t.Fatalf("Unexpected error: '%v'", err)
@@ -481,6 +560,21 @@ func TestEntityMarshal(t *testing.T) {
481560
}
482561
}
483562
}
563+
564+
mascotAttr, err := unmarshaled.GetAttribute("mascot")
565+
if err != nil {
566+
t.Fatalf("Unexpected error: '%v'", err)
567+
}
568+
if mascotAttr.Type != model.StructuredValueType {
569+
t.Fatalf("Expected %s type, got '%s'", model.StructuredValueType, mascotAttr.Type)
570+
}
571+
mascot := new(Dog)
572+
if err := mascotAttr.DecodeStructuredValue(mascot); err != nil {
573+
t.Fatalf("Unexpected error: '%v'", err)
574+
}
575+
if mascot.Name != "Ambra" || mascot.Age != 4 {
576+
t.Fatalf("Expected Ambra as mascot, got '%v'", mascot)
577+
}
484578
}
485579

486580
func TestDirectEntityAttributeAccess(t *testing.T) {

0 commit comments

Comments
 (0)