From a63975cda237f1d6e3e69b6e341383b37a225957 Mon Sep 17 00:00:00 2001 From: vinimachadosantana Date: Fri, 7 Aug 2026 02:08:32 -0300 Subject: [PATCH] feat: add API versioning and error contract --- app/controllers/api/v1/base_controller.rb | 41 +++++++++++++++++++++++ config/routes.rb | 10 +++--- spec/rails_helper.rb | 4 +-- spec/support/json_helpers.rb | 9 +++++ 4 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 app/controllers/api/v1/base_controller.rb create mode 100644 spec/support/json_helpers.rb diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb new file mode 100644 index 0000000..948a1d9 --- /dev/null +++ b/app/controllers/api/v1/base_controller.rb @@ -0,0 +1,41 @@ +module Api + module V1 + class BaseController < ApplicationController + rescue_from StandardError, with: :internal_server_error + rescue_from ActiveRecord::RecordNotFound, with: :not_found + rescue_from ActionController::ParameterMissing, with: :bad_request + rescue_from ActiveRecord::RecordInvalid, with: :unprocessable_entity + + private + + def render_error(code:, message:, status:, details: {}) + render json: { error: { code:, message:, details: } }, status: + end + + def not_found(_error) + render_error(code: "not_found", message: "Resource not found", status: :not_found) + end + + def bad_request(error) + render_error(code: "bad_request", message: error.message, status: :bad_request) + end + + def unprocessable_entity(error) + render_error( + code: "validation_failed", + message: "Validation failed", + status: :unprocessable_entity, + details: error.record.errors.to_hash + ) + end + + def internal_server_error(error) + Rails.logger.error("#{error.class}: #{error.message}") + Rails.logger.error(error.backtrace&.first(10)&.join("\n")) + raise error if Rails.env.local? + + render_error(code: "internal_error", message: "Something went wrong", status: :internal_server_error) + end + end + end +end diff --git a/config/routes.rb b/config/routes.rb index f7ea671..8ae278f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,10 +1,12 @@ Rails.application.routes.draw do - # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html - - # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. - # Can be used by load balancers and uptime monitors to verify that the app is live. get "up" => "rails/health#show", as: :rails_health_check get "/health", to: "health#show" + + namespace :api do + namespace :v1 do + end + end + # Defines the root path route ("/") # root "posts#index" end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 3bc0328..c4aedc6 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -22,8 +22,8 @@ # of increasing the boot-up time by auto-requiring all files in the support # directory. Alternatively, in the individual `*_spec.rb` files, manually # require only the support files necessary. -# -# Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f } + +Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f } # Ensures that the test database schema matches the current schema file. # If there are pending migrations it will invoke `db:test:prepare` to diff --git a/spec/support/json_helpers.rb b/spec/support/json_helpers.rb new file mode 100644 index 0000000..8d5bc41 --- /dev/null +++ b/spec/support/json_helpers.rb @@ -0,0 +1,9 @@ +module JsonHelpers + def json_body + JSON.parse(response.body) + end +end + +RSpec.configure do |config| + config.include JsonHelpers, type: :request +end