diff --git a/LICENSE b/LICENSE index 2071b23..73a5796 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) +Copyright (c) 2021 Jan-Lukas Else Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 7098738..02b74c9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ # go-geouri -Parse geo URI (RFC 5870) with Go \ No newline at end of file +Parse geo URI (RFC 5870) with Go + +https://datatracker.ietf.org/doc/html/rfc5870 + +Just the basics so far, doesn't check all the rules yet. \ No newline at end of file diff --git a/geouri.go b/geouri.go new file mode 100644 index 0000000..d4ed7cd --- /dev/null +++ b/geouri.go @@ -0,0 +1,62 @@ +package gogeouri + +import ( + "errors" + "strconv" + "strings" +) + +type Geo struct { + Latitude, Longitude, Altitude float64 + Parameters map[string][]string +} + +const scheme = "geo" + +func Parse(uri string) (*Geo, error) { + g := &Geo{ + Parameters: map[string][]string{}, + } + if !strings.HasPrefix(uri, scheme+":") { + return nil, errors.New("no or wrong scheme") + } + uri = strings.TrimPrefix(uri, scheme+":") + uriParts := strings.Split(uri, ";") + if len(strings.TrimSpace(uriParts[0])) < 1 { + return nil, errors.New("empty path") + } + coords := strings.Split(uriParts[0], ",") + if l := len(coords); l < 2 || l > 3 { + return nil, errors.New("wrong number of coordinates") + } + if f, e := strconv.ParseFloat(coords[0], 64); e == nil { + g.Latitude = f + } else { + return nil, errors.New("can't parse latitude") + } + if f, e := strconv.ParseFloat(coords[1], 64); e == nil { + g.Longitude = f + } else { + return nil, errors.New("can't parse longitude") + } + if len(coords) == 3 { + if f, e := strconv.ParseFloat(coords[2], 64); e == nil { + g.Altitude = f + } else { + return nil, errors.New("can't parse altitude") + } + } + for _, p := range uriParts[1:] { + pParts := strings.Split(p, "=") + if l := len(pParts); l == 1 { + if _, ok := g.Parameters[pParts[0]]; !ok { + g.Parameters[pParts[0]] = []string{} + } + } else if l == 2 { + g.Parameters[pParts[0]] = append(g.Parameters[pParts[0]], pParts[1]) + } else { + return nil, errors.New("wrong parameter") + } + } + return g, nil +} diff --git a/geouri_test.go b/geouri_test.go new file mode 100644 index 0000000..6c5f37b --- /dev/null +++ b/geouri_test.go @@ -0,0 +1,122 @@ +package gogeouri + +import ( + "reflect" + "testing" +) + +func TestParse(t *testing.T) { + type args struct { + uri string + } + type testCase struct { + name string + args args + want *Geo + wantErr bool + } + tests := []testCase{ + // Positive + { + name: "Simple", + args: args{"geo:37.786971,-122.399677"}, + want: &Geo{ + Latitude: 37.786971, + Longitude: -122.399677, + Parameters: map[string][]string{}, + }, + wantErr: false, + }, + { + name: "Altitude", + args: args{"geo:37.786971,-122.399677,-123.456"}, + want: &Geo{ + Latitude: 37.786971, + Longitude: -122.399677, + Altitude: -123.456, + Parameters: map[string][]string{}, + }, + wantErr: false, + }, + { + name: "Indigenous", + args: args{"geo:51.5258325,-0.1359825,0.0;name=london;url=https://hwclondon.co.uk"}, + want: &Geo{ + Latitude: 51.5258325, + Longitude: -0.1359825, + Altitude: 0.0, + Parameters: map[string][]string{ + "name": {"london"}, + "url": {"https://hwclondon.co.uk"}, + }, + }, + wantErr: false, + }, + { + name: "Parameter without value", + args: args{"geo:51.5258325,-0.1359825,0.0;name"}, + want: &Geo{ + Latitude: 51.5258325, + Longitude: -0.1359825, + Altitude: 0.0, + Parameters: map[string][]string{ + "name": {}, + }, + }, + wantErr: false, + }, + // Negative + { + name: "Missing scheme", + args: args{"37.786971,-122.399677"}, + wantErr: true, + }, + { + name: "Missing path", + args: args{"geo:"}, + wantErr: true, + }, + { + name: "1 coordinate", + args: args{"geo:37.786971"}, + wantErr: true, + }, + { + name: "4 coordinates", + args: args{"geo:123,123,123,123"}, + wantErr: true, + }, + { + name: "Malformed latitude", + args: args{"geo:12x,123"}, + wantErr: true, + }, + { + name: "Malformed longitude", + args: args{"geo:123,12x"}, + wantErr: true, + }, + { + name: "Malformed altitude", + args: args{"geo:123,123,12x"}, + wantErr: true, + }, + { + name: "Malformed parameter", + args: args{"geo:123,123;a=a=a"}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Parse(tt.args.uri) + if (err != nil) != tt.wantErr { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parse() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..444baf3 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.jlel.se/jlelse/go-geouri + +go 1.16 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29