Module: GrapeOAS::TypeResolvers::Base

Included in:
ArrayResolver, DryTypeResolver, PrimitiveResolver
Defined in:
lib/grape_oas/type_resolvers/base.rb

Overview

Base module that defines the interface for all type resolvers.

Why TypeResolvers Exist

Grape stores parameter types as strings for documentation purposes. When you declare ‘type: [MyApp::Types::UUID]` in Grape, it gets stored as the string “[MyApp::Types::UUID]” in route.params. This happens in Grape::Validations::ParamsDocumentation::TypeCache which calls `type.to_s` for memory optimization (singleton cache avoids repeated string allocations).

Why Not Direct Grape Access?

While the original type IS preserved in Grape’s CoerceValidator (accessible via ‘endpoint.send(:validations)`), this approach has drawbacks:

  1. **Protected API**: ‘validations` is a protected method, requiring `send`

  2. Coupling: Tight coupling to Grape’s internal validator structure

  3. Complexity: Matching validators to params by name is error-prone

  4. Performance: Iterating validators for each param is inefficient

TypeResolvers provide a cleaner abstraction:

  • Try to resolve stringified types back to actual classes via ‘Object.const_get`

  • Extract rich metadata from resolved types (Dry::Types format, constraints)

  • Fall back gracefully to string parsing when classes aren’t available

  • Extensible registry allows custom type handling

Implementing a Custom Resolver

class MyCustomTypeResolver
  extend GrapeOAS::TypeResolvers::Base

  def self.handles?(type)
    # Return true if this resolver can handle the type
    resolve_class(type)&.ancestors&.include?(MyCustomType)
  end

  def self.build_schema(type)
    # Build and return an ApiModel::Schema
  end
end

GrapeOAS.type_resolvers.register(MyCustomTypeResolver)

Instance Method Summary collapse

Instance Method Details

#build_schema(type) ⇒ ApiModel::Schema

Builds an OpenAPI schema from the given type.

Parameters:

  • type (String, Class, Object)

    The type to build schema for

Returns:

Raises:

  • (NotImplementedError)


61
62
63
# File 'lib/grape_oas/type_resolvers/base.rb', line 61

def build_schema(type)
  raise NotImplementedError, "#{self} must implement .build_schema(type)"
end

#handles?(type) ⇒ Boolean

Checks if this resolver can handle the given type.

Parameters:

  • type (String, Class, Object)

    The type to check (stringified or actual)

Returns:

  • (Boolean)

    true if this resolver can handle the type

Raises:

  • (NotImplementedError)


53
54
55
# File 'lib/grape_oas/type_resolvers/base.rb', line 53

def handles?(type)
  raise NotImplementedError, "#{self} must implement .handles?(type)"
end

#infer_format_from_name(name) ⇒ String?

Infers OpenAPI format from a type name suffix. Shared across resolvers for consistent format detection.

Parameters:

  • name (String)

    The type name to analyze

Returns:

  • (String, nil)

    The inferred format or nil



87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/grape_oas/type_resolvers/base.rb', line 87

def infer_format_from_name(name)
  last_segment = name.to_s.split("::").last.to_s
  return nil if last_segment.empty?

  return "uuid" if last_segment.end_with?("UUID")
  return "date-time" if last_segment.end_with?("DateTime")
  return "date" if last_segment.end_with?("Date")
  return "email" if last_segment.end_with?("Email")
  return "uri" if last_segment.end_with?("URI", "Url", "URL")

  nil
end

#primitive_to_schema_type(klass) ⇒ String

Converts a Ruby class to its OpenAPI schema type. Shared across resolvers for consistent type mapping.

Parameters:

  • klass (Class)

    The Ruby class

Returns:

  • (String)

    The OpenAPI schema type



105
106
107
# File 'lib/grape_oas/type_resolvers/base.rb', line 105

def primitive_to_schema_type(klass)
  Constants.primitive_type(klass) || Constants::SchemaTypes::STRING
end

#resolve_class(type) ⇒ Class?

Attempts to resolve a stringified type back to its actual class. Uses Object.const_get for resolution, similar to grape-swagger’s approach.

Parameters:

  • type (String, Class)

    The type to resolve

Returns:

  • (Class, nil)

    The resolved class, or nil if not resolvable



70
71
72
73
74
75
76
77
78
79
80
# File 'lib/grape_oas/type_resolvers/base.rb', line 70

def resolve_class(type)
  return type if type.is_a?(Class)
  return type if type.respond_to?(:primitive) # Dry::Type

  return nil unless type.is_a?(String)
  return nil if type.empty?

  Object.const_get(type)
rescue NameError
  nil
end