module SoberSwag module Controller ## # Describe a single controller endpoint. class Route def initialize(method, action_name, path) @method = method @path = path @action_name = action_name @response_serializers = {} @response_descriptions = {} end attr_reader :response_serializers, :response_descriptions, :controller, :method, :path, :action_name ## # What to parse the request body in to. attr_reader :request_body_class ## # What to parse the request query_params in to attr_reader :query_params_class ## # What to parse the path params into attr_reader :path_params_class ## # Define the request body, using SoberSwag's type-definition scheme. # The block passed will be used to define the body of a new sublcass of `base` (defaulted to {SoberSwag::InputObject}.) # If you want, you can also define utility methods in here def request_body(base = SoberSwag::InputObject, &block) @request_body_class = make_input_object!(base, &block) action_module.const_set('ResponseBody', @request_body_class) end ## # Does this route have a body defined? def request_body? !request_body_class.nil? end ## # Define the shape of the query_params parameters, using SoberSwag's type-definition scheme. # The block passed is the body of the newly-defined type. # You can also include a base type. def query_params(base = SoberSwag::InputObject, &block) @query_params_class = make_input_object!(base, &block) action_module.const_set('QueryParams', @query_params_class) end ## # Does this route have query params defined? def query_params? !query_params_class.nil? end ## # Define the shape of the *path* parameters, using SoberSwag's type-definition scheme. # The block passed will be the body of a new subclass of `base` (defaulted to {SoberSwag::InputObject}). # Names of this should match the names in the path template originally passed to {SoberSwag::Controller::Route.new} def path_params(base = SoberSwag::InputObject, &block) @path_params_class = make_input_object!(base, &block) action_module.const_set('PathParams', @path_params_class) end ## # Does this route have path params defined? def path_params? !path_params_class.nil? end ## # Define the body of the action method in the controller. def action(&body) return @action if body.nil? @action ||= body end def description(desc = nil) return @description if desc.nil? @description = desc end def summary(sum = nil) return @summary if sum.nil? @summary = sum end ## # The container module for all the constants this will eventually define. # Each class generated by this Route will be defined within this module. def action_module @action_module ||= Module.new end ## # Define a serializer for a response with the given status code. # You may either give a serializer you defined elsewhere, or define one inline as if passed to # {SoberSwag::OutputObject.define} def response(status_code, description, serializer = nil, &block) status_key = Rack::Utils.status_code(status_code) raise ArgumentError, 'Response defiend!' if @response_serializers.key?(status_key) serializer ||= SoberSwag::OutputObject.define(&block) response_module.const_set(status_code.to_s.classify, serializer) @response_serializers[status_key] = serializer @response_descriptions[status_key] = description end ## # What you should call the module of this action in your controller def action_module_name action_name.to_s.classify end private def response_module @response_module ||= Module.new.tap { |m| action_module.const_set(:Response, m) } end def make_input_object!(base, &block) Class.new(base, &block).tap do |e| e.transform_keys(&:to_sym) if [SoberSwag::InputObject, Dry::Struct].include?(base) end end end end end