├── .gitignore ├── .travis.yml ├── Dockerfile ├── Dockerfile-rpi ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── README.md ├── config.yaml ├── configuration ├── config.go ├── config_test.go ├── constants.go ├── environment.go ├── environment_test.go ├── reader.go └── reader_test.go ├── database └── postgis │ ├── datastream.go │ ├── datastream_test.go │ ├── dbinfo.go │ ├── dbinfo_test.go │ ├── featureofInterest_test.go │ ├── featureofinterest.go │ ├── filter.go │ ├── functions.go │ ├── historicallocation.go │ ├── location.go │ ├── observation.go │ ├── observation_test.go │ ├── observedproperty.go │ ├── postgis.go │ ├── postgis_test.go │ ├── querybuilder.go │ ├── querybuilder_test.go │ ├── queryexecutor.go │ ├── queryexecutor_test.go │ ├── sensor.go │ ├── sensor_test.go │ └── thing.go ├── errors ├── errors.go └── errors_test.go ├── http ├── gostserver.go ├── gostserver_test.go ├── router.go └── router_test.go ├── log ├── logger.go └── logger_test.go ├── main.go ├── main_test.go ├── mqtt ├── mqtt.go └── mqtt_test.go └── sensorthings ├── api ├── api.go ├── api_test.go ├── createobservations.go ├── datastream.go ├── featureofinterest.go ├── historicallocation.go ├── location.go ├── observation.go ├── observedproperty.go ├── sensor.go ├── thing.go └── thing_test.go ├── models └── models.go ├── mqtt ├── config.go ├── config_test.go └── handlers.go ├── odata ├── queryoptions.go ├── queryoptions_test.go ├── queryvalidator.go └── queryvalidator_test.go └── rest ├── config ├── config.go ├── config_test.go ├── endpoint_create_observations.go ├── endpoint_datastream.go ├── endpoint_datastream_test.go ├── endpoint_featureofinterest.go ├── endpoint_featureofinterest_test.go ├── endpoint_historicallocation.go ├── endpoint_historicallocation_test.go ├── endpoint_location.go ├── endpoint_location_test.go ├── endpoint_observation.go ├── endpoint_observation_test.go ├── endpoint_observedproperties.go ├── endpoint_observedproperties_test.go ├── endpoint_root.go ├── endpoint_root_test.go ├── endpoint_sensor.go ├── endpoint_sensor_test.go ├── endpoint_thing.go ├── endpoint_thing_test.go ├── endpoint_version.go └── endpoint_version_test.go ├── endpoint ├── endpoint.go └── endpoint_test.go ├── handlers ├── create_observations.go ├── datastream.go ├── datastream_test.go ├── featureofinterest.go ├── featureofinterest_test.go ├── historicallocation.go ├── historicallocation_test.go ├── location.go ├── location_test.go ├── method_delete.go ├── method_delete_test.go ├── method_get.go ├── method_get_test.go ├── method_patch.go ├── method_patch_test.go ├── method_post.go ├── method_post_test.go ├── method_put.go ├── method_put_test.go ├── observation.go ├── observation_test.go ├── observedproperties.go ├── observedproperties_test.go ├── root.go ├── root_test.go ├── sensor.go ├── sensor_test.go ├── thing.go ├── thing_test.go ├── version.go └── version_test.go ├── reader ├── reader.go └── reader_test.go └── writer ├── writer.go └── writer_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | *.exe 6 | *.bin 7 | *.db 8 | *.db.lock 9 | 10 | # Folders 11 | _obj/ 12 | _test/ 13 | pkg/ 14 | debug/ 15 | bin/ 16 | /out/ 17 | debug 18 | vendor 19 | 20 | # IDE / Editor exceptions 21 | .idea/ 22 | .vscode/ 23 | *.iml 24 | *.iws 25 | atlassian-ide-plugin.xml 26 | 27 | # Architecture specific extensions/prefixes 28 | *.[568vq] 29 | [568vq].out 30 | 31 | *.cgo1.go 32 | *.cgo2.c 33 | _cgo_defun.c 34 | _cgo_gotypes.go 35 | _cgo_export.* 36 | 37 | # Results 38 | *.test 39 | *.prof 40 | 41 | # Logs 42 | *.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.13.x 5 | 6 | before_install: 7 | - go get github.com/tebben/overalls 8 | - go get github.com/mattn/goveralls 9 | - go get golang.org/x/tools/cmd/cover 10 | 11 | script: 12 | - go test ./... 13 | - overalls -project=github.com/gost/server -covermode=count -ignore=".git" -debug true 14 | - goveralls -coverprofile=/home/travis/gopath/src/github.com/gost/server/overalls.coverprofile -service=travis-ci -repotoken $COVERALLS_TOKEN 15 | 16 | notifications: 17 | email: 18 | recipients: 19 | - tim.ebben@geodan.nl 20 | on_success: change 21 | on_failure: always 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.9.0-alpine3.6 AS gost-build 2 | WORKDIR /go/src/github.com/gost/server 3 | ADD . . 4 | RUN apk add --update --no-cache git \ 5 | && go get -u github.com/golang/dep/cmd/dep \ 6 | && dep ensure \ 7 | && go build -o /gostserver/gost github.com/gost/server \ 8 | && cp config.yaml /gostserver/config.yaml 9 | 10 | 11 | # final stage 12 | FROM alpine 13 | WORKDIR /app 14 | COPY --from=gost-build /gostserver /app/ 15 | ENTRYPOINT ./gost 16 | EXPOSE 8080 17 | -------------------------------------------------------------------------------- /Dockerfile-rpi: -------------------------------------------------------------------------------- 1 | FROM resin/raspberry-pi-golang AS gost-build 2 | WORKDIR /go/src/github.com/gost/server 3 | ADD . . 4 | RUN go get -u github.com/golang/dep/cmd/dep \ 5 | && dep ensure \ 6 | && go build -o /gostserver/gost github.com/gost/server \ 7 | && cp config.yaml /gostserver/config.yaml 8 | 9 | 10 | # final stage 11 | FROM hypriot/rpi-alpine 12 | WORKDIR /app 13 | COPY --from=gost-build /gostserver /app/ 14 | ENTRYPOINT /app/gost 15 | EXPOSE 8080 -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b" 6 | name = "github.com/davecgh/go-spew" 7 | packages = ["spew"] 8 | pruneopts = "" 9 | revision = "346938d642f2ec3594ed81d874461961cd0faa76" 10 | version = "v1.1.0" 11 | 12 | [[projects]] 13 | branch = "master" 14 | digest = "1:0324f38cd62cb1eedf329e9375e8f16fd82b0c292b13236d22ee35519d12b2db" 15 | name = "github.com/eclipse/paho.mqtt.golang" 16 | packages = [ 17 | ".", 18 | "packets", 19 | ] 20 | pruneopts = "" 21 | revision = "93c8a06e1e6dfeb17dd18a44e3e2f4e1db778237" 22 | 23 | [[projects]] 24 | digest = "1:20ed7daa9b3b38b6d1d39b48ab3fd31122be5419461470d0c28de3e121c93ecf" 25 | name = "github.com/gorilla/context" 26 | packages = ["."] 27 | pruneopts = "" 28 | revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a" 29 | version = "v1.1" 30 | 31 | [[projects]] 32 | digest = "1:74f252b12d195c61ef5e54a4e2ab677af765d9e4b68b42c8c64fd16a4502a0c8" 33 | name = "github.com/gorilla/mux" 34 | packages = ["."] 35 | pruneopts = "" 36 | revision = "24fca303ac6da784b9e8269f724ddeb0b2eea5e7" 37 | version = "v1.5.0" 38 | 39 | [[projects]] 40 | digest = "1:09aa5dd1332b93c96bde671bafb053249dc813febf7d5ca84e8f382ba255d67d" 41 | name = "github.com/gorilla/websocket" 42 | packages = ["."] 43 | pruneopts = "" 44 | revision = "66b9c49e59c6c48f0ffce28c2d8b8a5678502c6d" 45 | version = "v1.4.0" 46 | 47 | [[projects]] 48 | branch = "master" 49 | digest = "1:3f5897231d3fefd30f53c24ca7b2141c6381d7f320ccbcc5c5dfa018956359a6" 50 | name = "github.com/gost/core" 51 | packages = ["."] 52 | pruneopts = "" 53 | revision = "c7385ff4b5cd35dd62317d539a7cc56dc16d1412" 54 | 55 | [[projects]] 56 | branch = "master" 57 | digest = "1:987eacc4310bdd5f132551cdb8ca7420cbd552cada7b641cd267e358f2cb8f3a" 58 | name = "github.com/gost/godata" 59 | packages = ["."] 60 | pruneopts = "" 61 | revision = "5d9cf5efac7daf6cac36af9e7339546e5ab9cfec" 62 | 63 | [[projects]] 64 | digest = "1:d85416b6b2deb088f59b4db9706065f947b7f7e8a0b7c5f585d6abda61991b4a" 65 | name = "github.com/gost/now" 66 | packages = ["."] 67 | pruneopts = "" 68 | revision = "3be40983ef1c9b51654829678cbe3882b4eab6ac" 69 | version = "1.0" 70 | 71 | [[projects]] 72 | branch = "master" 73 | digest = "1:1559e09937fb91848d3d201213ef6e0aac0a521934d6f88166f0e381f085ef5f" 74 | name = "github.com/lib/pq" 75 | packages = [ 76 | ".", 77 | "oid", 78 | ] 79 | pruneopts = "" 80 | revision = "e42267488fe361b9dc034be7a6bffef5b195bceb" 81 | 82 | [[projects]] 83 | digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411" 84 | name = "github.com/pmezard/go-difflib" 85 | packages = ["difflib"] 86 | pruneopts = "" 87 | revision = "792786c7400a136282c1664665ae0a8db921c6c2" 88 | version = "v1.0.0" 89 | 90 | [[projects]] 91 | digest = "1:3ac248add5bb40a3c631c5334adcd09aa72d15af2768a5bc0274084ea7b2e5ba" 92 | name = "github.com/sirupsen/logrus" 93 | packages = ["."] 94 | pruneopts = "" 95 | revision = "f006c2ac4710855cf0f916dd6b77acf6b048dc6e" 96 | version = "v1.0.3" 97 | 98 | [[projects]] 99 | digest = "1:3926a4ec9a4ff1a072458451aa2d9b98acd059a45b38f7335d31e06c3d6a0159" 100 | name = "github.com/stretchr/testify" 101 | packages = ["assert"] 102 | pruneopts = "" 103 | revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0" 104 | version = "v1.1.4" 105 | 106 | [[projects]] 107 | branch = "master" 108 | digest = "1:71051c4cbf7a4503b4f6d0e815da76f2731a08a68ac813e84076fb84db8bf5ca" 109 | name = "golang.org/x/crypto" 110 | packages = ["ssh/terminal"] 111 | pruneopts = "" 112 | revision = "faadfbdc035307d901e69eea569f5dda451a3ee3" 113 | 114 | [[projects]] 115 | branch = "master" 116 | digest = "1:8240f80d73999e42db1451c60defa181e12a49f0a903dfb7512c08f1ee7eb742" 117 | name = "golang.org/x/net" 118 | packages = ["proxy"] 119 | pruneopts = "" 120 | revision = "b129b8e0fbeb39c8358e51a07ab6c50ad415e72e" 121 | 122 | [[projects]] 123 | branch = "master" 124 | digest = "1:cc11c1cd2fbbd5a2e883bb581b74c0a61857239fa9cd5cf96e274eb447689354" 125 | name = "golang.org/x/sys" 126 | packages = [ 127 | "unix", 128 | "windows", 129 | ] 130 | pruneopts = "" 131 | revision = "062cd7e4e68206d8bab9b18396626e855c992658" 132 | 133 | [[projects]] 134 | branch = "v2" 135 | digest = "1:81314a486195626940617e43740b4fa073f265b0715c9f54ce2027fee1cb5f61" 136 | name = "gopkg.in/yaml.v2" 137 | packages = ["."] 138 | pruneopts = "" 139 | revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f" 140 | 141 | [solve-meta] 142 | analyzer-name = "dep" 143 | analyzer-version = 1 144 | input-imports = [ 145 | "github.com/eclipse/paho.mqtt.golang", 146 | "github.com/gorilla/mux", 147 | "github.com/gost/core", 148 | "github.com/gost/godata", 149 | "github.com/gost/now", 150 | "github.com/lib/pq", 151 | "github.com/sirupsen/logrus", 152 | "github.com/stretchr/testify/assert", 153 | "gopkg.in/yaml.v2", 154 | ] 155 | solver-name = "gps-cdcl" 156 | solver-version = 1 157 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | [[constraint]] 2 | name = "github.com/sirupsen/logrus" 3 | version = "1.0.3" 4 | 5 | [[constraint]] 6 | name = "github.com/eclipse/paho.mqtt.golang" 7 | branch = "master" 8 | 9 | [[constraint]] 10 | name = "github.com/gorilla/mux" 11 | version = "1.4.0" 12 | 13 | [[constraint]] 14 | branch = "master" 15 | name = "github.com/gost/godata" 16 | 17 | [[constraint]] 18 | branch = "master" 19 | name = "github.com/gost/core" 20 | 21 | [[constraint]] 22 | name = "github.com/gost/now" 23 | version = "1.0.0" 24 | 25 | [[constraint]] 26 | branch = "master" 27 | name = "github.com/lib/pq" 28 | 29 | [[constraint]] 30 | name = "github.com/stretchr/testify" 31 | version = "1.1.4" 32 | 33 | [[constraint]] 34 | branch = "v2" 35 | name = "gopkg.in/yaml.v2" 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Geodan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | name: GOST Server 3 | host: 4 | port: 8080 5 | externalUri: http://localhost:8080 6 | maxEntityResponse: 20 7 | indentedJson: true 8 | https: false 9 | httpsCert: 10 | httpsKey: 11 | database: 12 | host: localhost 13 | port: 5432 14 | user: postgres 15 | password: postgres 16 | database: gost 17 | schema: v1 18 | ssl: false 19 | maxIdleConns: 30 20 | maxOpenConns: 100 21 | mqtt: 22 | enabled: true 23 | verbose: false 24 | host: localhost 25 | port: 1883 26 | prefix: GOST 27 | clientId: gost 28 | subscriptionQos: 1 29 | persistent: true 30 | order: true 31 | ssl: false 32 | caCertFile: 33 | clientCertFile: 34 | privateKeyFile: 35 | keepAliveSec: 300 36 | pingTimeoutSec: 20 37 | logger: 38 | fileName: 39 | verbose: false 40 | 41 | -------------------------------------------------------------------------------- /configuration/config.go: -------------------------------------------------------------------------------- 1 | package configuration 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // CurrentConfig will be set after loading so it can be accessed from outside 9 | var CurrentConfig Config 10 | 11 | // Config contains the settings for the Http server, databases and mqtt 12 | type Config struct { 13 | Server ServerConfig `yaml:"server"` 14 | Database DatabaseConfig `yaml:"database"` 15 | MQTT MQTTConfig `yaml:"mqtt"` 16 | Logger LoggerConfig `yaml:"logger"` 17 | } 18 | 19 | // ServerConfig contains the general server information 20 | type ServerConfig struct { 21 | Name string `yaml:"name"` 22 | Host string `yaml:"host"` 23 | Port int `yaml:"port"` 24 | ExternalURI string `yaml:"externalUri"` 25 | HTTPS bool `yaml:"https"` 26 | HTTPSCert string `yaml:"httpsCert"` 27 | HTTPSKey string `yaml:"httpsKey"` 28 | MaxEntityResponse int `yaml:"maxEntityResponse"` 29 | IndentedJSON bool `yaml:"indentedJson"` 30 | } 31 | 32 | // DatabaseConfig contains the database server information, can be overruled by environment variables 33 | type DatabaseConfig struct { 34 | Host string `yaml:"host"` 35 | Port int `yaml:"port"` 36 | User string `yaml:"user"` 37 | Password string `yaml:"password"` 38 | Database string `yaml:"database"` 39 | Schema string `yaml:"schema"` 40 | SSL bool `yaml:"ssl"` 41 | MaxIdleConns int `yaml:"maxIdleConns"` 42 | MaxOpenConns int `yaml:"maxOpenConns"` 43 | } 44 | 45 | // MQTTConfig contains the MQTT client information 46 | type MQTTConfig struct { 47 | Enabled bool `yaml:"enabled"` 48 | Verbose bool `yaml:"verbose"` 49 | Host string `yaml:"host"` 50 | Prefix string `yaml:"prefix"` 51 | ClientID string `yaml:"clientId"` 52 | Port int `yaml:"port"` 53 | SubscriptionQos byte `yaml:"subscriptionQos"` 54 | Persistent bool `yaml:"persistent"` 55 | Order bool `yaml:"order"` 56 | SSL bool `yaml:"ssl"` 57 | Username string `yaml:"username"` 58 | Password string `yaml:"password"` 59 | CaCertFile string `yaml:"caCertFile"` 60 | ClientCertFile string `yaml:"clientCertFile"` 61 | PrivateKeyFile string `yaml:"privateKeyFile"` 62 | KeepAliveSec int `yaml:"keepAliveSec"` 63 | PingTimeoutSec int `yaml:"pingTimeoutSec"` 64 | } 65 | 66 | // LoggerConfig contains the logging configuration used to initialize the logger 67 | type LoggerConfig struct { 68 | FileName string `yaml:"fileName"` 69 | Verbose bool `yaml:"verbose"` 70 | } 71 | 72 | // GetInternalServerURI gets the internal Http server address 73 | // for example: "localhost:8080" 74 | func (c *Config) GetInternalServerURI() string { 75 | return fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port) 76 | } 77 | 78 | // GetExternalServerURI gets the external Http server address, trailing slash is removed when present in Config.Server.ExternalUri 79 | // for example "http://www.mysensorplatform" 80 | func (c *Config) GetExternalServerURI() string { 81 | return strings.Trim(c.Server.ExternalURI, "/") 82 | } 83 | -------------------------------------------------------------------------------- /configuration/config_test.go: -------------------------------------------------------------------------------- 1 | package configuration 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestGetExternalUri(t *testing.T) { 10 | var testurl = "http://test.com/" 11 | // arrange 12 | cfg := Config{} 13 | cfg.Server.ExternalURI = testurl 14 | // act 15 | var uri = cfg.GetExternalServerURI() 16 | // assert 17 | assert.Equal(t, uri, "http://test.com", "Trailing slash not removed by GetExternalServerUri") 18 | } 19 | 20 | func TestGetInternalUri(t *testing.T) { 21 | // arrange 22 | cfg := Config{} 23 | cfg.Server.Host = "localhost" 24 | cfg.Server.Port = 8080 25 | // act 26 | var uri = cfg.GetInternalServerURI() 27 | // assert 28 | assert.Equal(t, "localhost:8080", uri, "Internal server uri not constructed correctly based on config server host and port") 29 | } 30 | -------------------------------------------------------------------------------- /configuration/constants.go: -------------------------------------------------------------------------------- 1 | package configuration 2 | 3 | const ( 4 | // ServerVersion specifies the current GOST Server version 5 | ServerVersion string = "v0.5" 6 | 7 | // SensorThingsAPIVersion specifies the supported SensorThings API version 8 | SensorThingsAPIVersion string = "v1.0" 9 | 10 | // DefaultMaxEntries is used when config maxEntries is empty or $top exceeds this default value 11 | DefaultMaxEntries int = 200 12 | ) 13 | -------------------------------------------------------------------------------- /configuration/environment_test.go: -------------------------------------------------------------------------------- 1 | package configuration 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "os" 8 | "strconv" 9 | ) 10 | 11 | func TestEnvironmentVariabelsEmpty(t *testing.T) { 12 | // arrange 13 | conf := Config{} 14 | 15 | // act 16 | SetEnvironmentVariables(&conf) 17 | 18 | // assert 19 | assert.NotNil(t, conf, "Configuration should not be nil") 20 | } 21 | 22 | func TestEnvironmentVariabels(t *testing.T) { 23 | // arrange 24 | conf := Config{} 25 | server := "server" 26 | host := "host" 27 | port := "8080" 28 | portParsed, _ := strconv.Atoi(port) 29 | externalUri := "uri" 30 | maxEntities := "1" 31 | maxEntitiesParsed, _ := strconv.Atoi(maxEntities) 32 | indentJSON := "true" 33 | indentJSONParsed, _ := strconv.ParseBool(indentJSON) 34 | https := "true" 35 | httpsParsed, _ := strconv.ParseBool(https) 36 | httpsKey := "key" 37 | httpsCert := "cert" 38 | mqttEnabled := "true" 39 | mqttEnabledParsed, _ := strconv.ParseBool(mqttEnabled) 40 | mqttHost := "mqtt_host" 41 | mqttPort := "9001" 42 | mqttPortParsed, _ := strconv.Atoi(mqttPort) 43 | dbSSLEnabled := "true" 44 | dbSSLEnabledParsed, _ := strconv.ParseBool(dbSSLEnabled) 45 | dbHost := "db_host" 46 | dbPort := "5432" 47 | dbPortParsed, _ := strconv.Atoi(dbPort) 48 | dbUser := "user" 49 | dbPassword := "secret" 50 | dbDB := "gost" 51 | dbSchema := "v1" 52 | dbMaxIdleCons := "1" 53 | dbMaxIdleConsParsed, _ := strconv.Atoi(dbMaxIdleCons) 54 | dbMaxOpenCons := "1" 55 | dbMaxOpenConsParsed, _ := strconv.Atoi(dbMaxOpenCons) 56 | // act 57 | os.Setenv("GOST_SERVER_NAME", server) 58 | os.Setenv("GOST_SERVER_HOST", host) 59 | os.Setenv("GOST_SERVER_PORT", port) 60 | os.Setenv("GOST_SERVER_EXTERNAL_URI", externalUri) 61 | os.Setenv("GOST_SERVER_MAX_ENTITIES", maxEntities) 62 | os.Setenv("GOST_SERVER_INDENT_JSON", indentJSON) 63 | os.Setenv("GOST_SERVER_HTTPS", https) 64 | os.Setenv("GOST_SERVER_HTTPS_KEY", httpsKey) 65 | os.Setenv("GOST_SERVER_HTTPS_CERT", httpsCert) 66 | os.Setenv("GOST_MQTT_ENABLED", mqttEnabled) 67 | os.Setenv("GOST_MQTT_HOST", mqttHost) 68 | os.Setenv("GOST_MQTT_PORT", mqttPort) 69 | os.Setenv("GOST_DB_HOST", dbHost) 70 | os.Setenv("GOST_DB_PORT", dbPort) 71 | os.Setenv("GOST_DB_USER", dbUser) 72 | os.Setenv("GOST_DB_PASSWORD", dbPassword) 73 | os.Setenv("GOST_DB_DATABASE", dbDB) 74 | os.Setenv("GOST_DB_SCHEMA", dbSchema) 75 | os.Setenv("GOST_DB_SSL_ENABLED", dbSSLEnabled) 76 | os.Setenv("GOST_DB_MAX_IDLE_CONS", dbMaxIdleCons) 77 | os.Setenv("GOST_DB_MAX_OPEN_CONS", dbMaxOpenCons) 78 | 79 | SetEnvironmentVariables(&conf) 80 | 81 | // assert 82 | assert.NotNil(t, conf, "Configuration should not be nil") 83 | assert.Equal(t, server, conf.Server.Name) 84 | assert.Equal(t, externalUri, conf.Server.ExternalURI) 85 | assert.Equal(t, host, conf.Server.Host) 86 | assert.Equal(t, httpsParsed, conf.Server.HTTPS) 87 | assert.Equal(t, httpsCert, conf.Server.HTTPSCert) 88 | assert.Equal(t, httpsKey, conf.Server.HTTPSKey) 89 | assert.Equal(t, indentJSONParsed, conf.Server.IndentedJSON) 90 | assert.Equal(t, maxEntitiesParsed, conf.Server.MaxEntityResponse) 91 | assert.Equal(t, portParsed, conf.Server.Port) 92 | assert.Equal(t, mqttHost, conf.MQTT.Host) 93 | assert.Equal(t, mqttEnabledParsed, conf.MQTT.Enabled) 94 | assert.Equal(t, mqttPortParsed, conf.MQTT.Port) 95 | assert.Equal(t, dbDB, conf.Database.Database) 96 | assert.Equal(t, dbPortParsed, conf.Database.Port) 97 | assert.Equal(t, dbHost, conf.Database.Host) 98 | assert.Equal(t, dbMaxIdleConsParsed, conf.Database.MaxIdleConns) 99 | assert.Equal(t, dbMaxOpenConsParsed, conf.Database.MaxOpenConns) 100 | assert.Equal(t, dbPassword, conf.Database.Password) 101 | assert.Equal(t, dbSchema, conf.Database.Schema) 102 | assert.Equal(t, dbSSLEnabledParsed, conf.Database.SSL) 103 | assert.Equal(t, dbUser, conf.Database.User) 104 | } 105 | -------------------------------------------------------------------------------- /configuration/reader.go: -------------------------------------------------------------------------------- 1 | package configuration 2 | 3 | import ( 4 | "io/ioutil" 5 | 6 | "gopkg.in/yaml.v2" 7 | ) 8 | 9 | // readFile reads the bytes from a given file 10 | func readFile(cfgFile string) ([]byte, error) { 11 | source, err := ioutil.ReadFile(cfgFile) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | return source, nil 17 | } 18 | 19 | // readConfig tries to parse the byte data into a config file 20 | func readConfig(fileContent []byte) (Config, error) { 21 | config := Config{} 22 | err := yaml.Unmarshal(fileContent, &config) 23 | return config, err 24 | } 25 | 26 | // GetConfig retrieves a new configuration from the given config file 27 | // Fatal when config does not exist or cannot be read 28 | func GetConfig(cfgFile string) (Config, error) { 29 | content, err := readFile(cfgFile) 30 | if err != nil { 31 | return Config{}, err 32 | } 33 | 34 | conf, err := readConfig(content) 35 | if err != nil { 36 | return Config{}, err 37 | } 38 | 39 | CurrentConfig = conf 40 | return CurrentConfig, nil 41 | } 42 | -------------------------------------------------------------------------------- /configuration/reader_test.go: -------------------------------------------------------------------------------- 1 | package configuration 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "io/ioutil" 8 | "os" 9 | ) 10 | 11 | var configLocation = "../config.yaml" 12 | var configFake = "nonexistingfile.yaml" 13 | var configWrongData = "testreadconfig.yaml" 14 | 15 | var data = ` 16 | server: 17 | name: GOST Server 18 | host: localhost 19 | port: 8080 20 | externalUri: localhost:8080/ 21 | database: 22 | host: 192.168.40.10 23 | port: 5432 24 | user: postgres 25 | password: postgres 26 | database: gost 27 | schema: v1 28 | ssl: false 29 | mqtt: 30 | enabled: true 31 | host: test.mosquitto.org 32 | port: 1883 33 | ` 34 | 35 | func TestReadFile(t *testing.T) { 36 | f, err := readFile(configLocation) 37 | if err != nil { 38 | t.Error("Please make sure there is a config.yaml file in the root directory ", err) 39 | } 40 | 41 | assert.NotNil(t, f, "config bytes should not be nil") 42 | 43 | _, err = readFile(configFake) 44 | if err == nil { 45 | t.Error("Reading non existing config file should have given an error") 46 | } 47 | } 48 | 49 | func TestReadConfig(t *testing.T) { 50 | // try parsing data, this should parse if not give error 51 | content := []byte(data) 52 | cfg, err := readConfig(content) 53 | if err != nil { 54 | t.Error("Given static config data could not be parsed into config struct") 55 | } 56 | assert.NotNil(t, cfg) 57 | 58 | // check some random params 59 | assert.Equal(t, 8080, cfg.Server.Port) 60 | assert.Equal(t, false, cfg.Database.SSL) 61 | assert.Equal(t, "192.168.40.10", cfg.Database.Host) 62 | 63 | // put in some false content this should fail 64 | falseContent := []byte("aaabbbccc") 65 | cfg, err = readConfig(falseContent) 66 | assert.NotNil(t, err, "ReadConfig should have returned an error") 67 | } 68 | 69 | func TestGetConfig(t *testing.T) { 70 | // Get the default config, should exist 71 | cfg, err := GetConfig(configLocation) 72 | if err != nil { 73 | t.Error("GetConfig returned an error ", err) 74 | } 75 | 76 | // Check if there is data 77 | assert.NotNil(t, cfg.Server.Host) 78 | assert.NotNil(t, cfg.Database.Database) 79 | 80 | // Try to get a fake config, should return error 81 | _, err = GetConfig(configFake) 82 | if err == nil { 83 | t.Error("Given fake config did not return an error", err) 84 | } 85 | 86 | // write file with some fake data, GetConfig should return error 87 | d1 := []byte("aaabbbccc") 88 | _ = ioutil.WriteFile(configWrongData, d1, 0644) 89 | _, err = GetConfig(configWrongData) 90 | _ = os.Remove(configWrongData) 91 | if err == nil { 92 | t.Error("GetConfig should have returned an error on reading a fake config", err) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /database/postgis/datastream_test.go: -------------------------------------------------------------------------------- 1 | package postgis 2 | 3 | import ( 4 | entities "github.com/gost/core" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestDatastreamParamFactory(t *testing.T) { 10 | // arrange 11 | values := map[string]interface{}{ 12 | "datastream_id": 4, 13 | "datastream_name": "name", 14 | "datastream_description": "desc", 15 | } 16 | 17 | // act 18 | entity, err := datastreamParamFactory(values) 19 | entitytype := entity.GetEntityType() 20 | 21 | // assert 22 | assert.True(t, entity != nil) 23 | // entities.. 24 | assert.True(t, err == nil) 25 | assert.True(t, entity.GetID() == 4) 26 | assert.True(t, entitytype == entities.EntityTypeDatastream) 27 | } 28 | -------------------------------------------------------------------------------- /database/postgis/featureofInterest_test.go: -------------------------------------------------------------------------------- 1 | package postgis 2 | 3 | import ( 4 | entities "github.com/gost/core" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestFoiParamFactory(t *testing.T) { 10 | // arrange 11 | values := map[string]interface{}{ 12 | "featureofinterest_id": 4, 13 | "featureofinterest_name": "name", 14 | "featureofinterest_description": "desc", 15 | } 16 | // todo: encodingtype + feature 17 | 18 | // act 19 | entity, err := featureOfInterestParamFactory(values) 20 | entitytype := entity.GetEntityType() 21 | 22 | // assert 23 | assert.True(t, entity != nil) 24 | // entities.. 25 | assert.True(t, err == nil) 26 | assert.True(t, entity.GetID() == 4) 27 | assert.True(t, entitytype == entities.EntityTypeFeatureOfInterest) 28 | } 29 | -------------------------------------------------------------------------------- /database/postgis/filter.go: -------------------------------------------------------------------------------- 1 | package postgis 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | entities "github.com/gost/core" 8 | "github.com/gost/godata" 9 | ) 10 | 11 | var filterToStringMap map[int]func(qb QueryBuilder, pn *godata.ParseNode, et entities.EntityType, ignoreSelectAs bool) string 12 | 13 | // init is used to work around the initialization loop error (circular reference) 14 | func init() { 15 | filterToStringMap = map[int]func(qb QueryBuilder, pn *godata.ParseNode, et entities.EntityType, ignoreSelectAs bool) string{ 16 | godata.FilterTokenNav: filterNavToString, 17 | godata.FilterTokenLogical: filterLogicalToString, 18 | godata.FilterTokenFunc: filterFuncToString, 19 | godata.FilterTokenOp: filterOpToString, 20 | godata.FilterTokenGeography: filterGeographyToString, 21 | godata.FilterTokenLiteral: filterLiteralToString, 22 | godata.FilterTokenLambda: filterDefaultToString, 23 | godata.FilterTokenNull: filterDefaultToString, 24 | godata.FilterTokenIt: filterDefaultToString, 25 | godata.FilterTokenRoot: filterDefaultToString, 26 | godata.FilterTokenFloat: filterDefaultToString, 27 | godata.FilterTokenInteger: filterDefaultToString, 28 | godata.FilterTokenString: filterDefaultToString, 29 | godata.FilterTokenDate: filterDefaultToString, 30 | godata.FilterTokenTime: filterDefaultToString, 31 | godata.FilterTokenDateTime: filterDefaultToString, 32 | godata.FilterTokenBoolean: filterDefaultToString, 33 | } 34 | } 35 | 36 | func filterNavToString(qb QueryBuilder, pn *godata.ParseNode, et entities.EntityType, ignoreSelectAs bool) string { 37 | q := "" 38 | for i, part := range pn.Children { 39 | if i == 0 { 40 | q += fmt.Sprintf("%v ", strings.ToLower(qb.createFilter(et, part, false))) 41 | continue 42 | } 43 | 44 | arrow := "->" 45 | if i+1 == len(pn.Children) { 46 | arrow = "->>" 47 | } 48 | q += fmt.Sprintf("%v '%v'", arrow, part.Token.Value) 49 | } 50 | return q 51 | } 52 | 53 | func filterLogicalToString(qb QueryBuilder, pn *godata.ParseNode, et entities.EntityType, ignoreSelectAs bool) string { 54 | left := qb.createFilter(et, pn.Children[0], false) 55 | 56 | if len(pn.Children) == 1 && strings.ToLower(pn.Token.Value) == "not" { 57 | return fmt.Sprintf("%v %v", qb.odataLogicalOperatorToPostgreSQL(pn.Token.Value), left) 58 | } 59 | 60 | right := qb.createFilter(et, pn.Children[1], false) 61 | left, right = qb.prepareFilter(et, pn.Children[0].Token.Value, left, pn.Children[1].Token.Value, right) 62 | 63 | // Workaround for faulty OGC test 64 | result := "observation.data -> 'result'" 65 | if len(qb.odataLogicalOperatorToPostgreSQL(pn.Token.Value)) > 0 { 66 | if left == result { 67 | if strings.Index(right, "'") != 0 { 68 | left = qb.CastObservationResult(left, "double precision") 69 | } else { 70 | left = "observation.data ->> 'result'" 71 | } 72 | } else if right == result { 73 | if strings.Index(left, "'") != 0 { 74 | right = qb.CastObservationResult(right, "double precision") 75 | } else { 76 | right = "observation.data ->> 'result'" 77 | } 78 | } 79 | } 80 | // End workaround 81 | 82 | return fmt.Sprintf("%v %v %v", left, qb.odataLogicalOperatorToPostgreSQL(pn.Token.Value), right) 83 | } 84 | 85 | func filterFuncToString(qb QueryBuilder, pn *godata.ParseNode, et entities.EntityType, ignoreSelectAs bool) string { 86 | if convertFunction, ok := funcToStringMap[pn.Token.Value]; ok { 87 | return convertFunction(qb, pn, et) 88 | } 89 | 90 | return "" 91 | } 92 | 93 | func filterDefaultToString(qb QueryBuilder, pn *godata.ParseNode, et entities.EntityType, ignoreSelectAs bool) string { 94 | return fmt.Sprintf("%v", pn.Token.Value) 95 | } 96 | 97 | func filterOpToString(qb QueryBuilder, pn *godata.ParseNode, et entities.EntityType, ignoreSelectAs bool) string { 98 | if pn.Token.Value == "add" { 99 | return qb.createArithmetic(et, pn, "+", "double precision") 100 | } else if pn.Token.Value == "sub" { 101 | return qb.createArithmetic(et, pn, "-", "double precision") 102 | } else if pn.Token.Value == "mul" { 103 | return qb.createArithmetic(et, pn, "*", "double precision") 104 | } else if pn.Token.Value == "div" { 105 | return qb.createArithmetic(et, pn, "/", "double precision") 106 | } else if pn.Token.Value == "mod" { 107 | return qb.createArithmetic(et, pn, "%", "integer") 108 | } 109 | 110 | return "" 111 | } 112 | 113 | func filterGeographyToString(qb QueryBuilder, pn *godata.ParseNode, et entities.EntityType, ignoreSelectAs bool) string { 114 | return fmt.Sprintf("ST_GeomFromText(%v)", pn.Children[0].Token.Value) 115 | } 116 | 117 | func filterLiteralToString(qb QueryBuilder, pn *godata.ParseNode, et entities.EntityType, ignoreSelectAs bool) string { 118 | p := selectMappings[et][strings.ToLower(pn.Token.Value)] 119 | if p != "" { 120 | return p 121 | } 122 | 123 | if ignoreSelectAs && selectMappingsIgnore[et][pn.Token.Value] { 124 | return fmt.Sprintf("%s.%v", et.ToString(), pn.Token.Value) 125 | } 126 | 127 | return pn.Token.Value 128 | } 129 | -------------------------------------------------------------------------------- /database/postgis/observation_test.go: -------------------------------------------------------------------------------- 1 | package postgis 2 | 3 | import ( 4 | entities "github.com/gost/core" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestObservationParamFactory(t *testing.T) { 10 | // arrange 11 | phenomenonTime := "2015-03-06T00:00:00.000Z" 12 | resultTime := "2015-03-06T00:00:00.000Z" 13 | validTime := "2015-03-06T00:00:00.000Z" 14 | 15 | values := map[string]interface{}{ 16 | "observation_id": 4, 17 | "observation_phenomenontime": phenomenonTime, 18 | "observation_result": "!0.5", 19 | "observation_resulttime": resultTime, 20 | "observation_resultquality": "goed", 21 | "observation_validtime": validTime, 22 | //"observation_parameters": "test", 23 | } 24 | 25 | // act 26 | entity, err := observationParamFactory(values) 27 | entitytype := entity.GetEntityType() 28 | // todo: how to get the observation?? 29 | 30 | // assert 31 | assert.True(t, entity != nil) 32 | // entities.. 33 | assert.True(t, err == nil) 34 | assert.True(t, entity.GetID() == 4) 35 | assert.True(t, entitytype == entities.EntityTypeObservation) 36 | // assert.True(t,*observation.ResultTime == resultTime) 37 | } 38 | -------------------------------------------------------------------------------- /database/postgis/postgis_test.go: -------------------------------------------------------------------------------- 1 | package postgis 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gost/godata" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func init() { 11 | setupLogger() 12 | } 13 | 14 | func TestToIntIDForString(t *testing.T) { 15 | // arrange 16 | fid := "4" 17 | 18 | // act 19 | intID, err := ToIntID(fid) 20 | 21 | // assert 22 | assert.True(t, intID == 4) 23 | assert.True(t, err) 24 | } 25 | 26 | func TestToIntIDForFloat(t *testing.T) { 27 | // arrange 28 | fid := 6.4 29 | 30 | // act 31 | intID, err := ToIntID(fid) 32 | 33 | // assert 34 | assert.True(t, intID == 6) 35 | assert.True(t, err) 36 | } 37 | 38 | func TestContainsToLower(t *testing.T) { 39 | // arrange 40 | ss, _ := godata.ParseSelectString("Hallo") 41 | search := "HALLO" 42 | 43 | // act 44 | res := ContainsToLower(ss.SelectItems, search) 45 | 46 | // assert 47 | assert.True(t, res) 48 | } 49 | 50 | func TestContainsNotToLower(t *testing.T) { 51 | // arrange 52 | ss, _ := godata.ParseSelectString("Halllo") 53 | search := "HALLO" 54 | 55 | // act 56 | res := ContainsToLower(ss.SelectItems, search) 57 | 58 | // assert 59 | assert.False(t, res) 60 | } 61 | 62 | func TestJsonToMapSucceeds(t *testing.T) { 63 | // arrange 64 | jsonstring := `{"value": [{"name": "Things","url": "http://gost.geodan.nl/v1.0/Things"}]}` 65 | 66 | // act 67 | res, err := JSONToMap(&jsonstring) 68 | 69 | // assert 70 | assert.Nil(t, err) 71 | assert.NotNil(t, res) 72 | assert.NotNil(t, res["value"]) 73 | } 74 | 75 | func TestJsonToMapFails(t *testing.T) { 76 | // arrange 77 | jsonstring := `` 78 | 79 | // act 80 | _, err := JSONToMap(&jsonstring) 81 | 82 | // assert 83 | assert.Nil(t, err) 84 | } 85 | 86 | func TestJsonToMapFailsWithWrongData(t *testing.T) { 87 | // arrange 88 | jsonstring := `hoho` 89 | 90 | // act 91 | _, err := JSONToMap(&jsonstring) 92 | 93 | // assert 94 | assert.NotNil(t, err) 95 | } 96 | -------------------------------------------------------------------------------- /database/postgis/queryexecutor_test.go: -------------------------------------------------------------------------------- 1 | package postgis 2 | 3 | import ( 4 | entities "github.com/gost/core" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestAddRelationToEntity(t *testing.T) { 10 | // parent entities.Entity, subEntities []entities.Entity 11 | // arrange 12 | thing := &entities.Thing{} 13 | location := &entities.Location{} 14 | historicalLocation := &entities.HistoricalLocation{} 15 | datastream := &entities.Datastream{} 16 | observation := &entities.Observation{} 17 | sensor := &entities.Sensor{} 18 | observedProperty := &entities.ObservedProperty{} 19 | foi := &entities.FeatureOfInterest{} 20 | 21 | // act 22 | addRelationToEntity(thing, []entities.Entity{location}) 23 | addRelationToEntity(thing, []entities.Entity{historicalLocation}) 24 | addRelationToEntity(thing, []entities.Entity{datastream}) 25 | addRelationToEntity(location, []entities.Entity{historicalLocation}) 26 | addRelationToEntity(location, []entities.Entity{thing}) 27 | addRelationToEntity(historicalLocation, []entities.Entity{thing}) 28 | addRelationToEntity(historicalLocation, []entities.Entity{location}) 29 | addRelationToEntity(datastream, []entities.Entity{observation}) 30 | addRelationToEntity(datastream, []entities.Entity{thing}) 31 | addRelationToEntity(datastream, []entities.Entity{sensor}) 32 | addRelationToEntity(datastream, []entities.Entity{observedProperty}) 33 | addRelationToEntity(sensor, []entities.Entity{datastream}) 34 | addRelationToEntity(observedProperty, []entities.Entity{datastream}) 35 | addRelationToEntity(observation, []entities.Entity{datastream}) 36 | addRelationToEntity(observation, []entities.Entity{foi}) 37 | addRelationToEntity(foi, []entities.Entity{observation}) 38 | 39 | // assert 40 | assert.True(t, len(thing.Locations) == 1) 41 | assert.True(t, len(thing.HistoricalLocations) == 1) 42 | assert.True(t, len(thing.Datastreams) == 1) 43 | assert.True(t, len(location.HistoricalLocations) == 1) 44 | assert.True(t, len(location.Things) == 1) 45 | assert.NotNil(t, historicalLocation.Thing) 46 | assert.True(t, len(historicalLocation.Locations) == 1) 47 | assert.True(t, len(datastream.Observations) == 1) 48 | assert.NotNil(t, datastream.Thing) 49 | assert.NotNil(t, datastream.Sensor) 50 | assert.NotNil(t, datastream.ObservedProperty) 51 | assert.True(t, len(sensor.Datastreams) == 1) 52 | assert.True(t, len(observedProperty.Datastreams) == 1) 53 | assert.NotNil(t, observation.Datastream) 54 | assert.NotNil(t, observation.FeatureOfInterest) 55 | assert.True(t, len(foi.Observations) == 1) 56 | 57 | } 58 | -------------------------------------------------------------------------------- /database/postgis/sensor_test.go: -------------------------------------------------------------------------------- 1 | package postgis 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestDatabase(t *testing.T) { 9 | // todo add database tests... 10 | // arrange 11 | var a = 1 12 | var b = 2 13 | // act 14 | var res = a + b 15 | // assert 16 | assert.Equal(t, 3, res, "computer error again") 17 | } 18 | -------------------------------------------------------------------------------- /errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // APIError holds information about an error including status codes. 8 | type APIError struct { 9 | error error 10 | httpStatusCode int 11 | } 12 | 13 | // GetHTTPErrorStatusCode returns the status code. 14 | func (e APIError) GetHTTPErrorStatusCode() int { 15 | return e.httpStatusCode 16 | } 17 | 18 | // Error implements the error interface for apiError 19 | func (e APIError) Error() string { 20 | return e.error.Error() 21 | } 22 | 23 | // NewErrorWithStatusCode creates a new apiError with a given status code 24 | func NewErrorWithStatusCode(err error, status int) error { 25 | return APIError{err, status} 26 | } 27 | 28 | // NewBadRequestError creates an apiError with status code 400. 29 | func NewBadRequestError(err error) error { 30 | return NewErrorWithStatusCode(err, http.StatusBadRequest) 31 | } 32 | 33 | // NewConflictRequestError creates an apiError with status code 409. 34 | func NewConflictRequestError(err error) error { 35 | return NewErrorWithStatusCode(err, http.StatusConflict) 36 | } 37 | 38 | // NewRequestNotImplemented creates an apiError with status code 501. 39 | func NewRequestNotImplemented(err error) error { 40 | return NewErrorWithStatusCode(err, http.StatusNotImplemented) 41 | } 42 | 43 | // NewRequestNotFound creates an apiError with status code 404. 44 | func NewRequestNotFound(err error) error { 45 | return NewErrorWithStatusCode(err, http.StatusNotFound) 46 | } 47 | 48 | // NewRequestMethodNotAllowed creates an apiError with status code 405. 49 | func NewRequestMethodNotAllowed(err error) error { 50 | return NewErrorWithStatusCode(err, http.StatusMethodNotAllowed) 51 | } 52 | 53 | // NewRequestInternalServerError creates an apiError with status code 500. 54 | func NewRequestInternalServerError(err error) error { 55 | return NewErrorWithStatusCode(err, http.StatusInternalServerError) 56 | } 57 | -------------------------------------------------------------------------------- /errors/errors_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "errors" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestApiError(t *testing.T) { 10 | // arrange 11 | err := errors.New("yo") 12 | var apierr = APIError{err, 200} 13 | 14 | // assert 15 | assert.Equal(t, 200, apierr.GetHTTPErrorStatusCode(), "should return 200") 16 | assert.Equal(t, "yo", apierr.Error(), "should return 200") 17 | } 18 | 19 | func TestRequestStatusCodes(t *testing.T) { 20 | // arrange 21 | badrequesterror := NewBadRequestError(errors.New("bad")) 22 | notfounderror := NewRequestNotFound(errors.New("notfound")) 23 | conflicterror := NewConflictRequestError(errors.New("conflict")) 24 | notimplementederror := NewRequestNotImplemented(errors.New("notimplemented")) 25 | notallowederror := NewRequestMethodNotAllowed(errors.New("notallowed")) 26 | internalservererror := NewRequestInternalServerError(errors.New("internalserver")) 27 | 28 | // assert 29 | assert.Equal(t, "bad", badrequesterror.Error()) 30 | assert.Equal(t, "notfound", notfounderror.Error()) 31 | assert.Equal(t, "conflict", conflicterror.Error()) 32 | assert.Equal(t, "notimplemented", notimplementederror.Error()) 33 | assert.Equal(t, "notallowed", notallowederror.Error()) 34 | assert.Equal(t, "internalserver", internalservererror.Error()) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /http/gostserver_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/gost/server/configuration" 10 | "github.com/gost/server/database/postgis" 11 | "github.com/gost/server/mqtt" 12 | "github.com/gost/server/sensorthings/api" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestCreateServer(t *testing.T) { 17 | // arrange 18 | server := createTestServer(8080, false) 19 | 20 | // act 21 | server.Stop() 22 | 23 | // assert 24 | assert.NotNil(t, server) 25 | } 26 | 27 | func TestFailRunServerHttp(t *testing.T) { 28 | // arrange 29 | server := createTestServer(789456456, false) 30 | 31 | //assert 32 | assert.Panics(t, func() { server.Start() }) 33 | } 34 | 35 | func TestFailRunServerHttps(t *testing.T) { 36 | // arrange 37 | server := createTestServer(8080, true) 38 | 39 | //assert 40 | assert.Panics(t, func() { server.Start() }) 41 | } 42 | 43 | func TestLowerCaseURI(t *testing.T) { 44 | n := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 45 | assert.True(t, req.URL.Path == "/test") 46 | }) 47 | ts := httptest.NewServer(LowerCaseURI(n)) 48 | defer ts.Close() 49 | res, err := http.Get(ts.URL + "/TEST") 50 | if err == nil && res != nil { 51 | defer res.Body.Close() 52 | b, _ := ioutil.ReadAll(res.Body) 53 | assert.NotNil(t, b) 54 | } else { 55 | t.Fail() 56 | } 57 | } 58 | 59 | func TestPostProcessHandler(t *testing.T) { 60 | n := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 61 | rw.WriteHeader(http.StatusTeapot) 62 | rw.Header().Add("Location", "tea location") 63 | rw.Write([]byte("hello teapot")) 64 | }) 65 | ts := httptest.NewServer(PostProcessHandler(n, "http://localhost:8080/")) 66 | defer ts.Close() 67 | client := &http.Client{} 68 | req, _ := http.NewRequest("GET", ts.URL+"/", nil) 69 | req.Header.Set("X-Forwarded-For", "coffee") 70 | res, err := client.Do(req) 71 | if err == nil && res != nil { 72 | defer res.Body.Close() 73 | b, _ := ioutil.ReadAll(res.Body) 74 | body := string(b) 75 | assert.NotNil(t, body) 76 | assert.True(t, body == "hello teapot") 77 | assert.True(t, res.StatusCode == http.StatusTeapot) 78 | assert.True(t, res.Header.Get("Location") == "tea location") 79 | } else { 80 | t.Fail() 81 | } 82 | } 83 | 84 | func TestIsValidRequestHandler(t *testing.T) { 85 | n := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 86 | // assert.True(t, req.URL.Path == "/test") 87 | }) 88 | ts := httptest.NewServer(RequestErrorHandler(n)) 89 | defer ts.Close() 90 | res, err := http.Get(ts.URL + "/sensors?$notexisting eq 'ho'") 91 | if err == nil && res != nil { 92 | defer res.Body.Close() 93 | b, _ := ioutil.ReadAll(res.Body) 94 | assert.True(t, res.StatusCode == 400) 95 | assert.NotNil(t, b) 96 | } else { 97 | t.Fail() 98 | } 99 | } 100 | 101 | func createTestServer(port int, https bool) Server { 102 | cfg := configuration.Config{ 103 | Server: configuration.ServerConfig{ExternalURI: "http://localhost:8080/"}, 104 | } 105 | mqttServer := mqtt.CreateMQTTClient(configuration.MQTTConfig{}) 106 | database := postgis.NewDatabase("", 123, "", "", "", "", false, 50, 100, 200) 107 | stAPI := api.NewAPI(database, cfg, mqttServer) 108 | return CreateServer("localhost", port, &stAPI, https, "", "") 109 | } 110 | -------------------------------------------------------------------------------- /http/router.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/gorilla/mux" 8 | "github.com/gost/server/sensorthings/models" 9 | "github.com/gost/server/sensorthings/rest/endpoint" 10 | ) 11 | 12 | // CreateRouter creates a new mux.Router and sets up all endpoints defined in the SensorThings api 13 | func CreateRouter(api *models.API) *mux.Router { 14 | // Note: tried julienschmidt/httprouter instead of gorilla/mux but had some 15 | // problems with interfering endpoints cause of the wildcard used for the (id) in requests 16 | a := *api 17 | eps := endpoint.EndpointsToSortedList(a.GetEndpoints()) 18 | router := mux.NewRouter().StrictSlash(false) 19 | 20 | for _, e := range eps { 21 | op := e 22 | operation := op.Operation 23 | method := fmt.Sprintf("%s", operation.OperationType) 24 | router.Methods(method). 25 | Path(operation.Path). 26 | HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 27 | operation.Handler(w, r, &op.Endpoint, api) 28 | }) 29 | } 30 | 31 | return router 32 | } 33 | -------------------------------------------------------------------------------- /http/router_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "github.com/gorilla/mux" 5 | "github.com/gost/server/configuration" 6 | "github.com/gost/server/database/postgis" 7 | "github.com/gost/server/mqtt" 8 | "github.com/gost/server/sensorthings/api" 9 | "github.com/stretchr/testify/assert" 10 | "net/http" 11 | "net/http/httptest" 12 | "testing" 13 | ) 14 | 15 | var router *mux.Router 16 | var req *http.Request 17 | var respRec *httptest.ResponseRecorder 18 | 19 | func setup() { 20 | // arrange 21 | cfg := configuration.Config{} 22 | mqttServer := mqtt.CreateMQTTClient(configuration.MQTTConfig{}) 23 | database := postgis.NewDatabase("", 123, "", "", "", "", false, 50, 100, 200) 24 | a := api.NewAPI(database, cfg, mqttServer) 25 | router = CreateRouter(&a) 26 | 27 | //The response recorder used to record HTTP responses 28 | respRec = httptest.NewRecorder() 29 | } 30 | 31 | // Test the router functionality 32 | func TestCreateRouter(t *testing.T) { 33 | // arrange 34 | setup() 35 | 36 | // assert 37 | assert.NotNil(t, router, "Router should be created") 38 | } 39 | 40 | func TestEndpoints(t *testing.T) { 41 | req, _ = http.NewRequest("GET", "/v1.0", nil) 42 | router.ServeHTTP(respRec, req) 43 | if respRec.Code != http.StatusOK { 44 | t.Fatal("Server endpoint /v1.0 error: Returned ", respRec.Code, " instead of ", http.StatusOK) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /log/logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | log "github.com/sirupsen/logrus" 10 | ) 11 | 12 | var ( 13 | gostLogger *log.Logger 14 | gostFile *os.File 15 | ) 16 | 17 | var ( 18 | //ErrLoggerNotInitialized is thrown when uninitialized logger instance is requested 19 | ErrLoggerNotInitialized = errors.New("LoggerNotInitialized") 20 | ) 21 | 22 | // GetLoggerInstance returns singleton instance of the logger 23 | func GetLoggerInstance() (*log.Logger, error) { 24 | if gostLogger == nil { 25 | return log.New(), ErrLoggerNotInitialized 26 | } 27 | 28 | return gostLogger, nil 29 | } 30 | 31 | // InitializeLogger with various properties 32 | func InitializeLogger(file *os.File, logFileName string, format log.Formatter, verboseFlag bool) (*log.Logger, error) { 33 | var err error 34 | gostLogger = log.New() 35 | gostFile = file 36 | 37 | if logFileName != "" { 38 | gostFile, err = os.OpenFile(logFileName+".log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666) 39 | if err != nil { 40 | fmt.Println("Log file cannot be opened") 41 | gostFile.Close() 42 | } 43 | } 44 | 45 | if logFileName == "" || err != nil { 46 | gostFile = os.Stdout 47 | } 48 | 49 | gostLogger.Out = gostFile 50 | if format != nil { 51 | gostLogger.Formatter = format 52 | } 53 | 54 | if verboseFlag { 55 | gostLogger.Level = log.DebugLevel 56 | } else { 57 | gostLogger.Level = log.InfoLevel 58 | } 59 | 60 | return gostLogger, err 61 | } 62 | 63 | // DebugWithElapsedTime writes a new debug line, including a field with elapsed time 64 | // call with defer at the start of a function: defer DebugWithElapsedTime(logger, time.Now(), "test") 65 | func DebugWithElapsedTime(entry *log.Entry, start time.Time, args ...interface{}) { 66 | elapsed := time.Since(start) 67 | l := entry.WithFields(log.Fields{"elapsed": elapsed}) 68 | l.Debug(args...) 69 | } 70 | 71 | // DebugfWithElapsedTime writes a new debug format line, including a field with elapsed time 72 | // call with defer at the start of a function: defer DebugWithElapsedTime(logger, time.Now(), "test %v", "test") 73 | func DebugfWithElapsedTime(entry *log.Entry, start time.Time, format string, args ...interface{}) { 74 | elapsed := time.Since(start) 75 | l := entry.WithFields(log.Fields{"elapsed": elapsed}) 76 | l.Debugf(format, args...) 77 | } 78 | 79 | // CleanUp after the logger is closed 80 | func CleanUp() { 81 | if gostFile != nil { 82 | gostFile.Close() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /log/logger_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | "testing" 8 | "time" 9 | 10 | "github.com/stretchr/testify/assert" 11 | 12 | log "github.com/sirupsen/logrus" 13 | ) 14 | 15 | var ( 16 | testFile *os.File 17 | testLogger *log.Logger 18 | err error 19 | testLogFileName = "testLog" 20 | testVerboseFlag = true 21 | ) 22 | 23 | func TestGetInstanceWithoutInitialization(t *testing.T) { 24 | testLogger, err := GetLoggerInstance() 25 | 26 | assert.Error(t, err, "Function must throw error") 27 | assert.EqualError(t, err, ErrLoggerNotInitialized.Error(), "Error should be logger not initialized") 28 | 29 | assert.NotNil(t, testLogger, "Logger should not be nil") 30 | } 31 | 32 | func TestLoggerLifecycle(t *testing.T) { 33 | //Setup 34 | testLogger, err = InitializeLogger(testFile, testLogFileName, new(log.TextFormatter), testVerboseFlag) 35 | assert.NoError(t, err, "Initialization error should be nil") 36 | assert.NotNil(t, testLogger, "Logger must have been initialized") 37 | 38 | //GetInstance 39 | loggerInstance, err := GetLoggerInstance() 40 | assert.NoError(t, err, "GetInstance should not return error") 41 | assert.NotNil(t, loggerInstance, "Logger instance should not be nil") 42 | loggerInstance.Debug("Debug") 43 | loggerInstance.Info("Info") 44 | loggerInstance.Warn("Warn") 45 | loggerInstance.Error("Error") 46 | _, err = os.Stat("./" + testLogFileName + ".log") 47 | assert.False(t, os.IsNotExist(err), "Test log file should exist") 48 | 49 | //Cleanup 50 | CleanUp() 51 | assert.Nil(t, testFile, "Test file should be Nil") 52 | } 53 | 54 | func TestCleanUpWithoutInitialization(t *testing.T) { 55 | assert.NotPanics(t, func() { CleanUp() }, "Must not panic") 56 | } 57 | 58 | func TestDebugWithElapsedTime(t *testing.T) { 59 | testLogger, err = InitializeLogger(testFile, "", new(log.TextFormatter), true) 60 | entry := testLogger.WithFields(log.Fields{"package": "gost.server.log"}) 61 | 62 | f := func() { DebugWithElapsedTime(entry, time.Now(), "test") } 63 | message := captureStdout(f) 64 | 65 | //assert 66 | assert.Contains(t, message, "elapsed") 67 | assert.Contains(t, message, "test") 68 | } 69 | 70 | func TestDebugfWithElapsedTime(t *testing.T) { 71 | testLogger, err = InitializeLogger(testFile, "", new(log.TextFormatter), true) 72 | entry := testLogger.WithFields(log.Fields{"package": "gost.server.log"}) 73 | 74 | f := func() { DebugfWithElapsedTime(entry, time.Now(), "test %s", "1") } 75 | message := captureStdout(f) 76 | 77 | //assert 78 | assert.Contains(t, message, "elapsed") 79 | assert.Contains(t, message, "test 1") 80 | } 81 | 82 | func captureStdout(f func()) string { 83 | old := testLogger.Out 84 | r, w, _ := os.Pipe() 85 | testLogger.Out = w 86 | 87 | f() 88 | 89 | w.Close() 90 | testLogger.Out = old 91 | 92 | var buf bytes.Buffer 93 | io.Copy(&buf, r) 94 | return buf.String() 95 | } 96 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | 10 | log "github.com/sirupsen/logrus" 11 | 12 | "github.com/gost/server/configuration" 13 | "github.com/gost/server/database/postgis" 14 | "github.com/gost/server/http" 15 | gostLog "github.com/gost/server/log" 16 | "github.com/gost/server/mqtt" 17 | "github.com/gost/server/sensorthings/api" 18 | "github.com/gost/server/sensorthings/models" 19 | ) 20 | 21 | var ( 22 | stAPI models.API 23 | gostServer http.Server 24 | mqttClient models.MQTTClient 25 | file *os.File 26 | logger *log.Logger 27 | mainLogger *log.Entry 28 | conf configuration.Config 29 | cfgFlag = flag.String("config", "config.yaml", "path of the config file") 30 | installFlag = flag.String("install", "", "path to the database creation file") 31 | ) 32 | 33 | func initialize() { 34 | flag.Parse() 35 | cfg := *cfgFlag 36 | var err error 37 | conf, err = configuration.GetConfig(cfg) 38 | if err != nil { 39 | log.Fatal("config read error: ", err) 40 | return 41 | } 42 | 43 | configuration.SetEnvironmentVariables(&conf) 44 | logger, err := gostLog.InitializeLogger(file, conf.Logger.FileName, &log.TextFormatter{FullTimestamp: true}, conf.Logger.Verbose) 45 | if err != nil { 46 | log.Println("Error initializing logger, defaulting to stdout. Error: " + err.Error()) 47 | } 48 | 49 | // Setting default fields for main logger 50 | mainLogger = logger.WithFields(log.Fields{"package": "main"}) 51 | } 52 | 53 | func main() { 54 | initialize() 55 | stop := make(chan os.Signal, 2) 56 | signal.Notify(stop, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) 57 | go func() { 58 | <-stop 59 | // mainLogger.Info("GOST stopped gracefully") 60 | cleanup() 61 | os.Exit(1) 62 | }() 63 | 64 | mainLogger.Info("Starting GOST") 65 | 66 | database := postgis.NewDatabase( 67 | conf.Database.Host, 68 | conf.Database.Port, 69 | conf.Database.User, 70 | conf.Database.Password, 71 | conf.Database.Database, 72 | conf.Database.Schema, 73 | conf.Database.SSL, 74 | conf.Database.MaxIdleConns, 75 | conf.Database.MaxOpenConns, 76 | conf.Server.MaxEntityResponse) 77 | go database.Start() 78 | 79 | // if install is supplied create database and close, if not start server 80 | sqlFile := *installFlag 81 | if len(sqlFile) != 0 { 82 | createDatabase(database, sqlFile) 83 | } else { 84 | mqttClient = mqtt.CreateMQTTClient(conf.MQTT) 85 | stAPI = api.NewAPI(database, conf, mqttClient) 86 | 87 | if conf.MQTT.Enabled { 88 | mqttClient.Start(&stAPI) 89 | } 90 | 91 | createAndStartServer(&stAPI) 92 | } 93 | } 94 | 95 | func createDatabase(db models.Database, sqlFile string) { 96 | mainLogger.Info("CREATING DATABASE") 97 | 98 | err := db.CreateSchema(sqlFile) 99 | if err != nil { 100 | mainLogger.Fatal(err) 101 | } 102 | 103 | mainLogger.Info("Database created successfully, you can start your server now") 104 | } 105 | 106 | // createAndStartServer creates the GOST HTTPServer and starts it 107 | func createAndStartServer(api *models.API) { 108 | a := *api 109 | a.Start() 110 | 111 | config := a.GetConfig() 112 | gostServer = http.CreateServer( 113 | config.Server.Host, 114 | config.Server.Port, 115 | api, 116 | config.Server.HTTPS, 117 | config.Server.HTTPSCert, 118 | config.Server.HTTPSKey) 119 | gostServer.Start() 120 | } 121 | 122 | func cleanup() { 123 | if gostServer != nil { 124 | gostServer.Stop() 125 | } 126 | 127 | gostLog.CleanUp() 128 | } 129 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestVersionHandler(t *testing.T) { 8 | //arrange 9 | /*server := "localhost" 10 | port := 8088 11 | 12 | cfg := configuration.Config{} 13 | mqttServer := mqtt.CreateMQTTClient(configuration.MQTTConfig{}) 14 | database := postgis.NewDatabase("", 123, "", "", "", "", false, 50, 100, 200) 15 | api := api.NewAPI(database, cfg, mqttServer) 16 | api.Start() 17 | 18 | gostServer := http.CreateServer(server, port, &api, false, nil, nil) 19 | go gostServer.Start() 20 | versionURL := fmt.Sprintf("%s/Version", "http://"+server+":"+strconv.Itoa(port)) 21 | 22 | fmt.Println(versionURL) 23 | // act 24 | request, _ := net.NewRequest("GET", versionURL, nil) 25 | res, _ := net.DefaultClient.Do(request) 26 | 27 | //assert 28 | assert.Equal(t, 200, res.StatusCode, "result should be http 200") 29 | 30 | // teardown 31 | gostServer.Stop()*/ 32 | } 33 | -------------------------------------------------------------------------------- /mqtt/mqtt_test.go: -------------------------------------------------------------------------------- 1 | package mqtt 2 | 3 | import ( 4 | "github.com/gost/server/configuration" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestMqtt(t *testing.T) { 10 | // arrange 11 | config := configuration.MQTTConfig{} 12 | config.Host = "iot.eclipse.org" 13 | config.Port = 1883 14 | 15 | // act 16 | mqttClient := CreateMQTTClient(config) 17 | 18 | // assert 19 | assert.NotNil(t, mqttClient, "function should return MqqtClient") 20 | } 21 | -------------------------------------------------------------------------------- /sensorthings/api/createobservations.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | 6 | entities "github.com/gost/core" 7 | ) 8 | 9 | // PostCreateObservations checks for correctness of the datastreams and observations and calls PostcreateObservations on the database 10 | // ToDo: use transactions 11 | func (a *APIv1) PostCreateObservations(data *entities.CreateObservations) ([]string, []error) { 12 | _, err := containsMandatoryParams(data) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | returnList := make([]string, 0) 18 | for i := 0; i < len(data.Datastreams); i++ { 19 | for j := 0; j < len(data.Datastreams[i].Observations); j++ { 20 | obs, errors := a.PostObservationByDatastream(data.Datastreams[i].ID, data.Datastreams[i].Observations[j]) 21 | if errors == nil || len(errors) == 0 { 22 | returnList = append(returnList, obs.GetSelfLink()) 23 | } else { 24 | errorString := "" 25 | for k := 0; k < len(errors); k++ { 26 | if len(errorString) > 0 { 27 | errorString += ", " 28 | } 29 | 30 | errorString += fmt.Sprintf("%v", errors[k].Error()) 31 | } 32 | returnList = append(returnList, errorString) 33 | } 34 | } 35 | } 36 | 37 | return returnList, nil 38 | } 39 | -------------------------------------------------------------------------------- /sensorthings/api/featureofinterest.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "errors" 5 | 6 | entities "github.com/gost/core" 7 | gostErrors "github.com/gost/server/errors" 8 | "github.com/gost/server/sensorthings/odata" 9 | ) 10 | 11 | // GetFeatureOfInterest returns a FeatureOfInterest by id 12 | func (a *APIv1) GetFeatureOfInterest(id interface{}, qo *odata.QueryOptions, path string) (*entities.FeatureOfInterest, error) { 13 | l, err := a.db.GetFeatureOfInterest(id, qo) 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | a.SetLinks(l, qo) 19 | return l, nil 20 | } 21 | 22 | // GetFeatureOfInterestByObservation retrieves a FeatureOfInterest by given Observation id 23 | func (a *APIv1) GetFeatureOfInterestByObservation(id interface{}, qo *odata.QueryOptions, path string) (*entities.FeatureOfInterest, error) { 24 | l, err := a.db.GetFeatureOfInterestByObservation(id, qo) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | a.SetLinks(l, qo) 30 | return l, nil 31 | } 32 | 33 | // GetFeatureOfInterests return FeaturesOfInterest based on the given QueryOptions 34 | func (a *APIv1) GetFeatureOfInterests(qo *odata.QueryOptions, path string) (*entities.ArrayResponse, error) { 35 | fois, count, hasNext, err := a.db.GetFeatureOfInterests(qo) 36 | return processFeatureOfInterest(a, fois, qo, path, count, hasNext, err) 37 | } 38 | 39 | func processFeatureOfInterest(a *APIv1, fois []*entities.FeatureOfInterest, qo *odata.QueryOptions, path string, count int, hasNext bool, err error) (*entities.ArrayResponse, error) { 40 | for idx, item := range fois { 41 | i := *item 42 | a.SetLinks(&i, qo) 43 | fois[idx] = &i 44 | } 45 | 46 | var data interface{} = fois 47 | return a.createArrayResponse(count, hasNext, path, qo, data), nil 48 | } 49 | 50 | // PostFeatureOfInterest adds a FeatureOfInterest to the database 51 | func (a *APIv1) PostFeatureOfInterest(foi *entities.FeatureOfInterest) (*entities.FeatureOfInterest, []error) { 52 | _, err := containsMandatoryParams(foi) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | if foi.EncodingType != entities.EncodingGeoJSON.Value { 58 | err := errors.New("Encoding not supported. Supported encoding: " + entities.EncodingGeoJSON.Value) 59 | return nil, []error{err} 60 | } 61 | 62 | l, err2 := a.db.PostFeatureOfInterest(foi) 63 | if err2 != nil { 64 | return nil, []error{err2} 65 | } 66 | 67 | l.SetAllLinks(a.config.GetExternalServerURI()) 68 | return l, nil 69 | } 70 | 71 | // PutFeatureOfInterest adds a FeatureOfInterest to the database 72 | func (a *APIv1) PutFeatureOfInterest(id interface{}, foi *entities.FeatureOfInterest) (*entities.FeatureOfInterest, []error) { 73 | supported, err2 := entities.CheckEncodingSupported(foi.EncodingType) 74 | if !supported || err2 != nil { 75 | return nil, []error{err2} 76 | } 77 | 78 | l, err2 := a.db.PutFeatureOfInterest(id, foi) 79 | if err2 != nil { 80 | return nil, []error{err2} 81 | } 82 | 83 | l.SetAllLinks(a.config.GetExternalServerURI()) 84 | return l, nil 85 | } 86 | 87 | // PatchFeatureOfInterest updates the given FeatureOfInterest in the database 88 | func (a *APIv1) PatchFeatureOfInterest(id interface{}, foi *entities.FeatureOfInterest) (*entities.FeatureOfInterest, error) { 89 | if foi.Observations != nil { 90 | return nil, gostErrors.NewBadRequestError(errors.New("Unable to deep patch FeatureOfInterest")) 91 | } 92 | 93 | if len(foi.EncodingType) != 0 { 94 | if foi.EncodingType != entities.EncodingGeoJSON.Value { 95 | err := errors.New("Encoding not supported. Supported encoding: " + entities.EncodingGeoJSON.Value) 96 | return nil, err 97 | } 98 | 99 | supported, err := entities.CheckEncodingSupported(foi.EncodingType) 100 | if !supported || err != nil { 101 | return nil, err 102 | } 103 | } 104 | 105 | return a.db.PatchFeatureOfInterest(id, foi) 106 | } 107 | 108 | // DeleteFeatureOfInterest deletes a given FeatureOfInterest from the database 109 | func (a *APIv1) DeleteFeatureOfInterest(id interface{}) error { 110 | return a.db.DeleteFeatureOfInterest(id) 111 | } 112 | -------------------------------------------------------------------------------- /sensorthings/api/historicallocation.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | entities "github.com/gost/core" 5 | "github.com/gost/server/sensorthings/odata" 6 | 7 | "errors" 8 | 9 | gostErrors "github.com/gost/server/errors" 10 | ) 11 | 12 | // GetHistoricalLocation retrieves a single HistoricalLocation by id 13 | func (a *APIv1) GetHistoricalLocation(id interface{}, qo *odata.QueryOptions, path string) (*entities.HistoricalLocation, error) { 14 | hl, err := a.db.GetHistoricalLocation(id, qo) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | a.SetLinks(hl, qo) 20 | return hl, nil 21 | } 22 | 23 | // GetHistoricalLocations retrieves all HistoricalLocations 24 | func (a *APIv1) GetHistoricalLocations(qo *odata.QueryOptions, path string) (*entities.ArrayResponse, error) { 25 | hl, count, hasNext, err := a.db.GetHistoricalLocations(qo) 26 | return processHistoricalLocations(a, hl, qo, path, count, hasNext, err) 27 | } 28 | 29 | // GetHistoricalLocationsByLocation retrieves all HistoricalLocations linked to a given location 30 | func (a *APIv1) GetHistoricalLocationsByLocation(locationID interface{}, qo *odata.QueryOptions, path string) (*entities.ArrayResponse, error) { 31 | hl, count, hasNext, err := a.db.GetHistoricalLocationsByLocation(locationID, qo) 32 | return processHistoricalLocations(a, hl, qo, path, count, hasNext, err) 33 | } 34 | 35 | // GetHistoricalLocationsByThing retrieves all HistoricalLocations linked to a given thing 36 | func (a *APIv1) GetHistoricalLocationsByThing(thingID interface{}, qo *odata.QueryOptions, path string) (*entities.ArrayResponse, error) { 37 | hl, count, hasNext, err := a.db.GetHistoricalLocationsByThing(thingID, qo) 38 | return processHistoricalLocations(a, hl, qo, path, count, hasNext, err) 39 | } 40 | 41 | func processHistoricalLocations(a *APIv1, historicalLocations []*entities.HistoricalLocation, qo *odata.QueryOptions, path string, count int, hasNext bool, err error) (*entities.ArrayResponse, error) { 42 | for idx, item := range historicalLocations { 43 | i := *item 44 | a.SetLinks(&i, qo) 45 | historicalLocations[idx] = &i 46 | } 47 | 48 | var data interface{} = historicalLocations 49 | return a.createArrayResponse(count, hasNext, path, qo, data), nil 50 | } 51 | 52 | // PostHistoricalLocation adds a new HistoricalLocation to the database 53 | func (a *APIv1) PostHistoricalLocation(hl *entities.HistoricalLocation) (*entities.HistoricalLocation, []error) { 54 | _, err := containsMandatoryParams(hl) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | l, err2 := a.db.PostHistoricalLocation(hl) 60 | if err2 != nil { 61 | return nil, []error{err2} 62 | } 63 | l.SetAllLinks(a.config.GetExternalServerURI()) 64 | return l, nil 65 | } 66 | 67 | // PutHistoricalLocation adds a new HistoricalLocation to the database 68 | func (a *APIv1) PutHistoricalLocation(id interface{}, hl *entities.HistoricalLocation) (*entities.HistoricalLocation, []error) { 69 | l, err2 := a.db.PutHistoricalLocation(id, hl) 70 | if err2 != nil { 71 | return nil, []error{err2} 72 | } 73 | l.SetAllLinks(a.config.GetExternalServerURI()) 74 | return l, nil 75 | } 76 | 77 | // PatchHistoricalLocation updates the given HistoricalLocation in the database 78 | func (a *APIv1) PatchHistoricalLocation(id interface{}, hl *entities.HistoricalLocation) (*entities.HistoricalLocation, error) { 79 | if hl.Locations != nil || hl.Thing != nil { 80 | return nil, gostErrors.NewBadRequestError(errors.New("Unable to deep patch HistoricalLocation")) 81 | } 82 | 83 | return a.db.PatchHistoricalLocation(id, hl) 84 | } 85 | 86 | // DeleteHistoricalLocation deletes a given HistoricalLocation from the database 87 | func (a *APIv1) DeleteHistoricalLocation(id interface{}) error { 88 | return a.db.DeleteHistoricalLocation(id) 89 | } 90 | -------------------------------------------------------------------------------- /sensorthings/api/observedproperty.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "errors" 5 | 6 | entities "github.com/gost/core" 7 | gostErrors "github.com/gost/server/errors" 8 | "github.com/gost/server/sensorthings/odata" 9 | ) 10 | 11 | // GetObservedProperty todo 12 | func (a *APIv1) GetObservedProperty(id interface{}, qo *odata.QueryOptions, path string) (*entities.ObservedProperty, error) { 13 | op, err := a.db.GetObservedProperty(id, qo) 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | a.SetLinks(op, qo) 19 | return op, nil 20 | } 21 | 22 | // GetObservedPropertyByDatastream todo 23 | func (a *APIv1) GetObservedPropertyByDatastream(datastreamID interface{}, qo *odata.QueryOptions, path string) (*entities.ObservedProperty, error) { 24 | op, err := a.db.GetObservedPropertyByDatastream(datastreamID, qo) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | a.SetLinks(op, qo) 30 | return op, nil 31 | } 32 | 33 | // GetObservedProperties todo 34 | func (a *APIv1) GetObservedProperties(qo *odata.QueryOptions, path string) (*entities.ArrayResponse, error) { 35 | ops, count, hasNext, err := a.db.GetObservedProperties(qo) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | for idx, item := range ops { 41 | i := *item 42 | a.SetLinks(&i, qo) 43 | ops[idx] = &i 44 | } 45 | 46 | var data interface{} = ops 47 | return a.createArrayResponse(count, hasNext, path, qo, data), nil 48 | } 49 | 50 | // PostObservedProperty todo 51 | func (a *APIv1) PostObservedProperty(op *entities.ObservedProperty) (*entities.ObservedProperty, []error) { 52 | _, err := containsMandatoryParams(op) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | nop, err2 := a.db.PostObservedProperty(op) 58 | if err2 != nil { 59 | return nil, []error{err2} 60 | } 61 | 62 | nop.SetAllLinks(a.config.GetExternalServerURI()) 63 | 64 | return nop, nil 65 | } 66 | 67 | // PatchObservedProperty patches a given ObservedProperty 68 | func (a *APIv1) PatchObservedProperty(id interface{}, op *entities.ObservedProperty) (*entities.ObservedProperty, error) { 69 | if op.Datastreams != nil { 70 | return nil, gostErrors.NewBadRequestError(errors.New("Unable to deep patch ObservedProperty")) 71 | } 72 | 73 | return a.db.PatchObservedProperty(id, op) 74 | } 75 | 76 | // PutObservedProperty patches a given ObservedProperty 77 | func (a *APIv1) PutObservedProperty(id interface{}, op *entities.ObservedProperty) (*entities.ObservedProperty, []error) { 78 | nop, err2 := a.db.PutObservedProperty(id, op) 79 | if err2 != nil { 80 | return nil, []error{err2} 81 | } 82 | 83 | nop.SetAllLinks(a.config.GetExternalServerURI()) 84 | 85 | return nop, nil 86 | } 87 | 88 | // DeleteObservedProperty deletes a given ObservedProperty from the database 89 | func (a *APIv1) DeleteObservedProperty(id interface{}) error { 90 | return a.db.DeleteObservedProperty(id) 91 | } 92 | -------------------------------------------------------------------------------- /sensorthings/api/sensor.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "errors" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/odata" 8 | 9 | gostErrors "github.com/gost/server/errors" 10 | ) 11 | 12 | // GetSensor retrieves a sensor by id and given query 13 | func (a *APIv1) GetSensor(id interface{}, qo *odata.QueryOptions, path string) (*entities.Sensor, error) { 14 | s, err := a.db.GetSensor(id, qo) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | a.SetLinks(s, qo) 20 | return s, nil 21 | } 22 | 23 | // GetSensorByDatastream retrieves a sensor by given datastream 24 | func (a *APIv1) GetSensorByDatastream(id interface{}, qo *odata.QueryOptions, path string) (*entities.Sensor, error) { 25 | s, err := a.db.GetSensorByDatastream(id, qo) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | a.SetLinks(s, qo) 31 | return s, nil 32 | } 33 | 34 | // GetSensors retrieves an array of sensors based on the given query 35 | func (a *APIv1) GetSensors(qo *odata.QueryOptions, path string) (*entities.ArrayResponse, error) { 36 | sensors, count, hasNext, err := a.db.GetSensors(qo) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | for idx, item := range sensors { 42 | i := *item 43 | a.SetLinks(&i, qo) 44 | sensors[idx] = &i 45 | } 46 | 47 | var data interface{} = sensors 48 | return a.createArrayResponse(count, hasNext, path, qo, data), nil 49 | } 50 | 51 | // PostSensor adds a new sensor to the database 52 | func (a *APIv1) PostSensor(sensor *entities.Sensor) (*entities.Sensor, []error) { 53 | _, err := containsMandatoryParams(sensor) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | if (sensor.EncodingType != entities.EncodingPDF.Value) && (sensor.EncodingType != entities.EncodingSensorML.Value) { 59 | err := errors.New("Encoding not supported. Supported encoding: " + entities.EncodingPDF.Value + ", " + entities.EncodingSensorML.Value) 60 | return nil, []error{err} 61 | } 62 | 63 | ns, err2 := a.db.PostSensor(sensor) 64 | if err2 != nil { 65 | return nil, []error{err2} 66 | } 67 | 68 | ns.SetAllLinks(a.config.GetExternalServerURI()) 69 | 70 | return ns, nil 71 | } 72 | 73 | // PatchSensor updates a sensor in the database 74 | func (a *APIv1) PatchSensor(id interface{}, sensor *entities.Sensor) (*entities.Sensor, error) { 75 | if sensor.Datastreams != nil { 76 | return nil, gostErrors.NewBadRequestError(errors.New("Unable to deep patch Sensor")) 77 | } 78 | 79 | if len(sensor.EncodingType) != 0 { 80 | if (sensor.EncodingType != entities.EncodingPDF.Value) && (sensor.EncodingType != entities.EncodingSensorML.Value) { 81 | err := errors.New("Encoding not supported. Supported encoding: " + entities.EncodingPDF.Value + ", " + entities.EncodingSensorML.Value) 82 | return nil, err 83 | } 84 | } 85 | 86 | return a.db.PatchSensor(id, sensor) 87 | } 88 | 89 | // PutSensor updates the given thing in the database 90 | func (a *APIv1) PutSensor(id interface{}, sensor *entities.Sensor) (*entities.Sensor, []error) { 91 | var err error 92 | putsensor, err := a.db.PutSensor(id, sensor) 93 | if err != nil { 94 | return nil, []error{err} 95 | } 96 | 97 | putsensor.SetAllLinks(a.config.GetExternalServerURI()) 98 | return putsensor, nil 99 | } 100 | 101 | // DeleteSensor deletes a sensor from the database by given sensor id 102 | func (a *APIv1) DeleteSensor(id interface{}) error { 103 | return a.db.DeleteSensor(id) 104 | } 105 | -------------------------------------------------------------------------------- /sensorthings/api/thing_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | -------------------------------------------------------------------------------- /sensorthings/mqtt/config.go: -------------------------------------------------------------------------------- 1 | package mqtt 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gost/server/sensorthings/models" 7 | ) 8 | 9 | // CreateTopics creates the pre-defined MQTT Topics 10 | func CreateTopics(prefix string) []models.Topic { 11 | topics := []models.Topic{ 12 | { 13 | Path: fmt.Sprintf("%s/#", prefix), 14 | Handler: MainMqttHandler, 15 | }, 16 | } 17 | 18 | return topics 19 | } 20 | -------------------------------------------------------------------------------- /sensorthings/mqtt/config_test.go: -------------------------------------------------------------------------------- 1 | package mqtt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCreateTopics(t *testing.T) { 10 | // arrange 11 | // act 12 | topics := CreateTopics("GOST") 13 | // assert 14 | assert.True(t, len(topics) > 0, "Must have more than zero topics") 15 | } 16 | -------------------------------------------------------------------------------- /sensorthings/mqtt/handlers.go: -------------------------------------------------------------------------------- 1 | package mqtt 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | entities "github.com/gost/core" 8 | "github.com/gost/server/sensorthings/models" 9 | ) 10 | 11 | var topics = map[string]models.MQTTInternalHandler{ 12 | "Datastreams()/Observations": observationsByDatastream, 13 | } 14 | 15 | // MainMqttHandler handles all messages on GOST/# and maps them to the appropriate 16 | // handler. Mapping is needed because of the ODATA (id) format 17 | func MainMqttHandler(a *models.API, prefix, topic string, message []byte) { 18 | topic = strings.Replace(topic, fmt.Sprintf("%s/", prefix), "", 1) 19 | topicMapName := "" 20 | id := "" 21 | if strings.Contains(topic, "(") { 22 | i := strings.Index(topic, "(") 23 | i2 := strings.Index(topic, ")") 24 | first := topic[0 : i+1] 25 | id = topic[i+1 : i2] 26 | last := topic[i2:] 27 | topicMapName = first + last 28 | } 29 | 30 | h := topics[topicMapName] 31 | if h != nil { 32 | h(a, message, id) 33 | } 34 | } 35 | 36 | func observationsByDatastream(a *models.API, message []byte, id string) { 37 | o := entities.Observation{} 38 | err := o.ParseEntity(message) 39 | if err != nil { 40 | return 41 | } 42 | 43 | api := *a 44 | api.PostObservationByDatastream(id, &o) 45 | } 46 | -------------------------------------------------------------------------------- /sensorthings/odata/queryvalidator.go: -------------------------------------------------------------------------------- 1 | package odata 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | var keywords = []string{"$filter", "$select", "$expand", "$orderby", "$top", "$skip", "$count"} 8 | 9 | func partHasKeyword(part string) bool { 10 | part1 := strings.ToLower(strings.Split(part, "=")[0]) 11 | decoded := strings.Replace(part1, "%24", "$", -1) 12 | 13 | for _, kw := range keywords { 14 | if decoded == kw { 15 | return true 16 | } 17 | } 18 | return false 19 | } 20 | 21 | // IsValidOdataQuery checks a querystring for containing correct keywords 22 | func IsValidOdataQuery(query string) bool { 23 | res := true 24 | parts := strings.Split(query, "&") 25 | for _, element := range parts { 26 | if !partHasKeyword(element) { 27 | return false 28 | } 29 | } 30 | return res 31 | } 32 | -------------------------------------------------------------------------------- /sensorthings/odata/queryvalidator_test.go: -------------------------------------------------------------------------------- 1 | package odata 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestQueryValidator(t *testing.T) { 11 | // arrange 12 | assert.Equal(t, true, IsValidOdataQuery("$filter=name eq 'ho'")) 13 | assert.Equal(t, true, IsValidOdataQuery(fmt.Sprintf("%sfilter=name eq 'ho'", "%24"))) 14 | assert.Equal(t, false, IsValidOdataQuery("$notexisting=name eq 'ho'")) 15 | } 16 | -------------------------------------------------------------------------------- /sensorthings/rest/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | entities "github.com/gost/core" 5 | "github.com/gost/server/sensorthings/models" 6 | ) 7 | 8 | // Endpoints contains the current set-up of the endpoints 9 | var Endpoints = map[entities.EntityType]models.Endpoint{} 10 | 11 | // CreateEndPoints creates the pre-defined endpoint config, the config contains all endpoint info 12 | // describing the SupportedQueryOptions (if needed) and EndpointOperation for each endpoint 13 | // parameter externalURL is the URL where the GOST service can be reached, main endpoint urls 14 | // are generated based upon this URL 15 | func CreateEndPoints(externalURL string) map[entities.EntityType]models.Endpoint { 16 | Endpoints = map[entities.EntityType]models.Endpoint{ 17 | entities.EntityTypeVersion: CreateVersionEndpoint(externalURL), 18 | entities.EntityTypeUnknown: CreateRootEndpoint(externalURL), 19 | entities.EntityTypeThing: CreateThingsEndpoint(externalURL), 20 | entities.EntityTypeDatastream: CreateDatastreamsEndpoint(externalURL), 21 | entities.EntityTypeObservedProperty: CreateObservedPropertiesEndpoint(externalURL), 22 | entities.EntityTypeLocation: CreateLocationsEndpoint(externalURL), 23 | entities.EntityTypeSensor: CreateSensorsEndpoint(externalURL), 24 | entities.EntityTypeObservation: CreateObservationsEndpoint(externalURL), 25 | entities.EntityTypeFeatureOfInterest: CreateFeaturesOfInterestEndpoint(externalURL), 26 | entities.EntityTypeHistoricalLocation: CreateHistoricalLocationsEndpoint(externalURL), 27 | entities.EntityTypeCreateObservations: CreateCreateObservationsEndpoint(externalURL), 28 | } 29 | 30 | return Endpoints 31 | } 32 | -------------------------------------------------------------------------------- /sensorthings/rest/config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestCreateEndPoints(t *testing.T) { 12 | //arrange 13 | endpoints := CreateEndPoints("http://test.com") 14 | 15 | //assert 16 | assert.Equal(t, 11, len(endpoints)) 17 | } 18 | 19 | func TestCreateEndPointVersion(t *testing.T) { 20 | //arrange 21 | ve := CreateVersionEndpoint("http://test.com") 22 | 23 | //assert 24 | containsVersionPath := containsEndpoint("version", ve.Operations) 25 | assert.Equal(t, true, containsVersionPath, "Version endpoint needs to contain an endpoint containing the path Version") 26 | } 27 | 28 | func containsEndpoint(epName string, eps []models.EndpointOperation) bool { 29 | for _, o := range eps { 30 | if strings.Contains(o.Path, epName) { 31 | return true 32 | } 33 | } 34 | 35 | return false 36 | } 37 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_create_observations.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gost/server/sensorthings/models" 7 | "github.com/gost/server/sensorthings/rest/endpoint" 8 | "github.com/gost/server/sensorthings/rest/handlers" 9 | ) 10 | 11 | // CreateCreateObservationsEndpoint constructs the CreateObservations endpoint configuration 12 | func CreateCreateObservationsEndpoint(externalURL string) *endpoint.Endpoint { 13 | return &endpoint.Endpoint{ 14 | Name: "CreateObservations", 15 | OutputInfo: false, 16 | URL: fmt.Sprintf("%s/%s/%s", externalURL, models.APIPrefix, fmt.Sprintf("%v", "CreateObservations")), 17 | Operations: []models.EndpointOperation{ 18 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/createobservations", Handler: handlers.HandlePostCreateObservations}, 19 | }, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_datastream_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestGetEndPointDatastream(t *testing.T) { 9 | // arrange 10 | ep := CreateDatastreamsEndpoint("http://www.nu.nl") 11 | ep.Name = "yo" 12 | 13 | // assert 14 | assert.True(t, ep != nil) 15 | assert.True(t, ep.GetName() == "yo") 16 | } 17 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_featureofinterest.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/rest/endpoint" 9 | "github.com/gost/server/sensorthings/rest/handlers" 10 | ) 11 | 12 | // CreateFeaturesOfInterestEndpoint constructs the featuresOfInterest endpoint configuration 13 | func CreateFeaturesOfInterestEndpoint(externalURL string) *endpoint.Endpoint { 14 | return &endpoint.Endpoint{ 15 | Name: "FeaturesOfInterest", 16 | EntityType: entities.EntityTypeFeatureOfInterest, 17 | OutputInfo: true, 18 | URL: fmt.Sprintf("%s/%s/%s", externalURL, models.APIPrefix, fmt.Sprintf("%v", "FeaturesOfInterest")), 19 | SupportedExpandParams: []string{ 20 | "observations", 21 | }, 22 | SupportedSelectParams: []string{ 23 | "id", 24 | "name", 25 | "description", 26 | "encodingtype", 27 | "feature", 28 | "observations", 29 | }, 30 | Operations: []models.EndpointOperation{ 31 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/featuresofinterest", Handler: handlers.HandleGetFeatureOfInterests}, 32 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/featuresofinterest{id}", Handler: handlers.HandleGetFeatureOfInterest}, 33 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/featuresofinterest{id}/{params}", Handler: handlers.HandleGetFeatureOfInterest}, 34 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/featuresofinterest{id}/{params}/$value", Handler: handlers.HandleGetFeatureOfInterest}, 35 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/featuresofinterest/{params}", Handler: handlers.HandleGetFeatureOfInterests}, 36 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/observations{id}/featureofinterest", Handler: handlers.HandleGetFeatureOfInterestByObservation}, 37 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/observations{id}/featureofinterest/{params}", Handler: handlers.HandleGetFeatureOfInterestByObservation}, 38 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/observations{id}/featureofinterest/{params}/$value", Handler: handlers.HandleGetFeatureOfInterestByObservation}, 39 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/featuresofinterest", Handler: handlers.HandlePostFeatureOfInterest}, 40 | {OperationType: models.HTTPOperationDelete, Path: "/v1.0/featuresofinterest{id}", Handler: handlers.HandleDeleteFeatureOfInterest}, 41 | {OperationType: models.HTTPOperationPatch, Path: "/v1.0/featuresofinterest{id}", Handler: handlers.HandlePatchFeatureOfInterest}, 42 | {OperationType: models.HTTPOperationPut, Path: "/v1.0/featuresofinterest{id}", Handler: handlers.HandlePutFeatureOfInterest}, 43 | 44 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/featuresofinterest", Handler: handlers.HandleGetFeatureOfInterests}, 45 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/featuresofinterest{id}", Handler: handlers.HandleGetFeatureOfInterest}, 46 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/featuresofinterest{id}/{params}", Handler: handlers.HandleGetFeatureOfInterest}, 47 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/featuresofinterest{id}/{params}/$value", Handler: handlers.HandleGetFeatureOfInterest}, 48 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/observations{id}/featureofinterest", Handler: handlers.HandleGetFeatureOfInterestByObservation}, 49 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/observations{id}/featureofinterest/{params}", Handler: handlers.HandleGetFeatureOfInterestByObservation}, 50 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/observations{id}/featureofinterest/{params}/$value", Handler: handlers.HandleGetFeatureOfInterestByObservation}, 51 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/featuresofinterest/{params}", Handler: handlers.HandleGetFeatureOfInterests}, 52 | 53 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/{c:.*}/featuresofinterest", Handler: handlers.HandlePostFeatureOfInterest}, 54 | {OperationType: models.HTTPOperationDelete, Path: "/v1.0/{c:.*}/featuresofinterest{id}", Handler: handlers.HandleDeleteFeatureOfInterest}, 55 | {OperationType: models.HTTPOperationPatch, Path: "/v1.0/{c:.*}/featuresofinterest{id}", Handler: handlers.HandlePatchFeatureOfInterest}, 56 | {OperationType: models.HTTPOperationPut, Path: "/v1.0/{c:.*}/featuresofinterest{id}", Handler: handlers.HandlePutFeatureOfInterest}, 57 | }, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_featureofinterest_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestGetEndPointFoi(t *testing.T) { 9 | // arrange 10 | ep := CreateFeaturesOfInterestEndpoint("http://www.nu.nl") 11 | ep.Name = "yo" 12 | 13 | // assert 14 | assert.True(t, ep != nil) 15 | assert.True(t, ep.GetName() == "yo") 16 | } 17 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_historicallocation.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/rest/endpoint" 9 | "github.com/gost/server/sensorthings/rest/handlers" 10 | ) 11 | 12 | // CreateHistoricalLocationsEndpoint constructs the HistoricalLocations endpoint configuration 13 | func CreateHistoricalLocationsEndpoint(externalURL string) *endpoint.Endpoint { 14 | return &endpoint.Endpoint{ 15 | Name: "HistoricalLocations", 16 | EntityType: entities.EntityTypeHistoricalLocation, 17 | OutputInfo: true, 18 | URL: fmt.Sprintf("%s/%s/%s", externalURL, models.APIPrefix, fmt.Sprintf("%v", "HistoricalLocations")), 19 | SupportedExpandParams: []string{ 20 | "locations", 21 | "thing", 22 | }, 23 | SupportedSelectParams: []string{ 24 | "id", 25 | "time", 26 | }, 27 | Operations: []models.EndpointOperation{ 28 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/historicallocations", Handler: handlers.HandleGetHistoricalLocations}, 29 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/historicallocations{id}", Handler: handlers.HandleGetHistoricalLocation}, 30 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/things{id}/historicallocations", Handler: handlers.HandleGetHistoricalLocationsByThing}, 31 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/things{id}/historicallocations/{params}", Handler: handlers.HandleGetHistoricalLocationsByThing}, 32 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/locations{id}/historicallocations", Handler: handlers.HandleGetHistoricalLocationsByLocation}, 33 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/locations{id}/historicallocations/{params}", Handler: handlers.HandleGetHistoricalLocationsByLocation}, 34 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/locations{id}/historicallocations/{params}/$value", Handler: handlers.HandleGetHistoricalLocationsByLocation}, 35 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/historicallocations{id}/{params}", Handler: handlers.HandleGetHistoricalLocation}, 36 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/historicallocations{id}/{params}/$value", Handler: handlers.HandleGetHistoricalLocation}, 37 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/historicallocations/{params}", Handler: handlers.HandleGetHistoricalLocations}, 38 | 39 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/historicallocations", Handler: handlers.HandlePostHistoricalLocation}, 40 | {OperationType: models.HTTPOperationDelete, Path: "/v1.0/historicallocations{id}", Handler: handlers.HandleDeleteHistoricalLocations}, 41 | {OperationType: models.HTTPOperationPatch, Path: "/v1.0/historicallocations{id}", Handler: handlers.HandlePatchHistoricalLocations}, 42 | {OperationType: models.HTTPOperationPut, Path: "/v1.0/historicallocations{id}", Handler: handlers.HandlePutHistoricalLocation}, 43 | 44 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/historicallocations", Handler: handlers.HandleGetHistoricalLocations}, 45 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/historicallocations{id}", Handler: handlers.HandleGetHistoricalLocation}, 46 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/things{id}/historicallocations", Handler: handlers.HandleGetHistoricalLocationsByThing}, 47 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/things{id}/historicallocations/{params}", Handler: handlers.HandleGetHistoricalLocationsByThing}, 48 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/locations{id}/historicallocations", Handler: handlers.HandleGetHistoricalLocationsByLocation}, 49 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/locations{id}/historicallocations/{params}", Handler: handlers.HandleGetHistoricalLocationsByLocation}, 50 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/locations{id}/historicallocations/{params}/$value", Handler: handlers.HandleGetHistoricalLocationsByLocation}, 51 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/historicallocations{id}/{params}", Handler: handlers.HandleGetHistoricalLocation}, 52 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/historicallocations{id}/{params}/$value", Handler: handlers.HandleGetHistoricalLocation}, 53 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/historicallocations/{params}", Handler: handlers.HandleGetHistoricalLocations}, 54 | 55 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/{c:.*}/historicallocations", Handler: handlers.HandlePostHistoricalLocation}, 56 | {OperationType: models.HTTPOperationDelete, Path: "/v1.0/{c:.*}/historicallocations{id}", Handler: handlers.HandleDeleteHistoricalLocations}, 57 | {OperationType: models.HTTPOperationPatch, Path: "/v1.0/{c:.*}/historicallocations{id}", Handler: handlers.HandlePatchHistoricalLocations}, 58 | {OperationType: models.HTTPOperationPut, Path: "/v1.0/{c:.*}/historicallocations{id}", Handler: handlers.HandlePutHistoricalLocation}, 59 | }, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_historicallocation_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestGetEndPointHistoricalLocation(t *testing.T) { 9 | // arrange 10 | ep := CreateHistoricalLocationsEndpoint("http://www.nu.nl") 11 | ep.Name = "yo" 12 | 13 | // assert 14 | assert.True(t, ep != nil) 15 | assert.True(t, ep.GetName() == "yo") 16 | } 17 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_location.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/rest/endpoint" 9 | "github.com/gost/server/sensorthings/rest/handlers" 10 | ) 11 | 12 | // CreateLocationsEndpoint constructs the Locations endpoint configuration 13 | func CreateLocationsEndpoint(externalURL string) *endpoint.Endpoint { 14 | return &endpoint.Endpoint{ 15 | Name: "Locations", 16 | EntityType: entities.EntityTypeLocation, 17 | OutputInfo: true, 18 | URL: fmt.Sprintf("%s/%s/%s", externalURL, models.APIPrefix, fmt.Sprintf("%v", "Locations")), 19 | SupportedExpandParams: []string{ 20 | "things", 21 | "historicallocations", 22 | }, 23 | SupportedSelectParams: []string{ 24 | "id", 25 | "name", 26 | "description", 27 | "encodingtype", 28 | "location", 29 | "things", 30 | "historicallocations", 31 | }, 32 | Operations: []models.EndpointOperation{ 33 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/locations", Handler: handlers.HandleGetLocations}, 34 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/locations{id}", Handler: handlers.HandleGetLocation}, 35 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/historicallocations{id}/locations", Handler: handlers.HandleGetLocationsByHistoricalLocations}, 36 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/historicallocations{id}/locations/{params}", Handler: handlers.HandleGetLocationsByHistoricalLocations}, 37 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/locations{id}/{params}", Handler: handlers.HandleGetLocation}, 38 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/locations{id}/{params}/$value", Handler: handlers.HandleGetLocation}, 39 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/things{id}/locations", Handler: handlers.HandleGetLocationsByThing}, 40 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/things{id}/locations/{params}", Handler: handlers.HandleGetLocationsByThing}, 41 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/locations/{params}", Handler: handlers.HandleGetLocations}, 42 | 43 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/locations", Handler: handlers.HandlePostLocation}, 44 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/things{id}/locations", Handler: handlers.HandlePostLocationByThing}, 45 | {OperationType: models.HTTPOperationDelete, Path: "/v1.0/locations{id}", Handler: handlers.HandleDeleteLocation}, 46 | {OperationType: models.HTTPOperationPatch, Path: "/v1.0/locations{id}", Handler: handlers.HandlePatchLocation}, 47 | {OperationType: models.HTTPOperationPut, Path: "/v1.0/locations{id}", Handler: handlers.HandlePutLocation}, 48 | 49 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/locations", Handler: handlers.HandleGetLocations}, 50 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/locations{id}", Handler: handlers.HandleGetLocation}, 51 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/historicallocations{id}/locations", Handler: handlers.HandleGetLocationsByHistoricalLocations}, 52 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/things{id}/locations", Handler: handlers.HandleGetLocationsByThing}, 53 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/things{id}/locations/{params}", Handler: handlers.HandleGetLocationsByThing}, 54 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/historicallocations{id}/locations/{params}", Handler: handlers.HandleGetLocationsByHistoricalLocations}, 55 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/locations{id}/{params}", Handler: handlers.HandleGetLocation}, 56 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/locations{id}/{params}/$value", Handler: handlers.HandleGetLocation}, 57 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/locations/{params}", Handler: handlers.HandleGetLocations}, 58 | 59 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/{c:.*}/locations", Handler: handlers.HandlePostLocation}, 60 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/{c:.*}/things{id}/locations", Handler: handlers.HandlePostLocationByThing}, 61 | {OperationType: models.HTTPOperationDelete, Path: "/v1.0/{c:.*}/locations{id}", Handler: handlers.HandleDeleteLocation}, 62 | {OperationType: models.HTTPOperationPatch, Path: "/v1.0/{c:.*}/locations{id}", Handler: handlers.HandlePatchLocation}, 63 | {OperationType: models.HTTPOperationPut, Path: "/v1.0/{c:.*}/locations{id}", Handler: handlers.HandlePutLocation}, 64 | }, 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_location_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestGetEndPointLocation(t *testing.T) { 9 | // arrange 10 | ep := CreateLocationsEndpoint("http://www.nu.nl") 11 | ep.Name = "yo" 12 | 13 | // assert 14 | assert.True(t, ep != nil) 15 | assert.True(t, ep.GetName() == "yo") 16 | } 17 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_observation.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/rest/endpoint" 9 | "github.com/gost/server/sensorthings/rest/handlers" 10 | ) 11 | 12 | // CreateObservationsEndpoint constructs the Observations endpoint configuration 13 | func CreateObservationsEndpoint(externalURL string) *endpoint.Endpoint { 14 | return &endpoint.Endpoint{ 15 | Name: "Observations", 16 | EntityType: entities.EntityTypeObservation, 17 | OutputInfo: true, 18 | URL: fmt.Sprintf("%s/%s/%s", externalURL, models.APIPrefix, fmt.Sprintf("%v", "Observations")), 19 | SupportedExpandParams: []string{ 20 | "datastream", 21 | "featureofinterest", 22 | }, 23 | SupportedSelectParams: []string{ 24 | "id", 25 | "result", 26 | "phenomenontime", 27 | "resulttime", 28 | "resultquality", 29 | "validtime", 30 | "parameters", 31 | "datastream", 32 | "featureofinterest", 33 | }, 34 | Operations: []models.EndpointOperation{ 35 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/observations", Handler: handlers.HandleGetObservations}, 36 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/observations{id}", Handler: handlers.HandleGetObservation}, 37 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/datastreams{id}/observations", Handler: handlers.HandleGetObservationsByDatastream}, 38 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/datastreams{id}/observations/{params}", Handler: handlers.HandleGetObservationsByDatastream}, 39 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/featureofinterest{id}/observations", Handler: handlers.HandleGetObservationsByFeatureOfInterest}, 40 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/featuresofinterest{id}/observations", Handler: handlers.HandleGetObservationsByFeatureOfInterest}, 41 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/featureofinterest{id}/observations/{params}", Handler: handlers.HandleGetObservationsByFeatureOfInterest}, 42 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/featuresofinterest{id}/observations/{params}", Handler: handlers.HandleGetObservationsByFeatureOfInterest}, 43 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/observations{id}/{params}", Handler: handlers.HandleGetObservation}, 44 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/observations{id}/{params}/$value", Handler: handlers.HandleGetObservation}, 45 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/observations/{params}", Handler: handlers.HandleGetObservations}, 46 | 47 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/observations", Handler: handlers.HandlePostObservation}, 48 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/datastreams{id}/observations", Handler: handlers.HandlePostObservationByDatastream}, 49 | {OperationType: models.HTTPOperationDelete, Path: "/v1.0/observations{id}", Handler: handlers.HandleDeleteObservation}, 50 | {OperationType: models.HTTPOperationPatch, Path: "/v1.0/observations{id}", Handler: handlers.HandlePatchObservation}, 51 | {OperationType: models.HTTPOperationPut, Path: "/v1.0/observations{id}", Handler: handlers.HandlePutObservation}, 52 | 53 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/observations", Handler: handlers.HandleGetObservations}, 54 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/observations{id}", Handler: handlers.HandleGetObservation}, 55 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/featuresofinterest{id}/observations", Handler: handlers.HandleGetObservationsByFeatureOfInterest}, 56 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/featuresofinterest{id}/observations/{params}", Handler: handlers.HandleGetObservationsByFeatureOfInterest}, 57 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/datastreams{id}/observations", Handler: handlers.HandleGetObservationsByDatastream}, 58 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/datastreams{id}/observations/{params}", Handler: handlers.HandleGetObservationsByDatastream}, 59 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/observations{id}/{params}", Handler: handlers.HandleGetObservation}, 60 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/observations{id}/{params}/$value", Handler: handlers.HandleGetObservation}, 61 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/observations/{params}", Handler: handlers.HandleGetObservations}, 62 | 63 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/{c:.*}/observations", Handler: handlers.HandlePostObservation}, 64 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/{c:.*}/datastreams{id}/observations", Handler: handlers.HandlePostObservationByDatastream}, 65 | {OperationType: models.HTTPOperationDelete, Path: "/v1.0/{c:.*}/observations{id}", Handler: handlers.HandleDeleteObservation}, 66 | {OperationType: models.HTTPOperationPatch, Path: "/v1.0/{c:.*}/observations{id}", Handler: handlers.HandlePatchObservation}, 67 | {OperationType: models.HTTPOperationPut, Path: "/v1.0/{c:.*}/observations{id}", Handler: handlers.HandlePutObservation}, 68 | }, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_observation_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestGetEndPointObservation(t *testing.T) { 9 | // arrange 10 | ep := CreateObservationsEndpoint("http://www.nu.nl") 11 | ep.Name = "yo" 12 | 13 | // assert 14 | assert.True(t, ep != nil) 15 | assert.True(t, ep.GetName() == "yo") 16 | } 17 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_observedproperties.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/rest/endpoint" 9 | "github.com/gost/server/sensorthings/rest/handlers" 10 | ) 11 | 12 | // CreateObservedPropertiesEndpoint constructs the ObservedProperties endpoint configuration 13 | func CreateObservedPropertiesEndpoint(externalURL string) *endpoint.Endpoint { 14 | return &endpoint.Endpoint{ 15 | Name: "ObservedProperties", 16 | EntityType: entities.EntityTypeObservedProperty, 17 | OutputInfo: true, 18 | URL: fmt.Sprintf("%s/%s/%s", externalURL, models.APIPrefix, fmt.Sprintf("%v", "ObservedProperties")), 19 | SupportedExpandParams: []string{ 20 | "datastreams", 21 | }, 22 | SupportedSelectParams: []string{ 23 | "id", 24 | "name", 25 | "definition", 26 | "description", 27 | "datastreams", 28 | }, 29 | Operations: []models.EndpointOperation{ 30 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/observedproperties", Handler: handlers.HandleGetObservedProperties}, 31 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/observedproperties{id}", Handler: handlers.HandleGetObservedProperty}, 32 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/datastreams{id}/observedproperty", Handler: handlers.HandleGetObservedPropertyByDatastream}, 33 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/datastreams{id}/observedproperty/{params}", Handler: handlers.HandleGetObservedPropertyByDatastream}, 34 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/observedproperties{id}/{params}", Handler: handlers.HandleGetObservedProperty}, 35 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/observedproperties{id}/{params}/$value", Handler: handlers.HandleGetObservedProperty}, 36 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/observedproperties/{params}", Handler: handlers.HandleGetObservedProperties}, 37 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/observedproperties/{params}/$value", Handler: handlers.HandleGetObservedProperties}, 38 | 39 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/observedproperties", Handler: handlers.HandlePostObservedProperty}, 40 | {OperationType: models.HTTPOperationDelete, Path: "/v1.0/observedproperties{id}", Handler: handlers.HandleDeleteObservedProperty}, 41 | {OperationType: models.HTTPOperationPatch, Path: "/v1.0/observedproperties{id}", Handler: handlers.HandlePatchObservedProperty}, 42 | {OperationType: models.HTTPOperationPut, Path: "/v1.0/observedproperties{id}", Handler: handlers.HandlePutObservedProperty}, 43 | 44 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/observedproperties", Handler: handlers.HandleGetObservedProperties}, 45 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/observedproperties{id}", Handler: handlers.HandleGetObservedProperty}, 46 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/datastreams{id}/observedproperty", Handler: handlers.HandleGetObservedPropertyByDatastream}, 47 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/datastreams{id}/observedproperty/{params}", Handler: handlers.HandleGetObservedPropertyByDatastream}, 48 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/observedproperties{id}/{params}", Handler: handlers.HandleGetObservedProperty}, 49 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/observedproperties{id}/{params}/$value", Handler: handlers.HandleGetObservedProperty}, 50 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/observedproperties/{params}", Handler: handlers.HandleGetObservedProperties}, 51 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/observedproperties/{params}/$value", Handler: handlers.HandleGetObservedProperties}, 52 | 53 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/{c:.*}/observedproperties", Handler: handlers.HandlePostObservedProperty}, 54 | {OperationType: models.HTTPOperationDelete, Path: "/v1.0/{c:.*}/observedproperties{id}", Handler: handlers.HandleDeleteObservedProperty}, 55 | {OperationType: models.HTTPOperationPatch, Path: "/v1.0/{c:.*}/observedproperties{id}", Handler: handlers.HandlePatchObservedProperty}, 56 | {OperationType: models.HTTPOperationPut, Path: "/v1.0/{c:.*}/observedproperties{id}", Handler: handlers.HandlePutObservedProperty}, 57 | }, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_observedproperties_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestGetEndPointObservedProperties(t *testing.T) { 9 | // arrange 10 | ep := CreateObservedPropertiesEndpoint("http://www.nu.nl") 11 | ep.Name = "yo" 12 | 13 | // assert 14 | assert.True(t, ep != nil) 15 | assert.True(t, ep.GetName() == "yo") 16 | } 17 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_root.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gost/server/sensorthings/models" 7 | "github.com/gost/server/sensorthings/rest/endpoint" 8 | "github.com/gost/server/sensorthings/rest/handlers" 9 | ) 10 | 11 | // CreateRootEndpoint creates the Root endpoint configuration 12 | func CreateRootEndpoint(externalURL string) *endpoint.Endpoint { 13 | return &endpoint.Endpoint{ 14 | Name: "Root", 15 | OutputInfo: false, 16 | URL: fmt.Sprintf("%s/%s", externalURL, "v1.0"), 17 | Operations: []models.EndpointOperation{ 18 | {OperationType: models.HTTPOperationGet, Path: "/v1.0", Handler: handlers.HandleAPIRoot}, 19 | }, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_root_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestGetEndPointRoot(t *testing.T) { 9 | // arrange 10 | ep := CreateRootEndpoint("http://www.nu.nl") 11 | ep.Name = "yo" 12 | 13 | // assert 14 | assert.True(t, ep != nil) 15 | assert.True(t, ep.GetName() == "yo") 16 | } 17 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_sensor.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/rest/endpoint" 9 | "github.com/gost/server/sensorthings/rest/handlers" 10 | ) 11 | 12 | // CreateSensorsEndpoint creates the Sensors endpoint configuration 13 | func CreateSensorsEndpoint(externalURL string) *endpoint.Endpoint { 14 | return &endpoint.Endpoint{ 15 | Name: "Sensors", 16 | EntityType: entities.EntityTypeSensor, 17 | OutputInfo: true, 18 | URL: fmt.Sprintf("%s/%s/%s", externalURL, models.APIPrefix, fmt.Sprintf("%v", "Sensors")), 19 | SupportedExpandParams: []string{ 20 | "datastreams", 21 | }, 22 | SupportedSelectParams: []string{ 23 | "id", 24 | "name", 25 | "description", 26 | "encodingtype", 27 | "metadata", 28 | "datastreams", 29 | }, 30 | Operations: []models.EndpointOperation{ 31 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/sensors", Handler: handlers.HandleGetSensors}, 32 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/sensors{id}", Handler: handlers.HandleGetSensor}, 33 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/datastreams{id}/sensor", Handler: handlers.HandleGetSensorByDatastream}, 34 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/datastreams{id}/sensor/{params}", Handler: handlers.HandleGetSensorByDatastream}, 35 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/datastreams{id}/sensor/{params}/$value", Handler: handlers.HandleGetSensorByDatastream}, 36 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/sensors{id}/{params}", Handler: handlers.HandleGetSensor}, 37 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/sensors{id}/{params}/$value", Handler: handlers.HandleGetSensor}, 38 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/sensors/{params}", Handler: handlers.HandleGetSensors}, 39 | 40 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/sensors", Handler: handlers.HandlePostSensors}, 41 | {OperationType: models.HTTPOperationDelete, Path: "/v1.0/sensors{id}", Handler: handlers.HandleDeleteSensor}, 42 | {OperationType: models.HTTPOperationPatch, Path: "/v1.0/sensors{id}", Handler: handlers.HandlePatchSensor}, 43 | {OperationType: models.HTTPOperationPut, Path: "/v1.0/sensors{id}", Handler: handlers.HandlePutSensor}, 44 | 45 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/sensors", Handler: handlers.HandleGetSensors}, 46 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/sensors{id}", Handler: handlers.HandleGetSensor}, 47 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/datastreams{id}/sensor", Handler: handlers.HandleGetSensorByDatastream}, 48 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/datastreams{id}/sensor/{params}", Handler: handlers.HandleGetSensorByDatastream}, 49 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/datastreams{id}/sensor/{params}/$value", Handler: handlers.HandleGetSensorByDatastream}, 50 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/sensors{id}/{params}", Handler: handlers.HandleGetSensor}, 51 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/sensors{id}/{params}/$value", Handler: handlers.HandleGetSensor}, 52 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/sensors/{params}", Handler: handlers.HandleGetSensors}, 53 | 54 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/{c:.*}/sensors", Handler: handlers.HandlePostSensors}, 55 | {OperationType: models.HTTPOperationDelete, Path: "/v1.0/{c:.*}/sensors{id}", Handler: handlers.HandleDeleteSensor}, 56 | {OperationType: models.HTTPOperationPatch, Path: "/v1.0/{c:.*}/sensors{id}", Handler: handlers.HandlePatchSensor}, 57 | {OperationType: models.HTTPOperationPut, Path: "/v1.0/{c:.*}/sensors{id}", Handler: handlers.HandlePutSensor}, 58 | }, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_sensor_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestGetEndPointSensor(t *testing.T) { 9 | // arrange 10 | ep := CreateSensorsEndpoint("http://www.nu.nl") 11 | ep.Name = "yo" 12 | 13 | // assert 14 | assert.True(t, ep != nil) 15 | assert.True(t, ep.GetName() == "yo") 16 | } 17 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_thing.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/rest/endpoint" 9 | "github.com/gost/server/sensorthings/rest/handlers" 10 | ) 11 | 12 | // CreateThingsEndpoint creates the Things endpoint configuration 13 | func CreateThingsEndpoint(externalURL string) *endpoint.Endpoint { 14 | return &endpoint.Endpoint{ 15 | Name: "Things", 16 | EntityType: entities.EntityTypeThing, 17 | OutputInfo: true, 18 | URL: fmt.Sprintf("%s/%s/%s", externalURL, models.APIPrefix, fmt.Sprintf("%v", "Things")), 19 | SupportedExpandParams: []string{ 20 | "locations", 21 | "datastreams", 22 | "historicallocations", 23 | }, 24 | SupportedSelectParams: []string{ 25 | "id", 26 | "name", 27 | "properties", 28 | "description", 29 | "Locations", 30 | "datastreams", 31 | "historicallocations", 32 | }, 33 | Operations: []models.EndpointOperation{ 34 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/things", Handler: handlers.HandleGetThings}, 35 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/things{id}", Handler: handlers.HandleGetThing}, 36 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/historicallocations{id}/thing", Handler: handlers.HandleGetThingByHistoricalLocation}, 37 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/historicallocations{id}/thing/{params}", Handler: handlers.HandleGetThingByHistoricalLocation}, 38 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/historicallocations{id}/thing/{params}/$value", Handler: handlers.HandleGetThingByHistoricalLocation}, 39 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/datastreams{id}/thing", Handler: handlers.HandleGetThingByDatastream}, 40 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/datastreams{id}/thing/{params}", Handler: handlers.HandleGetThingByDatastream}, 41 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/datastreams{id}/thing/{params}/$value", Handler: handlers.HandleGetThingByDatastream}, 42 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/locations{id}/things", Handler: handlers.HandleGetThingsByLocation}, 43 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/locations{id}/things/{params}", Handler: handlers.HandleGetThingsByLocation}, 44 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/things{id}/{params}", Handler: handlers.HandleGetThing}, 45 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/things{id}/{params}/$value", Handler: handlers.HandleGetThing}, 46 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/things/{params}", Handler: handlers.HandleGetThings}, 47 | 48 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/things", Handler: handlers.HandlePostThing}, 49 | {OperationType: models.HTTPOperationDelete, Path: "/v1.0/things{id}", Handler: handlers.HandleDeleteThing}, 50 | {OperationType: models.HTTPOperationPatch, Path: "/v1.0/things{id}", Handler: handlers.HandlePatchThing}, 51 | {OperationType: models.HTTPOperationPut, Path: "/v1.0/things{id}", Handler: handlers.HandlePutThing}, 52 | 53 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/things", Handler: handlers.HandleGetThings}, 54 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/things{id}", Handler: handlers.HandleGetThing}, 55 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/locations{id}/things", Handler: handlers.HandleGetThingsByLocation}, 56 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/locations{id}/things/{params}", Handler: handlers.HandleGetThingsByLocation}, 57 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/datastreams{id}/thing", Handler: handlers.HandleGetThingByDatastream}, 58 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/datastreams{id}/thing/{params}", Handler: handlers.HandleGetThingByDatastream}, 59 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/datastreams{id}/thing/{params}/$value", Handler: handlers.HandleGetThingByDatastream}, 60 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/historicallocations{id}/thing", Handler: handlers.HandleGetThingByHistoricalLocation}, 61 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/historicallocations{id}/thing/{params}", Handler: handlers.HandleGetThingByHistoricalLocation}, 62 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/historicallocations{id}/thing/{params}/$value", Handler: handlers.HandleGetThingByHistoricalLocation}, 63 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/things{id}/{params}", Handler: handlers.HandleGetThing}, 64 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/things{id}/{params}/$value", Handler: handlers.HandleGetThing}, 65 | {OperationType: models.HTTPOperationGet, Path: "/v1.0/{c:.*}/things/{params}", Handler: handlers.HandleGetThings}, 66 | 67 | {OperationType: models.HTTPOperationPost, Path: "/v1.0/{c:.*}/things", Handler: handlers.HandlePostThing}, 68 | {OperationType: models.HTTPOperationDelete, Path: "/v1.0/{c:.*}/things{id}", Handler: handlers.HandleDeleteThing}, 69 | {OperationType: models.HTTPOperationPatch, Path: "/v1.0/{c:.*}/things{id}", Handler: handlers.HandlePatchThing}, 70 | {OperationType: models.HTTPOperationPut, Path: "/v1.0/{c:.*}/things{id}", Handler: handlers.HandlePutThing}, 71 | }, 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_thing_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestGetEndPointThing(t *testing.T) { 9 | // arrange 10 | ep := CreateThingsEndpoint("http://www.nu.nl") 11 | ep.Name = "yo" 12 | 13 | // assert 14 | assert.True(t, ep != nil) 15 | assert.True(t, ep.GetName() == "yo") 16 | } 17 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_version.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gost/server/sensorthings/models" 7 | "github.com/gost/server/sensorthings/rest/endpoint" 8 | "github.com/gost/server/sensorthings/rest/handlers" 9 | ) 10 | 11 | // CreateVersionEndpoint creates the Version endpoint configuration 12 | func CreateVersionEndpoint(externalURL string) *endpoint.Endpoint { 13 | return &endpoint.Endpoint{ 14 | Name: "Version", 15 | OutputInfo: false, 16 | URL: fmt.Sprintf("%s/%s", externalURL, "Version"), 17 | Operations: []models.EndpointOperation{ 18 | {OperationType: models.HTTPOperationGet, Path: "/version", Handler: handlers.HandleVersion}, 19 | }, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sensorthings/rest/config/endpoint_version_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestGetEndPointVersion(t *testing.T) { 9 | // arrange 10 | ep := CreateVersionEndpoint("http://www.nu.nl") 11 | ep.Name = "yo" 12 | 13 | // assert 14 | assert.True(t, ep.GetName() == "yo") 15 | } 16 | -------------------------------------------------------------------------------- /sensorthings/rest/endpoint/endpoint.go: -------------------------------------------------------------------------------- 1 | package endpoint 2 | 3 | import ( 4 | entities "github.com/gost/core" 5 | "github.com/gost/server/sensorthings/models" 6 | "sort" 7 | "strings" 8 | ) 9 | 10 | // Endpoint contains all information for creating and handling a main SensorThings endpoint. 11 | // A SensorThings endpoint contains multiple EndpointOperations 12 | // Endpoint can be marshalled to JSON for returning endpoint information requested 13 | // by the user: http://www.sensorup.com/docs/#resource-path 14 | type Endpoint struct { 15 | Name string `json:"name"` // Name of the endpoint 16 | URL string `json:"url"` // External URL to the endpoint 17 | EntityType entities.EntityType `json:"-"` 18 | OutputInfo bool `json:"-"` //Output when BasePathInfo is requested by the user 19 | Operations []models.EndpointOperation `json:"-"` 20 | SupportedExpandParams []string `json:"-"` 21 | SupportedSelectParams []string `json:"-"` 22 | } 23 | 24 | // GetName returns the endpoint name 25 | func (e *Endpoint) GetName() string { 26 | return e.Name 27 | } 28 | 29 | // ShowOutputInfo returns true if the endpoint should output his info when BasePathInfo is requested 30 | func (e *Endpoint) ShowOutputInfo() bool { 31 | return e.OutputInfo 32 | } 33 | 34 | // GetURL returns the external url 35 | func (e *Endpoint) GetURL() string { 36 | return e.URL 37 | } 38 | 39 | // GetOperations returns all operations for this endpoint such as GET, POST 40 | func (e *Endpoint) GetOperations() []models.EndpointOperation { 41 | return e.Operations 42 | } 43 | 44 | // GetSupportedExpandParams returns which entities can be expanded 45 | func (e *Endpoint) GetSupportedExpandParams() []string { 46 | return e.SupportedExpandParams 47 | } 48 | 49 | // GetSupportedSelectParams returns the supported select parameters for this endpoint 50 | func (e *Endpoint) GetSupportedSelectParams() []string { 51 | return e.SupportedSelectParams 52 | } 53 | 54 | // SortedEndpoints is a slice of *EndpointWrapper's, it implements some functions to be able to 55 | // sort the slice 56 | type SortedEndpoints []*EndpointWrapper 57 | 58 | // EndpointWrapper combines a SensorThings endpoint and operation in preparation to add 59 | // it to the router 60 | type EndpointWrapper struct { 61 | Endpoint models.Endpoint 62 | Operation models.EndpointOperation 63 | } 64 | 65 | // Len returns the number of elements in the collection 66 | func (a SortedEndpoints) Len() int { return len(a) } 67 | 68 | // Swap swaps the elements 69 | func (a SortedEndpoints) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 70 | 71 | // Less holds the custom sorting logic 72 | func (a SortedEndpoints) Less(i, j int) bool { 73 | firstDynamic := isDynamic(a[i].Operation.Path) 74 | secondDynamic := isDynamic(a[j].Operation.Path) 75 | 76 | if strings.Contains(a[i].Operation.Path, "{c:.*}") && !strings.Contains(a[j].Operation.Path, "{c:.*}") { 77 | return false 78 | } 79 | 80 | if !strings.Contains(a[i].Operation.Path, "{c:.*}") && strings.Contains(a[j].Operation.Path, "{c:.*}") { 81 | return true 82 | } 83 | 84 | if firstDynamic && !secondDynamic { 85 | return false 86 | } 87 | 88 | if !firstDynamic && secondDynamic { 89 | return true 90 | } 91 | 92 | if firstDynamic && secondDynamic { 93 | dynamicI := strings.Count(a[i].Operation.Path, "{") 94 | dynamicJ := strings.Count(a[j].Operation.Path, "{") 95 | if dynamicI == dynamicJ { 96 | return len(a[i].Operation.Path) > len(a[j].Operation.Path) 97 | } 98 | return strings.Count(a[i].Operation.Path, "{") < strings.Count(a[j].Operation.Path, "{") 99 | } 100 | 101 | if len(a[i].Operation.Path) != len(a[j].Operation.Path) { 102 | return len(a[i].Operation.Path) > len(a[j].Operation.Path) 103 | } 104 | 105 | if a[i].Operation.OperationType != a[j].Operation.OperationType { 106 | return a[i].Operation.OperationType != models.HTTPOperationGet 107 | } 108 | 109 | if a[i].Operation.Path == a[j].Operation.Path { 110 | panic("Two endpoints can't be same") 111 | } 112 | 113 | return true 114 | } 115 | 116 | // EndpointsToSortedList sorts all the endpoints so they can be added 117 | // to the routes in the right order else requests will be picked up by the wrong handlers 118 | func EndpointsToSortedList(endpoints *map[entities.EntityType]models.Endpoint) SortedEndpoints { 119 | eps := SortedEndpoints{} 120 | for _, endpoint := range *endpoints { 121 | for _, op := range endpoint.GetOperations() { 122 | e := &EndpointWrapper{Endpoint: endpoint, Operation: op} 123 | eps = append(eps, e) 124 | } 125 | } 126 | 127 | sort.Sort(eps) 128 | return eps 129 | } 130 | 131 | func isDynamic(url string) bool { 132 | return strings.Contains(url, "{") && strings.Contains(url, "}") 133 | } 134 | -------------------------------------------------------------------------------- /sensorthings/rest/endpoint/endpoint_test.go: -------------------------------------------------------------------------------- 1 | package endpoint 2 | 3 | import ( 4 | "testing" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/stretchr/testify/assert" 9 | "sort" 10 | ) 11 | 12 | func TestEndPointGetNameShouldReturnCorrectName(t *testing.T) { 13 | //arrange 14 | endpoint := Endpoint{} 15 | endpoint.Name = "test" 16 | endpoint.URL = "http://www.nu.nl" 17 | 18 | // act 19 | name := endpoint.GetName() 20 | output := endpoint.ShowOutputInfo() 21 | url := endpoint.GetURL() 22 | ops := endpoint.GetOperations() 23 | expand := endpoint.GetSupportedExpandParams() 24 | sel := endpoint.GetSupportedSelectParams() 25 | // point.AreQueryOptionsSupported() 26 | 27 | //assert 28 | assert.Equal(t, "test", name, "name should be correct") 29 | assert.True(t, !output) 30 | assert.Equal(t, url, "http://www.nu.nl") 31 | assert.True(t, len(ops) == 0) 32 | assert.True(t, len(expand) == 0) 33 | assert.True(t, len(sel) == 0) 34 | } 35 | 36 | func TestIsDynamic(t *testing.T) { 37 | // arrange 38 | urlDynamic := "http://www.{}.nl" 39 | urlNotDynamic := "http://www.nu.nl" 40 | 41 | // act 42 | resultNotDynamic := isDynamic(urlNotDynamic) 43 | resultDynamic := isDynamic(urlDynamic) 44 | 45 | // assert 46 | assert.False(t, resultNotDynamic) 47 | assert.True(t, resultDynamic) 48 | } 49 | 50 | func TestEndPointSortDuplicate(t *testing.T) { 51 | // arrange 52 | ep1 := &EndpointWrapper{Operation: models.EndpointOperation{Path: "ep1", OperationType: models.HTTPOperationGet}} 53 | ep2 := &EndpointWrapper{Operation: models.EndpointOperation{Path: "ep1", OperationType: models.HTTPOperationGet}} 54 | eps := SortedEndpoints{ep1, ep2} 55 | 56 | // assert 57 | assert.Panics(t, func() { sort.Sort(eps) }) 58 | } 59 | 60 | func TestEndPointSort(t *testing.T) { 61 | // arrange 62 | endpoints := map[entities.EntityType]models.Endpoint{} 63 | endpoints[entities.EntityTypeDatastream] = &Endpoint{ 64 | Operations: []models.EndpointOperation{ 65 | {OperationType: models.HTTPOperationGet, Path: "ep1"}, 66 | {OperationType: models.HTTPOperationPost, Path: "ep2"}, 67 | {OperationType: models.HTTPOperationGet, Path: "{c:.*}ep3"}, 68 | {OperationType: models.HTTPOperationGet, Path: "ep4{c:.*}"}, 69 | {OperationType: models.HTTPOperationGet, Path: "{c:.*}ep5{test}"}, 70 | {OperationType: models.HTTPOperationGet, Path: "ep6{test}"}, 71 | {OperationType: models.HTTPOperationGet, Path: "ep7"}, 72 | }, 73 | } 74 | 75 | // act 76 | eps := EndpointsToSortedList(&endpoints) 77 | 78 | // assert 79 | assert.True(t, len(eps) == 7, "Number of Endpoints should be 7") 80 | // post becomes first after sorting 81 | assert.True(t, eps[0].Operation.Path == "ep2") 82 | assert.True(t, eps[1].Operation.Path == "ep1") 83 | assert.True(t, eps[2].Operation.Path == "ep7") 84 | assert.True(t, eps[3].Operation.Path == "ep6{test}") 85 | assert.True(t, eps[4].Operation.Path == "{c:.*}ep3") 86 | assert.True(t, eps[5].Operation.Path == "ep4{c:.*}") 87 | assert.True(t, eps[6].Operation.Path == "{c:.*}ep5{test}") 88 | } 89 | 90 | func TestEndPointSortDynamic(t *testing.T) { 91 | // arrange 92 | httpep1 := &EndpointWrapper{} 93 | httpep1.Operation.Path = "ep1{}" 94 | httpep1.Operation.OperationType = models.HTTPOperationGet 95 | httpep2 := &EndpointWrapper{} 96 | httpep2.Operation.Path = "ep2{}longer" 97 | httpep2.Operation.OperationType = models.HTTPOperationPost 98 | 99 | eps := SortedEndpoints{httpep1, httpep2} 100 | 101 | // act 102 | sort.Sort(eps) 103 | 104 | // assert 105 | assert.True(t, len(eps) == 2, "Number of Endpoints should be 2") 106 | // when both urls are dynamic, the longer path comes first 107 | assert.True(t, eps[0].Operation.Path == "ep2{}longer") 108 | assert.True(t, eps[1].Operation.Path == "ep1{}") 109 | } 110 | 111 | func TestEndPointSortlength(t *testing.T) { 112 | // arrange 113 | httpep1 := &EndpointWrapper{} 114 | httpep1.Operation.Path = "ep1" 115 | httpep1.Operation.OperationType = models.HTTPOperationGet 116 | httpep2 := &EndpointWrapper{} 117 | httpep2.Operation.Path = "ep2longer" 118 | httpep2.Operation.OperationType = models.HTTPOperationPost 119 | 120 | eps := SortedEndpoints{httpep1, httpep2} 121 | 122 | // act 123 | sort.Sort(eps) 124 | 125 | // assert 126 | assert.True(t, len(eps) == 2, "Number of Endpoints should be 2") 127 | // when both urls are dynamic, the longer path comes first 128 | assert.True(t, eps[0].Operation.Path == "ep2longer") 129 | assert.True(t, eps[1].Operation.Path == "ep1") 130 | } 131 | 132 | func TestEndPointNotDynamic(t *testing.T) { 133 | // arrange 134 | httpep1 := &EndpointWrapper{} 135 | httpep1.Operation.Path = "ep1 {c:.*}" 136 | httpep1.Operation.OperationType = models.HTTPOperationGet 137 | httpep2 := &EndpointWrapper{} 138 | httpep2.Operation.Path = "ep2longer" 139 | httpep2.Operation.OperationType = models.HTTPOperationGet 140 | eps := SortedEndpoints{httpep1, httpep2} 141 | 142 | // act 143 | sort.Sort(eps) 144 | 145 | // assert 146 | assert.True(t, eps[0].Operation.Path == "ep2longer") 147 | assert.True(t, eps[1].Operation.Path == "ep1 {c:.*}") 148 | } 149 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/create_observations.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | ) 9 | 10 | // HandlePostCreateObservations ... 11 | func HandlePostCreateObservations(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 12 | a := *api 13 | ob := &entities.CreateObservations{} 14 | handle := func() (interface{}, []error) { return a.PostCreateObservations(ob) } 15 | handlePostRequest(w, endpoint, r, ob, &handle, a.GetConfig().Server.IndentedJSON) 16 | } 17 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/datastream.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/odata" 9 | "github.com/gost/server/sensorthings/rest/reader" 10 | ) 11 | 12 | // HandleGetDatastreams retrieves datastreams based on Query Parameters 13 | func HandleGetDatastreams(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 14 | a := *api 15 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { return a.GetDatastreams(q, path) } 16 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 17 | } 18 | 19 | // HandleGetDatastream retrieves a datastream by given id 20 | func HandleGetDatastream(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 21 | a := *api 22 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 23 | return a.GetDatastream(reader.GetEntityID(r), q, path) 24 | } 25 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 26 | } 27 | 28 | // HandleGetDatastreamByObservation ... 29 | func HandleGetDatastreamByObservation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 30 | a := *api 31 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 32 | return a.GetDatastreamByObservation(reader.GetEntityID(r), q, path) 33 | } 34 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 35 | } 36 | 37 | // HandleGetDatastreamsByThing ... 38 | func HandleGetDatastreamsByThing(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 39 | a := *api 40 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 41 | return a.GetDatastreamsByThing(reader.GetEntityID(r), q, path) 42 | } 43 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 44 | } 45 | 46 | // HandleGetDatastreamsBySensor ... 47 | func HandleGetDatastreamsBySensor(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 48 | a := *api 49 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 50 | return a.GetDatastreamsBySensor(reader.GetEntityID(r), q, path) 51 | } 52 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 53 | } 54 | 55 | // HandleGetDatastreamsByObservedProperty ... 56 | func HandleGetDatastreamsByObservedProperty(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 57 | a := *api 58 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 59 | return a.GetDatastreamsByObservedProperty(reader.GetEntityID(r), q, path) 60 | } 61 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 62 | } 63 | 64 | // HandlePostDatastream ... 65 | func HandlePostDatastream(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 66 | a := *api 67 | ds := &entities.Datastream{} 68 | handle := func() (interface{}, []error) { return a.PostDatastream(ds) } 69 | handlePostRequest(w, endpoint, r, ds, &handle, a.GetConfig().Server.IndentedJSON) 70 | } 71 | 72 | // HandlePostDatastreamByThing ... 73 | func HandlePostDatastreamByThing(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 74 | a := *api 75 | ds := &entities.Datastream{} 76 | handle := func() (interface{}, []error) { return a.PostDatastreamByThing(reader.GetEntityID(r), ds) } 77 | handlePostRequest(w, endpoint, r, ds, &handle, a.GetConfig().Server.IndentedJSON) 78 | } 79 | 80 | // HandleDeleteDatastream ... 81 | func HandleDeleteDatastream(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 82 | a := *api 83 | handle := func() error { return a.DeleteDatastream(reader.GetEntityID(r)) } 84 | handleDeleteRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON) 85 | } 86 | 87 | // HandlePatchDatastream ... 88 | func HandlePatchDatastream(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 89 | a := *api 90 | ds := &entities.Datastream{} 91 | handle := func() (interface{}, error) { return a.PatchDatastream(reader.GetEntityID(r), ds) } 92 | handlePatchRequest(w, endpoint, r, ds, &handle, a.GetConfig().Server.IndentedJSON) 93 | } 94 | 95 | // HandlePutDatastream ... 96 | func HandlePutDatastream(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 97 | a := *api 98 | ds := &entities.Datastream{} 99 | handle := func() (interface{}, []error) { return a.PutDatastream(reader.GetEntityID(r), ds) } 100 | handlePutRequest(w, endpoint, r, ds, &handle, a.GetConfig().Server.IndentedJSON) 101 | } 102 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/datastream_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | entities "github.com/gost/core" 7 | "github.com/stretchr/testify/assert" 8 | "io/ioutil" 9 | "net/http" 10 | "testing" 11 | ) 12 | 13 | func TestGetDatastream(t *testing.T) { 14 | getAndAssertDatastream("/v1.0/datastreams(1)", t) 15 | } 16 | 17 | func TestGetDatastreams(t *testing.T) { 18 | getAndAssertDatastreams("/v1.0/datastreams", t) 19 | } 20 | 21 | func TestGetDatastreamByObservation(t *testing.T) { 22 | getAndAssertDatastream("/v1.0/observations(1)/datastream", t) 23 | } 24 | 25 | func TestGetDatastreamsByThing(t *testing.T) { 26 | getAndAssertDatastreams("/v1.0/things(1)/datastreams", t) 27 | } 28 | 29 | func TestGetDatastreamsBySensor(t *testing.T) { 30 | getAndAssertDatastreams("/v1.0/sensors(1)/datastreams", t) 31 | } 32 | 33 | func TestGetDatastreamsByObservedProperty(t *testing.T) { 34 | getAndAssertDatastreams("/v1.0/observedproperties(1)/datastreams", t) 35 | } 36 | 37 | func TestPostDatastream(t *testing.T) { 38 | // arrange 39 | mockDatastream := newMockDatastream(1) 40 | 41 | // act 42 | r := request("POST", "/v1.0/datastreams", mockDatastream) 43 | 44 | // arrange 45 | parseAndAssertDatastream(*mockDatastream, r, http.StatusCreated, t) 46 | } 47 | 48 | func TestPostDatastreamByThing(t *testing.T) { 49 | // arrange 50 | mockDatastream := newMockDatastream(1) 51 | 52 | // act 53 | r := request("POST", "/v1.0/things(1)/datastreams", mockDatastream) 54 | 55 | // arrange 56 | parseAndAssertDatastream(*mockDatastream, r, http.StatusCreated, t) 57 | } 58 | 59 | func TestPutDatastream(t *testing.T) { 60 | // arrange 61 | mockDatastream := newMockDatastream(1) 62 | mockDatastream.Name = "patched" 63 | 64 | // act 65 | r := request("PUT", "/v1.0/datastreams(1)", mockDatastream) 66 | 67 | parseAndAssertDatastream(*mockDatastream, r, http.StatusOK, t) 68 | } 69 | 70 | func TestPatchDatastream(t *testing.T) { 71 | // arrange 72 | mockDatastream := newMockDatastream(1) 73 | mockDatastream.Name = "patched" 74 | 75 | // act 76 | r := request("PATCH", "/v1.0/datastreams(1)", mockDatastream) 77 | 78 | // assert 79 | parseAndAssertDatastream(*mockDatastream, r, http.StatusOK, t) 80 | } 81 | 82 | func TestDeleteDatastream(t *testing.T) { 83 | r := request("DELETE", "/v1.0/datastreams(1)", nil) 84 | 85 | // assert 86 | assertStatusCode(http.StatusOK, r, t) 87 | } 88 | 89 | func getAndAssertDatastream(url string, t *testing.T) { 90 | r := request("GET", url, nil) 91 | parseAndAssertDatastream(*newMockDatastream(1), r, http.StatusOK, t) 92 | } 93 | 94 | func parseAndAssertDatastream(created entities.Datastream, r *http.Response, expectedStatusCode int, t *testing.T) { 95 | d := entities.Datastream{} 96 | body, err := ioutil.ReadAll(r.Body) 97 | err = json.Unmarshal(body, &d) 98 | 99 | assert.Nil(t, err) 100 | assertStatusCode(expectedStatusCode, r, t) 101 | assertDatastream(created, d, t) 102 | } 103 | 104 | func assertDatastream(created, returned entities.Datastream, t *testing.T) { 105 | assert.Equal(t, fmt.Sprintf("%v", created.ID), fmt.Sprintf("%v", returned.ID)) 106 | assert.Equal(t, created.Name, returned.Name) 107 | assert.Equal(t, created.Description, returned.Description) 108 | } 109 | 110 | func getAndAssertDatastreams(url string, t *testing.T) { 111 | // act 112 | r, _ := http.Get(getServer().URL + url) 113 | ar := entities.ArrayResponseDatastreams{} 114 | body, err := ioutil.ReadAll(r.Body) 115 | err = json.Unmarshal(body, &ar) 116 | 117 | // assert 118 | assert.Nil(t, err) 119 | assertStatusCode(http.StatusOK, r, t) 120 | assert.Equal(t, 2, ar.Count) 121 | 122 | for _, entity := range ar.Data { 123 | expected := newMockDatastream(int(entity.ID.(float64))) 124 | assertDatastream(*expected, *entity, t) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/featureofinterest.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/odata" 9 | "github.com/gost/server/sensorthings/rest/reader" 10 | ) 11 | 12 | // HandleGetFeatureOfInterests ... 13 | func HandleGetFeatureOfInterests(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 14 | a := *api 15 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { return a.GetFeatureOfInterests(q, path) } 16 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 17 | } 18 | 19 | // HandleGetFeatureOfInterest ... 20 | func HandleGetFeatureOfInterest(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 21 | a := *api 22 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 23 | return a.GetFeatureOfInterest(reader.GetEntityID(r), q, path) 24 | } 25 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 26 | } 27 | 28 | // HandleGetFeatureOfInterestByObservation ... 29 | func HandleGetFeatureOfInterestByObservation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 30 | a := *api 31 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 32 | return a.GetFeatureOfInterestByObservation(reader.GetEntityID(r), q, path) 33 | } 34 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 35 | } 36 | 37 | // HandlePostFeatureOfInterest ... 38 | func HandlePostFeatureOfInterest(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 39 | a := *api 40 | foi := &entities.FeatureOfInterest{} 41 | handle := func() (interface{}, []error) { return a.PostFeatureOfInterest(foi) } 42 | handlePostRequest(w, endpoint, r, foi, &handle, a.GetConfig().Server.IndentedJSON) 43 | } 44 | 45 | // HandleDeleteFeatureOfInterest ... 46 | func HandleDeleteFeatureOfInterest(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 47 | a := *api 48 | handle := func() error { return a.DeleteFeatureOfInterest(reader.GetEntityID(r)) } 49 | handleDeleteRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON) 50 | } 51 | 52 | // HandlePatchFeatureOfInterest ... 53 | func HandlePatchFeatureOfInterest(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 54 | a := *api 55 | foi := &entities.FeatureOfInterest{} 56 | handle := func() (interface{}, error) { return a.PatchFeatureOfInterest(reader.GetEntityID(r), foi) } 57 | handlePatchRequest(w, endpoint, r, foi, &handle, a.GetConfig().Server.IndentedJSON) 58 | } 59 | 60 | // HandlePutFeatureOfInterest ... 61 | func HandlePutFeatureOfInterest(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 62 | a := *api 63 | foi := &entities.FeatureOfInterest{} 64 | handle := func() (interface{}, []error) { return a.PutFeatureOfInterest(reader.GetEntityID(r), foi) } 65 | handlePutRequest(w, endpoint, r, foi, &handle, a.GetConfig().Server.IndentedJSON) 66 | } 67 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/featureofinterest_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | entities "github.com/gost/core" 7 | "github.com/stretchr/testify/assert" 8 | "io/ioutil" 9 | "net/http" 10 | "testing" 11 | ) 12 | 13 | func TestGetFeatureOfInterest(t *testing.T) { 14 | getAndAssertFeatureOfInterest("/v1.0/featuresofinterest(1)", t) 15 | } 16 | 17 | func TestGetFeatureOfInterestByObservation(t *testing.T) { 18 | getAndAssertFeatureOfInterest("/v1.0/observations(1)/featureofinterest", t) 19 | } 20 | 21 | func TestGetFeaturesOfInterest(t *testing.T) { 22 | getAndAssertFeaturesOfInterest("/v1.0/featuresofinterest", t) 23 | } 24 | 25 | func TestPostFeatureOfInterest(t *testing.T) { 26 | // arrange 27 | mockFeatureOfInterest := newMockFeatureOfInterest(1) 28 | 29 | // act 30 | r := request("POST", "/v1.0/featuresofinterest", mockFeatureOfInterest) 31 | 32 | // arrange 33 | parseAndAssertFeatureOfInterest(*mockFeatureOfInterest, r, http.StatusCreated, t) 34 | } 35 | 36 | func TestPutFeatureOfInterest(t *testing.T) { 37 | // arrange 38 | mockFeatureOfInterest := newMockFeatureOfInterest(1) 39 | mockFeatureOfInterest.Name = "patched" 40 | 41 | // act 42 | r := request("PUT", "/v1.0/featuresofinterest(1)", mockFeatureOfInterest) 43 | 44 | parseAndAssertFeatureOfInterest(*mockFeatureOfInterest, r, http.StatusOK, t) 45 | } 46 | 47 | func TestPatchFeatureOfInterest(t *testing.T) { 48 | // arrange 49 | mockFeatureOfInterest := newMockFeatureOfInterest(1) 50 | mockFeatureOfInterest.Name = "patched" 51 | 52 | // act 53 | r := request("PATCH", "/v1.0/featuresofinterest(1)", mockFeatureOfInterest) 54 | 55 | // assert 56 | parseAndAssertFeatureOfInterest(*mockFeatureOfInterest, r, http.StatusOK, t) 57 | } 58 | 59 | func TestDeleteFeatureOfInterest(t *testing.T) { 60 | r := request("DELETE", "/v1.0/featuresofinterest(1)", nil) 61 | 62 | // assert 63 | assertStatusCode(http.StatusOK, r, t) 64 | } 65 | 66 | func getAndAssertFeatureOfInterest(url string, t *testing.T) { 67 | r := request("GET", url, nil) 68 | parseAndAssertFeatureOfInterest(*newMockFeatureOfInterest(1), r, http.StatusOK, t) 69 | } 70 | 71 | func parseAndAssertFeatureOfInterest(created entities.FeatureOfInterest, r *http.Response, expectedStatusCode int, t *testing.T) { 72 | foi := entities.FeatureOfInterest{} 73 | body, err := ioutil.ReadAll(r.Body) 74 | err = json.Unmarshal(body, &foi) 75 | 76 | assert.Nil(t, err) 77 | assertStatusCode(expectedStatusCode, r, t) 78 | assertFeatureOfInterest(created, foi, t) 79 | } 80 | 81 | func assertFeatureOfInterest(created, returned entities.FeatureOfInterest, t *testing.T) { 82 | assert.Equal(t, fmt.Sprintf("%v", created.ID), fmt.Sprintf("%v", returned.ID)) 83 | assert.Equal(t, created.Name, returned.Name) 84 | assert.Equal(t, created.Description, returned.Description) 85 | assert.Equal(t, created.EncodingType, returned.EncodingType) 86 | } 87 | 88 | func getAndAssertFeaturesOfInterest(url string, t *testing.T) { 89 | // act 90 | r, _ := http.Get(getServer().URL + url) 91 | ar := entities.ArrayResponseFeaturesOfInterest{} 92 | body, err := ioutil.ReadAll(r.Body) 93 | err = json.Unmarshal(body, &ar) 94 | 95 | // assert 96 | assert.Nil(t, err) 97 | assertStatusCode(http.StatusOK, r, t) 98 | assert.Equal(t, 2, ar.Count) 99 | 100 | for _, entity := range ar.Data { 101 | expected := newMockFeatureOfInterest(int(entity.ID.(float64))) 102 | assertFeatureOfInterest(*expected, *entity, t) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/historicallocation.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/odata" 9 | "github.com/gost/server/sensorthings/rest/reader" 10 | ) 11 | 12 | // HandleGetHistoricalLocations ... 13 | func HandleGetHistoricalLocations(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 14 | a := *api 15 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 16 | return a.GetHistoricalLocations(q, path) 17 | } 18 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 19 | } 20 | 21 | // HandleGetHistoricalLocationsByThing ... 22 | func HandleGetHistoricalLocationsByThing(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 23 | a := *api 24 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 25 | return a.GetHistoricalLocationsByThing(reader.GetEntityID(r), q, path) 26 | } 27 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 28 | } 29 | 30 | // HandleGetHistoricalLocationsByLocation ... 31 | func HandleGetHistoricalLocationsByLocation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 32 | a := *api 33 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 34 | return a.GetHistoricalLocationsByLocation(reader.GetEntityID(r), q, path) 35 | } 36 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 37 | } 38 | 39 | // HandleGetHistoricalLocation ... 40 | func HandleGetHistoricalLocation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 41 | a := *api 42 | id := reader.GetEntityID(r) 43 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 44 | return a.GetHistoricalLocation(id, q, path) 45 | } 46 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 47 | } 48 | 49 | // HandlePostHistoricalLocation ... 50 | func HandlePostHistoricalLocation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 51 | a := *api 52 | hl := &entities.HistoricalLocation{} 53 | handle := func() (interface{}, []error) { return a.PostHistoricalLocation(hl) } 54 | handlePostRequest(w, endpoint, r, hl, &handle, a.GetConfig().Server.IndentedJSON) 55 | } 56 | 57 | // HandlePutHistoricalLocation ... 58 | func HandlePutHistoricalLocation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 59 | a := *api 60 | hl := &entities.HistoricalLocation{} 61 | handle := func() (interface{}, []error) { return a.PutHistoricalLocation(reader.GetEntityID(r), hl) } 62 | handlePutRequest(w, endpoint, r, hl, &handle, a.GetConfig().Server.IndentedJSON) 63 | } 64 | 65 | // HandleDeleteHistoricalLocations ... 66 | func HandleDeleteHistoricalLocations(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 67 | a := *api 68 | handle := func() error { return a.DeleteHistoricalLocation(reader.GetEntityID(r)) } 69 | handleDeleteRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON) 70 | } 71 | 72 | // HandlePatchHistoricalLocations ... 73 | func HandlePatchHistoricalLocations(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 74 | a := *api 75 | hl := &entities.HistoricalLocation{} 76 | handle := func() (interface{}, error) { return a.PatchHistoricalLocation(reader.GetEntityID(r), hl) } 77 | handlePatchRequest(w, endpoint, r, hl, &handle, a.GetConfig().Server.IndentedJSON) 78 | } 79 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/historicallocation_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | entities "github.com/gost/core" 7 | "github.com/stretchr/testify/assert" 8 | "io/ioutil" 9 | "net/http" 10 | "testing" 11 | ) 12 | 13 | func TestGetHistoricalLocation(t *testing.T) { 14 | getAndAssertHistoricalLocation("/v1.0/historicallocations(1)", t) 15 | } 16 | 17 | func TestGetHistoricalLocationsByThing(t *testing.T) { 18 | getAndAssertHistoricalLocations("/v1.0/things(1)/historicallocations", t) 19 | } 20 | 21 | func TestGetHistoricalLocationsByLocation(t *testing.T) { 22 | getAndAssertHistoricalLocations("/v1.0/locations(1)/historicallocations", t) 23 | } 24 | 25 | func TestGetHistoricalLocations(t *testing.T) { 26 | getAndAssertHistoricalLocations("/v1.0/historicallocations", t) 27 | } 28 | 29 | func TestPostHistoricalLocation(t *testing.T) { 30 | // arrange 31 | mockHistoricalLocation := newMockHistoricalLocation(1) 32 | 33 | // act 34 | r := request("POST", "/v1.0/historicallocations", mockHistoricalLocation) 35 | 36 | // arrange 37 | parseAndAssertHistoricalLocation(*mockHistoricalLocation, r, http.StatusCreated, t) 38 | } 39 | 40 | func TestPutHistoricalLocation(t *testing.T) { 41 | // arrange 42 | mockHistoricalLocation := newMockHistoricalLocation(1) 43 | mockHistoricalLocation.Time = "2017-07-17T07:04:09.222Z" 44 | 45 | // act 46 | r := request("PUT", "/v1.0/historicallocations(1)", mockHistoricalLocation) 47 | 48 | parseAndAssertHistoricalLocation(*mockHistoricalLocation, r, http.StatusOK, t) 49 | } 50 | 51 | func TestPatchHistoricalLocation(t *testing.T) { 52 | // arrange 53 | mockHistoricalLocation := newMockHistoricalLocation(1) 54 | mockHistoricalLocation.Time = "2017-07-17T07:04:09.222Z" 55 | 56 | // act 57 | r := request("PATCH", "/v1.0/historicallocations(1)", mockHistoricalLocation) 58 | 59 | // assert 60 | parseAndAssertHistoricalLocation(*mockHistoricalLocation, r, http.StatusOK, t) 61 | } 62 | 63 | func TestDeleteHistoricalLocation(t *testing.T) { 64 | r := request("DELETE", "/v1.0/historicallocations(1)", nil) 65 | 66 | // assert 67 | assertStatusCode(http.StatusOK, r, t) 68 | } 69 | 70 | func getAndAssertHistoricalLocation(url string, t *testing.T) { 71 | r := request("GET", url, nil) 72 | parseAndAssertHistoricalLocation(*newMockHistoricalLocation(1), r, http.StatusOK, t) 73 | } 74 | 75 | func parseAndAssertHistoricalLocation(created entities.HistoricalLocation, r *http.Response, expectedStatusCode int, t *testing.T) { 76 | historicalLocation := entities.HistoricalLocation{} 77 | body, err := ioutil.ReadAll(r.Body) 78 | err = json.Unmarshal(body, &historicalLocation) 79 | 80 | assert.Nil(t, err) 81 | assertStatusCode(expectedStatusCode, r, t) 82 | assertHistoricalLocation(created, historicalLocation, t) 83 | } 84 | 85 | func assertHistoricalLocation(created, returned entities.HistoricalLocation, t *testing.T) { 86 | assert.Equal(t, fmt.Sprintf("%v", created.ID), fmt.Sprintf("%v", returned.ID)) 87 | assert.Equal(t, created.Time, returned.Time) 88 | } 89 | 90 | func getAndAssertHistoricalLocations(url string, t *testing.T) { 91 | // act 92 | r, _ := http.Get(getServer().URL + url) 93 | ar := entities.ArrayResponseHistoricalLocations{} 94 | body, err := ioutil.ReadAll(r.Body) 95 | err = json.Unmarshal(body, &ar) 96 | 97 | // assert 98 | assert.Nil(t, err) 99 | assertStatusCode(http.StatusOK, r, t) 100 | assert.Equal(t, 2, ar.Count) 101 | 102 | for _, entity := range ar.Data { 103 | expected := newMockHistoricalLocation(int(entity.ID.(float64))) 104 | assertHistoricalLocation(*expected, *entity, t) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/location.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/odata" 9 | "github.com/gost/server/sensorthings/rest/reader" 10 | ) 11 | 12 | // HandleGetLocations retrieves multiple locations based on query parameters 13 | func HandleGetLocations(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 14 | a := *api 15 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { return a.GetLocations(q, path) } 16 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 17 | } 18 | 19 | // HandleGetLocationsByHistoricalLocations retrieves the locations linked to the given Historical Location (id) 20 | func HandleGetLocationsByHistoricalLocations(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 21 | a := *api 22 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 23 | return a.GetLocationsByHistoricalLocation(reader.GetEntityID(r), q, path) 24 | } 25 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 26 | } 27 | 28 | // HandleGetLocationsByThing retrieves the locations by given thing (id) 29 | func HandleGetLocationsByThing(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 30 | a := *api 31 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 32 | return a.GetLocationsByThing(reader.GetEntityID(r), q, path) 33 | } 34 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 35 | } 36 | 37 | // HandleGetLocation retrieves a location by given id 38 | func HandleGetLocation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 39 | a := *api 40 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 41 | return a.GetLocation(reader.GetEntityID(r), q, path) 42 | } 43 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 44 | } 45 | 46 | // HandlePostLocation posts a new location 47 | func HandlePostLocation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 48 | a := *api 49 | loc := &entities.Location{} 50 | handle := func() (interface{}, []error) { return a.PostLocation(loc) } 51 | handlePostRequest(w, endpoint, r, loc, &handle, a.GetConfig().Server.IndentedJSON) 52 | } 53 | 54 | // HandlePostLocationByThing posts a new location linked to the given thing 55 | func HandlePostLocationByThing(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 56 | a := *api 57 | loc := &entities.Location{} 58 | handle := func() (interface{}, []error) { return a.PostLocationByThing(reader.GetEntityID(r), loc) } 59 | handlePostRequest(w, endpoint, r, loc, &handle, a.GetConfig().Server.IndentedJSON) 60 | } 61 | 62 | // HandleDeleteLocation deletes a location 63 | func HandleDeleteLocation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 64 | a := *api 65 | handle := func() error { return a.DeleteLocation(reader.GetEntityID(r)) } 66 | handleDeleteRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON) 67 | } 68 | 69 | // HandlePatchLocation patches a location by given id 70 | func HandlePatchLocation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 71 | a := *api 72 | loc := &entities.Location{} 73 | handle := func() (interface{}, error) { return a.PatchLocation(reader.GetEntityID(r), loc) } 74 | handlePatchRequest(w, endpoint, r, loc, &handle, a.GetConfig().Server.IndentedJSON) 75 | } 76 | 77 | // HandlePutLocation patches a location by given id 78 | func HandlePutLocation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 79 | a := *api 80 | loc := &entities.Location{} 81 | handle := func() (interface{}, []error) { return a.PutLocation(reader.GetEntityID(r), loc) } 82 | handlePutRequest(w, endpoint, r, loc, &handle, a.GetConfig().Server.IndentedJSON) 83 | } 84 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/location_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | entities "github.com/gost/core" 7 | "github.com/stretchr/testify/assert" 8 | "io/ioutil" 9 | "net/http" 10 | "testing" 11 | ) 12 | 13 | func TestGetLocation(t *testing.T) { 14 | getAndAssertLocation("/v1.0/locations(1)", t) 15 | } 16 | 17 | func TestGetLocationsByThing(t *testing.T) { 18 | getAndAssertLocations("/v1.0/things(1)/locations", t) 19 | } 20 | 21 | func TestHandleGetLocationsByHistoricalLocation(t *testing.T) { 22 | getAndAssertLocations("/v1.0/historicallocations(1)/locations", t) 23 | } 24 | 25 | func TestGetLocations(t *testing.T) { 26 | getAndAssertLocations("/v1.0/locations", t) 27 | } 28 | 29 | func TestPostLocation(t *testing.T) { 30 | // arrange 31 | mockLocation := newMockLocation(1) 32 | 33 | // act 34 | r := request("POST", "/v1.0/locations", mockLocation) 35 | 36 | // arrange 37 | parseAndAssertLocation(*mockLocation, r, http.StatusCreated, t) 38 | } 39 | 40 | func TestPostLocationByThing(t *testing.T) { 41 | // arrange 42 | mockLocation := newMockLocation(1) 43 | 44 | // act 45 | r := request("POST", "/v1.0/things(1)/locations", mockLocation) 46 | 47 | // arrange 48 | parseAndAssertLocation(*mockLocation, r, http.StatusCreated, t) 49 | } 50 | 51 | func TestPutLocation(t *testing.T) { 52 | // arrange 53 | mockLocation := newMockLocation(1) 54 | mockLocation.Name = "patched" 55 | 56 | // act 57 | r := request("PUT", "/v1.0/locations(1)", mockLocation) 58 | 59 | parseAndAssertLocation(*mockLocation, r, http.StatusOK, t) 60 | } 61 | 62 | func TestPatchLocation(t *testing.T) { 63 | // arrange 64 | mockLocation := newMockLocation(1) 65 | mockLocation.Name = "patched" 66 | 67 | // act 68 | r := request("PATCH", "/v1.0/locations(1)", mockLocation) 69 | 70 | // assert 71 | parseAndAssertLocation(*mockLocation, r, http.StatusOK, t) 72 | } 73 | 74 | func TestDeleteLocation(t *testing.T) { 75 | r := request("DELETE", "/v1.0/locations(1)", nil) 76 | 77 | // assert 78 | assertStatusCode(http.StatusOK, r, t) 79 | } 80 | 81 | func getAndAssertLocation(url string, t *testing.T) { 82 | r := request("GET", url, nil) 83 | parseAndAssertLocation(*newMockLocation(1), r, http.StatusOK, t) 84 | } 85 | 86 | func parseAndAssertLocation(created entities.Location, r *http.Response, expectedStatusCode int, t *testing.T) { 87 | location := entities.Location{} 88 | body, err := ioutil.ReadAll(r.Body) 89 | err = json.Unmarshal(body, &location) 90 | 91 | assert.Nil(t, err) 92 | assertStatusCode(expectedStatusCode, r, t) 93 | assertLocation(created, location, t) 94 | } 95 | 96 | func assertLocation(created, returned entities.Location, t *testing.T) { 97 | assert.Equal(t, fmt.Sprintf("%v", created.ID), fmt.Sprintf("%v", returned.ID)) 98 | assert.Equal(t, created.Name, returned.Name) 99 | assert.Equal(t, created.Description, returned.Description) 100 | assert.Equal(t, created.EncodingType, returned.EncodingType) 101 | assert.Equal(t, created.Location, returned.Location) 102 | } 103 | 104 | func getAndAssertLocations(url string, t *testing.T) { 105 | // act 106 | r, _ := http.Get(getServer().URL + url) 107 | ar := entities.ArrayResponseLocations{} 108 | body, err := ioutil.ReadAll(r.Body) 109 | err = json.Unmarshal(body, &ar) 110 | 111 | // assert 112 | assert.Nil(t, err) 113 | assertStatusCode(http.StatusOK, r, t) 114 | assert.Equal(t, 2, ar.Count) 115 | 116 | for _, entity := range ar.Data { 117 | expected := newMockLocation(int(entity.ID.(float64))) 118 | assertLocation(*expected, *entity, t) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/method_delete.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gost/server/sensorthings/models" 7 | "github.com/gost/server/sensorthings/rest/writer" 8 | ) 9 | 10 | // handleDeleteRequest 11 | func handleDeleteRequest(w http.ResponseWriter, e *models.Endpoint, r *http.Request, h *func() error, indentJSON bool) { 12 | handle := *h 13 | err := handle() 14 | if err != nil { 15 | writer.SendError(w, []error{err}, indentJSON) 16 | return 17 | } 18 | 19 | writer.SendJSONResponse(w, http.StatusOK, nil, nil, indentJSON) 20 | } 21 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/method_delete_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "errors" 5 | "github.com/stretchr/testify/assert" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func testHandlerDelete() error { 12 | return nil 13 | } 14 | 15 | func testHandlerDeleteError() error { 16 | return errors.New("Test error") 17 | } 18 | 19 | func TestHandleDeleteWithError(t *testing.T) { 20 | // arrange 21 | rr := httptest.NewRecorder() 22 | req, _ := http.NewRequest("DELETE", "/bla", nil) 23 | handle := func() error { return testHandlerDeleteError() } 24 | 25 | // act 26 | handleDeleteRequest(rr, nil, req, &handle, false) 27 | 28 | // assert 29 | assert.Equal(t, http.StatusInternalServerError, rr.Code) 30 | } 31 | 32 | func TestHandleDeleteTestOk(t *testing.T) { 33 | // arrange 34 | rr := httptest.NewRecorder() 35 | req, _ := http.NewRequest("DELETE", "/bla", nil) 36 | handle := func() error { return testHandlerDelete() } 37 | 38 | // act 39 | handleDeleteRequest(rr, nil, req, &handle, false) 40 | 41 | // assert 42 | 43 | assert.Equal(t, http.StatusOK, rr.Code) 44 | } 45 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/method_get.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "fmt" 7 | 8 | "github.com/gost/server/sensorthings/models" 9 | "github.com/gost/server/sensorthings/odata" 10 | "github.com/gost/server/sensorthings/rest/writer" 11 | ) 12 | 13 | // handleGetRequest is the default function to handle incoming GET requests 14 | func handleGetRequest(w http.ResponseWriter, e *models.Endpoint, r *http.Request, h *func(q *odata.QueryOptions, path string) (interface{}, error), indentJSON bool, maxEntities int, externalURI string) { 15 | // Parse query options from request 16 | queryOptions, err := odata.GetQueryOptions(r, maxEntities) 17 | if err != nil && len(err) > 0 { 18 | writer.SendError(w, err, indentJSON) 19 | return 20 | } 21 | 22 | // Run the handler func such as Api.GetThingById 23 | handler := *h 24 | data, err2 := handler(queryOptions, fmt.Sprintf(externalURI+r.URL.RawPath)) 25 | if err2 != nil { 26 | writer.SendError(w, []error{err2}, indentJSON) 27 | return 28 | } 29 | 30 | writer.SendJSONResponse(w, http.StatusOK, data, queryOptions, indentJSON) 31 | } 32 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/method_get_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | entities "github.com/gost/core" 10 | "github.com/gost/server/sensorthings/odata" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func testHandlerGet() (*entities.Thing, error) { 15 | return nil, nil 16 | } 17 | 18 | func testHandlerGetError() (*entities.Thing, error) { 19 | return nil, errors.New("Test error") 20 | } 21 | 22 | func TestHandleGetTestWithQueroOptionsError(t *testing.T) { 23 | // arrange 24 | rr := httptest.NewRecorder() 25 | req, _ := http.NewRequest("PATCH", "/things?$sort=bla&$skip='10'", nil) 26 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { return testHandlerGet() } 27 | 28 | // act 29 | handleGetRequest(rr, nil, req, &handle, false, 10, "") 30 | 31 | // assert 32 | assert.Equal(t, http.StatusInternalServerError, rr.Code) 33 | } 34 | 35 | func TestHandleGetTestWithError(t *testing.T) { 36 | // arrange 37 | rr := httptest.NewRecorder() 38 | req, _ := http.NewRequest("GET", "/things?$filter=name nonsens 'test1'", nil) 39 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { return testHandlerGetError() } 40 | 41 | // act 42 | handleGetRequest(rr, nil, req, &handle, false, 10, "") 43 | 44 | // assert 45 | assert.Equal(t, http.StatusInternalServerError, rr.Code) 46 | } 47 | 48 | func TestHandleGetTestOk(t *testing.T) { 49 | // arrange 50 | rr := httptest.NewRecorder() 51 | req, _ := http.NewRequest("PATCH", "/bla", nil) 52 | req.Header.Set("Content-Type", "application/json") 53 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { return testHandlerGet() } 54 | 55 | // act 56 | handleGetRequest(rr, nil, req, &handle, false, 10, "") 57 | 58 | // assert 59 | 60 | assert.Equal(t, http.StatusOK, rr.Code) 61 | } 62 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/method_patch.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/rest/reader" 9 | "github.com/gost/server/sensorthings/rest/writer" 10 | ) 11 | 12 | // handlePatchRequest todo: currently almost same as handlePostRequest, merge if it stays like this 13 | func handlePatchRequest(w http.ResponseWriter, e *models.Endpoint, r *http.Request, entity entities.Entity, h *func() (interface{}, error), indentJSON bool) { 14 | if !reader.CheckContentType(w, r, indentJSON) { 15 | return 16 | } 17 | 18 | byteData := reader.CheckAndGetBody(w, r, indentJSON) 19 | if byteData == nil { 20 | return 21 | } 22 | 23 | err := reader.ParseEntity(entity, byteData) 24 | if err != nil { 25 | writer.SendError(w, []error{err}, indentJSON) 26 | return 27 | } 28 | 29 | handle := *h 30 | data, err2 := handle() 31 | if err2 != nil { 32 | writer.SendError(w, []error{err2}, indentJSON) 33 | return 34 | } 35 | 36 | w.Header().Add("Location", entity.GetSelfLink()) 37 | 38 | writer.SendJSONResponse(w, http.StatusOK, data, nil, indentJSON) 39 | } 40 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/method_patch_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | entities "github.com/gost/core" 7 | "github.com/stretchr/testify/assert" 8 | "net/http" 9 | "net/http/httptest" 10 | "testing" 11 | ) 12 | 13 | func testHandlerPatch() (*entities.Thing, error) { 14 | return nil, nil 15 | } 16 | 17 | func testHandlerPatchError() (*entities.Thing, error) { 18 | return nil, errors.New("Test error") 19 | } 20 | 21 | func TestHandlePatchTestWithWrongDataType(t *testing.T) { 22 | // arrange 23 | rr := httptest.NewRecorder() 24 | thing := &entities.Thing{} 25 | req, _ := http.NewRequest("PATCH", "/bla", nil) 26 | req.Header.Set("Content-Type", "this is an invalid content-type") 27 | handle := func() (interface{}, error) { return testHandlerPatch() } 28 | 29 | // act 30 | handlePatchRequest(rr, nil, req, thing, &handle, false) 31 | 32 | // assert 33 | assert.Equal(t, http.StatusBadRequest, rr.Code) 34 | } 35 | 36 | func TestHandlePatchTestWithNoBody(t *testing.T) { 37 | // arrange 38 | rr := httptest.NewRecorder() 39 | thing := &entities.Thing{} 40 | req, _ := http.NewRequest("PATCH", "/bla", nil) 41 | req.Header.Set("Content-Type", "application/json") 42 | handle := func() (interface{}, error) { return testHandlerPatch() } 43 | 44 | // act 45 | handlePatchRequest(rr, nil, req, thing, &handle, false) 46 | 47 | // assert 48 | assert.Equal(t, http.StatusBadRequest, rr.Code) 49 | } 50 | 51 | func TestHandlePatchTestWithWrongBody(t *testing.T) { 52 | // arrange 53 | rr := httptest.NewRecorder() 54 | thing := &entities.Thing{} 55 | req, _ := http.NewRequest("PATCH", "/bla", bytes.NewReader([]byte("{\"name\": 10}"))) 56 | req.Header.Set("Content-Type", "application/json") 57 | handle := func() (interface{}, error) { return testHandlerPatch() } 58 | 59 | // act 60 | handlePatchRequest(rr, nil, req, thing, &handle, false) 61 | 62 | // assert 63 | assert.Equal(t, http.StatusBadRequest, rr.Code) 64 | } 65 | 66 | func TestHandlePatchTestWithPutError(t *testing.T) { 67 | // arrange 68 | rr := httptest.NewRecorder() 69 | thing := &entities.Thing{} 70 | req, _ := http.NewRequest("PATCH", "/bla", bytes.NewReader([]byte("{\"name\": \"thing1\", \"description\": \"test thing 1\"}"))) 71 | req.Header.Set("Content-Type", "application/json") 72 | handle := func() (interface{}, error) { return testHandlerPatchError() } 73 | 74 | // act 75 | handlePatchRequest(rr, nil, req, thing, &handle, false) 76 | 77 | // assert 78 | assert.Equal(t, http.StatusInternalServerError, rr.Code) 79 | } 80 | 81 | func TestHandlePatchTestWithGoodBody(t *testing.T) { 82 | // arrange 83 | rr := httptest.NewRecorder() 84 | thing := &entities.Thing{} 85 | thing.SetAllLinks("localhost") 86 | thing.ID = 1 87 | req, _ := http.NewRequest("PATCH", "/bla", bytes.NewReader([]byte("{\"@iot.id\": 1, \"name\": \"thing1\", \"description\": \"test thing 1\"}"))) 88 | req.Header.Set("Content-Type", "application/json") 89 | handle := func() (interface{}, error) { return testHandlerPatch() } 90 | 91 | // act 92 | handlePatchRequest(rr, nil, req, thing, &handle, false) 93 | 94 | // assert 95 | 96 | assert.Equal(t, http.StatusOK, rr.Code) 97 | assert.Equal(t, thing.GetSelfLink(), rr.HeaderMap.Get("Location"), "Expected header with Location to entity") 98 | } 99 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/method_post.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/rest/reader" 9 | "github.com/gost/server/sensorthings/rest/writer" 10 | ) 11 | 12 | // handlePostRequest 13 | func handlePostRequest(w http.ResponseWriter, e *models.Endpoint, r *http.Request, entity entities.Entity, h *func() (interface{}, []error), indentJSON bool) { 14 | if !reader.CheckContentType(w, r, indentJSON) { 15 | return 16 | } 17 | 18 | byteData := reader.CheckAndGetBody(w, r, indentJSON) 19 | if byteData == nil { 20 | return 21 | } 22 | 23 | err := reader.ParseEntity(entity, byteData) 24 | if err != nil { 25 | writer.SendError(w, []error{err}, indentJSON) 26 | return 27 | } 28 | 29 | handle := *h 30 | data, err2 := handle() 31 | if err2 != nil { 32 | writer.SendError(w, err2, indentJSON) 33 | return 34 | } 35 | 36 | w.Header().Add("Location", entity.GetSelfLink()) 37 | 38 | writer.SendJSONResponse(w, http.StatusCreated, data, nil, indentJSON) 39 | } 40 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/method_post_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "bytes" 5 | entities "github.com/gost/core" 6 | "github.com/stretchr/testify/assert" 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | ) 11 | 12 | func TestHandlePostTestWithWrongDataType(t *testing.T) { 13 | // arrange 14 | rr := httptest.NewRecorder() 15 | thing := &entities.Thing{} 16 | req, _ := http.NewRequest("POST", "/bla", nil) 17 | req.Header.Set("Content-Type", "this is an invalid content-type") 18 | handle := func() (interface{}, []error) { return testHandler() } 19 | 20 | // act 21 | handlePostRequest(rr, nil, req, thing, &handle, false) 22 | 23 | // assert 24 | assert.Equal(t, http.StatusBadRequest, rr.Code) 25 | } 26 | 27 | func TestHandlePostTestWithNoBody(t *testing.T) { 28 | // arrange 29 | rr := httptest.NewRecorder() 30 | thing := &entities.Thing{} 31 | req, _ := http.NewRequest("POST", "/bla", nil) 32 | req.Header.Set("Content-Type", "application/json") 33 | handle := func() (interface{}, []error) { return testHandler() } 34 | 35 | // act 36 | handlePostRequest(rr, nil, req, thing, &handle, false) 37 | 38 | // assert 39 | assert.Equal(t, http.StatusBadRequest, rr.Code) 40 | } 41 | 42 | func TestHandlePostTestWithWrongBody(t *testing.T) { 43 | // arrange 44 | rr := httptest.NewRecorder() 45 | thing := &entities.Thing{} 46 | req, _ := http.NewRequest("POST", "/bla", bytes.NewReader([]byte("{\"name\": 10}"))) 47 | req.Header.Set("Content-Type", "application/json") 48 | handle := func() (interface{}, []error) { return testHandler() } 49 | 50 | // act 51 | handlePostRequest(rr, nil, req, thing, &handle, false) 52 | 53 | // assert 54 | assert.Equal(t, http.StatusBadRequest, rr.Code) 55 | } 56 | 57 | func TestHandlePostTestWithError(t *testing.T) { 58 | // arrange 59 | rr := httptest.NewRecorder() 60 | thing := &entities.Thing{} 61 | req, _ := http.NewRequest("POST", "/bla", bytes.NewReader([]byte("{\"name\": \"thing1\", \"description\": \"test thing 1\"}"))) 62 | req.Header.Set("Content-Type", "application/json") 63 | handle := func() (interface{}, []error) { return testHandlerError() } 64 | 65 | // act 66 | handlePostRequest(rr, nil, req, thing, &handle, false) 67 | 68 | // assert 69 | assert.Equal(t, http.StatusInternalServerError, rr.Code) 70 | } 71 | 72 | func TestHandlePostTestWithGoodBody(t *testing.T) { 73 | // arrange 74 | rr := httptest.NewRecorder() 75 | thing := &entities.Thing{} 76 | thing.SetAllLinks("localhost") 77 | thing.ID = 1 78 | req, _ := http.NewRequest("POST", "/bla", bytes.NewReader([]byte("{ \"name\": \"thing1\", \"description\": \"test thing 1\"}"))) 79 | req.Header.Set("Content-Type", "application/json") 80 | handle := func() (interface{}, []error) { return testHandler() } 81 | 82 | // act 83 | handlePostRequest(rr, nil, req, thing, &handle, false) 84 | 85 | // assert 86 | 87 | assert.Equal(t, http.StatusCreated, rr.Code) 88 | assert.Equal(t, thing.GetSelfLink(), rr.HeaderMap.Get("Location"), "Expected header with Location to entity") 89 | } 90 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/method_put.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/rest/reader" 9 | "github.com/gost/server/sensorthings/rest/writer" 10 | ) 11 | 12 | // handlePutRequest todo: currently almost same as handlePostRequest, merge if it stays like this 13 | func handlePutRequest(w http.ResponseWriter, e *models.Endpoint, r *http.Request, entity entities.Entity, h *func() (interface{}, []error), indentJSON bool) { 14 | if !reader.CheckContentType(w, r, indentJSON) { 15 | return 16 | } 17 | 18 | byteData := reader.CheckAndGetBody(w, r, indentJSON) 19 | if byteData == nil { 20 | return 21 | } 22 | 23 | err := reader.ParseEntity(entity, byteData) 24 | if err != nil { 25 | writer.SendError(w, []error{err}, indentJSON) 26 | return 27 | } 28 | 29 | handle := *h 30 | data, err2 := handle() 31 | if err2 != nil { 32 | writer.SendError(w, err2, indentJSON) 33 | return 34 | } 35 | selfLink := entity.GetSelfLink() 36 | w.Header().Add("Location", selfLink) 37 | writer.SendJSONResponse(w, http.StatusOK, data, nil, indentJSON) 38 | } 39 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/method_put_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | entities "github.com/gost/core" 7 | "github.com/stretchr/testify/assert" 8 | "net/http" 9 | "net/http/httptest" 10 | "testing" 11 | ) 12 | 13 | func testHandler() (*entities.Thing, []error) { 14 | return nil, nil 15 | } 16 | 17 | func testHandlerError() (*entities.Thing, []error) { 18 | return nil, []error{errors.New("Test error")} 19 | } 20 | 21 | func TestHandlePutTestWithWrongDataType(t *testing.T) { 22 | // arrange 23 | rr := httptest.NewRecorder() 24 | thing := &entities.Thing{} 25 | req, _ := http.NewRequest("PUT", "/bla", nil) 26 | req.Header.Set("Content-Type", "this is an invalid content-type") 27 | handle := func() (interface{}, []error) { return testHandler() } 28 | 29 | // act 30 | handlePutRequest(rr, nil, req, thing, &handle, false) 31 | 32 | // assert 33 | assert.Equal(t, http.StatusBadRequest, rr.Code) 34 | } 35 | 36 | func TestHandlePutTestWithNoBody(t *testing.T) { 37 | // arrange 38 | rr := httptest.NewRecorder() 39 | thing := &entities.Thing{} 40 | req, _ := http.NewRequest("PUT", "/bla", nil) 41 | req.Header.Set("Content-Type", "application/json") 42 | handle := func() (interface{}, []error) { return testHandler() } 43 | 44 | // act 45 | handlePutRequest(rr, nil, req, thing, &handle, false) 46 | 47 | // assert 48 | assert.Equal(t, http.StatusBadRequest, rr.Code) 49 | } 50 | 51 | func TestHandlePutTestWithWrongBody(t *testing.T) { 52 | // arrange 53 | rr := httptest.NewRecorder() 54 | thing := &entities.Thing{} 55 | req, _ := http.NewRequest("PUT", "/bla", bytes.NewReader([]byte("{\"name\": 10}"))) 56 | req.Header.Set("Content-Type", "application/json") 57 | handle := func() (interface{}, []error) { return testHandler() } 58 | 59 | // act 60 | handlePutRequest(rr, nil, req, thing, &handle, false) 61 | 62 | // assert 63 | assert.Equal(t, http.StatusBadRequest, rr.Code) 64 | } 65 | 66 | func TestHandlePutTestWithPutError(t *testing.T) { 67 | // arrange 68 | rr := httptest.NewRecorder() 69 | thing := &entities.Thing{} 70 | req, _ := http.NewRequest("PUT", "/bla", bytes.NewReader([]byte("{\"name\": \"thing1\", \"description\": \"test thing 1\"}"))) 71 | req.Header.Set("Content-Type", "application/json") 72 | handle := func() (interface{}, []error) { return testHandlerError() } 73 | 74 | // act 75 | handlePutRequest(rr, nil, req, thing, &handle, false) 76 | 77 | // assert 78 | assert.Equal(t, http.StatusInternalServerError, rr.Code) 79 | } 80 | 81 | func TestHandlePutTestWithGoodBody(t *testing.T) { 82 | // arrange 83 | rr := httptest.NewRecorder() 84 | thing := &entities.Thing{} 85 | thing.SetAllLinks("localhost") 86 | thing.ID = 1 87 | req, _ := http.NewRequest("PUT", "/bla", bytes.NewReader([]byte("{\"@iot.id\": 1, \"name\": \"thing1\", \"description\": \"test thing 1\"}"))) 88 | req.Header.Set("Content-Type", "application/json") 89 | handle := func() (interface{}, []error) { return testHandler() } 90 | 91 | // act 92 | handlePutRequest(rr, nil, req, thing, &handle, false) 93 | 94 | // assert 95 | 96 | assert.Equal(t, http.StatusOK, rr.Code) 97 | assert.Equal(t, thing.GetSelfLink(), rr.HeaderMap.Get("Location"), "Expected header with Location to entity") 98 | } 99 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/observation.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/odata" 9 | "github.com/gost/server/sensorthings/rest/reader" 10 | ) 11 | 12 | // HandleGetObservations ... 13 | func HandleGetObservations(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 14 | a := *api 15 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { return a.GetObservations(q, path) } 16 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 17 | } 18 | 19 | // HandleGetObservation ... 20 | func HandleGetObservation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 21 | a := *api 22 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 23 | return a.GetObservation(reader.GetEntityID(r), q, path) 24 | } 25 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 26 | } 27 | 28 | // HandleGetObservationsByFeatureOfInterest ... 29 | func HandleGetObservationsByFeatureOfInterest(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 30 | a := *api 31 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 32 | return a.GetObservationsByFeatureOfInterest(reader.GetEntityID(r), q, path) 33 | } 34 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 35 | } 36 | 37 | // HandleGetObservationsByDatastream ... 38 | func HandleGetObservationsByDatastream(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 39 | a := *api 40 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 41 | return a.GetObservationsByDatastream(reader.GetEntityID(r), q, path) 42 | } 43 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 44 | } 45 | 46 | // HandlePostObservation ... 47 | func HandlePostObservation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 48 | a := *api 49 | ob := &entities.Observation{} 50 | handle := func() (interface{}, []error) { return a.PostObservation(ob) } 51 | handlePostRequest(w, endpoint, r, ob, &handle, a.GetConfig().Server.IndentedJSON) 52 | } 53 | 54 | // HandlePostObservationByDatastream ... 55 | func HandlePostObservationByDatastream(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 56 | a := *api 57 | ob := &entities.Observation{} 58 | handle := func() (interface{}, []error) { return a.PostObservationByDatastream(reader.GetEntityID(r), ob) } 59 | handlePostRequest(w, endpoint, r, ob, &handle, a.GetConfig().Server.IndentedJSON) 60 | } 61 | 62 | // HandleDeleteObservation ... 63 | func HandleDeleteObservation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 64 | a := *api 65 | handle := func() error { return a.DeleteObservation(reader.GetEntityID(r)) } 66 | handleDeleteRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON) 67 | } 68 | 69 | // HandlePatchObservation ... 70 | func HandlePatchObservation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 71 | a := *api 72 | ob := &entities.Observation{} 73 | handle := func() (interface{}, error) { return a.PatchObservation(reader.GetEntityID(r), ob) } 74 | handlePatchRequest(w, endpoint, r, ob, &handle, a.GetConfig().Server.IndentedJSON) 75 | } 76 | 77 | // HandlePutObservation ... 78 | func HandlePutObservation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 79 | a := *api 80 | ob := &entities.Observation{} 81 | handle := func() (interface{}, []error) { return a.PutObservation(reader.GetEntityID(r), ob) } 82 | handlePutRequest(w, endpoint, r, ob, &handle, a.GetConfig().Server.IndentedJSON) 83 | } 84 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/observation_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | entities "github.com/gost/core" 7 | "github.com/stretchr/testify/assert" 8 | "io/ioutil" 9 | "net/http" 10 | "testing" 11 | ) 12 | 13 | func TestGetObservation(t *testing.T) { 14 | getAndAssertObservation("/v1.0/observations(1)", t) 15 | } 16 | 17 | func TestGetObservations(t *testing.T) { 18 | getAndAssertObservations("/v1.0/observations", t) 19 | } 20 | 21 | func TestGetObservationByDatastream(t *testing.T) { 22 | getAndAssertObservations("/v1.0/datastreams(1)/observations", t) 23 | } 24 | 25 | func TestGetObservationByFOI(t *testing.T) { 26 | getAndAssertObservations("/v1.0/featuresofinterest(1)/observations", t) 27 | } 28 | 29 | func TestPostObservation(t *testing.T) { 30 | // arrange 31 | mockObs := newMockObservation(1) 32 | 33 | // act 34 | r := request("POST", "/v1.0/observations", mockObs) 35 | 36 | // arrange 37 | parseAndAssertObservation(*mockObs, r, http.StatusCreated, t) 38 | } 39 | 40 | func TestPostObservationByDatastream(t *testing.T) { 41 | // arrange 42 | mockObs := newMockObservation(1) 43 | 44 | // act 45 | r := request("POST", "/v1.0/datastreams(1)/observations", mockObs) 46 | 47 | // arrange 48 | parseAndAssertObservation(*mockObs, r, http.StatusCreated, t) 49 | } 50 | 51 | func TestPutObservation(t *testing.T) { 52 | // arrange 53 | mockObs := newMockObservation(1) 54 | mockObs.PhenomenonTime = "2017-07-17T05:13:09.161Z" 55 | 56 | // act 57 | r := request("PUT", "/v1.0/observations(1)", mockObs) 58 | 59 | parseAndAssertObservation(*mockObs, r, http.StatusOK, t) 60 | } 61 | 62 | func TestPatchObservation(t *testing.T) { 63 | // arrange 64 | mockObs := newMockObservation(1) 65 | mockObs.PhenomenonTime = "2017-07-17T05:13:09.161Z" 66 | 67 | // act 68 | r := request("PATCH", "/v1.0/observations(1)", mockObs) 69 | 70 | // assert 71 | parseAndAssertObservation(*mockObs, r, http.StatusOK, t) 72 | } 73 | 74 | func TestDeleteObservations(t *testing.T) { 75 | r := request("DELETE", "/v1.0/observations(1)", nil) 76 | 77 | // assert 78 | assertStatusCode(http.StatusOK, r, t) 79 | } 80 | 81 | func getAndAssertObservation(url string, t *testing.T) { 82 | r := request("GET", url, nil) 83 | parseAndAssertObservation(*newMockObservation(1), r, http.StatusOK, t) 84 | } 85 | 86 | func parseAndAssertObservation(created entities.Observation, r *http.Response, expectedStatusCode int, t *testing.T) { 87 | obs := entities.Observation{} 88 | body, err := ioutil.ReadAll(r.Body) 89 | err = json.Unmarshal(body, &obs) 90 | 91 | assert.Nil(t, err) 92 | assertStatusCode(expectedStatusCode, r, t) 93 | assertObservation(created, obs, t) 94 | } 95 | 96 | func assertObservation(created, returned entities.Observation, t *testing.T) { 97 | assert.Equal(t, fmt.Sprintf("%v", created.ID), fmt.Sprintf("%v", returned.ID)) 98 | assert.Equal(t, fmt.Sprintf("%v", created.Result), fmt.Sprintf("%v", returned.Result)) 99 | assert.Equal(t, created.PhenomenonTime, returned.PhenomenonTime) 100 | } 101 | 102 | func getAndAssertObservations(url string, t *testing.T) { 103 | // act 104 | r, _ := http.Get(getServer().URL + url) 105 | ar := entities.ArrayResponseObservations{} 106 | body, err := ioutil.ReadAll(r.Body) 107 | err = json.Unmarshal(body, &ar) 108 | 109 | // assert 110 | assert.Nil(t, err) 111 | assertStatusCode(http.StatusOK, r, t) 112 | assert.Equal(t, 2, ar.Count) 113 | 114 | for _, obs := range ar.Data { 115 | expected := newMockObservation(int(obs.ID.(float64))) 116 | assertObservation(*expected, *obs, t) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/observedproperties.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/odata" 9 | "github.com/gost/server/sensorthings/rest/reader" 10 | ) 11 | 12 | // HandleGetObservedProperty retrieves an ObservedProperty by id 13 | func HandleGetObservedProperty(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 14 | a := *api 15 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 16 | return a.GetObservedProperty(reader.GetEntityID(r), q, path) 17 | } 18 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 19 | } 20 | 21 | // HandleGetObservedProperties retrieves ObservedProperties 22 | func HandleGetObservedProperties(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 23 | a := *api 24 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { return a.GetObservedProperties(q, path) } 25 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 26 | } 27 | 28 | // HandleGetObservedPropertyByDatastream retrieves the ObservedProperty by given Datastream id 29 | func HandleGetObservedPropertyByDatastream(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 30 | a := *api 31 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 32 | return a.GetObservedPropertyByDatastream(reader.GetEntityID(r), q, path) 33 | } 34 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 35 | } 36 | 37 | // HandlePostObservedProperty posts a new ObservedProperty 38 | func HandlePostObservedProperty(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 39 | a := *api 40 | op := &entities.ObservedProperty{} 41 | handle := func() (interface{}, []error) { return a.PostObservedProperty(op) } 42 | handlePostRequest(w, endpoint, r, op, &handle, a.GetConfig().Server.IndentedJSON) 43 | } 44 | 45 | // HandleDeleteObservedProperty Deletes an ObservedProperty by id 46 | func HandleDeleteObservedProperty(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 47 | a := *api 48 | handle := func() error { return a.DeleteObservedProperty(reader.GetEntityID(r)) } 49 | handleDeleteRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON) 50 | } 51 | 52 | // HandlePatchObservedProperty patches an Observes property by id 53 | func HandlePatchObservedProperty(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 54 | a := *api 55 | op := &entities.ObservedProperty{} 56 | handle := func() (interface{}, error) { return a.PatchObservedProperty(reader.GetEntityID(r), op) } 57 | handlePatchRequest(w, endpoint, r, op, &handle, a.GetConfig().Server.IndentedJSON) 58 | } 59 | 60 | // HandlePutObservedProperty posts a new ObservedProperty 61 | func HandlePutObservedProperty(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 62 | a := *api 63 | op := &entities.ObservedProperty{} 64 | handle := func() (interface{}, []error) { return a.PutObservedProperty(reader.GetEntityID(r), op) } 65 | handlePutRequest(w, endpoint, r, op, &handle, a.GetConfig().Server.IndentedJSON) 66 | } 67 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/observedproperties_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | entities "github.com/gost/core" 7 | "github.com/stretchr/testify/assert" 8 | "io/ioutil" 9 | "net/http" 10 | "testing" 11 | ) 12 | 13 | func TestGetObservedProperty(t *testing.T) { 14 | getAndAssertObservedProperty("/v1.0/observedproperties(1)", t) 15 | } 16 | 17 | func TestGetObservedPropertyByDatastream(t *testing.T) { 18 | getAndAssertObservedProperty("/v1.0/datastreams(1)/observedproperty", t) 19 | } 20 | 21 | func TestGetObservedProperties(t *testing.T) { 22 | getAndAssertObservedProperties("/v1.0/observedproperties", t) 23 | } 24 | 25 | func TestPostObservedProperty(t *testing.T) { 26 | // arrange 27 | mockObservedProperty := newMockObservedProperty(1) 28 | 29 | // act 30 | r := request("POST", "/v1.0/observedproperties", mockObservedProperty) 31 | 32 | // arrange 33 | parseAndAssertObservedProperty(*mockObservedProperty, r, http.StatusCreated, t) 34 | } 35 | 36 | func TestPutObservedProperty(t *testing.T) { 37 | // arrange 38 | mockObservedProperty := newMockObservedProperty(1) 39 | mockObservedProperty.Name = "patched" 40 | 41 | // act 42 | r := request("PUT", "/v1.0/observedproperties(1)", mockObservedProperty) 43 | 44 | parseAndAssertObservedProperty(*mockObservedProperty, r, http.StatusOK, t) 45 | } 46 | 47 | func TestPatchObservedProperty(t *testing.T) { 48 | // arrange 49 | mockObservedProperty := newMockObservedProperty(1) 50 | mockObservedProperty.Name = "patched" 51 | 52 | // act 53 | r := request("PATCH", "/v1.0/observedproperties(1)", mockObservedProperty) 54 | 55 | // assert 56 | parseAndAssertObservedProperty(*mockObservedProperty, r, http.StatusOK, t) 57 | } 58 | 59 | func TestDeleteObservedProperty(t *testing.T) { 60 | r := request("DELETE", "/v1.0/observedproperties(1)", nil) 61 | 62 | // assert 63 | assertStatusCode(http.StatusOK, r, t) 64 | } 65 | 66 | func getAndAssertObservedProperty(url string, t *testing.T) { 67 | r := request("GET", url, nil) 68 | parseAndAssertObservedProperty(*newMockObservedProperty(1), r, http.StatusOK, t) 69 | } 70 | 71 | func parseAndAssertObservedProperty(created entities.ObservedProperty, r *http.Response, expectedStatusCode int, t *testing.T) { 72 | observedProperty := entities.ObservedProperty{} 73 | body, err := ioutil.ReadAll(r.Body) 74 | err = json.Unmarshal(body, &observedProperty) 75 | 76 | assert.Nil(t, err) 77 | assertStatusCode(expectedStatusCode, r, t) 78 | assertObservedProperty(created, observedProperty, t) 79 | } 80 | 81 | func assertObservedProperty(created, returned entities.ObservedProperty, t *testing.T) { 82 | assert.Equal(t, fmt.Sprintf("%v", created.ID), fmt.Sprintf("%v", returned.ID)) 83 | assert.Equal(t, created.Name, returned.Name) 84 | assert.Equal(t, created.Description, returned.Description) 85 | } 86 | 87 | func getAndAssertObservedProperties(url string, t *testing.T) { 88 | // act 89 | r, _ := http.Get(getServer().URL + url) 90 | ar := entities.ArrayResponseObservedProperty{} 91 | body, err := ioutil.ReadAll(r.Body) 92 | err = json.Unmarshal(body, &ar) 93 | 94 | // assert 95 | assert.Nil(t, err) 96 | assertStatusCode(http.StatusOK, r, t) 97 | assert.Equal(t, 2, ar.Count) 98 | 99 | for _, entity := range ar.Data { 100 | expected := newMockObservedProperty(int(entity.ID.(float64))) 101 | assertObservedProperty(*expected, *entity, t) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/root.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gost/server/sensorthings/models" 7 | "github.com/gost/server/sensorthings/rest/writer" 8 | ) 9 | 10 | // HandleAPIRoot will return a JSON array of the available SensorThings resource endpoints. 11 | func HandleAPIRoot(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 12 | a := *api 13 | bpi := a.GetBasePathInfo() 14 | writer.SendJSONResponse(w, http.StatusOK, bpi, nil, a.GetConfig().Server.IndentedJSON) 15 | } 16 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/root_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | entities "github.com/gost/core" 6 | "github.com/stretchr/testify/assert" 7 | "io/ioutil" 8 | "testing" 9 | ) 10 | 11 | func TestHandleRoot(t *testing.T) { 12 | // arrange 13 | count := 0 14 | eps := newMockAPI().GetEndpoints() 15 | for _, e := range *eps { 16 | if e.ShowOutputInfo() { 17 | count++ 18 | } 19 | } 20 | 21 | // act 22 | r := request("GET", "/v1.0", nil) 23 | arrayResponse := entities.ArrayResponseEndpoint{} 24 | body, _ := ioutil.ReadAll(r.Body) 25 | json.Unmarshal(body, &arrayResponse) 26 | 27 | // assert 28 | assert.NotNil(t, arrayResponse) 29 | assert.Len(t, arrayResponse.Data, count) 30 | } 31 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/sensor.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/odata" 9 | "github.com/gost/server/sensorthings/rest/reader" 10 | ) 11 | 12 | // HandleGetSensorByDatastream ... 13 | func HandleGetSensorByDatastream(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 14 | a := *api 15 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 16 | return a.GetSensorByDatastream(reader.GetEntityID(r), q, path) 17 | } 18 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 19 | } 20 | 21 | // HandleGetSensor ... 22 | func HandleGetSensor(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 23 | a := *api 24 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 25 | return a.GetSensor(reader.GetEntityID(r), q, path) 26 | } 27 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 28 | } 29 | 30 | // HandleGetSensors ... 31 | func HandleGetSensors(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 32 | a := *api 33 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { return a.GetSensors(q, path) } 34 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 35 | } 36 | 37 | // HandlePostSensors ... 38 | func HandlePostSensors(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 39 | a := *api 40 | sensor := &entities.Sensor{} 41 | handle := func() (interface{}, []error) { return a.PostSensor(sensor) } 42 | handlePostRequest(w, endpoint, r, sensor, &handle, a.GetConfig().Server.IndentedJSON) 43 | } 44 | 45 | // HandleDeleteSensor ... 46 | func HandleDeleteSensor(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 47 | a := *api 48 | handle := func() error { return a.DeleteSensor(reader.GetEntityID(r)) } 49 | handleDeleteRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON) 50 | } 51 | 52 | // HandlePatchSensor ... 53 | func HandlePatchSensor(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 54 | a := *api 55 | sensor := &entities.Sensor{} 56 | handle := func() (interface{}, error) { return a.PatchSensor(reader.GetEntityID(r), sensor) } 57 | handlePatchRequest(w, endpoint, r, sensor, &handle, a.GetConfig().Server.IndentedJSON) 58 | } 59 | 60 | // HandlePutSensor ... 61 | func HandlePutSensor(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 62 | a := *api 63 | sensor := &entities.Sensor{} 64 | handle := func() (interface{}, []error) { return a.PutSensor(reader.GetEntityID(r), sensor) } 65 | handlePutRequest(w, endpoint, r, sensor, &handle, a.GetConfig().Server.IndentedJSON) 66 | } 67 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/sensor_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | entities "github.com/gost/core" 7 | "github.com/stretchr/testify/assert" 8 | "io/ioutil" 9 | "net/http" 10 | "testing" 11 | ) 12 | 13 | func TestGetSensor(t *testing.T) { 14 | getAndAssertSensor("/v1.0/sensors(1)", t) 15 | } 16 | 17 | func TestGetSensorByDatastream(t *testing.T) { 18 | getAndAssertSensor("/v1.0/datastreams(1)/sensor", t) 19 | } 20 | 21 | func TestGetSensors(t *testing.T) { 22 | getAndAssertSensors("/v1.0/sensors", t) 23 | } 24 | 25 | func TestPostSensor(t *testing.T) { 26 | // arrange 27 | mockSensor := newMockSensor(1) 28 | 29 | // act 30 | r := request("POST", "/v1.0/sensors", mockSensor) 31 | 32 | // arrange 33 | parseAndAssertSensor(*mockSensor, r, http.StatusCreated, t) 34 | } 35 | 36 | func TestPutSensor(t *testing.T) { 37 | // arrange 38 | mockSensor := newMockSensor(1) 39 | mockSensor.Name = "patched" 40 | 41 | // act 42 | r := request("PUT", "/v1.0/sensors(1)", mockSensor) 43 | 44 | parseAndAssertSensor(*mockSensor, r, http.StatusOK, t) 45 | } 46 | 47 | func TestPatchSensor(t *testing.T) { 48 | // arrange 49 | mockSensor := newMockSensor(1) 50 | mockSensor.Name = "patched" 51 | 52 | // act 53 | r := request("PATCH", "/v1.0/sensors(1)", mockSensor) 54 | 55 | // assert 56 | parseAndAssertSensor(*mockSensor, r, http.StatusOK, t) 57 | } 58 | 59 | func TestDeleteSensor(t *testing.T) { 60 | r := request("DELETE", "/v1.0/sensors(1)", nil) 61 | 62 | // assert 63 | assertStatusCode(http.StatusOK, r, t) 64 | } 65 | 66 | func getAndAssertSensor(url string, t *testing.T) { 67 | r := request("GET", url, nil) 68 | parseAndAssertSensor(*newMockSensor(1), r, http.StatusOK, t) 69 | } 70 | 71 | func parseAndAssertSensor(created entities.Sensor, r *http.Response, expectedStatusCode int, t *testing.T) { 72 | sensor := entities.Sensor{} 73 | body, err := ioutil.ReadAll(r.Body) 74 | err = json.Unmarshal(body, &sensor) 75 | 76 | assert.Nil(t, err) 77 | assertStatusCode(expectedStatusCode, r, t) 78 | assertSensor(created, sensor, t) 79 | } 80 | 81 | func assertSensor(created, returned entities.Sensor, t *testing.T) { 82 | assert.Equal(t, fmt.Sprintf("%v", created.ID), fmt.Sprintf("%v", returned.ID)) 83 | assert.Equal(t, created.Name, returned.Name) 84 | assert.Equal(t, created.Description, returned.Description) 85 | assert.Equal(t, created.EncodingType, returned.EncodingType) 86 | assert.Equal(t, created.Metadata, returned.Metadata) 87 | } 88 | 89 | func getAndAssertSensors(url string, t *testing.T) { 90 | // act 91 | r, _ := http.Get(getServer().URL + url) 92 | ar := entities.ArrayResponseSensors{} 93 | body, err := ioutil.ReadAll(r.Body) 94 | err = json.Unmarshal(body, &ar) 95 | 96 | // assert 97 | assert.Nil(t, err) 98 | assertStatusCode(http.StatusOK, r, t) 99 | assert.Equal(t, 2, ar.Count) 100 | 101 | for _, entity := range ar.Data { 102 | expected := newMockSensor(int(entity.ID.(float64))) 103 | assertSensor(*expected, *entity, t) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/thing.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | entities "github.com/gost/core" 7 | "github.com/gost/server/sensorthings/models" 8 | "github.com/gost/server/sensorthings/odata" 9 | "github.com/gost/server/sensorthings/rest/reader" 10 | ) 11 | 12 | // HandleGetThings retrieves and sends Things based on the given filter if provided 13 | func HandleGetThings(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 14 | a := *api 15 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { return a.GetThings(q, path) } 16 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 17 | } 18 | 19 | // HandleGetThing retrieves and sends a specific Thing based on the given ID and filter 20 | func HandleGetThing(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 21 | a := *api 22 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 23 | return a.GetThing(reader.GetEntityID(r), q, path) 24 | } 25 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 26 | } 27 | 28 | // HandleGetThingByDatastream retrieves and sends a specific Thing based on the given datastream ID and filter 29 | func HandleGetThingByDatastream(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 30 | a := *api 31 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 32 | return a.GetThingByDatastream(reader.GetEntityID(r), q, path) 33 | } 34 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 35 | } 36 | 37 | // HandleGetThingsByLocation retrieves and sends Things based on the given Location ID and filter 38 | func HandleGetThingsByLocation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 39 | a := *api 40 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 41 | return a.GetThingsByLocation(reader.GetEntityID(r), q, path) 42 | } 43 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 44 | } 45 | 46 | // HandleGetThingByHistoricalLocation retrieves and sends a specific Thing based on the given HistoricalLocation ID and filter 47 | func HandleGetThingByHistoricalLocation(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 48 | a := *api 49 | handle := func(q *odata.QueryOptions, path string) (interface{}, error) { 50 | return a.GetThingByHistoricalLocation(reader.GetEntityID(r), q, path) 51 | } 52 | handleGetRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON, a.GetConfig().Server.MaxEntityResponse, a.GetConfig().Server.ExternalURI) 53 | } 54 | 55 | // HandlePostThing tries to insert a new Thing and sends back the created Thing 56 | func HandlePostThing(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 57 | a := *api 58 | thing := &entities.Thing{} 59 | handle := func() (interface{}, []error) { return a.PostThing(thing) } 60 | handlePostRequest(w, endpoint, r, thing, &handle, a.GetConfig().Server.IndentedJSON) 61 | } 62 | 63 | // HandleDeleteThing deletes a thing by given id 64 | func HandleDeleteThing(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 65 | a := *api 66 | handle := func() error { return a.DeleteThing(reader.GetEntityID(r)) } 67 | handleDeleteRequest(w, endpoint, r, &handle, a.GetConfig().Server.IndentedJSON) 68 | } 69 | 70 | // HandlePatchThing patches a thing by given id 71 | func HandlePatchThing(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 72 | a := *api 73 | thing := &entities.Thing{} 74 | handle := func() (interface{}, error) { return a.PatchThing(reader.GetEntityID(r), thing) } 75 | handlePatchRequest(w, endpoint, r, thing, &handle, a.GetConfig().Server.IndentedJSON) 76 | } 77 | 78 | // HandlePutThing patches a thing by given id 79 | func HandlePutThing(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 80 | a := *api 81 | thing := &entities.Thing{} 82 | handle := func() (interface{}, []error) { return a.PutThing(reader.GetEntityID(r), thing) } 83 | handlePutRequest(w, endpoint, r, thing, &handle, a.GetConfig().Server.IndentedJSON) 84 | } 85 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/thing_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | entities "github.com/gost/core" 7 | "github.com/stretchr/testify/assert" 8 | "io/ioutil" 9 | "net/http" 10 | "testing" 11 | ) 12 | 13 | func TestGetThing(t *testing.T) { 14 | getAndAssertThing("/v1.0/things(1)", t) 15 | } 16 | 17 | func TestGetThingByDatastream(t *testing.T) { 18 | getAndAssertThing("/v1.0/datastreams(1)/thing", t) 19 | } 20 | 21 | func TestGetThingByHistoricalLocation(t *testing.T) { 22 | getAndAssertThing("/v1.0/historicallocations(1)/thing", t) 23 | } 24 | 25 | func TestGetThings(t *testing.T) { 26 | getAndAssertThings("/v1.0/things", t) 27 | } 28 | 29 | func TestGetThingsByLocation(t *testing.T) { 30 | getAndAssertThings("/v1.0/locations(1)/things", t) 31 | } 32 | 33 | func TestPostThing(t *testing.T) { 34 | // arrange 35 | mockThing := newMockThing(1) 36 | 37 | // act 38 | r := request("POST", "/v1.0/things", mockThing) 39 | 40 | // arrange 41 | parseAndAssertThing(*mockThing, r, http.StatusCreated, t) 42 | } 43 | 44 | func TestPutThing(t *testing.T) { 45 | // arrange 46 | mockThing := newMockThing(1) 47 | mockThing.Name = "patched" 48 | 49 | // act 50 | r := request("PUT", "/v1.0/things(1)", mockThing) 51 | 52 | parseAndAssertThing(*mockThing, r, http.StatusOK, t) 53 | } 54 | 55 | func TestPatchThing(t *testing.T) { 56 | // arrange 57 | mockThing := newMockThing(1) 58 | mockThing.Name = "patched" 59 | 60 | // act 61 | r := request("PATCH", "/v1.0/things(1)", mockThing) 62 | 63 | // assert 64 | parseAndAssertThing(*mockThing, r, http.StatusOK, t) 65 | } 66 | 67 | func TestDeleteThing(t *testing.T) { 68 | r := request("DELETE", "/v1.0/things(1)", nil) 69 | 70 | // assert 71 | assertStatusCode(http.StatusOK, r, t) 72 | } 73 | 74 | func getAndAssertThing(url string, t *testing.T) { 75 | r := request("GET", url, nil) 76 | parseAndAssertThing(*newMockThing(1), r, http.StatusOK, t) 77 | } 78 | 79 | func parseAndAssertThing(created entities.Thing, r *http.Response, expectedStatusCode int, t *testing.T) { 80 | thing := entities.Thing{} 81 | body, err := ioutil.ReadAll(r.Body) 82 | err = json.Unmarshal(body, &thing) 83 | 84 | assert.Nil(t, err) 85 | assertStatusCode(expectedStatusCode, r, t) 86 | assertThing(created, thing, t) 87 | } 88 | 89 | func assertThing(created, returned entities.Thing, t *testing.T) { 90 | assert.Equal(t, fmt.Sprintf("%v", created.ID), fmt.Sprintf("%v", returned.ID)) 91 | assert.Equal(t, created.Name, returned.Name) 92 | assert.Equal(t, created.Description, returned.Description) 93 | assert.Equal(t, created.Properties, returned.Properties) 94 | } 95 | 96 | func getAndAssertThings(url string, t *testing.T) { 97 | // act 98 | r, _ := http.Get(getServer().URL + url) 99 | ar := entities.ArrayResponseThings{} 100 | body, err := ioutil.ReadAll(r.Body) 101 | err = json.Unmarshal(body, &ar) 102 | 103 | // assert 104 | assert.Nil(t, err) 105 | assertStatusCode(http.StatusOK, r, t) 106 | assert.Equal(t, 2, ar.Count) 107 | 108 | for _, thing := range ar.Data { 109 | expected := newMockThing(int(thing.ID.(float64))) 110 | assertThing(*expected, *thing, t) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /sensorthings/rest/handlers/version.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gost/server/sensorthings/models" 7 | "github.com/gost/server/sensorthings/rest/writer" 8 | ) 9 | 10 | // HandleVersion retrieves current version information and sends it back to the user 11 | func HandleVersion(w http.ResponseWriter, r *http.Request, endpoint *models.Endpoint, api *models.API) { 12 | a := *api 13 | versionInfo := a.GetVersionInfo() 14 | writer.SendJSONResponse(w, http.StatusOK, versionInfo, nil, a.GetConfig().Server.IndentedJSON) 15 | } 16 | -------------------------------------------------------------------------------- /sensorthings/rest/reader/reader.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "strings" 7 | 8 | "fmt" 9 | "io/ioutil" 10 | 11 | "github.com/gorilla/mux" 12 | entities "github.com/gost/core" 13 | gostErrors "github.com/gost/server/errors" 14 | "github.com/gost/server/sensorthings/rest/writer" 15 | ) 16 | 17 | // GetEntityID retrieves the id from the request, for example 18 | // the request http://mysensor.com/V1.0/Things(1236532) returns 1236532 as id 19 | func GetEntityID(r *http.Request) string { 20 | vars := mux.Vars(r) 21 | value := vars["id"] 22 | substring := value[1 : len(value)-1] 23 | return substring 24 | } 25 | 26 | // CheckContentType checks if there is a content-type header, if so check if it is of type 27 | // application/json, if not return an error, SensorThings server only accepts application/json 28 | func CheckContentType(w http.ResponseWriter, r *http.Request, indentJSON bool) bool { 29 | // maybe needs to add case-insentive check? 30 | if len(r.Header.Get("Content-Type")) > 0 { 31 | if !strings.Contains(r.Header.Get("Content-Type"), "application/json") { 32 | writer.SendError(w, []error{gostErrors.NewBadRequestError(errors.New("Missing or wrong Content-Type, accepting: application/json"))}, indentJSON) 33 | return false 34 | } 35 | } 36 | 37 | return true 38 | } 39 | 40 | // CheckAndGetBody checks if the request body is not nil and tries to read it in a byte slice 41 | // when an error occurs an error will be send back using the ResponseWriter 42 | func CheckAndGetBody(w http.ResponseWriter, r *http.Request, indentJSON bool) []byte { 43 | if r.Body == nil { 44 | writer.SendError(w, []error{gostErrors.NewBadRequestError(fmt.Errorf("No body found in request"))}, indentJSON) 45 | return nil 46 | } 47 | 48 | byteData, _ := ioutil.ReadAll(r.Body) 49 | return byteData 50 | } 51 | 52 | // ParseEntity tries to convert the byte data into the given interface of type entity 53 | // if an error returns it will be wraped inside an gosterror 54 | func ParseEntity(entity entities.Entity, data []byte) error { 55 | var err error 56 | 57 | err = entity.ParseEntity(data) 58 | 59 | if err != nil { 60 | err = gostErrors.NewBadRequestError(err) 61 | } 62 | 63 | return err 64 | } 65 | -------------------------------------------------------------------------------- /sensorthings/rest/reader/reader_test.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | entities "github.com/gost/core" 8 | gostErrors "github.com/gost/server/errors" 9 | "github.com/stretchr/testify/assert" 10 | 11 | "bytes" 12 | "io/ioutil" 13 | "net/http/httptest" 14 | 15 | "github.com/gorilla/mux" 16 | ) 17 | 18 | func TestGetEntityId(t *testing.T) { 19 | // arrange 20 | router := mux.NewRouter() 21 | router.HandleFunc("/v1.0/Things{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 22 | id := GetEntityID(r) 23 | w.Write([]byte(id)) 24 | // fmt.Println("func called") 25 | })) 26 | 27 | ts := httptest.NewServer(router) 28 | defer ts.Close() 29 | 30 | // act 31 | resp, _ := http.Get(ts.URL + "/v1.0/Things(35)") 32 | 33 | // assert 34 | assert.True(t, resp != nil) 35 | assert.True(t, http.StatusOK == resp.StatusCode) 36 | body := resp.Body 37 | result, _ := ioutil.ReadAll(body) 38 | assert.True(t, string(result) == "35") 39 | } 40 | 41 | func TestCheckContentTypeWithoutHeadersShouldReturnFalse(t *testing.T) { 42 | // arrange 43 | req, _ := http.NewRequest("GET", "/v1.0/Things(1)", nil) 44 | w := httptest.NewRecorder() 45 | 46 | // act 47 | res := CheckContentType(w, req, false) 48 | 49 | // assert 50 | assert.True(t, res) 51 | } 52 | 53 | func TestCheckContentTypeWithContentTypeHeaderShouldReturnTrue(t *testing.T) { 54 | // arrange 55 | req, _ := http.NewRequest("GET", "/v1.0/Things(1)", nil) 56 | req.Header.Add("Content-Type", "application/json") 57 | w := httptest.NewRecorder() 58 | 59 | // act 60 | res := CheckContentType(w, req, false) 61 | 62 | // assert 63 | assert.True(t, res) 64 | } 65 | 66 | func TestCheckContentTypeWithoutContentTypeHeaderShouldReturnFalse(t *testing.T) { 67 | // arrange 68 | req, _ := http.NewRequest("GET", "/v1.0/Things(1)", nil) 69 | req.Header.Add("Content-Type", "superformat") 70 | w := httptest.NewRecorder() 71 | 72 | // act 73 | res := CheckContentType(w, req, false) 74 | 75 | // assert 76 | assert.False(t, res) 77 | } 78 | 79 | func TestCheckAndGetBodyWithNoBody(t *testing.T) { 80 | // arrange 81 | rr := httptest.NewRecorder() 82 | req, _ := http.NewRequest("GET", "/bla", nil) 83 | 84 | // act 85 | CheckAndGetBody(rr, req, false) 86 | 87 | // assert 88 | assert.Equal(t, http.StatusBadRequest, rr.Code) 89 | } 90 | 91 | func TestCheckAndGetBody(t *testing.T) { 92 | // arrange 93 | rr := httptest.NewRecorder() 94 | req, _ := http.NewRequest("GET", "/bla", bytes.NewReader([]byte(""))) 95 | 96 | // act 97 | CheckAndGetBody(rr, req, false) 98 | 99 | // assert 100 | assert.Equal(t, http.StatusOK, rr.Code) 101 | } 102 | 103 | func TestParseEntity(t *testing.T) { 104 | // arrange 105 | thing := &entities.Thing{} 106 | thingBytes := []byte("{\"name\": \"thing1\", \"description\": \"test thing 1\"}") 107 | 108 | location := &entities.Location{} 109 | historicalLocation := &entities.HistoricalLocation{} 110 | datastream := &entities.Datastream{} 111 | sensor := &entities.Sensor{} 112 | observedProperty := &entities.ObservedProperty{} 113 | observation := &entities.Observation{} 114 | featureOfinterest := &entities.FeatureOfInterest{} 115 | 116 | // act 117 | tErr := ParseEntity(thing, thingBytes) 118 | lErr := ParseEntity(location, nil) 119 | hlErr := ParseEntity(historicalLocation, nil) 120 | dErr := ParseEntity(datastream, nil) 121 | sErr := ParseEntity(sensor, nil) 122 | opErr := ParseEntity(observedProperty, nil) 123 | oErr := ParseEntity(observation, nil) 124 | fErr := ParseEntity(featureOfinterest, nil) 125 | 126 | // assert 127 | assert.Nil(t, tErr) 128 | assert.Equal(t, thing.Name, "thing1") 129 | assert.Equal(t, thing.Description, "test thing 1") 130 | assert.Equal(t, 400, getStatusCode(lErr)) 131 | assert.Equal(t, 400, getStatusCode(hlErr)) 132 | assert.Equal(t, 400, getStatusCode(dErr)) 133 | assert.Equal(t, 400, getStatusCode(sErr)) 134 | assert.Equal(t, 400, getStatusCode(opErr)) 135 | assert.Equal(t, 400, getStatusCode(oErr)) 136 | assert.Equal(t, 400, getStatusCode(fErr)) 137 | } 138 | 139 | func getStatusCode(err error) int { 140 | switch e := err.(type) { 141 | case gostErrors.APIError: 142 | return e.GetHTTPErrorStatusCode() 143 | } 144 | 145 | return 0 146 | } 147 | -------------------------------------------------------------------------------- /sensorthings/rest/writer/writer.go: -------------------------------------------------------------------------------- 1 | package writer 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "strings" 9 | 10 | "errors" 11 | 12 | gostErrors "github.com/gost/server/errors" 13 | "github.com/gost/server/sensorthings/models" 14 | "github.com/gost/server/sensorthings/odata" 15 | ) 16 | 17 | // SendJSONResponse sends the desired message to the user 18 | // the message will be marshalled into JSON 19 | func SendJSONResponse(w http.ResponseWriter, status int, data interface{}, qo *odata.QueryOptions, indentJSON bool) { 20 | w.Header().Set("Content-Type", "application/json; charset=UTF-8") 21 | 22 | if data != nil { 23 | b, err := JSONMarshal(data, true, indentJSON) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | if qo != nil { 29 | // $count for collection is requested url/$count and not the query ?$count=true, ToDo: move to API code?e 30 | if qo.CollectionCount != nil && bool(*qo.CollectionCount) == true { 31 | b, err = convertForCountResponse(b, qo) 32 | } else if qo.Value != nil && bool(*qo.Value) == true { 33 | b, err = convertForValueResponse(b, qo) 34 | } 35 | 36 | if err != nil { 37 | SendError(w, []error{err}, indentJSON) 38 | } 39 | } 40 | 41 | w.WriteHeader(status) 42 | w.Write(b) 43 | } 44 | } 45 | 46 | //JSONMarshal converts the data and converts special characters such as & 47 | func JSONMarshal(data interface{}, safeEncoding, indentJSON bool) ([]byte, error) { 48 | var b []byte 49 | var err error 50 | if indentJSON { 51 | b, err = json.MarshalIndent(data, "", " ") 52 | } else { 53 | b, err = json.Marshal(data) 54 | } 55 | 56 | // This code is needed if the response contains special characters like &, <, >, 57 | // and those characters must not be converted to safe encoding. 58 | if safeEncoding { 59 | b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1) 60 | b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1) 61 | b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1) 62 | } 63 | return b, err 64 | } 65 | 66 | // SendError creates an ErrorResponse message and sets it to the user 67 | // using SendJSONResponse 68 | func SendError(w http.ResponseWriter, error []error, indentJSON bool) { 69 | //errors cannot be marshalled, create strings 70 | errors := make([]string, len(error)) 71 | for idx, value := range error { 72 | errors[idx] = value.Error() 73 | } 74 | 75 | // Set the status code, default for error, check if there is an ApiError an get 76 | var statusCode = http.StatusInternalServerError 77 | 78 | if len(error) > 0 { 79 | // if there is Encoding type error, sends bad request (400 range) 80 | if strings.Contains(errors[0], "Encoding not supported") || 81 | strings.Contains(errors[0], "No matching token") || 82 | strings.Contains(errors[0], "invalid input syntax") || 83 | strings.Contains(errors[0], "Error executing query") { 84 | 85 | statusCode = http.StatusBadRequest 86 | } 87 | 88 | switch e := error[0].(type) { 89 | case gostErrors.APIError: 90 | statusCode = e.GetHTTPErrorStatusCode() 91 | } 92 | } 93 | 94 | statusText := http.StatusText(statusCode) 95 | errorResponse := models.ErrorResponse{ 96 | Error: models.ErrorContent{ 97 | StatusText: statusText, 98 | StatusCode: statusCode, 99 | Messages: errors, 100 | }, 101 | } 102 | 103 | SendJSONResponse(w, statusCode, errorResponse, nil, indentJSON) 104 | } 105 | 106 | func convertForValueResponse(b []byte, qo *odata.QueryOptions) ([]byte, error) { 107 | // $value is requested only send back the value 108 | errMessage := fmt.Errorf("Unable to retrieve $value for %v", qo.Select.SelectItems) 109 | var m map[string]json.RawMessage 110 | err := json.Unmarshal(b, &m) 111 | if err != nil || qo.Select == nil || qo.Select.SelectItems == nil || len(qo.Select.SelectItems) == 0 { 112 | return nil, gostErrors.NewRequestInternalServerError(errMessage) 113 | } 114 | 115 | // if selected equals the key in json add to mVal 116 | mVal := []byte{} 117 | for k, v := range m { 118 | if strings.ToLower(k) == qo.Select.SelectItems[0].Segments[0].Value { 119 | mVal = v 120 | } 121 | } 122 | 123 | if len(mVal) == 0 { 124 | return nil, gostErrors.NewBadRequestError(errMessage) 125 | } 126 | 127 | value := string(mVal[:]) 128 | value = strings.TrimPrefix(value, "\"") 129 | value = strings.TrimSuffix(value, "\"") 130 | 131 | b = []byte(value) 132 | 133 | return b, nil 134 | } 135 | 136 | func convertForCountResponse(b []byte, qo *odata.QueryOptions) ([]byte, error) { 137 | var m map[string]json.RawMessage 138 | json.Unmarshal(b, &m) 139 | if count, ok := m["@iot.count"]; ok { 140 | b = []byte(string(count)) 141 | } else { 142 | return nil, gostErrors.NewBadRequestError(errors.New("/$count not available for endpoint")) 143 | } 144 | 145 | return b, nil 146 | } 147 | -------------------------------------------------------------------------------- /sensorthings/rest/writer/writer_test.go: -------------------------------------------------------------------------------- 1 | package writer 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | entities "github.com/gost/core" 7 | "github.com/gost/godata" 8 | "github.com/gost/server/sensorthings/odata" 9 | "github.com/stretchr/testify/assert" 10 | "io/ioutil" 11 | "net/http" 12 | "net/http/httptest" 13 | "testing" 14 | ) 15 | 16 | func TestSendErrorWithNoError(t *testing.T) { 17 | // arrange 18 | rr := httptest.NewRecorder() 19 | 20 | // act 21 | SendError(rr, nil, true) 22 | 23 | // assert 24 | assert.True(t, rr.Code == http.StatusInternalServerError) 25 | } 26 | 27 | func TestSendErrorWithNoIdentJson(t *testing.T) { 28 | // arrange 29 | rr := httptest.NewRecorder() 30 | thing := &entities.Thing{} 31 | thing.Name = "yo" 32 | 33 | // act 34 | SendJSONResponse(rr, http.StatusTeapot, thing, nil, false) 35 | 36 | // assert 37 | assert.True(t, rr.Code == http.StatusTeapot) 38 | } 39 | 40 | func TestSendErrorWithError(t *testing.T) { 41 | // arrange 42 | rr := httptest.NewRecorder() 43 | err1 := errors.New("wrong") 44 | errs := []error{err1} 45 | 46 | // act 47 | SendError(rr, errs, false) 48 | 49 | // assert 50 | assert.True(t, rr.Code == http.StatusInternalServerError) 51 | } 52 | 53 | func TestSendJsonResponseWithNoData(t *testing.T) { 54 | // arrange 55 | rr := httptest.NewRecorder() 56 | 57 | // act 58 | SendJSONResponse(rr, http.StatusTeapot, nil, nil, false) 59 | 60 | // assert 61 | assert.True(t, rr.Code == http.StatusOK) 62 | } 63 | 64 | func TestSendJsonResponseWithData(t *testing.T) { 65 | // arrange 66 | rr := httptest.NewRecorder() 67 | thing := &entities.Thing{} 68 | thing.Name = "yo" 69 | 70 | // act 71 | SendJSONResponse(rr, http.StatusTeapot, thing, nil, false) 72 | 73 | // assert 74 | assert.True(t, rr.Code == http.StatusTeapot) 75 | } 76 | 77 | func TestSendJsonResponseWithDataAndQueryOptions(t *testing.T) { 78 | // arrange 79 | rr := httptest.NewRecorder() 80 | thing := &entities.Thing{} 81 | thing.Name = "yo" 82 | req, _ := http.NewRequest("GET", "/v1.0/Things?$top=1&$select=name,id,description", nil) 83 | qo, _ := odata.GetQueryOptions(req, 20) 84 | 85 | val := odata.GoDataValueQuery(true) 86 | qo.Value = &val 87 | 88 | // act 89 | SendJSONResponse(rr, http.StatusTeapot, thing, qo, false) 90 | 91 | // assert 92 | assert.True(t, rr.Code == http.StatusTeapot) 93 | } 94 | 95 | func TestSendJsonResponseErrorOnMarshalError(t *testing.T) { 96 | // arrange 97 | rr := httptest.NewRecorder() 98 | c := make(chan int) 99 | m := map[string]interface{}{"chan": c} 100 | 101 | // assert 102 | assert.Panics(t, func() { SendJSONResponse(rr, http.StatusTeapot, m, nil, false) }) 103 | } 104 | 105 | func TestSendJsonResponseWithRefAndNovalueError(t *testing.T) { 106 | // arrange 107 | rr := httptest.NewRecorder() 108 | thing := &entities.Thing{Name: "yo"} 109 | qo := &odata.QueryOptions{} 110 | valQuery := odata.GoDataValueQuery(true) 111 | qo.Value = &valQuery 112 | qo.Select = &godata.GoDataSelectQuery{SelectItems: nil} 113 | 114 | // act 115 | SendJSONResponse(rr, http.StatusTeapot, thing, qo, false) 116 | 117 | // assert 118 | assert.Equal(t, http.StatusInternalServerError, rr.Code) 119 | } 120 | 121 | func TestRequestValueWithNonexistingParam(t *testing.T) { 122 | // arrange 123 | rr := httptest.NewRecorder() 124 | thing := &entities.Thing{Name: "yo"} 125 | qo := &odata.QueryOptions{} 126 | valQuery := odata.GoDataValueQuery(true) 127 | qo.Value = &valQuery 128 | qo.Select = &godata.GoDataSelectQuery{SelectItems: []*godata.SelectItem{{Segments: []*godata.Token{{Value: "nonexistingparam"}}}}} 129 | 130 | // act 131 | SendJSONResponse(rr, http.StatusTeapot, thing, qo, false) 132 | 133 | // assert 134 | assert.Equal(t, http.StatusBadRequest, rr.Code) 135 | } 136 | 137 | func TestEncodingNotSupported(t *testing.T) { 138 | // arrange 139 | rr := httptest.NewRecorder() 140 | err := []error{errors.New("Encoding not supported")} 141 | 142 | // act 143 | SendError(rr, err, false) 144 | 145 | // assert 146 | assert.Equal(t, http.StatusBadRequest, rr.Code) 147 | } 148 | 149 | func TestCountCollection(t *testing.T) { 150 | // arrange 151 | rr := httptest.NewRecorder() 152 | qo := &odata.QueryOptions{} 153 | c := odata.GoDataCollectionCountQuery(true) 154 | qo.CollectionCount = &c 155 | ar := entities.ArrayResponse{Count: 10} 156 | 157 | // act 158 | SendJSONResponse(rr, http.StatusOK, ar, qo, false) 159 | body, _ := ioutil.ReadAll(rr.Body) 160 | 161 | // assert 162 | assert.Equal(t, http.StatusOK, rr.Code) 163 | assert.Equal(t, string(body), fmt.Sprintf("%v", ar.Count)) 164 | } 165 | 166 | func TestCountCollectionError(t *testing.T) { 167 | // arrange 168 | rr := httptest.NewRecorder() 169 | qo := &odata.QueryOptions{} 170 | c := odata.GoDataCollectionCountQuery(true) 171 | qo.CollectionCount = &c 172 | ar := entities.ArrayResponse{} 173 | 174 | // act 175 | SendJSONResponse(rr, http.StatusOK, ar, qo, false) 176 | 177 | // assert 178 | assert.Equal(t, http.StatusBadRequest, rr.Code) 179 | } 180 | --------------------------------------------------------------------------------