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
Source
# File lib/praxis/action_definition.rb, line 30 def self.decorate_docs(&callback) doc_decorations << callback end
Source
# 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
Source
# 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
Source
# 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
Source
# 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
Source
# 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
Source
# 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
Source
# 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
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.
Source
# 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
Source
# File lib/praxis/action_definition.rb, line 166 def description(text = nil) @description = text if text @description end
Source
# 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
Source
# 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
Source
# 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
Source
# File lib/praxis/action_definition.rb, line 315 def nodoc! metadata[:doc_visibility] = :none end
Source
# 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
Source
# 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
Source
# 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
Source
# 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
Source
# 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
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
Source
# 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
- DEPRECATED
-
Warn of the change of method name for the transition
-
Source
# 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
Source
# File lib/praxis/action_definition.rb, line 160 def routing(&block) @routing_config.instance_eval(&block) @route = @routing_config.route end
Source
# 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
Source
# File lib/praxis/action_definition.rb, line 71 def update_attribute(attribute, options, block) attribute.options.merge!(options) attribute.type.attributes(**options, &block) end