module Eco module API module Common module Loaders class Parser < Eco::API::Common::Loaders::CaseBase # Helper class to scope what required attributes it depends on class RequiredAttrs < Struct.new(:attr, :type, :attrs) def active?(*input_attrs) missing(*input_attrs).empty? end def dependant?(attr) attrs.include?(attr) end def missing(*input_attrs) return [] if input_attrs.include?(attr) match = input_attrs & attrs miss = attrs - match return [] if miss.empty? return attrs if match.empty? return miss if type == :all [] end end class << self attr_reader :active_when # @param value [String, Symbol] the attribute or type to be parsed/serialized # 1. when `String`: the internal name of the field/attribute this parser/serializer manages for. # 2. when `Symbol`: the type of data this parser converts between `String` and the specific type. # @return [String] the `type` of usecase (i.e. `:sync`, `:transform`, `:import`, `:other`) def attribute(value = nil) unless value return @attribute || raise("You should specify the 'attribute' this parser/serializer, #{self.class}, is linked to") end name value @attribute = value end # Some parsers require dependencies to do their job. def dependencies(**value) @dependencies ||= {} return @dependencies.merge({ required_attrs: @active_when_attrs }) unless !value.empty? raise "Expected Hash. Given: '#{value.class}'" unless value.is_a?(Hash) @dependencies.merge!(value) end # Define or get the `phase` that the `parser` kicks in. # @param phase [Symbol] the phase when this parser should be active. # Must be one of [`:internal`, `:final`] def parsing_phase(phase = nil) @parsing_phase ||= :internal return @parsing_phase unless phase @parsing_phase = phase end # Define or get the `phase` that the `serializer` kicks in. # @param phase [Symbol] the phase when this serializer should be active. # Must be one of [`:person,` `:final`, `:internal`] def serializing_phase(phase = nil) @serializing_phase ||= :person return @serializing_phase unless phase @serializing_phase = phase end # Helper to build the `active_when` condition. def active_when_any(*attrs) @active_when_attrs = RequiredAttrs.new(attribute, :any, attrs) @active_when = Proc.new do |source_data| keys = data_keys(source_data) attrs.any? {|key| keys.include?(key)} end end # Helper to build the `active_when` condition. def active_when_all(*attrs) @active_when_attrs = RequiredAttrs.new(attribute, :all, attrs) @active_when = Proc.new do |source_data| keys = data_keys(source_data) attrs.all? {|key| keys.include?(key)} end end private # Helper to obtain the current internal named attributes of the data # @param source_data [Array, Hash] if `Array` those are already the `keys`, if `Hash` it gets the `keys` # @return [Array] `keys` of `source_data` def data_keys(source_data) case source_data when Array keys = source_data when Hash keys = source_data.keys else keys = [] end end end inheritable_class_vars :attribute, :parsing_phase, :serializing_phase def initialize(person_parser) raise "Expected Eco::API::Common::People::PersonParser. Given #{policies.class}" unless person_parser.is_a?(Eco::API::Common::People::PersonParser) person_parser.define_attribute(self.attribute, dependencies: self.class.dependencies) do |attr_parser| _define_parser(attr_parser) _define_serializer(attr_parser) end end # @param data [Hash] all the person data at the specified `parsing_phase`: # - when `:internal`: the parser will receive external types (i.e. String with '|' delimiters instead of an Array). # - when `:final`: the parser will receive the typed values (i.e. Array instread of String with '|' delimiters). # @param deps [Hash] the merged dependencies (default to the class object and when calling the parser). def parser(data, deps) raise "You should implement this method" end # @param data [Hash, Ecoportal::API::V1::Person] all the person data at the specified `serializing_phase`: # - when `:internal`: it will receive a `Hash` with the **internal values** but the types already serialized to `String`. # - when `:final`: it will receive a `Hash` with the **internal values** and **types**. # - when `:person`: it will receive the `person` object. # @param deps [Hash] the merged dependencies (default to the class object and when calling the parser). # def serializer(data, deps) # raise "You should implement this method" # end # @return [String, Symbol] the field/attribute or type this parser is linked to. def attribute self.class.attribute end private def _define_parser(attr_parser) if active_when = self.class.active_when attr_parser.def_parser(self.class.parsing_phase, active_when: active_when, &self.method(:parser)) else attr_parser.def_parser(self.class.parsing_phase, &self.method(:parser)) end end def _define_serializer(attr_parser) return unless respond_to?(:serializer, true) attr_parser.def_serializer(self.class.serializing_phase, &self.method(:serializer)) end end end end end end