jsonapi-resources-anchor 2.11.1 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/anchor/concerns/typeable.rb +26 -0
  3. data/lib/anchor/config.rb +5 -1
  4. data/lib/anchor/inference/active_record/infer/base.rb +12 -0
  5. data/lib/anchor/inference/active_record/infer/columns.rb +42 -0
  6. data/lib/anchor/inference/active_record/infer/enums.rb +17 -0
  7. data/lib/anchor/inference/active_record/infer/model.rb +64 -0
  8. data/lib/anchor/inference/active_record/infer/rbs.rb +30 -0
  9. data/lib/anchor/inference/active_record/infer.rb +8 -0
  10. data/lib/anchor/inference/active_record/types/base.rb +9 -0
  11. data/lib/anchor/inference/active_record/types/column_comments.rb +29 -0
  12. data/lib/anchor/inference/active_record/types/defaulted.rb +17 -0
  13. data/lib/anchor/inference/active_record/types/overridden.rb +23 -0
  14. data/lib/anchor/inference/active_record/types/presence_required.rb +33 -0
  15. data/lib/anchor/inference/active_record/types/serialized.rb +13 -0
  16. data/lib/anchor/inference/active_record/types.rb +8 -0
  17. data/lib/anchor/inference/jsonapi/infer/anchor_def.rb +86 -0
  18. data/lib/anchor/inference/jsonapi/infer/base.rb +12 -0
  19. data/lib/anchor/inference/jsonapi/infer/rbs.rb +30 -0
  20. data/lib/anchor/inference/jsonapi/infer/relationship_references.rb +38 -0
  21. data/lib/anchor/inference/jsonapi/infer/resource.rb +72 -0
  22. data/lib/anchor/inference/jsonapi/infer/shell.rb +24 -0
  23. data/lib/anchor/inference/jsonapi/infer.rb +8 -0
  24. data/lib/anchor/inference/jsonapi/read_type.rb +47 -0
  25. data/lib/anchor/inference/jsonapi/types/active_record_relationships_wrapper.rb +44 -0
  26. data/lib/anchor/inference/jsonapi/types/anchor_comments.rb +23 -0
  27. data/lib/anchor/inference/jsonapi/types/base.rb +9 -0
  28. data/lib/anchor/inference/jsonapi/types/empty.rb +9 -0
  29. data/lib/anchor/inference/jsonapi/types/overridden.rb +14 -0
  30. data/lib/anchor/inference/jsonapi/types/readable.rb +22 -0
  31. data/lib/anchor/inference/jsonapi/types/relationships_wrapper.rb +29 -0
  32. data/lib/anchor/inference/jsonapi/types.rb +8 -0
  33. data/lib/anchor/json_schema/resource.rb +8 -8
  34. data/lib/anchor/json_schema/schema_generator.rb +1 -3
  35. data/lib/anchor/json_schema/serializer.rb +2 -0
  36. data/lib/anchor/schema.rb +1 -1
  37. data/lib/anchor/schema_generator.rb +1 -2
  38. data/lib/anchor/type_script/file_structure.rb +4 -3
  39. data/lib/anchor/type_script/multifile_schema_generator.rb +1 -4
  40. data/lib/anchor/type_script/resource.rb +17 -15
  41. data/lib/anchor/type_script/schema_generator.rb +1 -3
  42. data/lib/anchor/type_script/serializer.rb +2 -0
  43. data/lib/anchor/types/inference/active_record.rb +11 -35
  44. data/lib/anchor/types/inference/rbs.rb +95 -0
  45. data/lib/anchor/types.rb +123 -10
  46. data/lib/anchor/version.rb +1 -1
  47. data/lib/jsonapi-resources-anchor.rb +36 -2
  48. metadata +34 -5
  49. data/lib/anchor/resource.rb +0 -201
  50. data/lib/anchor/types/inference/jsonapi.rb +0 -14
@@ -0,0 +1,47 @@
1
+ module Anchor
2
+ module Inference
3
+ module JSONAPI
4
+ class ReadType
5
+ include Anchor::Typeable
6
+
7
+ attr_reader :t
8
+
9
+ delegate :convert_case, to: Anchor::Types
10
+
11
+ def initialize(klass, context: {}, include_all_fields: false)
12
+ @klass = klass
13
+ @t = Anchor::Inference::JSONAPI::Infer::Resource.infer(klass)
14
+ @context = context
15
+ @include_all_fields = include_all_fields
16
+ end
17
+
18
+ def self.infer(...) = new(...).infer
19
+
20
+ def infer
21
+ id +
22
+ type +
23
+ readable(attributes).convert_case +
24
+ object([property(
25
+ "relationships",
26
+ readable(relationships.nullable_to_optional).convert_case,
27
+ )]) +
28
+ meta +
29
+ links
30
+ end
31
+
32
+ def readable(t)
33
+ Anchor::Inference::JSONAPI::Types::Readable.new(
34
+ @klass, context: @context, include_all_fields: @include_all_fields
35
+ ).wrap(t)
36
+ end
37
+
38
+ def id = t.pick(["id"])
39
+ def type = t.pick(["type"])
40
+ def attributes = t.pick(@klass._attributes.except(:id).keys.map(&:to_s))
41
+ def relationships = t.pick(@klass._relationships.keys.map(&:to_s))
42
+ def meta = t["meta"].type.is_a?(unknown.singleton_class) ? t.pick([]) : t.pick(["meta"])
43
+ def links = t["links"].type.is_a?(unknown.singleton_class) ? t.pick([]) : t.pick(["links"])
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,44 @@
1
+ module Anchor::Inference::JSONAPI::Types
2
+ class ActiveRecordRelationshipsWrapper < Base
3
+ include Anchor::Typeable
4
+
5
+ def initialize(klass)
6
+ super
7
+ @model_klass = klass._model_class
8
+ end
9
+
10
+ def wrap(t) = t.apply_higher(wrapper_type).pick(wrapper_type.keys)
11
+
12
+ private
13
+
14
+ def wrapper_type
15
+ return @wrapper_type if defined?(@wrapper_type)
16
+
17
+ props = @klass._relationships.filter_map do |name, rel|
18
+ relation_name = rel.options[:relation_name]&.to_s || name.to_s
19
+
20
+ next unless (ref = @model_klass.reflections[relation_name])
21
+
22
+ # TODO: comments from DB?
23
+ property(name.to_s, wrapper(ref), false, nil)
24
+ end
25
+
26
+ @wrapper_type = object(props)
27
+ end
28
+
29
+ def wrapper(reflection)
30
+ case reflection
31
+ when ::ActiveRecord::Reflection::BelongsToReflection then belongs_to_type(reflection)
32
+ when ::ActiveRecord::Reflection::HasOneReflection then Anchor::Types::Maybe
33
+ when ::ActiveRecord::Reflection::HasManyReflection then Anchor::Types::Array
34
+ when ::ActiveRecord::Reflection::HasAndBelongsToManyReflection then Anchor::Types::Array
35
+ when ::ActiveRecord::Reflection::ThroughReflection then wrapper(reflection.send(:delegate_reflection))
36
+ else raise "#{reflection.class.name} not supported" # TODO: make this unknown wrapper somehow ?
37
+ end
38
+ end
39
+
40
+ def belongs_to_type(reflection)
41
+ reflection.options[:optional] ? Anchor::Types::Maybe : Anchor::Types::Identity
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,23 @@
1
+ module Anchor::Inference::JSONAPI::Types
2
+ class AnchorComments < Base
3
+ include Anchor::Typeable
4
+
5
+ def wrap(t) = object(properties(t))
6
+
7
+ private
8
+
9
+ def properties(t)
10
+ t.properties.map do |prop|
11
+ prop.dup(description: comments[prop.name] || prop.description)
12
+ end
13
+ end
14
+
15
+ def comments
16
+ return @comments if defined?(@comments)
17
+ attr_descs = @klass.try(:anchor_attributes_descriptions) || {}
18
+ rel_descs = @klass.try(:anchor_relationships_descriptions) || {}
19
+
20
+ @comments = attr_descs.merge(rel_descs).reject { |_, d| d.nil? }.transform_keys(&:to_s)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ module Anchor::Inference::JSONAPI::Types
2
+ class Base
3
+ def initialize(klass)
4
+ @klass = klass
5
+ end
6
+
7
+ def wrap(t) = raise NotImplementedError
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Anchor::Inference::JSONAPI::Types
2
+ class Empty < Base
3
+ def initialize(klass = nil)
4
+ super(klass)
5
+ end
6
+
7
+ def wrap(t) = Anchor::Types::Object.new([])
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ module Anchor::Inference::JSONAPI::Types
2
+ class Overridden < Base
3
+ def wrap(t) = t.untype(names)
4
+
5
+ private
6
+
7
+ def names
8
+ count = @klass.anchor_method_added_count || Hash.new(0)
9
+ @klass._attributes.keys.filter_map do |name|
10
+ name.to_s if count[name.to_sym] > 1
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ module Anchor::Inference::JSONAPI::Types
2
+ class Readable < Base
3
+ def initialize(klass, context: {}, include_all_fields: false)
4
+ super(klass)
5
+ @context = context
6
+ @include_all_fields = include_all_fields
7
+ end
8
+
9
+ def wrap(t) = t.pick(names.map(&:to_s))
10
+
11
+ private
12
+
13
+ def names
14
+ return @klass.fields unless statically_determinable_fetchable_fields? && !@include_all_fields
15
+ @klass.anchor_fetchable_fields(@context)
16
+ end
17
+
18
+ def statically_determinable_fetchable_fields?
19
+ @klass.singleton_class.method_defined?(:anchor_fetchable_fields)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ module Anchor::Inference::JSONAPI::Types
2
+ class RelationshipsWrapper < Base
3
+ include Anchor::Typeable
4
+
5
+ def wrap(t) = t.apply_higher(wrapper_type).pick(wrapper_type.keys)
6
+
7
+ private
8
+
9
+ def wrapper_type
10
+ return @wrapper_type if defined?(@wrapper_type)
11
+ props = @klass._relationships.map do |name, rel|
12
+ property(name.to_s, wrapper(rel), false, nil)
13
+ end
14
+ @wrapper_type = object(props)
15
+ end
16
+
17
+ def wrapper(relationship)
18
+ case relationship
19
+ when ::JSONAPI::Relationship::ToOne then Anchor::Types::Identity
20
+ when ::JSONAPI::Relationship::ToMany then Anchor::Types::Array
21
+ else raise "#{relationship.class.name} not supported"
22
+ end
23
+ end
24
+
25
+ def belongs_to_type(reflection)
26
+ reflection.options[:optional] ? Anchor::Types::Maybe : Anchor::Types::Identity
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,8 @@
1
+ module Anchor
2
+ module Inference
3
+ module JSONAPI
4
+ module Types
5
+ end
6
+ end
7
+ end
8
+ end
@@ -1,14 +1,14 @@
1
1
  module Anchor::JSONSchema
2
- class Resource < Anchor::Resource
3
- def express(context: {}, include_all_fields:, exclude_fields:)
4
- included_fields = schema_fetchable_fields(context:, include_all_fields:)
5
- included_fields -= exclude_fields if exclude_fields
2
+ class Resource
3
+ delegate :anchor_schema_name, to: :@klass
6
4
 
7
- properties = [id_property, type_property] +
8
- Array.wrap(anchor_attributes_properties(included_fields:)) +
9
- Array.wrap(anchor_relationships_property(included_fields:))
5
+ def initialize(klass)
6
+ @klass = klass
7
+ end
10
8
 
11
- Anchor::Types::Object.new(properties)
9
+ def express(context: {}, include_all_fields:)
10
+ t = Anchor::Inference::JSONAPI::ReadType.infer(@klass, context:, include_all_fields:).omit(["meta", "links"])
11
+ t["relationships"].type.properties.count > 0 ? t : t.omit(["relationships"])
12
12
  end
13
13
  end
14
14
  end
@@ -2,11 +2,10 @@ module Anchor::JSONSchema
2
2
  class SchemaGenerator < Anchor::SchemaGenerator
3
3
  delegate :type_property, to: Anchor::JSONSchema::Serializer
4
4
 
5
- def initialize(register:, context: {}, include_all_fields: false, exclude_fields: nil) # rubocop:disable Lint/MissingSuper
5
+ def initialize(register:, context: {}, include_all_fields: false) # rubocop:disable Lint/MissingSuper
6
6
  @register = register
7
7
  @context = context
8
8
  @include_all_fields = include_all_fields
9
- @exclude_fields = exclude_fields
10
9
  end
11
10
 
12
11
  def call
@@ -41,7 +40,6 @@ module Anchor::JSONSchema
41
40
  resource.anchor_schema_name => type_property(resource.express(
42
41
  context: @context,
43
42
  include_all_fields: @include_all_fields,
44
- exclude_fields: @exclude_fields.nil? ? [] : @exclude_fields[r.anchor_schema_name.to_sym],
45
43
  )),
46
44
  }
47
45
  end.reduce(&:merge)
@@ -20,7 +20,9 @@ module Anchor::JSONSchema
20
20
  when Anchor::Types::Reference then { "$ref" => "#/$defs/#{type.name}" }
21
21
  when Anchor::Types::Object, Anchor::Types::Object.singleton_class then serialize_object(type)
22
22
  when Anchor::Types::Enum.singleton_class then { enum: type.values.map(&:second) }
23
+ when Anchor::Types::Identity then type_property(type.type)
23
24
  when Anchor::Types::Unknown.singleton_class then {}
25
+ when Anchor::Types::Intersection then {}
24
26
  else raise RuntimeError
25
27
  end
26
28
  end
data/lib/anchor/schema.rb CHANGED
@@ -3,7 +3,7 @@ module Anchor
3
3
  class DuplicateTypeError < StandardError; end
4
4
 
5
5
  class << self
6
- Register = Struct.new(:resources, :enums, keyword_init: true)
6
+ Register = Data.define(:resources, :enums)
7
7
 
8
8
  def register
9
9
  Register.new(resources: @resources || [], enums: @enums || [])
@@ -1,10 +1,9 @@
1
1
  module Anchor
2
2
  class SchemaGenerator
3
- def initialize(register:, context:, include_all_fields:, exclude_fields:)
3
+ def initialize(register:, context:, include_all_fields:)
4
4
  @register = register
5
5
  @context = context
6
6
  @include_all_fields = include_all_fields
7
- @exclude_fields = exclude_fields
8
7
  end
9
8
 
10
9
  def self.call(...)
@@ -2,7 +2,7 @@ module Anchor::TypeScript
2
2
  class FileStructure
3
3
  # @param file_name [String] name of file, e.g. model.ts
4
4
  # @param type [Anchor::Types]
5
- Import = Struct.new(:file_name, :type, keyword_init: true)
5
+ Import = Data.define(:file_name, :type)
6
6
  class FileUtils
7
7
  def self.imports_to_code(imports)
8
8
  imports.group_by(&:file_name).map do |file_name, file_imports|
@@ -71,16 +71,17 @@ module Anchor::TypeScript
71
71
  end
72
72
 
73
73
  def relationships_to_import
74
- relationships = @object.properties.find { |p| p.name == :relationships }
74
+ relationships = @object.properties.find { |p| p.name.to_sym == :relationships }
75
75
  return [] if relationships.nil? || relationships.type.try(:properties).nil?
76
76
  relationships.type.properties.flat_map { |p| references_from_type(p.type) }.uniq.sort_by(&:anchor_schema_name)
77
77
  end
78
78
 
79
79
  def references_from_type(type)
80
80
  case type
81
- when Anchor::Types::Array, Anchor::Types::Maybe then references_from_type(type.type)
81
+ when Anchor::Types::Array, Anchor::Types::Maybe, Anchor::Types::Identity then references_from_type(type.type)
82
82
  when Anchor::Types::Union then type.types.flat_map { |t| references_from_type(t) }
83
83
  when Anchor::Types::Reference then [type]
84
+ when Anchor::Types::Unknown.singleton_class then []
84
85
  end.uniq.sort_by(&:anchor_schema_name)
85
86
  end
86
87
 
@@ -1,6 +1,6 @@
1
1
  module Anchor::TypeScript
2
2
  class MultifileSchemaGenerator < Anchor::SchemaGenerator
3
- Result = Struct.new(:name, :text, :type, keyword_init: true)
3
+ Result = Data.define(:name, :text, :type)
4
4
 
5
5
  module FileType
6
6
  RESOURCE = "resource"
@@ -11,14 +11,12 @@ module Anchor::TypeScript
11
11
  register:,
12
12
  context: {},
13
13
  include_all_fields: false,
14
- exclude_fields: nil,
15
14
  manually_editable: true,
16
15
  resource_file_extension: ".ts"
17
16
  )
18
17
  @register = register
19
18
  @context = context
20
19
  @include_all_fields = include_all_fields
21
- @exclude_fields = exclude_fields
22
20
  @manually_editable = manually_editable
23
21
  @resource_file_extension = "." + resource_file_extension.sub(/^\./, "")
24
22
  end
@@ -47,7 +45,6 @@ module Anchor::TypeScript
47
45
  definition = r.definition(
48
46
  context: @context,
49
47
  include_all_fields: @include_all_fields,
50
- exclude_fields: @exclude_fields.nil? ? [] : @exclude_fields[r.anchor_schema_name.to_sym],
51
48
  )
52
49
 
53
50
  file_structure = ::Anchor::TypeScript::FileStructure.new(definition, extension: @resource_file_extension)
@@ -1,6 +1,12 @@
1
1
  module Anchor::TypeScript
2
- class Resource < Anchor::Resource
3
- Definition = Struct.new(:name, :object, keyword_init: true)
2
+ class Resource
3
+ Definition = Data.define(:name, :object)
4
+
5
+ delegate :anchor_schema_name, to: :@klass
6
+
7
+ def initialize(klass)
8
+ @klass = klass
9
+ end
4
10
 
5
11
  def express(...)
6
12
  @object = object(...)
@@ -13,21 +19,17 @@ module Anchor::TypeScript
13
19
  Definition.new(name: anchor_schema_name, object: @object)
14
20
  end
15
21
 
16
- def object(context: {}, include_all_fields:, exclude_fields:)
17
- included_fields = schema_fetchable_fields(context:, include_all_fields:)
18
- included_fields -= exclude_fields if exclude_fields
22
+ def object(context: {}, include_all_fields:)
23
+ t = Anchor::Inference::JSONAPI::ReadType.infer(@klass, context:, include_all_fields:)
24
+ return t if t["relationships"].type.properties.count > 0
25
+ return t.omit(["relationships"]) unless Anchor.config.empty_relationship_type
19
26
 
20
- relationships_property = anchor_relationships_property(included_fields:)
21
- if relationships_property.nil? && Anchor.config.empty_relationship_type
22
- relationships_property = Anchor::Types::Property.new(:relationships, Anchor.config.empty_relationship_type.call)
23
- end
27
+ t.overwrite(Anchor::Types::Object.new([empty_relationships_property]))
28
+ end
24
29
 
25
- properties = [id_property, type_property] +
26
- Array.wrap(anchor_attributes_properties(included_fields:)) +
27
- Array.wrap(relationships_property) +
28
- [anchor_meta_property].compact + [anchor_links_property].compact
30
+ private
29
31
 
30
- Anchor::Types::Object.new(properties)
31
- end
32
+ def empty_relationships_property = property("relationships", Anchor.config.empty_relationship_type.call)
33
+ def property(...) = Anchor::Types::Property.new(...)
32
34
  end
33
35
  end
@@ -1,10 +1,9 @@
1
1
  module Anchor::TypeScript
2
2
  class SchemaGenerator < Anchor::SchemaGenerator
3
- def initialize(register:, context: {}, include_all_fields: false, exclude_fields: nil) # rubocop:disable Lint/MissingSuper
3
+ def initialize(register:, context: {}, include_all_fields: false) # rubocop:disable Lint/MissingSuper
4
4
  @register = register
5
5
  @context = context
6
6
  @include_all_fields = include_all_fields
7
- @exclude_fields = exclude_fields
8
7
  end
9
8
 
10
9
  def call
@@ -15,7 +14,6 @@ module Anchor::TypeScript
15
14
  r.express(
16
15
  context: @context,
17
16
  include_all_fields: @include_all_fields,
18
- exclude_fields: @exclude_fields.nil? ? [] : @exclude_fields[r.anchor_schema_name.to_sym],
19
17
  )
20
18
  end
21
19
 
@@ -11,12 +11,14 @@ module Anchor::TypeScript
11
11
  when Anchor::Types::Null.singleton_class then "null"
12
12
  when Anchor::Types::Record, Anchor::Types::Record.singleton_class then "Record<string, #{type_string(type.try(:value_type) || Anchor::Types::Unknown)}>"
13
13
  when Anchor::Types::Union then type.types.map { |type| type_string(type, depth) }.join(" | ")
14
+ when Anchor::Types::Intersection then type.types.map { |type| "(#{type_string(type, depth)})" }.join(" & ")
14
15
  when Anchor::Types::Maybe then Anchor.config.maybe_as_union ? type_string(Anchor::Types::Union.new([type.type, Anchor::Types::Null]), depth) : "Maybe<#{type_string(type.type, depth)}>"
15
16
  when Anchor::Types::Array then Anchor.config.array_bracket_notation ? "(#{type_string(type.type, depth)})[]" : "Array<#{type_string(type.type, depth)}>"
16
17
  when Anchor::Types::Literal then serialize_literal(type.value)
17
18
  when Anchor::Types::Reference then type.name
18
19
  when Anchor::Types::Object, Anchor::Types::Object.singleton_class then serialize_object(type, depth)
19
20
  when Anchor::Types::Enum.singleton_class then type.anchor_schema_name
21
+ when Anchor::Types::Identity then type_string(type.type)
20
22
  when Anchor::Types::Unknown.singleton_class then "unknown"
21
23
  else raise RuntimeError
22
24
  end
@@ -1,49 +1,26 @@
1
1
  module Anchor::Types::Inference
2
2
  module ActiveRecord
3
- class << self
4
- # @return [Proc{Type => Type, Anchor::Types::Maybe<Type>, Anchor::Types::Array<Type>}]
5
- def wrapper_from_reflection(reflection)
6
- case reflection
7
- when ::ActiveRecord::Reflection::BelongsToReflection then ->(type) { belongs_to_type(reflection, type) }
8
- when ::ActiveRecord::Reflection::HasOneReflection then ->(type) { Anchor::Types::Maybe.new(type) }
9
- when ::ActiveRecord::Reflection::HasManyReflection then ->(type) { Anchor::Types::Array.new(type) }
10
- when ::ActiveRecord::Reflection::HasAndBelongsToManyReflection then ->(type) { Anchor::Types::Array.new(type) }
11
- when ::ActiveRecord::Reflection::ThroughReflection then wrapper_from_reflection(reflection.send(:delegate_reflection))
12
- else raise "#{reflection.class.name} not supported"
13
- end
14
- end
15
-
16
- private
17
-
18
- # @param reflection [::ActiveRecord::Reflection::BelongsToReflection]
19
- # @param type [Anchor::Types]
20
- # @return [Anchor::Types::Maybe<Type>, Type]
21
- def belongs_to_type(reflection, type)
22
- reflection.options[:optional] ? Anchor::Types::Maybe.new(type) : type
23
- end
24
- end
25
-
26
3
  module SQL
27
4
  class << self
28
- def from(column, check_config: true)
29
- return Anchor.config.ar_column_to_type.call(column) if check_config && Anchor.config.ar_column_to_type
30
- type = from_sql_type(column.type)
5
+ def default_ar_column_to_type(column)
6
+ metadata_type = from_sql_type_metadata(column.sql_type_metadata)
7
+ column_type = from_column_type(column.type)
31
8
 
32
- if ["character varying[]", "text[]"].include?(column.sql_type_metadata.sql_type)
33
- type = Anchor::Types::Array.new(Anchor::Types::String)
34
- end
9
+ type = [metadata_type, column_type, Anchor::Types::Unknown].compact.first
35
10
 
36
11
  column.null ? Anchor::Types::Maybe.new(type) : type
37
12
  end
38
13
 
39
- def default_ar_column_to_type(column)
40
- from(column, check_config: false)
41
- end
42
-
43
14
  private
44
15
 
16
+ def from_sql_type_metadata(sql_type_metadata)
17
+ case sql_type_metadata.sql_type
18
+ when "character varying[]", "text[]" then Anchor::Types::Array.new(Anchor::Types::String)
19
+ end
20
+ end
21
+
45
22
  # inspiration from https://github.com/ElMassimo/types_from_serializers/blob/146ba40bc1a0da37473cd3b705a8ca982c2d173f/types_from_serializers/lib/types_from_serializers/generator.rb#L382
46
- def from_sql_type(type)
23
+ def from_column_type(type)
47
24
  case type
48
25
  when :boolean then Anchor::Types::Boolean
49
26
  when :date then Anchor::Types::String
@@ -57,7 +34,6 @@ module Anchor::Types::Inference
57
34
  when :text then Anchor::Types::String
58
35
  when :time then Anchor::Types::String
59
36
  when :uuid then Anchor::Types::String
60
- else Anchor::Types::Unknown
61
37
  end
62
38
  end
63
39
  end
@@ -0,0 +1,95 @@
1
+ module Anchor::Types::Inference
2
+ module RBS
3
+ class << self
4
+ def enabled?
5
+ Anchor.config.rbs.to_s == "fallback"
6
+ end
7
+
8
+ def validate!
9
+ return if defined?(::RBS) && ::RBS::VERSION.first == "3"
10
+ raise "RBS version conflict: rbs ~> 3 required."
11
+ end
12
+
13
+ # @param klass [Class]
14
+ # @return [Class, nil]
15
+ def get_definition(klass)
16
+ env.class_decls.keys.find do |definition|
17
+ # TODO: Do we need both absolute and relative here?
18
+ [klass.name, "::#{klass.name}"].include?(definition.to_s)
19
+ end
20
+ end
21
+
22
+ def build_instance(klass)
23
+ if (definition = get_definition(klass))
24
+ builder.build_instance(definition)
25
+ end
26
+ end
27
+
28
+ def from_rbs_type(type)
29
+ case type
30
+ when ::RBS::Types::ClassInstance then from_class_instance(type)
31
+ when ::RBS::Types::Literal then Types::Literal.new(type.literal)
32
+ when ::RBS::Types::Bases::Bool then Types::Boolean
33
+ when ::RBS::Types::Bases::Nil then Types::Null
34
+ when ::RBS::Types::Bases::Void then Types::Unknown
35
+ when ::RBS::Types::Bases::Any then Types::Unknown
36
+ when ::RBS::Types::Optional then Types::Maybe.new(from_rbs_type(type.type))
37
+ when ::RBS::Types::Record then from_record(type)
38
+ when ::RBS::Types::Union then from_union(type)
39
+ when ::RBS::Types::Intersection then from_intersection(type)
40
+ when ::RBS::Types::Tuple then Types::Unknown # TODO
41
+ else Types::Unknown
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def builder
48
+ @builder ||= ::RBS::DefinitionBuilder.new(env:)
49
+ end
50
+
51
+ def env
52
+ @env ||= ::RBS::Environment.from_loader(loader).resolve_type_names
53
+ end
54
+
55
+ def loader
56
+ @loader ||= ::RBS::EnvironmentLoader.new.tap do |l|
57
+ l.add(path: Anchor.config.rbs_sig_path)
58
+ end
59
+ end
60
+
61
+ def from_record(type)
62
+ properties = type.fields.map do |name, type|
63
+ Types::Property.new(name, from_rbs_type(type))
64
+ end
65
+ optional_properties = type.optional_fields.map do |name, type|
66
+ Types::Property.new(name, from_rbs_type(type), true)
67
+ end
68
+ Types::Object.new(properties + optional_properties)
69
+ end
70
+
71
+ def from_union(type)
72
+ types = type.types.map { |type| from_rbs_type(type) }
73
+ Types::Union.new(types)
74
+ end
75
+
76
+ def from_intersection(type)
77
+ types = type.types.map { |type| from_rbs_type(type) }
78
+ Types::Intersection.new(types)
79
+ end
80
+
81
+ def from_class_instance(type)
82
+ case type.name.to_s
83
+ when "::String" then Types::String
84
+ when "::Numeric" then Types::Float
85
+ when "::Integer" then Types::Integer
86
+ when "::Float" then Types::Float
87
+ when "::BigDecimal" then Types::String
88
+ when "::Boolean" then Types::Boolean
89
+ when "::Array" then Types::Array.new(from_rbs_type(type.args.first))
90
+ else Types::Unknown
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end