diff --git a/main.go b/main.go index 9bd47207..380d29f3 100644 --- a/main.go +++ b/main.go @@ -40,7 +40,7 @@ func main() { } swaggerSpec := *doc.Spec() - if err := validation.Validate(swaggerSpec); err != nil { + if err := validation.Validate(*doc); err != nil { log.Fatalf("Swagger file not valid: %s", err) } diff --git a/samples/gen-go/client/client.go b/samples/gen-go/client/client.go index 5c30afeb..d0c382ad 100644 --- a/samples/gen-go/client/client.go +++ b/samples/gen-go/client/client.go @@ -184,35 +184,35 @@ func (c *WagClient) GetBooks(ctx context.Context, i *models.GetBooksInput) ([]mo } } -// GetBookByID makes a GET request to /books/{book_id}. -// Returns a book -func (c *WagClient) GetBookByID(ctx context.Context, i *models.GetBookByIDInput) (models.GetBookByIDOutput, error) { - path := c.basePath + "/v1/books/{book_id}" +// CreateBook makes a POST request to /books. +// Creates a book +func (c *WagClient) CreateBook(ctx context.Context, i *models.Book) (*models.Book, error) { + path := c.basePath + "/v1/books" urlVals := url.Values{} var body []byte - path = strings.Replace(path, "{book_id}", strconv.FormatInt(i.BookID, 10), -1) - if i.AuthorID != nil { - urlVals.Add("authorID", *i.AuthorID) - } - if i.RandomBytes != nil { - urlVals.Add("randomBytes", string(*i.RandomBytes)) - } path = path + "?" + urlVals.Encode() + if i != nil { + + var err error + body, err = json.Marshal(i) + + if err != nil { + return nil, err + } + + } + client := &http.Client{Transport: c.transport} - req, err := http.NewRequest("GET", path, bytes.NewBuffer(body)) + req, err := http.NewRequest("POST", path, bytes.NewBuffer(body)) if err != nil { return nil, err } - if i.Authorization != nil { - req.Header.Set("authorization", *i.Authorization) - } - // Add the opname for doers like tracing - ctx = context.WithValue(ctx, opNameCtx{}, "getBookByID") + ctx = context.WithValue(ctx, opNameCtx{}, "createBook") req = req.WithContext(ctx) // Don't add the timeout in a "doer" because we don't want to call "defer.cancel()" // until we've finished all the processing of the request object. Otherwise we'll cancel @@ -231,22 +231,13 @@ func (c *WagClient) GetBookByID(ctx context.Context, i *models.GetBookByIDInput) defer resp.Body.Close() switch resp.StatusCode { case 200: - var output models.GetBookByID200Output + var output models.Book if err := json.NewDecoder(resp.Body).Decode(&output); err != nil { return nil, models.DefaultInternalError{Msg: err.Error()} } return &output, nil - case 204: - var output models.GetBookByID204Output - return output, nil - case 401: - var output models.GetBookByID401Output - return nil, output - case 404: - var output models.GetBookByID404Output - return nil, output case 400: var output models.DefaultBadRequest @@ -269,35 +260,35 @@ func (c *WagClient) GetBookByID(ctx context.Context, i *models.GetBookByIDInput) } } -// CreateBook makes a POST request to /books/{book_id}. -// Creates a book -func (c *WagClient) CreateBook(ctx context.Context, i *models.Book) (*models.Book, error) { +// GetBookByID makes a GET request to /books/{book_id}. +// Returns a book +func (c *WagClient) GetBookByID(ctx context.Context, i *models.GetBookByIDInput) (models.GetBookByIDOutput, error) { path := c.basePath + "/v1/books/{book_id}" urlVals := url.Values{} var body []byte - path = path + "?" + urlVals.Encode() - - if i != nil { - - var err error - body, err = json.Marshal(i) - - if err != nil { - return nil, err - } - + path = strings.Replace(path, "{book_id}", strconv.FormatInt(i.BookID, 10), -1) + if i.AuthorID != nil { + urlVals.Add("authorID", *i.AuthorID) } + if i.RandomBytes != nil { + urlVals.Add("randomBytes", string(*i.RandomBytes)) + } + path = path + "?" + urlVals.Encode() client := &http.Client{Transport: c.transport} - req, err := http.NewRequest("POST", path, bytes.NewBuffer(body)) + req, err := http.NewRequest("GET", path, bytes.NewBuffer(body)) if err != nil { return nil, err } + if i.Authorization != nil { + req.Header.Set("authorization", *i.Authorization) + } + // Add the opname for doers like tracing - ctx = context.WithValue(ctx, opNameCtx{}, "createBook") + ctx = context.WithValue(ctx, opNameCtx{}, "getBookByID") req = req.WithContext(ctx) // Don't add the timeout in a "doer" because we don't want to call "defer.cancel()" // until we've finished all the processing of the request object. Otherwise we'll cancel @@ -316,13 +307,22 @@ func (c *WagClient) CreateBook(ctx context.Context, i *models.Book) (*models.Boo defer resp.Body.Close() switch resp.StatusCode { case 200: - var output models.Book + var output models.GetBookByID200Output if err := json.NewDecoder(resp.Body).Decode(&output); err != nil { return nil, models.DefaultInternalError{Msg: err.Error()} } return &output, nil + case 204: + var output models.GetBookByID204Output + return output, nil + case 401: + var output models.GetBookByID401Output + return nil, output + case 404: + var output models.GetBookByID404Output + return nil, output case 400: var output models.DefaultBadRequest @@ -345,10 +345,10 @@ func (c *WagClient) CreateBook(ctx context.Context, i *models.Book) (*models.Boo } } -// GetBookByID2 makes a GET request to /books/{id}. +// GetBookByID2 makes a GET request to /books2/{id}. // Retrieve a book func (c *WagClient) GetBookByID2(ctx context.Context, i *models.GetBookByID2Input) (*models.Book, error) { - path := c.basePath + "/v1/books/{id}" + path := c.basePath + "/v1/books2/{id}" urlVals := url.Values{} var body []byte diff --git a/samples/gen-go/client/interface.go b/samples/gen-go/client/interface.go index 82efe8c0..ba847628 100644 --- a/samples/gen-go/client/interface.go +++ b/samples/gen-go/client/interface.go @@ -15,15 +15,15 @@ type Client interface { // Returns a list of books GetBooks(ctx context.Context, i *models.GetBooksInput) ([]models.Book, error) + // CreateBook makes a POST request to /books. + // Creates a book + CreateBook(ctx context.Context, i *models.Book) (*models.Book, error) + // GetBookByID makes a GET request to /books/{book_id}. // Returns a book GetBookByID(ctx context.Context, i *models.GetBookByIDInput) (models.GetBookByIDOutput, error) - // CreateBook makes a POST request to /books/{book_id}. - // Creates a book - CreateBook(ctx context.Context, i *models.Book) (*models.Book, error) - - // GetBookByID2 makes a GET request to /books/{id}. + // GetBookByID2 makes a GET request to /books2/{id}. // Retrieve a book GetBookByID2(ctx context.Context, i *models.GetBookByID2Input) (*models.Book, error) diff --git a/samples/gen-go/client/mock_client.go b/samples/gen-go/client/mock_client.go index 81adbaf9..f463c3de 100644 --- a/samples/gen-go/client/mock_client.go +++ b/samples/gen-go/client/mock_client.go @@ -41,26 +41,26 @@ func (_mr *_MockClientRecorder) GetBooks(arg0, arg1 interface{}) *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "GetBooks", arg0, arg1) } -func (_m *MockClient) GetBookByID(ctx context.Context, i *models.GetBookByIDInput) (models.GetBookByIDOutput, error) { - ret := _m.ctrl.Call(_m, "GetBookByID", ctx, i) - ret0, _ := ret[0].(models.GetBookByIDOutput) +func (_m *MockClient) CreateBook(ctx context.Context, i *models.Book) (*models.Book, error) { + ret := _m.ctrl.Call(_m, "CreateBook", ctx, i) + ret0, _ := ret[0].(*models.Book) ret1, _ := ret[1].(error) return ret0, ret1 } -func (_mr *_MockClientRecorder) GetBookByID(arg0, arg1 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetBookByID", arg0, arg1) +func (_mr *_MockClientRecorder) CreateBook(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "CreateBook", arg0, arg1) } -func (_m *MockClient) CreateBook(ctx context.Context, i *models.Book) (*models.Book, error) { - ret := _m.ctrl.Call(_m, "CreateBook", ctx, i) - ret0, _ := ret[0].(*models.Book) +func (_m *MockClient) GetBookByID(ctx context.Context, i *models.GetBookByIDInput) (models.GetBookByIDOutput, error) { + ret := _m.ctrl.Call(_m, "GetBookByID", ctx, i) + ret0, _ := ret[0].(models.GetBookByIDOutput) ret1, _ := ret[1].(error) return ret0, ret1 } -func (_mr *_MockClientRecorder) CreateBook(arg0, arg1 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "CreateBook", arg0, arg1) +func (_mr *_MockClientRecorder) GetBookByID(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetBookByID", arg0, arg1) } func (_m *MockClient) GetBookByID2(ctx context.Context, i *models.GetBookByID2Input) (*models.Book, error) { diff --git a/samples/gen-go/server/handlers.go b/samples/gen-go/server/handlers.go index f025239b..4b8925eb 100644 --- a/samples/gen-go/server/handlers.go +++ b/samples/gen-go/server/handlers.go @@ -243,6 +243,86 @@ func newGetBooksInput(r *http.Request) (*models.GetBooksInput, error) { return &input, nil } +// statusCodeForCreateBook returns the status code corresponding to the returned +// object. It returns -1 if the type doesn't correspond to anything. +func statusCodeForCreateBook(obj interface{}) int { + + switch obj.(type) { + + case *models.Book: + return 200 + + case models.Book: + return 200 + + case models.DefaultBadRequest: + return 400 + case models.DefaultInternalError: + return 500 + default: + return -1 + } +} + +func (h handler) CreateBookHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { + + input, err := newCreateBookInput(r) + if err != nil { + logger.FromContext(ctx).AddContext("error", err.Error()) + http.Error(w, jsonMarshalNoError(models.DefaultBadRequest{Msg: err.Error()}), http.StatusBadRequest) + return + } + + err = input.Validate(nil) + if err != nil { + logger.FromContext(ctx).AddContext("error", err.Error()) + http.Error(w, jsonMarshalNoError(models.DefaultBadRequest{Msg: err.Error()}), http.StatusBadRequest) + return + } + + resp, err := h.CreateBook(ctx, input) + + if err != nil { + logger.FromContext(ctx).AddContext("error", err.Error()) + statusCode := statusCodeForCreateBook(err) + if statusCode != -1 { + http.Error(w, err.Error(), statusCode) + } else { + http.Error(w, jsonMarshalNoError(models.DefaultInternalError{Msg: err.Error()}), http.StatusInternalServerError) + } + return + } + + respBytes, err := json.Marshal(resp) + if err != nil { + logger.FromContext(ctx).AddContext("error", err.Error()) + http.Error(w, jsonMarshalNoError(models.DefaultInternalError{Msg: err.Error()}), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCodeForCreateBook(resp)) + w.Write(respBytes) + +} + +// newCreateBookInput takes in an http.Request an returns the input struct. +func newCreateBookInput(r *http.Request) (*models.Book, error) { + var input models.Book + + var err error + _ = err + + data, err := ioutil.ReadAll(r.Body) + if len(data) > 0 { + if err := json.NewDecoder(bytes.NewReader(data)).Decode(&input); err != nil { + return nil, err + } + } + + return &input, nil +} + // statusCodeForGetBookByID returns the status code corresponding to the returned // object. It returns -1 if the type doesn't correspond to anything. func statusCodeForGetBookByID(obj interface{}) int { @@ -378,86 +458,6 @@ func newGetBookByIDInput(r *http.Request) (*models.GetBookByIDInput, error) { return &input, nil } -// statusCodeForCreateBook returns the status code corresponding to the returned -// object. It returns -1 if the type doesn't correspond to anything. -func statusCodeForCreateBook(obj interface{}) int { - - switch obj.(type) { - - case *models.Book: - return 200 - - case models.Book: - return 200 - - case models.DefaultBadRequest: - return 400 - case models.DefaultInternalError: - return 500 - default: - return -1 - } -} - -func (h handler) CreateBookHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - - input, err := newCreateBookInput(r) - if err != nil { - logger.FromContext(ctx).AddContext("error", err.Error()) - http.Error(w, jsonMarshalNoError(models.DefaultBadRequest{Msg: err.Error()}), http.StatusBadRequest) - return - } - - err = input.Validate(nil) - if err != nil { - logger.FromContext(ctx).AddContext("error", err.Error()) - http.Error(w, jsonMarshalNoError(models.DefaultBadRequest{Msg: err.Error()}), http.StatusBadRequest) - return - } - - resp, err := h.CreateBook(ctx, input) - - if err != nil { - logger.FromContext(ctx).AddContext("error", err.Error()) - statusCode := statusCodeForCreateBook(err) - if statusCode != -1 { - http.Error(w, err.Error(), statusCode) - } else { - http.Error(w, jsonMarshalNoError(models.DefaultInternalError{Msg: err.Error()}), http.StatusInternalServerError) - } - return - } - - respBytes, err := json.Marshal(resp) - if err != nil { - logger.FromContext(ctx).AddContext("error", err.Error()) - http.Error(w, jsonMarshalNoError(models.DefaultInternalError{Msg: err.Error()}), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(statusCodeForCreateBook(resp)) - w.Write(respBytes) - -} - -// newCreateBookInput takes in an http.Request an returns the input struct. -func newCreateBookInput(r *http.Request) (*models.Book, error) { - var input models.Book - - var err error - _ = err - - data, err := ioutil.ReadAll(r.Body) - if len(data) > 0 { - if err := json.NewDecoder(bytes.NewReader(data)).Decode(&input); err != nil { - return nil, err - } - } - - return &input, nil -} - // statusCodeForGetBookByID2 returns the status code corresponding to the returned // object. It returns -1 if the type doesn't correspond to anything. func statusCodeForGetBookByID2(obj interface{}) int { diff --git a/samples/gen-go/server/interface.go b/samples/gen-go/server/interface.go index 76858e54..9db75e9f 100644 --- a/samples/gen-go/server/interface.go +++ b/samples/gen-go/server/interface.go @@ -15,15 +15,15 @@ type Controller interface { // Returns a list of books GetBooks(ctx context.Context, i *models.GetBooksInput) ([]models.Book, error) + // CreateBook makes a POST request to /books. + // Creates a book + CreateBook(ctx context.Context, i *models.Book) (*models.Book, error) + // GetBookByID makes a GET request to /books/{book_id}. // Returns a book GetBookByID(ctx context.Context, i *models.GetBookByIDInput) (models.GetBookByIDOutput, error) - // CreateBook makes a POST request to /books/{book_id}. - // Creates a book - CreateBook(ctx context.Context, i *models.Book) (*models.Book, error) - - // GetBookByID2 makes a GET request to /books/{id}. + // GetBookByID2 makes a GET request to /books2/{id}. // Retrieve a book GetBookByID2(ctx context.Context, i *models.GetBookByID2Input) (*models.Book, error) diff --git a/samples/gen-go/server/mock_controller.go b/samples/gen-go/server/mock_controller.go index d07bc4a7..7f7116ab 100644 --- a/samples/gen-go/server/mock_controller.go +++ b/samples/gen-go/server/mock_controller.go @@ -41,26 +41,26 @@ func (_mr *_MockControllerRecorder) GetBooks(arg0, arg1 interface{}) *gomock.Cal return _mr.mock.ctrl.RecordCall(_mr.mock, "GetBooks", arg0, arg1) } -func (_m *MockController) GetBookByID(ctx context.Context, i *models.GetBookByIDInput) (models.GetBookByIDOutput, error) { - ret := _m.ctrl.Call(_m, "GetBookByID", ctx, i) - ret0, _ := ret[0].(models.GetBookByIDOutput) +func (_m *MockController) CreateBook(ctx context.Context, i *models.Book) (*models.Book, error) { + ret := _m.ctrl.Call(_m, "CreateBook", ctx, i) + ret0, _ := ret[0].(*models.Book) ret1, _ := ret[1].(error) return ret0, ret1 } -func (_mr *_MockControllerRecorder) GetBookByID(arg0, arg1 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetBookByID", arg0, arg1) +func (_mr *_MockControllerRecorder) CreateBook(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "CreateBook", arg0, arg1) } -func (_m *MockController) CreateBook(ctx context.Context, i *models.Book) (*models.Book, error) { - ret := _m.ctrl.Call(_m, "CreateBook", ctx, i) - ret0, _ := ret[0].(*models.Book) +func (_m *MockController) GetBookByID(ctx context.Context, i *models.GetBookByIDInput) (models.GetBookByIDOutput, error) { + ret := _m.ctrl.Call(_m, "GetBookByID", ctx, i) + ret0, _ := ret[0].(models.GetBookByIDOutput) ret1, _ := ret[1].(error) return ret0, ret1 } -func (_mr *_MockControllerRecorder) CreateBook(arg0, arg1 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "CreateBook", arg0, arg1) +func (_mr *_MockControllerRecorder) GetBookByID(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetBookByID", arg0, arg1) } func (_m *MockController) GetBookByID2(ctx context.Context, i *models.GetBookByID2Input) (*models.Book, error) { diff --git a/samples/gen-go/server/router.go b/samples/gen-go/server/router.go index 4852aac4..4bb0c825 100644 --- a/samples/gen-go/server/router.go +++ b/samples/gen-go/server/router.go @@ -58,17 +58,17 @@ func New(c Controller, addr string) *Server { h.GetBooksHandler(r.Context(), w, r) }) + r.Methods("POST").Path("/v1/books").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + logger.FromContext(r.Context()).AddContext("op", "createBook") + h.CreateBookHandler(r.Context(), w, r) + }) + r.Methods("GET").Path("/v1/books/{book_id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { logger.FromContext(r.Context()).AddContext("op", "getBookByID") h.GetBookByIDHandler(r.Context(), w, r) }) - r.Methods("POST").Path("/v1/books/{book_id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - logger.FromContext(r.Context()).AddContext("op", "createBook") - h.CreateBookHandler(r.Context(), w, r) - }) - - r.Methods("GET").Path("/v1/books/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + r.Methods("GET").Path("/v1/books2/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { logger.FromContext(r.Context()).AddContext("op", "getBookByID2") h.GetBookByID2Handler(r.Context(), w, r) }) diff --git a/samples/swagger.yml b/samples/swagger.yml index aa131ae8..345fb131 100644 --- a/samples/swagger.yml +++ b/samples/swagger.yml @@ -63,24 +63,7 @@ paths: description: "Error" schema: $ref: "#/definitions/Error" - post: - operationId: createBook - description: Creates a book - parameters: - - name: newBook - in: body - schema: - $ref: "#/definitions/Book" - responses: - 200: - description: "Success" - schema: - $ref: "#/definitions/Book" - default: - description: "Error" - schema: - $ref: "#/definitions/Error" - /books/{id}: + /books2/{id}: get: operationId: getBookByID2 description: Retrieve a book @@ -164,6 +147,23 @@ paths: description: "Error" schema: $ref: "#/definitions/Error" + post: + operationId: createBook + description: Creates a book + parameters: + - name: newBook + in: body + schema: + $ref: "#/definitions/Book" + responses: + 200: + description: "Success" + schema: + $ref: "#/definitions/Book" + default: + description: "Error" + schema: + $ref: "#/definitions/Error" definitions: Book: diff --git a/validation/validation.go b/validation/validation.go index 454f000f..c1bf9460 100644 --- a/validation/validation.go +++ b/validation/validation.go @@ -1,12 +1,17 @@ package validation import ( + "errors" "fmt" "regexp" "strings" "github.com/Clever/wag/swagger" + swaggererrors "github.com/go-openapi/errors" + "github.com/go-openapi/loads" "github.com/go-openapi/spec" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" ) // A regex requiring the field to be start with a letter and be alphanumeric @@ -94,7 +99,18 @@ func validateOp(path, method string, op *spec.Operation) error { // we don't support. Note that this isn't a comprehensive check for all things // we don't support, so this may not return an error, but the Swagger file might // have values we don't support -func Validate(s spec.Swagger) error { +func Validate(d loads.Document) error { + s := d.Spec() + + goSwaggerError := validate.Spec(&d, strfmt.Default) + if goSwaggerError != nil { + str := "" + for _, desc := range goSwaggerError.(*swaggererrors.CompositeError).Errors { + str += fmt.Sprintf("- %s\n", desc) + } + return errors.New(str) + } + if s.Swagger != "2.0" { return fmt.Errorf("Unsupported Swagger version %s", s.Swagger) }