class SoberSwag::Compiler::Type

A compiler for swagger-able types.

This class turns Swagger-able types into a schema. This Schema may be:

As such, it compiles all types to all applicable schemas.

While this class compiles one type at a time, it *keeps track* of the other types needed to describe this schema. It stores these types in a set, available at {#found_types}.

For example, with a schema like:

“`ruby class Bar < SoberSwag::InputObject

attribute :baz, primitive(:String)

end

class Foo < SoberSwag::InputObject

attribute :bar, Bar

end “`

If you compile `Foo` with this class, {#found_types} will include `Bar`.

Constants

DEFAULT_QUERY_SCHEMA_ATTRS
METADATA_KEYS

A list of acceptable keys to use as metadata for an object schema. All other metadata keys defined on a type with {SoberSwag::InputObject.meta} will be ignored.

@return [Array<Symbol>] valid keys.

Attributes

type[R]

@return [Class] the type we are compiling.

Public Class Methods

new(type) click to toggle source

Create a new compiler for a swagger-able type. @param type [Class] the type to compile

# File lib/sober_swag/compiler/type.rb, line 53
def initialize(type)
  @type = type
end

Public Instance Methods

eql?(other) click to toggle source

Standard ruby equality.

# File lib/sober_swag/compiler/type.rb, line 144
def eql?(other)
  other.class == self.class && other.type == type
end
found_types() click to toggle source

Get a set of all other types needed to compile this type. This set will not include the type being compiled.

@return [Set<Class>]

# File lib/sober_swag/compiler/type.rb, line 128
def found_types
  @found_types ||=
    begin
      (_, found_types) = parsed_result
      found_types
    end
end
hash() click to toggle source

Standard ruby hashing method. Compilers hash to the same value if they are compiling the same type.

# File lib/sober_swag/compiler/type.rb, line 151
def hash
  [self.class, type].hash
end
object_schema() click to toggle source

Get back the [schema object](github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#schemaObject) for the type described.

@return [Hash]

# File lib/sober_swag/compiler/type.rb, line 74
def object_schema
  @object_schema ||=
    make_object_schema
end
parsed_result() click to toggle source

This type, parsed into an AST.

# File lib/sober_swag/compiler/type.rb, line 138
def parsed_result
  @parsed_result ||= Parser.new(type_for_parser).run_parser
end
path_schema() click to toggle source

The schema for this type when it is path of the path.

@raise [TooComplicatedForPathError] when the compiled type is too complicated to use in a path @return [Hash] a [path parameters hash](swagger.io/docs/specification/describing-parameters/#path-parameters) for this type.

# File lib/sober_swag/compiler/type.rb, line 93
def path_schema
  path_schema_stub.map do |e|
    ensure_uncomplicated(e[:name], e[:schema])
    e.merge(in: :path)
  end
rescue TooComplicatedError => e
  raise TooComplicatedForPathError, e.message
end
query_schema() click to toggle source

The schema for this type when it is part of the query. @raise [TooComplicatedForQueryError] when this type is too complicated to use in a query schema @return [Hash] a [query parameters hash](swagger.io/docs/specification/describing-parameters/#query-parameters) for this type.

# File lib/sober_swag/compiler/type.rb, line 108
def query_schema
  path_schema_stub.map { |e| DEFAULT_QUERY_SCHEMA_ATTRS.merge(e) }
rescue TooComplicatedError => e
  raise TooComplicatedForQueryError, e.message
end
ref_name() click to toggle source

Get the name of this type if it is to be used in a `$ref` key. This is useful if we are going to use this type compiler to compile an attribute of another object.

@return [String] a reference specifier for this type

# File lib/sober_swag/compiler/type.rb, line 119
def ref_name
  SoberSwag::Compiler::Primitive.new(type).ref_name
end
schema_stub() click to toggle source

Give a “stub type” for this schema. This is suitable to use as the schema for attributes of other schemas. Almost always generates a ref object. @return [Hash] the OpenAPI V3 schema stub

# File lib/sober_swag/compiler/type.rb, line 84
def schema_stub
  @schema_stub ||= generate_schema_stub
end
standalone?() click to toggle source

Is this type standalone, IE, worth serializing on its own in the schemas section of our schema? @return [true,false]

# File lib/sober_swag/compiler/type.rb, line 65
def standalone?
  type.is_a?(Class)
end

Private Instance Methods

ensure_uncomplicated(key, value) click to toggle source
# File lib/sober_swag/compiler/type.rb, line 301
      def ensure_uncomplicated(key, value)
        return if value[:type]

        return value[:oneOf].each { |member| ensure_uncomplicated(key, member) } if value[:oneOf]

        raise TooComplicatedError, <<~ERROR
          Property #{key} has object-schema #{value}, but this type of param should be simple (IE a primitive of some kind)
        ERROR
      end
flatten_one_ofs(object) click to toggle source
# File lib/sober_swag/compiler/type.rb, line 224
def flatten_one_ofs(object)
  case object
  when Nodes::OneOf
    Nodes::OneOf.new(object.deconstruct.uniq)
  else
    object
  end
end
flatten_oneofs_hash(object) click to toggle source
# File lib/sober_swag/compiler/type.rb, line 281
def flatten_oneofs_hash(object)
  object.map { |h|
    h[:oneOf] || h
  }.flatten
end
generate_schema_stub() click to toggle source
# File lib/sober_swag/compiler/type.rb, line 181
def generate_schema_stub
  if type.is_a?(Class)
    SoberSwag::Compiler::Primitive.new(type).type_hash
  else
    object_schema
  end
end
make_object_schema(metadata_keys: METADATA_KEYS) click to toggle source
# File lib/sober_swag/compiler/type.rb, line 198
def make_object_schema(metadata_keys: METADATA_KEYS)
  normalize(mapped_type).cata { |e| to_object_schema(e, metadata_keys) }.merge(object_schema_meta)
end
mapped_type() click to toggle source
# File lib/sober_swag/compiler/type.rb, line 177
def mapped_type
  @mapped_type ||= parsed_type.map { |v| SoberSwag::Compiler::Primitive.new(v).type_hash }
end
normalize(object) click to toggle source
# File lib/sober_swag/compiler/type.rb, line 202
def normalize(object)
  object.cata { |e| rewrite_sums(e) }.cata { |e| flatten_one_ofs(e) }
end
object_schema_meta() click to toggle source

Get metadata attributes to be used if compiling an object schema.

@return [Hash]

# File lib/sober_swag/compiler/type.rb, line 161
def object_schema_meta
  return {} unless standalone? && type <= SoberSwag::Type::Named

  {
    description: type.description
  }.reject { |_, v| v.nil? }
end
one_of_to_schema(object) click to toggle source
# File lib/sober_swag/compiler/type.rb, line 268
def one_of_to_schema(object)
  if object.deconstruct.include?({ type: :null })
    rejected = object.deconstruct.reject { |e| e[:type] == :null }
    if rejected.length == 1
      rejected.first.merge(nullable: true)
    else
      { oneOf: flatten_oneofs_hash(rejected), nullable: true }
    end
  else
    { oneOf: flatten_oneofs_hash(object.deconstruct) }
  end
end
parsed_type() click to toggle source
# File lib/sober_swag/compiler/type.rb, line 169
def parsed_type
  @parsed_type ||=
    begin
      (parsed,) = parsed_result
      parsed
    end
end
path_schema_stub() click to toggle source
# File lib/sober_swag/compiler/type.rb, line 287
def path_schema_stub
  @path_schema_stub ||=
    make_object_schema(metadata_keys: METADATA_KEYS | %i[style explode])[:properties].map do |k, v|
      # ensure_uncomplicated(k, v)
      {
        name: k,
        schema: v.reject { |key, _| %i[required nullable explode style].include?(key) },
        required: object_schema[:required].include?(k) || false,
        style: v[:style],
        explode: v[:explode]
      }.reject { |_, v2| v2.nil? }
    end
end
rewrite_sums(object) click to toggle source
# File lib/sober_swag/compiler/type.rb, line 206
def rewrite_sums(object) # rubocop:disable Metrics/MethodLength
  case object
  when Nodes::Sum
    lhs, rhs = object.deconstruct
    if lhs.is_a?(Nodes::OneOf) && rhs.is_a?(Nodes::OneOf)
      Nodes::OneOf.new(lhs.deconstruct + rhs.deconstruct)
    elsif lhs.is_a?(Nodes::OneOf)
      Nodes::OneOf.new([*lhs.deconstruct, rhs])
    elsif rhs.is_a?(Nodes::OneOf)
      Nodes::OneOf.new([lhs, *rhs.deconstruct])
    else
      Nodes::OneOf.new([lhs, rhs])
    end
  else
    object
  end
end
to_object_schema(object, metadata_keys = METADATA_KEYS) click to toggle source
# File lib/sober_swag/compiler/type.rb, line 233
def to_object_schema(object, metadata_keys = METADATA_KEYS) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
  case object
  when Nodes::List
    { type: :array, items: object.element }
  when Nodes::Enum
    { type: :string, enum: object.values }
  when Nodes::OneOf
    one_of_to_schema(object)
  when Nodes::Object
    # openAPI requires that you give a list of required attributes
    # (which IMO is the *totally* wrong thing to do but whatever)
    # so we must do this garbage
    required = object.deconstruct.filter { |(_, b)| b[:required] }.map(&:first)
    {
      type: :object,
      properties: object.deconstruct.map { |(a, b)|
        [a, b.reject { |k, _| k == :required }]
      }.to_h,
      required: required
    }
  when Nodes::Attribute
    name, req, value, meta = object.deconstruct
    value = value.merge(meta&.select { |k, _| metadata_keys.include?(k) } || {})
    if req
      [name, value.merge(required: true)]
    else
      [name, value]
    end
  when Nodes::Primitive
    object.value.merge(object.metadata.select { |k, _| metadata_keys.include?(k) })
  else
    raise ArgumentError, "Got confusing node #{object} (#{object.class})"
  end
end
type_for_parser() click to toggle source
# File lib/sober_swag/compiler/type.rb, line 189
def type_for_parser
  if type.is_a?(Class)
    type.schema.type
  else
    # Probably a constrained array
    type
  end
end