lib/jsonapionify/api/attribute.rb in jsonapionify-0.9.0 vs lib/jsonapionify/api/attribute.rb in jsonapionify-0.9.1

- old
+ new

@@ -1,46 +1,123 @@ require 'unstrict_proc' module JSONAPIonify::Api class Attribute using UnstrictProc - attr_reader :name, :type, :description, :read, :write, :required + attr_reader :name, :type, :description, :read, :write, :required, :block - def initialize(name, type, description, read: true, write: true, required: false, example: nil) - raise ArgumentError, 'required attributes must be writable' if required && !write + def initialize( + name, + type, + description, + read: true, + write: true, + required: false, + example: nil, + &block + ) unless type.is_a? JSONAPIonify::Types::BaseType raise TypeError, "#{type} is not a valid JSON type" end - @name = name - @type = type - @description = description - @example = example - @read = read - @write = write - @required = write ? required : false + + @name = name.to_sym + @type = type&.freeze + @description = description&.freeze + @example = example&.freeze + @read = read&.freeze + @write = write&.freeze + @required = required&.freeze + @block = block&.freeze + @writeable_actions = write + @readable_actions = read + + freeze end def ==(other) self.class == other.class && self.name == other.name end - def options_json - { - name: name, - required: required - } + def supports_read_for_action?(action_name, context) + case (setting = @readable_actions) + when TrueClass, FalseClass + setting + when Hash + !!JSONAPIonify::Continuation.new(setting).check(action_name, context) { true } + when Array + setting.map(&:to_sym).include? action_name + when Symbol, String + setting.to_sym === action_name + else + false + end end - def required? - !!@required + def supports_write_for_action?(action_name, context) + action = context.resource.class.actions.find { |a| a.name == action_name } + return false unless %{POST PUT PATCH}.include? action.request_method + case (setting = @writeable_actions) + when TrueClass, FalseClass + setting + when Hash + !!JSONAPIonify::Continuation.new(setting).check(action_name, context) { true } + when Array + setting.map(&:to_sym).include? action_name + when Symbol, String + setting.to_sym === action_name + else + false + end end - def optional? - !required? + def resolve(instance, context, example_id: nil) + if context.respond_to?(:_is_example_) && context._is_example_ == true + return example(example_id) + end + block = self.block || proc { |attr, i| i.send attr } + type.dump block.unstrict.call(self.name, instance, context) + rescue JSONAPIonify::Types::DumpError => ex + error_block = + context.resource.class.error_definitions[:attribute_type_error] + context.errors.evaluate( + name, + error_block: error_block, + backtrace: ex.backtrace, + runtime_block: proc { + detail ex.message + } + ) + rescue JSONAPIonify::Types::NotNullError => ex + error_block = + context.resource.class.error_definitions[:attribute_cannot_be_null] + context.errors.evaluate( + name, + error_block: error_block, + backtrace: ex.backtrace, + runtime_block: proc {} + ) + nil end + def required_for_action?(action_name, context) + supports_write_for_action?(action_name, context) && + (required === true || Array.wrap(required).include?(action_name)) + end + + def options_json_for_action(action_name, context) + { + name: @name, + type: @type.to_s, + description: JSONAPIonify::Documentation.onelinify_markdown(description), + example: example(context.resource.class.generate_id) + }.tap do |opts| + opts[:not_null] = true if @type.not_null? + opts[:required] = true if required_for_action?(action_name, context) + end + end + def read? !!@read end def write? @@ -60,10 +137,10 @@ def documentation_object OpenStruct.new( name: name, type: type.name, - required: required?, + required: Array.wrap(required).join(', '), description: JSONAPIonify::Documentation.render_markdown(description), allow: allow ) end