require 'sober_swag/serializer' module SoberSwag ## # Create a serializer that is heavily inspired by the "Blueprinter" library. # This allows you to make "views" and such inside. # # Under the hood, this is actually all based on {SoberSwag::Serialzier::Base}. class OutputObject < SoberSwag::Serializer::Base autoload(:Field, 'sober_swag/output_object/field') autoload(:Definition, 'sober_swag/output_object/definition') autoload(:FieldSyntax, 'sober_swag/output_object/field_syntax') autoload(:View, 'sober_swag/output_object/view') ## # Use a OutputObject to define a new serializer. # It will be based on {SoberSwag::Serializer::Base}. # # An example is illustrative: # # PersonSerializer = SoberSwag::OutputObject.define do # field :id, primitive(:Integer) # field :name, primtive(:String).optional # # view :complex do # field :age, primitive(:Integer) # field :title, primitive(:String) # end # end # # Note: This currently will generate a new *class* that does serialization. # However, this is only a hack to get rid of the weird naming issue when # generating swagger from dry structs: their section of the schema area # is defined by their *Ruby Class Name*. In the future, if we get rid of this, # we might be able to keep this on the value-level, in which case {#define} # can simply return an *instance* of SoberSwag::Serializer that does # the correct thing, with the name you give it. This works for now, though. def self.define(&block) d = Definition.new.tap do |o| o.instance_eval(&block) end new(d.fields, d.views, d.identifier) end def initialize(fields, views, identifier) @fields = fields @views = views @identifier = identifier end attr_reader :fields, :views, :identifier def serialize(obj, opts = {}) serializer.serialize(obj, opts) end def type serializer.type end def view(name) return base_serializer if name == :base @views.find { |v| v.name == name } end def base base_serializer end ## # Compile down this to an appropriate serializer. # It uses {SoberSwag::Serializer::Conditional} to do view-parsing, # and {SoberSwag::Serializer::FieldList} to do the actual serialization. def serializer # rubocop:disable Metrics/MethodLength @serializer ||= begin views.reduce(base_serializer) do |base, view| view_serializer = view.serializer view_serializer.identifier("#{identifier}.#{view.name.to_s.classify}") if identifier SoberSwag::Serializer::Conditional.new( proc do |object, options| if options[:view].to_s == view.name.to_s [:left, object] else [:right, object] end end, view_serializer, base ) end end end def base_serializer @base_serializer ||= SoberSwag::Serializer::FieldList.new(fields).tap do |s| s.identifier(identifier) end end end end