class Praxis::ActionDefinition
Attributes
opaque hash of user-defined medata, used to decorate the definition, and also available in the generated JSON documents
Setter/reader for a possible ‘sister’ action that is defined as post, and has the payload with the same structure as this GET action (with the exception of the params in the path attributes)
Setter/reader for a possible ‘sister’ action that is defined as post, and has the payload with the same structure as this GET action (with the exception of the params in the path attributes)
Setter/reader for a possible ‘sister’ action that is defined as post, and has the payload with the same structure as this GET action (with the exception of the params in the path attributes)
Public Class Methods
# File lib/praxis/action_definition.rb, line 30 def self.decorate_docs(&callback) doc_decorations << callback end
# File lib/praxis/action_definition.rb, line 34 def initialize(name, endpoint_definition, **_opts, &block) @name = name @endpoint_definition = endpoint_definition @responses = {} @metadata = {} @route = nil @traits = [] if (media_type = endpoint_definition.media_type) && (media_type.is_a?(Class) && media_type < Praxis::Types::MediaTypeCommon) @reference_media_type = media_type end version = endpoint_definition.version api_info = ApiDefinition.instance.info(endpoint_definition.version) route_base = "#{api_info.base_path}#{endpoint_definition.version_prefix}" prefix = Array(endpoint_definition.routing_prefix) @routing_config = RoutingConfig.new(version: version, base: route_base, prefix: prefix) endpoint_definition.traits.each do |trait| self.trait(trait) end endpoint_definition.action_defaults.apply!(self) instance_eval(&block) if block_given? end
# File lib/praxis/action_definition.rb, line 171 def self.url_description(route:, params_example:, params:) route_description = route.describe example_hash = params_example ? params_example.dump : {} hash = route.example(example_hash: example_hash, params: params) query_string = URI.encode_www_form(hash[:query_params]) url = hash[:url] url = [url, query_string].join('?') unless query_string.empty? route_description[:example] = url route_description end
Public Instance Methods
# File lib/praxis/action_definition.rb, line 381 def _internal_set(**args) @payload = args[:payload] if args.key?(:payload) @params = args[:params] if args.key?(:params) end
# File lib/praxis/action_definition.rb, line 365 def clone_action_as(name:) cloned = clone cloned.instance_eval do @name = name.to_sym @description = @description.clone @metadata = @metadata.clone @params = @params.clone @responses = @responses.clone @route = @route.clone @routing_config = @routing_config.clone @sister_post_action = @sister_post_action.clone @traits = @traits.clone end cloned end
# File lib/praxis/action_definition.rb, line 328 def clone_action_as_post(at:) action_name = name cloned = clone_action_as(name: "#{action_name}_with_post") # route raise "Only GET actions support the 'enable_large_params_proxy_action' DSL. Action #{action_name} is a #{rt.verb}" unless route.verb == 'GET' cloned.instance_eval do routing do # Double slash, as we do know the complete prefixed orig path at this point and we don't want the prefix to be applied again... post "/#{at}" end end # Payload raise "Using enable_large_params_proxy_action for an action requires the GET payload to be empty. Action #{name} has a payload defined" unless payload.nil? route_params = route.path.named_captures.keys.collect(&:to_sym) params_in_route = [] params_in_query = [] cloned.params.type.attributes.each do |k, _val| if route_params.include? k params_in_route.push k else params_in_query.push k end end cloned._internal_set( payload: cloned.params.duplicate(type: params.type.clone.slice!(*params_in_query)), params: cloned.params.duplicate(type: params.type.clone.slice!(*params_in_route)) ) cloned.sister_get_action = self self.sister_post_action = cloned cloned end
# File lib/praxis/action_definition.rb, line 89 def create_attribute(type = Attributor::Struct, **opts, &block) opts[:reference] = @reference_media_type if !opts.key?(:reference) && (@reference_media_type && block) Attributor::Attribute.new(type, opts, &block) end
Determine the content_type to report for a given example, using handler_name if possible.
Considers any pre-defined set of values on the content_type attributge of the headers.
# File lib/praxis/action_definition.rb, line 259 def derive_content_type(example, handler_name) # MultipartArrays *must* use the provided content_type return MediaTypeIdentifier.load(example.content_type) if example.is_a? Praxis::Types::MultipartArray _, content_type_attribute = headers&.attributes&.find { |k, _v| k.to_s =~ /^content[-_]{1}type$/i } if content_type_attribute&.options&.key?(:values) # if any defined value match the preferred handler_name, return it content_type_attribute.options[:values].each do |ct| mti = MediaTypeIdentifier.load(ct) return mti if mti.handler_name == handler_name end # otherwise, pick the first pick = MediaTypeIdentifier.load(content_type_attribute.options[:values].first) # and return that one if it already corresponds to a registered handler # otherwise, add the encoding return pick if Praxis::Application.instance.handlers.include?(pick.handler_name) return pick + handler_name end # generic default encoding MediaTypeIdentifier.load("application/#{handler_name}") end
# File lib/praxis/action_definition.rb, line 185 def describe(context: nil) {}.tap do |hash| hash[:description] = description hash[:name] = name hash[:metadata] = metadata if headers headers_example = headers.example(context) hash[:headers] = headers_description(example: headers_example) end if params params_example = params.example(context) hash[:params] = params_description(example: params_example) end if payload payload_example = payload.example(context) hash[:payload] = payload_description(example: payload_example) end hash[:responses] = responses.each_with_object({}) do |(_response_name, response), memo| memo[response.name] = response.describe(context: context) end hash[:traits] = traits if traits.any? # FIXME: change to :routes along with api browser # FIXME: change urls to url ... (along with the browser) hash[:urls] = [ActionDefinition.url_description(route: route, params: params, params_example: params_example)] self.class.doc_decorations.each do |callback| callback.call(self, hash) end end end
# File lib/praxis/action_definition.rb, line 166 def description(text = nil) @description = text if text @description end
# File lib/praxis/action_definition.rb, line 319 def enable_large_params_proxy_action(at: true) self.sister_post_action = at # Just true to mark it for now (needs to be lazily evaled) end
# File lib/praxis/action_definition.rb, line 129 def headers(type = nil, **opts, &block) return @headers unless block unless opts.key? :required opts[:required] = true # Make the payload required by default end if @headers update_attribute(@headers, opts, block) else type ||= Attributor::Hash.of(key: String) @headers = create_attribute(type, dsl_compiler: HeadersDSLCompiler, case_insensitive_load: true, **opts, &block) @headers end @precomputed_header_keys_for_rack = nil # clear memoized data end
# File lib/praxis/action_definition.rb, line 217 def headers_description(example:) output = headers.describe(example: example) required_headers = headers.attributes.select { |_k, attr| attr.options && attr.options[:required] == true } output[:example] = required_headers.each_with_object({}) do |(name, _attr), hash| hash[name] = example[name].to_s # Some simple types (like Boolean) can be used as header values, but must convert back to s end output end
# File lib/praxis/action_definition.rb, line 315 def nodoc! metadata[:doc_visibility] = :none end
# File lib/praxis/action_definition.rb, line 95 def params(type = Attributor::Struct, **opts, &block) return @params if !block && (opts.nil? || opts.empty?) && type == Attributor::Struct unless opts.key? :required opts[:required] = true # Make the payload required by default end if @params raise Exceptions::InvalidConfiguration, "Invalid type received for extending params: #{type.name}" unless type == Attributor::Struct && @params.type < Attributor::Struct update_attribute(@params, opts, block) else @params = create_attribute(type, **opts, &block) end @params end
# File lib/praxis/action_definition.rb, line 226 def params_description(example:) route_params = [] if route.nil? warn "Warning: No route defined for #{endpoint_definition.name}##{name}." else route_params = route.path .named_captures .keys .collect(&:to_sym) end desc = params.describe(example: example) desc[:type][:attributes].each_key do |k| source = if route_params.include? k 'url' else 'query' end desc[:type][:attributes][k][:source] = source end required_params = desc[:type][:attributes].select { |_k, v| v[:source] == 'query' && v[:required] == true }.keys phash = required_params.each_with_object({}) do |name, hash| hash[name] = example[name] end desc[:example] = URI.encode_www_form(phash) desc end
# File lib/praxis/action_definition.rb, line 113 def payload(type = Attributor::Struct, **opts, &block) return @payload if !block && (opts.nil? || opts.empty?) && type == Attributor::Struct unless opts.key?(:required) opts = { required: true, null: false }.merge(opts) # Make the payload required and non-nullable by default end if @payload raise Exceptions::InvalidConfiguration, "Invalid type received for extending params: #{type.name}" unless type == Attributor::Struct && @payload.type < Attributor::Struct update_attribute(@payload, opts, block) else @payload = create_attribute(type, **opts, &block) end end
# File lib/praxis/action_definition.rb, line 286 def payload_description(example:) hash = payload.describe(example: example) hash[:examples] = {} default_handlers = ApiDefinition.instance.info.consumes default_handlers.each do |default_handler| dumped_payload = payload.dump(example, default_format: default_handler) content_type = derive_content_type(example, default_handler) handler = Praxis::Application.instance.handlers[content_type.handler_name] # in case handler is nil, use dumped_payload as-is. generated_payload = if handler.nil? dumped_payload else handler.generate(dumped_payload) end hash[:examples][default_handler] = { content_type: content_type.to_s, body: generated_payload } end hash end
Good optimization to avoid creating lots of strings and comparisons on a per-request basis. However, this is hacky, as it is rack-specific, and does not really belong here
# File lib/praxis/action_definition.rb, line 152 def precomputed_header_keys_for_rack @precomputed_header_keys_for_rack ||= @headers.attributes.keys.each_with_object({}) do |key, hash| name = key.to_s name = "HTTP_#{name.gsub('-', '_').upcase}" unless %w[CONTENT_TYPE CONTENT_LENGTH].include?(name) hash[name] = key end end
- DEPRECATED
-
Warn of the change of method name for the transition
-
# File lib/praxis/action_definition.rb, line 324 def resource_definition raise 'Praxis::ActionDefinition does not use `resource_definition` any longer. Use `endpoint_definition` instead.' end
# File lib/praxis/action_definition.rb, line 76 def response(name, type = nil, **args, &block) if type # should verify type is a media type type = type.construct(block) if block_given? args[:media_type] = type end template = ApiDefinition.instance.response(name) @responses[name] = template.compile(self, **args) end
# File lib/praxis/action_definition.rb, line 160 def routing(&block) @routing_config.instance_eval(&block) @route = @routing_config.route end
# File lib/praxis/action_definition.rb, line 63 def trait(trait_name) raise Exceptions::InvalidTrait, "Trait #{trait_name} not found in the system" unless ApiDefinition.instance.traits.key? trait_name trait = ApiDefinition.instance.traits.fetch(trait_name) trait.apply!(self) traits << trait_name end
# File lib/praxis/action_definition.rb, line 71 def update_attribute(attribute, options, block) attribute.options.merge!(options) attribute.type.attributes(**options, &block) end