diff --git a/Makefile b/Makefile index 73e99c0..193d532 100644 --- a/Makefile +++ b/Makefile @@ -21,3 +21,6 @@ check-build: clean: rm -rf $(BINDIR) + +run: check-build + $(BIN) diff --git a/cmd/go-jsonapi-example/go-jsonapi-example.exe b/cmd/go-jsonapi-example/go-jsonapi-example.exe new file mode 100644 index 0000000..4449375 Binary files /dev/null and b/cmd/go-jsonapi-example/go-jsonapi-example.exe differ diff --git a/cmd/go-jsonapi-example/main.go b/cmd/go-jsonapi-example/main.go index aa6e5f9..8e1e319 100644 --- a/cmd/go-jsonapi-example/main.go +++ b/cmd/go-jsonapi-example/main.go @@ -17,20 +17,51 @@ import ( "fmt" "net/http" + "context" + "os" + "os/signal" + "syscall" + "go-jsonapi-example/internal/model" "go-jsonapi-example/internal/resource" "go-jsonapi-example/internal/storage" "github.com/manyminds/api2go" + + "github.com/juju/gnuflag" ) func main() { - port := 8080 - baseURL := fmt.Sprintf("http://localhost:%d", port) - api := api2go.NewAPIWithBaseURL("v1", baseURL) - carStorage := storage.NewCarStorage() - api.AddResource(model.Car{}, resource.CarResource{CarStorage: carStorage}) + host := gnuflag.String("host", "localhost", "host") + port := gnuflag.Int("port", 8080, "port") + gnuflag.Parse(true) - fmt.Printf("Listening on :%d", port) - http.ListenAndServe(fmt.Sprintf(":%d", port), api.Handler()) + addr := fmt.Sprintf("%s:%d", *host, *port) + baseURL := fmt.Sprintf("http://%s", addr) + + api := api2go.NewAPIWithBaseURL("v1", baseURL) + api.AddResource(model.Car{}, resource.CarResource{CarStorage: storage.NewCarStorage()}) + + server := &http.Server{Addr: addr, Handler: api.Handler()} + closeHandler(server) + + fmt.Printf("Listening on %s\n", addr) + if err := server.ListenAndServe(); err != http.ErrServerClosed { + fmt.Printf("error: %s\n", err) + os.Exit(1) + } else { + fmt.Println(err) + os.Exit(0) + } +} + +func closeHandler(server *http.Server) { + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM) + go func() { + <-interrupt + if err := server.Shutdown(context.Background()); err != nil { + fmt.Printf("error: %s\n", err) + } + }() } diff --git a/go.mod b/go.mod index 58c72b6..7b55099 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,6 @@ go 1.14 require ( github.com/gorilla/mux v1.8.0 // indirect + github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d github.com/manyminds/api2go v0.0.0-20210211132652-5457038544fa ) diff --git a/go.sum b/go.sum index 3cf8ac6..a15425c 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d h1:c93kUJDtVAXFEhsCh5jSxyOJmFHuzcihnslQiX8Urwo= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= diff --git a/internal/model/model_car.go b/internal/model/model_car.go index 0d80875..b9fc4ed 100644 --- a/internal/model/model_car.go +++ b/internal/model/model_car.go @@ -3,6 +3,9 @@ package model import ( "errors" + "net/http" + + "github.com/manyminds/api2go" "github.com/manyminds/api2go/jsonapi" ) @@ -10,47 +13,97 @@ type Car struct { ID string `json:"-"` Brand string `json:"brand"` Model string `json:"model"` - Price uint `json:"price"` - Status string `json:"status"` + Price uint64 `json:"price"` + Status string `json:"status"` // OnTheWay, InStock, Sold, Discontinued +} + +func (c Car) Verify() (bool, api2go.HTTPError) { + var verifyErrors []api2go.Error + var httpError api2go.HTTPError + + if c.Brand == "" { + newErr := newVerifyError( + "Invalid Attribute", + "attribute cannot be empty", + "/data/attributes/brand") + verifyErrors = append(verifyErrors, newErr) + } + if c.Model == "" { + newErr := newVerifyError( + "Invalid Attribute", + "attribute cannot be empty", + "/data/attributes/model") + verifyErrors = append(verifyErrors, newErr) + } + if c.Status != "OnTheWay" && + c.Status != "InStock" && + c.Status != "Sold" && + c.Status != "Discontinued" { + newErr := newVerifyError( + "Invalid Attribute", + "attribute must be one of: OnTheWay, InStock, Sold, Discontinued", + "/data/attributes/brand") + verifyErrors = append(verifyErrors, newErr) + } + + ok := len(verifyErrors) == 0 + + if !ok { + httpError := api2go.NewHTTPError( + errors.New("Invalid content"), + "Invalid content", + http.StatusBadRequest) + httpError.Errors = verifyErrors + } + + return ok, httpError +} + +func newVerifyError(title string, detail string, pointer string) api2go.Error { + var newError api2go.Error + newError.Title = title + newError.Detail = detail + newError.Source = &api2go.ErrorSource{Pointer: pointer} + return newError } // GetID to satisfy jsonapi.MarshalIdentifier interface -func (u Car) GetID() string { - return u.ID +func (c Car) GetID() string { + return c.ID } // SetID to satisfy jsonapi.UnmarshalIdentifier interface -func (u *Car) SetID(id string) error { - u.ID = id +func (c *Car) SetID(id string) error { + c.ID = id return nil } // GetReferences to satisfy the jsonapi.MarshalReferences interface -func (u Car) GetReferences() []jsonapi.Reference { +func (c Car) GetReferences() []jsonapi.Reference { return []jsonapi.Reference{} } // GetReferencedIDs to satisfy the jsonapi.MarshalLinkedRelations interface -func (u Car) GetReferencedIDs() []jsonapi.ReferenceID { +func (c Car) GetReferencedIDs() []jsonapi.ReferenceID { return []jsonapi.ReferenceID{} } // GetReferencedStructs to satisfy the jsonapi.MarhsalIncludedRelations interface -func (u Car) GetReferencedStructs() []jsonapi.MarshalIdentifier { +func (c Car) GetReferencedStructs() []jsonapi.MarshalIdentifier { return []jsonapi.MarshalIdentifier{} } // SetToManyReferenceIDs sets the sweets reference IDs and satisfies the jsonapi.UnmarshalToManyRelations interface -func (u *Car) SetToManyReferenceIDs(name string, IDs []string) error { +func (c *Car) SetToManyReferenceIDs(name string, IDs []string) error { return errors.New("There is no to-many relationship with the name " + name) } // AddToManyIDs adds some new sweets that a users loves so much -func (u *Car) AddToManyIDs(name string, IDs []string) error { +func (c *Car) AddToManyIDs(name string, IDs []string) error { return errors.New("There is no to-many relationship with the name " + name) } // DeleteToManyIDs removes some sweets from a users because they made him very sick -func (u *Car) DeleteToManyIDs(name string, IDs []string) error { +func (c *Car) DeleteToManyIDs(name string, IDs []string) error { return errors.New("There is no to-many relationship with the name " + name) } diff --git a/internal/resource/resource_car.go b/internal/resource/resource_car.go index f95dced..193a1e7 100644 --- a/internal/resource/resource_car.go +++ b/internal/resource/resource_car.go @@ -122,6 +122,10 @@ func (s CarResource) Create(obj interface{}, r api2go.Request) (api2go.Responder return &Response{}, api2go.NewHTTPError(errors.New("Invalid instance given"), "Invalid instance given", http.StatusBadRequest) } + if ok, httpErr := car.Verify(); !ok { + return &Response{}, httpErr + } + id := s.CarStorage.Insert(car) car.ID = id @@ -141,6 +145,10 @@ func (s CarResource) Update(obj interface{}, r api2go.Request) (api2go.Responder return &Response{}, api2go.NewHTTPError(errors.New("Invalid instance given"), "Invalid instance given", http.StatusBadRequest) } + if ok, httpErr := car.Verify(); !ok { + return &Response{}, httpErr + } + err := s.CarStorage.Update(car) - return &Response{Res: car, Code: http.StatusNoContent}, err + return &Response{Code: http.StatusOK}, err } diff --git a/internal/resource/response.go b/internal/resource/response.go index d7557af..a89cd8a 100644 --- a/internal/resource/response.go +++ b/internal/resource/response.go @@ -8,9 +8,7 @@ type Response struct { func (r Response) Metadata() map[string]interface{} { return map[string]interface{}{ - "author": "GenZmeY", - "license": "wtfpl", - "license-url": "http://www.wtfpl.net", + "author": "GenZmeY", } }