Commit 9adc0c38 authored by Arnaud Delcasse's avatar Arnaud Delcasse

journeys.json implementation and basic server stuff

parent 383cb2ca
......@@ -15,6 +15,12 @@ This library implements :
You can easily generate a server implementation of the RDEX API, by implementing the function to retrieve the carpooling data.
Example implementation :
You will find an example implementation [in the "examples/server" folder](https://gitlab.scity.coop/go-libs/rdex-golang/tree/master/examples/server)
To run the example, go to this folder and run : "go run server.go"
### Client
## Contributions
......
......@@ -11,9 +11,11 @@ import (
type ExampleHandler struct{}
func (h ExampleHandler) Journeys(rdex.RDEXParameters) []rdex.RDEXJourney {
func (h ExampleHandler) Journeys(params rdex.RDEXParameters) ([]rdex.RDEXJourney, error) {
fmt.Println("## JOURNEYS IMPLEMENTATION")
fmt.Println(params)
journeys := []rdex.RDEXJourney{}
fmt.Println("journeys")
jsonJourneys := `
[ {
"uuid": "10945310",
......@@ -55,7 +57,6 @@ func (h ExampleHandler) Journeys(rdex.RDEXParameters) []rdex.RDEXJourney {
"distance": 8998,
"duration": 725,
"cost": {
"fixed": "",
"variable": 0.08
},
"details": "",
......@@ -82,9 +83,9 @@ func (h ExampleHandler) Journeys(rdex.RDEXParameters) []rdex.RDEXJourney {
}]
`
if err := json.Unmarshal([]byte(jsonJourneys), &journeys); err != nil {
fmt.Println(err)
return journeys, err
}
return journeys
return journeys, nil
}
func main() {
......
module gitlab.scity.coop/go-libs/rdex-golang
go 1.14
require github.com/gorilla/schema v1.2.0
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
package rdex
import (
"bytes"
"errors"
"fmt"
"net/http"
)
type ConvertibleBoolean bool
func (bit *ConvertibleBoolean) UnmarshalJSON(data []byte) error {
asString := string(data)
if asString == "1" || asString == "true" {
*bit = true
} else if asString == "0" || asString == "false" {
*bit = false
} else {
return errors.New(fmt.Sprintf("Boolean unmarshal error: invalid input %s", asString))
}
return nil
}
func (bit *ConvertibleBoolean) MarshalJSON(data []byte) ([]byte, error) {
if *bit {
return []byte("1"), nil
} else {
return []byte("0"), nil
}
}
func respond(w http.ResponseWriter, body []byte, code int) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(code)
_, _ = w.Write(body)
}
func respondCORS(w http.ResponseWriter, code int) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Origin,Access,token,Authorization,appid")
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
w.WriteHeader(code)
}
func isSupported(method string) bool {
return method == "POST" || method == "GET" || method == "OPTIONS"
}
func errorJSON(name string) []byte {
buf := bytes.Buffer{}
fmt.Fprintf(&buf, `{"error": { "name": "%s" }}`, name)
return buf.Bytes()
}
......@@ -4,52 +4,52 @@ import "time"
// RDEXJourney is the carpooling journey base type of the RDEX protocol
type RDEXJourney struct {
UUID string `json:"uuid"`
Operator string `json:"operatir"`
Origin string `json:"origin"`
URL string `json:"url,omitempty"`
Driver RDEXPerson `json:"driver,omitempty"`
Passenger RDEXPerson `json:"passenger,omitempty"`
From RDEXGeography `json:"from"`
To RDEXGeography `json:"to"`
Distance int64 `json:"distance"`
Duration time.Duration `json:"duration"`
Route string `json:"route,omitempty"`
NumberOfWaypoints int `json:"number_of_waypoints,omitempty"`
Waypoints []RDEXGeography `json:"waypoints,omitempty"`
Cost RDEXCost `json:"cost"`
Details string `json:"details,omitempty"`
Vehicle RDEXVehicle `json:"vehicle,omitempty"`
Frequency string `json:"frequency"` // punctual or regular
Type string `json:"type"` // one-way or round-trip
Realtime bool `json:"real_time,omitempty"`
Stopped bool `json:"stopped,omitempty"`
Days RDEXDays `json:"days"`
Outward RDEXSchedule `json:"outward"`
Return RDEXSchedule `json:"return,omitempty"`
UUID string `json:"uuid"`
Operator string `json:"operator"`
Origin string `json:"origin"`
URL *string `json:"url,omitempty"`
Driver *RDEXPerson `json:"driver,omitempty"`
Passenger *RDEXPerson `json:"passenger,omitempty"`
From RDEXGeography `json:"from"`
To RDEXGeography `json:"to"`
Distance int64 `json:"distance"`
Duration time.Duration `json:"duration"`
Route *string `json:"route,omitempty"`
NumberOfWaypoints *int `json:"number_of_waypoints,omitempty"`
Waypoints *[]RDEXGeography `json:"waypoints,omitempty"`
Cost RDEXCost `json:"cost"`
Details *string `json:"details,omitempty"`
Vehicle *RDEXVehicle `json:"vehicle,omitempty"`
Frequency string `json:"frequency"` // punctual or regular
Type string `json:"type"` // one-way or round-trip
Realtime *ConvertibleBoolean `json:"real_time,omitempty"`
Stopped *ConvertibleBoolean `json:"stopped,omitempty"`
Days RDEXDays `json:"days"`
Outward RDEXSchedule `json:"outward"`
Return *RDEXSchedule `json:"return,omitempty"`
}
// RDEXPerson defines a driver or a passenger
type RDEXPerson struct {
UUID string `json:"uuid,omitempty"`
Alias string `json:"alias,omitempty"`
Image string `json:"image,omitempty"`
Seats int `json:"seats,omitempty"`
State bool `json:"state,omitempty"`
UUID *string `json:"uuid,omitempty"`
Alias *string `json:"alias,omitempty"`
Image *string `json:"image,omitempty"`
Seats *int `json:"seats,omitempty"`
State ConvertibleBoolean `json:"state,omitempty"`
}
// RDEXGeography is a geographical point used in From, To or Waypoints
type RDEXGeography struct {
Address string `json:"address"`
City string `json:"city"`
Postalcode string `json:"postalcode,omitempty"`
Country string `json:"country"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
StepDistance int64 `json:"step_distance"`
StepDuration time.Duration `json:"step_duration"`
Type string `json:"type"` // pick-up or drop-point
Mandatory bool `json:"mandatory"`
Address string `json:"address"`
City string `json:"city"`
Postalcode string `json:"postalcode,omitempty"`
Country string `json:"country,omitempty"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
StepDistance int64 `json:"step_distance,omitempty"`
StepDuration time.Duration `json:"step_duration,omitempty"`
Type string `json:"type,omitempty"` // pick-up or drop-point
Mandatory *ConvertibleBoolean `json:"mandatory,omitempty"`
}
// RDEXCost is the cost of the journey, either fixed or variable
......@@ -67,26 +67,26 @@ type RDEXVehicle struct {
// RDEXDays defines which days of the week the journey happens
type RDEXDays struct {
Monday bool `json:"monday"`
Tuesday bool `json:"tuesday"`
Wednesday bool `json:"wednesday"`
Thursday bool `json:"thursday"`
Friday bool `json:"friday"`
Saturday bool `json:"saturday"`
Sunday bool `json:"sunday"`
Monday ConvertibleBoolean `json:"monday"`
Tuesday ConvertibleBoolean `json:"tuesday"`
Wednesday ConvertibleBoolean `json:"wednesday"`
Thursday ConvertibleBoolean `json:"thursday"`
Friday ConvertibleBoolean `json:"friday"`
Saturday ConvertibleBoolean `json:"saturday"`
Sunday ConvertibleBoolean `json:"sunday"`
}
// RDEXSchedule is used to define outward and return datetimes
type RDEXSchedule struct {
MinDate string `json:"mindate"`
MaxDate string `json:"maxdat"`
Monday RDEXTimeLimits `json:"monday,omitempty"`
Tuesday RDEXTimeLimits `json:"tuesday,omitempty"`
Wednesday RDEXTimeLimits `json:"wednesday,omitempty"`
Thursday RDEXTimeLimits `json:"thursday,omitempty"`
Friday RDEXTimeLimits `json:"friday,omitempty"`
Saturday RDEXTimeLimits `json:"saturday,omitempty"`
Sunday RDEXTimeLimits `json:"sunday,omitempty"`
MinDate string `json:"mindate" schema:"mindate"`
MaxDate string `json:"maxdate" schema:"maxdate"`
Monday *RDEXTimeLimits `json:"monday,omitempty"`
Tuesday *RDEXTimeLimits `json:"tuesday,omitempty"`
Wednesday *RDEXTimeLimits `json:"wednesday,omitempty"`
Thursday *RDEXTimeLimits `json:"thursday,omitempty"`
Friday *RDEXTimeLimits `json:"friday,omitempty"`
Saturday *RDEXTimeLimits `json:"saturday,omitempty"`
Sunday *RDEXTimeLimits `json:"sunday,omitempty"`
}
// RDEXTimeLimits defines the minimum time and maximum time for the journey in a day
......
package rdex
import (
"fmt"
"net/http"
"strconv"
"github.com/gorilla/schema"
)
var decoder = schema.NewDecoder()
// RDEXRequest describes a base request as sent and received using RDEX protocol
type RDEXRequest struct {
Timestamp int64
Apikey string
Timestamp int64 `schema:"timestamp"`
Apikey string `schema:"apikey"`
P RDEXParameters // TODO handle both journeys and connections
Signature string
Signature string `schema:"signature"`
}
// RDEXParameters defines the RDEX parameters used to search a journey
type RDEXParameters struct {
Driver bool
Passenger bool
From RDEXGeography
To RDEXGeography
Frequency string // regular or punctal
Driver bool `schema:"p[driver]"`
Passenger bool `schema:"p[passenger]"`
From RDEXParamFrom
To RDEXParamTo
Frequency string `schema:"p[frequency]"` // regular or punctal
Outward RDEXSchedule
}
// RDEXParamFrom is a latitude/longitude parameter from the RDEX query origin
type RDEXParamFrom struct {
Latitude float64 `json:"p[from][latitude]"`
Longitude float64 `json:"p[from][longitude]"`
}
// RDEXParamTo is a latitude/longitude parameter from the RDEX query destination
type RDEXParamTo struct {
Latitude float64 `json:"p[to][latitude]"`
Longitude float64 `json:"p[to][longitude]"`
}
func ParseRDEXRequest(r *http.Request) (req RDEXRequest) {
params := r.URL.Query()
for key, value := range params {
switch {
case key == "timestamp":
req.Timestamp, _ = strconv.ParseInt(value[0], 10, 64)
case key == "apikey":
req.Apikey = value[0]
case key == "signature":
req.Signature = value[0]
case key == "p[driver][state]":
if value[0] == "1" {
req.P.Driver = true
} else {
req.P.Driver = false
}
case key == "p[passenger][state]":
if value[0] == "1" {
req.P.Passenger = true
} else {
req.P.Passenger = false
}
case key == "p[from][latitude]":
req.P.From.Latitude, _ = strconv.ParseFloat(value[0], 64)
case key == "p[from][longitude]":
req.P.From.Longitude, _ = strconv.ParseFloat(value[0], 64)
case key == "p[to][latitude]":
req.P.To.Latitude, _ = strconv.ParseFloat(value[0], 64)
case key == "p[to][longitude]":
req.P.To.Longitude, _ = strconv.ParseFloat(value[0], 64)
case key == "frequency":
req.P.Frequency = value[0]
case key == "p[outward][mindate]":
req.P.Outward.MinDate = value[0]
case key == "p[outward][maxdate]":
req.P.Outward.MaxDate = value[0]
default:
fmt.Println("Does not match an existing parameter")
}
}
return
}
......@@ -7,7 +7,7 @@ import (
)
type RDEXServerHandler interface {
Journeys(RDEXParameters) []RDEXJourney
Journeys(RDEXParameters) ([]RDEXJourney, error)
}
// RDEXServer is a handler to create a RDEX API server
......@@ -27,29 +27,44 @@ func NewServer(baseUrl string, handler RDEXServerHandler) (*RDEXServer, error) {
}
func (s RDEXServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if ok := isSupported(r.Method); !ok {
respond(w, errorJSON("unsupported_http_verb"), http.StatusMethodNotAllowed)
return
}
if r.Method == "OPTIONS" {
respondCORS(w, http.StatusOK)
return
}
request := ParseRDEXRequest(r)
//TODO implement signature
fmt.Println("Serve HTTP")
journeys := s.Handler.Journeys(RDEXParameters{})
resp, err := json.Marshal(journeys)
path := r.URL.Path
switch {
case path == "/journeys.json":
journeys, err := s.Handler.Journeys(request.P)
if err != nil {
http.Error(w, "internal_error", http.StatusInternalServerError)
return
}
resp, err := json.Marshal(journeys)
if err != nil {
fmt.Println(err)
}
if err != nil {
fmt.Println(err)
respond(w, []byte(resp), 200)
case path == "/connections.json":
http.Error(w, "not_implemented", http.StatusNotImplemented)
default:
http.NotFound(w, r)
return
}
respondJSON(w, []byte(resp), 200)
}
// AddAuthorizedOperator adds credentials (public key / private key) for an operator accessing the API
func (s *RDEXServer) AddAuthorizedOperator(o RDEXOperator) {
s.AuthorizedOperators = append(s.AuthorizedOperators, o)
}
func respondJSON(w http.ResponseWriter, body []byte, code int) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(code)
_, _ = w.Write(body)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment