odata_duty 0.30.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 (51) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +312 -0
  3. data/lib/generators/odata_duty/entity_set/entity_set_generator.rb +56 -0
  4. data/lib/generators/odata_duty/entity_set/templates/entity_set.rb.erb +20 -0
  5. data/lib/generators/odata_duty/entity_set/templates/entity_set_spec.rb.erb +38 -0
  6. data/lib/generators/odata_duty/entity_set/templates/entity_type.rb.erb +6 -0
  7. data/lib/generators/odata_duty/entity_set/templates/entity_type_spec.rb.erb +20 -0
  8. data/lib/generators/odata_duty/entity_set/templates/odata_active_record_concern.rb.erb +109 -0
  9. data/lib/generators/odata_duty/entity_set/templates/resolver.rb.erb +39 -0
  10. data/lib/generators/odata_duty/entity_set/templates/resolver_spec.rb.erb +50 -0
  11. data/lib/generators/odata_duty/install/install_generator.rb +60 -0
  12. data/lib/generators/odata_duty/install/templates/controller.rb.tt +43 -0
  13. data/lib/generators/odata_duty/install/templates/schema.rb.tt +16 -0
  14. data/lib/metadata.xml.erb +116 -0
  15. data/lib/odata_duty/complex_type.rb +70 -0
  16. data/lib/odata_duty/context_wrapper.rb +26 -0
  17. data/lib/odata_duty/create_complex_type_hash_wrapper.rb +49 -0
  18. data/lib/odata_duty/dynamic_object_wrapper.rb.erb +79 -0
  19. data/lib/odata_duty/edms.rb +122 -0
  20. data/lib/odata_duty/edmx_schema.rb +19 -0
  21. data/lib/odata_duty/entity_type.rb +72 -0
  22. data/lib/odata_duty/enum_type.rb +71 -0
  23. data/lib/odata_duty/errors.rb +52 -0
  24. data/lib/odata_duty/executor.rb +277 -0
  25. data/lib/odata_duty/filter.rb +76 -0
  26. data/lib/odata_duty/filter_predicate.rb +12 -0
  27. data/lib/odata_duty/mapper_builder.rb +86 -0
  28. data/lib/odata_duty/mcp_input_schemas.rb +59 -0
  29. data/lib/odata_duty/mcp_server_builder.rb +107 -0
  30. data/lib/odata_duty/oas2/collection_get_path.rb +97 -0
  31. data/lib/odata_duty/oas2/collection_post_path.rb +63 -0
  32. data/lib/odata_duty/oas2/individual_delete_path.rb +42 -0
  33. data/lib/odata_duty/oas2/individual_get_path.rb +37 -0
  34. data/lib/odata_duty/oas2/individual_patch_path.rb +63 -0
  35. data/lib/odata_duty/oas2.rb +113 -0
  36. data/lib/odata_duty/parslet_search_expression.rb +163 -0
  37. data/lib/odata_duty/property/collection_prop.rb +25 -0
  38. data/lib/odata_duty/property/single_prop.rb +131 -0
  39. data/lib/odata_duty/property.rb +49 -0
  40. data/lib/odata_duty/railtie.rb +11 -0
  41. data/lib/odata_duty/schema_builder/complex_type.rb +34 -0
  42. data/lib/odata_duty/schema_builder/container.rb +16 -0
  43. data/lib/odata_duty/schema_builder/data_type.rb +18 -0
  44. data/lib/odata_duty/schema_builder/endpoint.rb +117 -0
  45. data/lib/odata_duty/schema_builder/entity_set.rb +67 -0
  46. data/lib/odata_duty/schema_builder/entity_type.rb +49 -0
  47. data/lib/odata_duty/schema_builder/enum_type.rb +32 -0
  48. data/lib/odata_duty/schema_builder.rb +130 -0
  49. data/lib/odata_duty/set_resolver.rb +51 -0
  50. data/lib/odata_duty.rb +311 -0
  51. metadata +168 -0
@@ -0,0 +1,25 @@
1
+ require_relative 'single_prop'
2
+
3
+ module OdataDuty
4
+ module Property
5
+ class CollectionProp < SingleProp
6
+ def convert(value, context)
7
+ value.map { |v| super(v, context) }
8
+ rescue NoMethodError
9
+ raise InvalidValue, "#{value} is not an collection"
10
+ end
11
+
12
+ def to_oas2_type
13
+ if scalar?
14
+ raw_type.to_oas2(is_collection: true)
15
+ else
16
+ { 'type' => 'array', 'items' => { '$ref' => "#/definitions/#{raw_type.name}" } }
17
+ end
18
+ end
19
+
20
+ def collection?
21
+ true
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,131 @@
1
+ module OdataDuty
2
+ module Property
3
+ CORE_ANNOTATION_TERMS = { computed: 'Org.OData.Core.V1.Computed',
4
+ immutable: 'Org.OData.Core.V1.Immutable' }.freeze
5
+
6
+ class SingleProp
7
+ attr_reader :name, :nullable, :calling_method, :line__defined__at, :raw_type, :type,
8
+ :set_type, :method_name, :mutability
9
+
10
+ def initialize(name, type, line__defined__at:, nullable:, method:, mutability:)
11
+ @line__defined__at = line__defined__at
12
+ @name = name.to_sym
13
+ @calling_method = method if method.respond_to?(:call)
14
+ method = nil if method.respond_to?(:call)
15
+ @method_name = (method || name).to_sym
16
+ @nullable = nullable ? true : false
17
+ @mutability = mutability
18
+ load_type_instance_vars(type)
19
+ end
20
+
21
+ def computed?
22
+ mutability == :computed
23
+ end
24
+
25
+ def immutable?
26
+ mutability == :immutable
27
+ end
28
+
29
+ def non_insertable?
30
+ mutability == :non_insertable
31
+ end
32
+
33
+ def core_annotation_term
34
+ CORE_ANNOTATION_TERMS[mutability]
35
+ end
36
+
37
+ def settable_on_create?
38
+ !computed? && !non_insertable?
39
+ end
40
+
41
+ def settable_on_update?
42
+ !computed? && !immutable?
43
+ end
44
+
45
+ def calling_method?
46
+ !calling_method.nil?
47
+ end
48
+
49
+ def nullable?
50
+ nullable
51
+ end
52
+
53
+ def boolean?
54
+ raw_type == EdmBool
55
+ end
56
+
57
+ def string?
58
+ raw_type == EdmString
59
+ end
60
+
61
+ def int?
62
+ raw_type == EdmInt64
63
+ end
64
+
65
+ def date?
66
+ raw_type == EdmDate
67
+ end
68
+
69
+ def datetime?
70
+ raw_type == EdmDateTimeOffset
71
+ end
72
+
73
+ def enum?
74
+ raw_type.respond_to?(:members)
75
+ end
76
+
77
+ def enum_members
78
+ raw_type.members
79
+ end
80
+
81
+ def scalar?
82
+ raw_type.scalar?
83
+ end
84
+
85
+ def to_value(value, context)
86
+ convert(value, context)
87
+ rescue InvalidValue => e
88
+ raise InvalidValue, "#{name} : #{e}"
89
+ end
90
+
91
+ def convert(value, context)
92
+ return if value.nil?
93
+
94
+ @set_type.to_value(value, context)
95
+ end
96
+
97
+ def filter_convert(value, context)
98
+ convert(value, context)
99
+ rescue OdataDuty::InvalidValue
100
+ raise InvalidFilterValue, "Invalid value #{value} for #{name}"
101
+ end
102
+
103
+ def to_oas2
104
+ to_oas2_type.dup.tap do |oas2|
105
+ oas2.merge!('readOnly' => true) if computed?
106
+ oas2.merge!('x-nullable' => true) if nullable
107
+ end
108
+ end
109
+
110
+ def to_oas2_type
111
+ return raw_type.to_oas2(is_collection: false) if scalar? && !enum?
112
+
113
+ { '$ref' => "#/definitions/#{raw_type.name}" }
114
+ end
115
+
116
+ # nil (falsy) rather than false: callers only consume truthiness
117
+ def collection?; end
118
+
119
+ private
120
+
121
+ def load_type_instance_vars(type)
122
+ type = Array(type).first
123
+ @set_type = TYPES_MAPPING[type] || type
124
+ raise "Invalid type #{type.inspect} for #{name}" unless @set_type
125
+
126
+ @raw_type = @set_type.respond_to?(:__metadata) ? @set_type.__metadata : @set_type
127
+ @type = @raw_type.respond_to?(:property_type) ? @raw_type.property_type : @raw_type.name
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,49 @@
1
+ require 'odata_duty/edms'
2
+ require 'odata_duty/property/single_prop'
3
+ require 'odata_duty/property/collection_prop'
4
+
5
+ module OdataDuty
6
+ module Property
7
+ MUTABILITIES = %i[read_write immutable non_insertable computed].freeze
8
+ MUTABILITIES_LIST = MUTABILITIES.map(&:inspect).join(', ').freeze
9
+
10
+ def self.new(name, type = String, line__defined__at: nil, nullable: true, method: nil,
11
+ computed: :unset, mutability: :unset)
12
+ unless valid_name?(name)
13
+ raise InvalidNCNamesError, "\"#{name}\" is not a valid property name"
14
+ end
15
+
16
+ prop_class = type.instance_of?(Array) ? CollectionProp : SingleProp
17
+ prop_class.new(name, type,
18
+ line__defined__at: line__defined__at,
19
+ nullable: nullable,
20
+ method: method,
21
+ mutability: resolve_mutability(name, computed, mutability))
22
+ end
23
+
24
+ def self.resolve_mutability(name, computed, mutability)
25
+ mutability = computed_to_mutability(name, computed, mutability) unless computed == :unset
26
+ mutability = :read_write if mutability == :unset
27
+ return mutability if MUTABILITIES.include?(mutability)
28
+
29
+ raise ArgumentError,
30
+ "#{name}: invalid mutability #{mutability.inspect}, " \
31
+ "must be one of #{MUTABILITIES_LIST}"
32
+ end
33
+
34
+ def self.computed_to_mutability(name, computed, mutability)
35
+ unless mutability == :unset
36
+ raise ArgumentError,
37
+ "#{name}: pass either `mutability:` or `computed:`, not both \u2014 they control " \
38
+ 'the same axis'
39
+ end
40
+ computed ? :computed : :read_write
41
+ end
42
+
43
+ NAME_REGEXP = Regexp.new('\A(?:\p{L}|_)(?:[\p{L}\p{Nd}_])*\z').freeze
44
+
45
+ def self.valid_name?(name)
46
+ name.match?(NAME_REGEXP)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,11 @@
1
+ require 'rails/railtie'
2
+
3
+ module OdataDuty
4
+ class Railtie < Rails::Railtie
5
+ # Auto-load generators
6
+ generators do
7
+ require 'generators/odata_duty/install/install_generator'
8
+ require 'generators/odata_duty/entity_set/entity_set_generator'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ require_relative 'data_type'
2
+ require_relative '../mapper_builder'
3
+
4
+ module OdataDuty
5
+ module SchemaBuilder
6
+ class ComplexType < DataType
7
+ attr_reader :properties
8
+
9
+ def initialize(**kwargs)
10
+ super
11
+ @properties = []
12
+ end
13
+
14
+ def property(name, *args, line__defined__at: caller.first, **kwargs)
15
+ if properties.any? { |p| p.name == name.to_sym }
16
+ raise PropertyAlreadyDefinedError, "#{name} is already defined"
17
+ end
18
+
19
+ Property.new(name, *args, line__defined__at: line__defined__at, **kwargs).tap do |property|
20
+ properties << property
21
+ end
22
+ end
23
+
24
+ def to_oas2
25
+ {
26
+ 'type' => 'object',
27
+ 'properties' => properties.to_h do |property|
28
+ [property.name.to_s, property.to_oas2]
29
+ end
30
+ }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ module OdataDuty
2
+ module SchemaBuilder
3
+ class Container
4
+ attr_reader :name, :_defined_at_
5
+
6
+ def initialize(name:)
7
+ @name = name.clone
8
+ @_defined_at_ = caller.find { |line| !line.include?('/lib/odata_duty/') }
9
+
10
+ return if Property.valid_name?(@name)
11
+
12
+ raise InvalidNCNamesError, "\"#{@name}\" is not a valid property name"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ module OdataDuty
2
+ module SchemaBuilder
3
+ class DataType
4
+ attr_reader :name, :_defined_at_
5
+
6
+ def initialize(name:)
7
+ @name = name.clone
8
+ @_defined_at_ = caller.find { |line| !line.include?('/lib/odata_duty/') }
9
+
10
+ return if Property.valid_name?(@name)
11
+
12
+ raise InvalidNCNamesError, "\"#{@name}\" is not a valid property name"
13
+ end
14
+
15
+ def scalar?; end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,117 @@
1
+ module OdataDuty
2
+ module SchemaBuilder
3
+ class Endpoint
4
+ attr_reader :entity_set
5
+
6
+ def initialize(entity_set)
7
+ @entity_set = entity_set
8
+ end
9
+
10
+ def name = entity_set.name
11
+ def url = entity_set.url
12
+
13
+ def new_entity_set(context:)
14
+ entity_set.resolver_class.new(context: context, init_args: entity_set.init_args)
15
+ end
16
+
17
+ def entity_type = entity_set.entity_type
18
+
19
+ def collection(set_builder, context:, selected:)
20
+ unless set_builder.respond_to?(:collection)
21
+ raise NoImplementationError, "collection not implemented for #{set_builder.class}"
22
+ end
23
+
24
+ values = set_builder.collection
25
+ mapper = entity_type.mapper(context, selected: selected)
26
+
27
+ # builder mapping never reads context; pass nil (accept equivalent mutant)
28
+ values.map { |v| mapper.obj_to_hash(v, nil) }
29
+ rescue StandardError => e
30
+ extend_error(e, set_builder, :collection)
31
+ end
32
+
33
+ def individual(set_builder, id, context:, selected:)
34
+ unless set_builder.respond_to?(:individual)
35
+ raise NoImplementationError, "individual not implemented for #{set_builder.class}"
36
+ end
37
+
38
+ result = set_builder.individual(converted_id(id))
39
+ raise ResourceNotFoundError, "No such entity #{id}" unless result
40
+
41
+ entity_type.mapper(context, selected: selected).obj_to_hash(result, nil)
42
+ rescue StandardError => e
43
+ extend_error(e, set_builder, :individual)
44
+ end
45
+
46
+ def create(context:)
47
+ wrapper = CreateComplexTypeHashWrapper.new(context.query_options, entity_type,
48
+ operation: :create, context: context)
49
+ result = new_entity_set(context: context).create(wrapper)
50
+ mapper = entity_type.mapper(context, selected: nil)
51
+ mapper.obj_to_hash(result, nil)
52
+ end
53
+
54
+ def supports_search?
55
+ entity_set.supports_search?
56
+ end
57
+
58
+ def supports_collection?
59
+ entity_set.supports_collection?
60
+ end
61
+
62
+ def supports_individual?
63
+ entity_set.supports_individual?
64
+ end
65
+
66
+ def supports_count?
67
+ entity_set.supports_count?
68
+ end
69
+
70
+ def update(id, context:)
71
+ wrapper = CreateComplexTypeHashWrapper.new(context.query_options, entity_type,
72
+ operation: :update, context: context)
73
+ result = new_entity_set(context: context).update(converted_id(id), wrapper)
74
+ raise ResourceNotFoundError, "No such entity #{id}" unless result
75
+
76
+ entity_type.mapper(context, selected: nil).obj_to_hash(result, nil)
77
+ end
78
+
79
+ def delete(id, context:)
80
+ result = new_entity_set(context: context).delete(converted_id(id))
81
+ raise ResourceNotFoundError, "No such entity #{id}" unless result
82
+ end
83
+
84
+ def supports_create?
85
+ entity_set.supports_create?
86
+ end
87
+
88
+ def supports_update?
89
+ entity_set.supports_update?
90
+ end
91
+
92
+ def supports_delete?
93
+ entity_set.supports_delete?
94
+ end
95
+
96
+ private
97
+
98
+ def extend_error(err, set_builder, method_name)
99
+ klass = set_builder.class
100
+ err.backtrace.unshift(entity_set._defined_at_)
101
+ if set_builder.respond_to?(:od_after_init)
102
+ err.backtrace.unshift(klass.instance_method(:od_after_init).source_location.join(':'))
103
+ end
104
+ if set_builder.respond_to?(method_name)
105
+ err.backtrace.unshift(klass.instance_method(method_name).source_location.join(':'))
106
+ end
107
+ raise
108
+ end
109
+
110
+ def converted_id(id)
111
+ entity_type.property_refs.first.convert(id, nil)
112
+ rescue OdataDuty::InvalidValue => e
113
+ raise InvalidPropertyReferenceValue, "Invalid individual id : #{e}"
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,67 @@
1
+ require 'delegate'
2
+ require_relative 'container'
3
+
4
+ module OdataDuty
5
+ module SchemaBuilder
6
+ class EntitySet < Container
7
+ attr_reader :entity_type, :url, :resolver, :init_args
8
+
9
+ def initialize(entity_type:, resolver:, name: nil, url: nil, init_args: nil)
10
+ @resolver = resolver.clone
11
+ name = name&.to_s || @resolver.split('::').last.sub(/Resolver\z/, '')
12
+ super(name: name)
13
+ @url = (url&.to_s || @name).clone
14
+ @entity_type = entity_type
15
+ @init_args = init_args
16
+ end
17
+
18
+ def entity_type_name = entity_type.name
19
+
20
+ def resolver_class = Module.const_get(resolver)
21
+
22
+ def supports_search?
23
+ # Check if the resolver class supports search by looking for the od_search method
24
+ resolver_class.method_defined?(:od_search)
25
+ end
26
+
27
+ def supports_filter_or?
28
+ resolver_class.method_defined?(:od_filter_or)
29
+ end
30
+
31
+ def supports_collection?
32
+ # Check if the resolver class supports read by looking for the collection method
33
+ resolver_class.method_defined?(:collection)
34
+ end
35
+
36
+ def supports_individual?
37
+ # Check if the resolver class supports read-by-id via the individual method
38
+ resolver_class.method_defined?(:individual)
39
+ end
40
+
41
+ def supports_count?
42
+ # Check if the resolver class supports counting via the count method
43
+ resolver_class.method_defined?(:count)
44
+ end
45
+
46
+ def supports_create?
47
+ # Check if the resolver class supports create by looking for the create method
48
+ resolver_class.method_defined?(:create)
49
+ end
50
+
51
+ def supports_update?
52
+ # Check if the resolver class supports update by looking for the update method
53
+ resolver_class.method_defined?(:update)
54
+ end
55
+
56
+ def supports_delete?
57
+ # Check if the resolver class supports delete by looking for the delete method
58
+ resolver_class.method_defined?(:delete)
59
+ end
60
+
61
+ def non_insertable_property_names
62
+ @non_insertable_property_names ||=
63
+ entity_type.properties.select(&:non_insertable?).map(&:name)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,49 @@
1
+ require_relative 'complex_type'
2
+
3
+ module OdataDuty
4
+ module SchemaBuilder
5
+ class EntityType < ComplexType
6
+ attr_reader :property_refs
7
+
8
+ def initialize(**kwargs)
9
+ super
10
+ @property_refs = []
11
+ end
12
+
13
+ def property_ref(*args, **kwargs)
14
+ kwargs[:mutability] = :computed unless kwargs.key?(:mutability) || kwargs.key?(:computed)
15
+ property(*args, nullable: false, **kwargs).tap do |property|
16
+ raise 'Multiple Property Reference not yet supported' if property_refs.size.positive?
17
+
18
+ property_refs << property
19
+ end
20
+ end
21
+
22
+ def mapper(context, selected:)
23
+ context.current['odata_url_base'] ||= context.od_full_url(context.endpoint.url)
24
+ if integer_property_ref?
25
+ int_mapper(context, selected: selected)
26
+ else
27
+ string_mapper(context, selected: selected)
28
+ end
29
+ end
30
+
31
+ def int_mapper(context, selected:)
32
+ MapperBuilder.build(self, selected: selected) do |result, obj|
33
+ result['@odata.id'] = "#{context.current.fetch('odata_url_base')}(#{obj.id})"
34
+ end
35
+ end
36
+
37
+ def string_mapper(context, selected:)
38
+ MapperBuilder.build(self, selected: selected) do |result, obj|
39
+ result['@odata.id'] = "#{context.current.fetch('odata_url_base')}('#{obj.id}')"
40
+ end
41
+ end
42
+
43
+ def integer_property_ref?
44
+ @property_ref_raw_type ||= property_refs.first.raw_type
45
+ @property_ref_raw_type == EdmInt64
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'data_type'
2
+
3
+ module OdataDuty
4
+ module SchemaBuilder
5
+ class EnumType < DataType
6
+ attr_reader :members
7
+
8
+ def initialize(**kwargs)
9
+ super
10
+ @members = []
11
+ end
12
+
13
+ def member(*)
14
+ @members << EnumMember.new(*)
15
+ end
16
+
17
+ def scalar?
18
+ true
19
+ end
20
+
21
+ def to_value(val, _context)
22
+ return val if members.map(&:name).include?(val)
23
+
24
+ raise InvalidValue, "#{val} is not a valid member of #{members}"
25
+ end
26
+
27
+ def to_oas2
28
+ { 'type' => 'string', 'enum' => members.map(&:name) }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,130 @@
1
+ require_relative 'schema_builder/enum_type'
2
+ require_relative 'schema_builder/complex_type'
3
+ require_relative 'schema_builder/entity_type'
4
+ require_relative 'schema_builder/entity_set'
5
+ require_relative 'schema_builder/endpoint'
6
+
7
+ module OdataDuty
8
+ module SchemaBuilder
9
+ def self.build(**kwargs, &block)
10
+ Schema.new(**kwargs).tap { |s| block.call(s) }
11
+ end
12
+
13
+ class Schema
14
+ attr_reader :namespace, :host, :scheme, :base_path, :base_url, :types
15
+ attr_accessor :version, :title
16
+
17
+ def initialize(namespace:, host: 'localhost', scheme: 'https', base_path: '')
18
+ @namespace = namespace.to_str.clone.freeze
19
+ @host = host.to_str.clone.freeze
20
+ @scheme = scheme.to_str.clone.freeze
21
+ @base_path = base_path.to_str.clone.freeze
22
+ @base_url = [scheme, '://', host, base_path].join.freeze
23
+ @types = {}
24
+ @containers = {}
25
+ end
26
+
27
+ def inspect
28
+ "#<#{self.class} @namespace=#{@namespace} @base_path=#{@base_path} \
29
+ containers=#{@containers.keys} types=#{@types.keys}>"
30
+ end
31
+
32
+ def add_complex_type(**kwargs, &block)
33
+ ComplexType.new(**kwargs).tap do |complex_type|
34
+ add_type complex_type
35
+ block.call(complex_type)
36
+ end
37
+ end
38
+
39
+ def add_enum_type(**kwargs, &block)
40
+ EnumType.new(**kwargs).tap do |enum_type|
41
+ add_type enum_type
42
+ block.call(enum_type)
43
+ end
44
+ end
45
+
46
+ def add_entity_type(**kwargs, &block)
47
+ EntityType.new(**kwargs).tap do |entity_type|
48
+ add_type entity_type
49
+ block.call(entity_type)
50
+ end
51
+ end
52
+
53
+ def add_entity_set(**kwargs)
54
+ EntitySet.new(**kwargs).tap { |entity_set| add_container(entity_set) }
55
+ end
56
+
57
+ def endpoints
58
+ entity_sets.map { |entity_set| Endpoint.new(entity_set) }
59
+ end
60
+
61
+ def enum_types
62
+ all_types.grep(EnumType)
63
+ end
64
+
65
+ def complex_types
66
+ all_types.select { |t| t.instance_of?(ComplexType) }
67
+ end
68
+
69
+ def entity_types
70
+ all_types.grep(EntityType)
71
+ end
72
+
73
+ def entity_sets = all_containers
74
+
75
+ def collection_entity_sets
76
+ entity_sets.select { |t| t.resolver_class.method_defined?(:collection) }
77
+ end
78
+
79
+ def individual_entity_sets
80
+ entity_sets.select { |t| t.resolver_class.method_defined?(:individual) }
81
+ end
82
+
83
+ def execute(url, context:, query_options: {})
84
+ Executor.execute(url: url, context: context, query_options: query_options, schema: self)
85
+ end
86
+
87
+ def create(url, context:, query_options: nil)
88
+ Executor.create(url: url, context: context, query_options: query_options, schema: self)
89
+ end
90
+
91
+ def update(url, context:, query_options: nil)
92
+ Executor.update(url: url, context: context, query_options: query_options, schema: self)
93
+ end
94
+
95
+ def delete(url, context:, query_options: nil)
96
+ Executor.delete(url: url, context: context, query_options: query_options, schema: self)
97
+ end
98
+
99
+ def to_mcp_server
100
+ McpServerBuilder.build(self)
101
+ end
102
+
103
+ def metadata_xml
104
+ EdmxSchema.metadata_xml(self)
105
+ end
106
+
107
+ def index_hash
108
+ EdmxSchema.index_hash(self)
109
+ end
110
+
111
+ private
112
+
113
+ def add_type(type)
114
+ raise "Duplicate #{type.name} type" if @types.key?(type.name)
115
+
116
+ @types[type.name] = type
117
+ end
118
+
119
+ def all_types = @types.values.sort_by(&:name)
120
+
121
+ def add_container(type)
122
+ raise "Duplicate #{type.name} Container" if @containers.key?(type.name)
123
+
124
+ @containers[type.name] = type
125
+ end
126
+
127
+ def all_containers = @containers.values.sort_by(&:name)
128
+ end
129
+ end
130
+ end