module Praxis::EndpointDefinition::ClassMethods

Attributes

actions[R]
controller[RW]
metadata[R]

opaque hash of user-defined medata, used to decorate the definition, and also available in the generated JSON documents

parent_prefix[R]
responses[R]
routing_prefix[W]
traits[R]
version_options[R]
version_prefix[R]

Public Instance Methods

action(name, &block) click to toggle source
# File lib/praxis/endpoint_definition.rb, line 222
def action(name, &block)
  raise ArgumentError, 'can not create ActionDefinition without block' unless block_given?
  raise ArgumentError, "Action names must be defined using symbols (Got: #{name} (of type #{name.class}))" unless name.is_a? Symbol

  action = ActionDefinition.new(name, self, &block)
  if action.sister_post_action
    post_path = \
      if action.sister_post_action == true
        "#{action.route.prefixed_path}/actions/#{action.name}"
      elsif action.sister_post_action.start_with?('//')
        action.sister_post_action # Avoid appending prefix
      else
        # Make sure to cleanup the leading '/' if any, as we're always adding it below
        cleaned_path = action.sister_post_action.start_with?('/') ? action.sister_post_action[1..] : action.sister_post_action
        "#{action.route.prefixed_path}/#{cleaned_path}"
      end

    # Save the finalization of the twin POST actions once we've loaded the endpoint definition
    on_finalize do
      # Create the sister POST action with a payload matching the original params
      post_action = action.clone_action_as_post(at: post_path)
      @actions[post_action.name] = post_action
    end
  end
  @actions[name] = action
end
action_defaults(&block) click to toggle source
# File lib/praxis/endpoint_definition.rb, line 216
def action_defaults(&block)
  @action_defaults.instance_eval(&block) if block_given?

  @action_defaults
end
canonical_path(action_name = nil) click to toggle source
# File lib/praxis/endpoint_definition.rb, line 175
def canonical_path(action_name = nil)
  if action_name
    raise "Canonical path for #{name} is already defined as: '#{@canonical_action_name}'. 'canonical_path' can only be defined once." if @canonical_action_name

    @canonical_action_name = action_name
  else
    # Resolution of the actual action definition needs to be done lazily, since we can use the `canonical_path` stanza
    # at the top of the resource, well before the actual action is defined.
    unless @canonical_action
      href_action = @canonical_action_name || DEFAULT_RESOURCE_HREF_ACTION
      @canonical_action = actions.fetch(href_action) do
        raise "Error: trying to set canonical_href of #{name}. Action '#{href_action}' does not exist"
      end
    end
    @canonical_action
  end
end
describe(context: nil) click to toggle source
# File lib/praxis/endpoint_definition.rb, line 258
def describe(context: nil)
  {}.tap do |hash|
    hash[:description] = description
    hash[:media_type] = media_type.describe(true) if media_type
    hash[:actions] = actions.values.collect { |action| action.describe(context: context) }
    hash[:name] = name
    hash[:parent] = parent.id if parent
    hash[:display_name] = display_name
    hash[:metadata] = metadata
    hash[:traits] = traits
  end
end
description(text = nil) click to toggle source
# File lib/praxis/endpoint_definition.rb, line 249
def description(text = nil)
  @description = text if text
  @description
end
display_name(string = nil) click to toggle source
# File lib/praxis/endpoint_definition.rb, line 77
def display_name(string = nil)
  unless string
    return @display_name ||= name.split('::').last # Best guess at a display name?
  end

  @display_name = string
end
id() click to toggle source
# File lib/praxis/endpoint_definition.rb, line 254
def id
  name.gsub('::', '-')
end
inherit_params_from_parent(parent_action, **mapping) click to toggle source
# File lib/praxis/endpoint_definition.rb, line 146
def inherit_params_from_parent(parent_action, **mapping)
  actions.each do |_name, action|
    action.params do
      mapping.each do |parent_name, name|
        next if action.params&.attributes&.key?(name)

        parent_attribute = parent_action.params.attributes[parent_name]

        attribute name, parent_attribute.type, **parent_attribute.options
      end
    end
  end
end
media_type(media_type = nil) click to toggle source
# File lib/praxis/endpoint_definition.rb, line 98
def media_type(media_type = nil)
  return @media_type if media_type.nil?

  media_type = SimpleMediaType.new(media_type) if media_type.is_a?(String)
  @media_type = media_type
end
nodoc!() click to toggle source
# File lib/praxis/endpoint_definition.rb, line 271
def nodoc!
  metadata[:doc_visibility] = :none
end
on_finalize(&block) click to toggle source
# File lib/praxis/endpoint_definition.rb, line 85
def on_finalize(&block)
  @on_finalize << proc(&block) if block_given?

  @on_finalize
end
parent(parent = nil, **mapping) click to toggle source
# File lib/praxis/endpoint_definition.rb, line 105
def parent(parent = nil, **mapping)
  return @parent if parent.nil?

  @routing_prefix = nil # reset routing_prefix

  parent_action = parent.canonical_path
  parent_route = parent_action.route.path

  # if a mapping is passed, it *must* resolve any param name conflicts
  unless mapping.any?
    # assume last capture is the relevant one to replace
    # if not... then I quit.
    parent_param_name = parent_route.names.last

    # more assumptions about names
    parent_name = parent.name.demodulize.underscore.singularize

    # put it together to find what we should call this new param
    param = "#{parent_name}_#{parent_param_name}".to_sym
    mapping[parent_param_name.to_sym] = param
  end

  # complete the mapping and massage the route
  parent_route.names.collect(&:to_sym).each do |name|
    if mapping.key?(name)
      param = mapping[name]
      # FIXME: this won't handle URI Template type paths, ie '/{parent_id}'
      prefixed_path = parent_action.route.prefixed_path
      @parent_prefix = prefixed_path.gsub(/(:)(#{name})(\W+|$)/, "\\1#{param}\\3")
    else
      mapping[name] = name
    end
  end

  on_finalize do
    inherit_params_from_parent(parent_action, **mapping)
  end

  @parent = parent
end
parse_href(path) click to toggle source
# File lib/praxis/endpoint_definition.rb, line 197
def parse_href(path)
  path = path.path if path.is_a?(::URI::Generic)
  param_values = canonical_path.route.path.params(path)
  attrs = canonical_path.params.attributes
  param_values.each_with_object({}) do |(key, value), hash|
    hash[key.to_sym] = attrs[key.to_sym].load(value, [key])
  end
rescue StandardError => e
  raise Praxis::Exception, "Error parsing or coercing parameters from href: #{path}\n" + e.message
end
prefix(prefix = nil) click to toggle source
# File lib/praxis/endpoint_definition.rb, line 91
def prefix(prefix = nil)
  return @prefix if prefix.nil?

  @routing_prefix = nil # reset routing_prefix
  @prefix = prefix
end
routing_prefix() click to toggle source
# File lib/praxis/endpoint_definition.rb, line 162
def routing_prefix
  return @routing_prefix if @routing_prefix

  @routing_prefix = parent_prefix + prefix
end
to_href(params) click to toggle source
# File lib/praxis/endpoint_definition.rb, line 193
def to_href(params)
  canonical_path.route.path.expand(params.transform_values(&:to_s))
end
trait(trait_name) click to toggle source
# File lib/praxis/endpoint_definition.rb, line 208
def trait(trait_name)
  raise Exceptions::InvalidTrait, "Trait #{trait_name} not found in the system" unless ApiDefinition.instance.traits.key? trait_name

  # TODO: We're only storing the names here, should we store the actual traits in a hash?
  @traits << trait_name
end
Also aliased as: use
use(trait_name)
Alias for: trait
version(version = nil) click to toggle source
# File lib/praxis/endpoint_definition.rb, line 168
def version(version = nil)
  return @version unless version

  @version = version
  @action_defaults.instance_eval(&EndpointDefinition.generate_defaults_block(version: version))
end