microsoft_kiota_abstractions 0.24.0 → 0.25.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 (22) hide show
  1. checksums.yaml +4 -4
  2. data/lib/microsoft_kiota_abstractions/api_client_builder.rb +37 -0
  3. data/lib/microsoft_kiota_abstractions/authentication/allowed_hosts_validator.rb +8 -4
  4. data/lib/microsoft_kiota_abstractions/multipart_body.rb +115 -0
  5. data/lib/microsoft_kiota_abstractions/request_adapter.rb +5 -0
  6. data/lib/microsoft_kiota_abstractions/request_information.rb +24 -1
  7. data/lib/microsoft_kiota_abstractions/serialization/iso_duration.rb +2 -2
  8. data/lib/microsoft_kiota_abstractions/serialization/parse_node.rb +7 -0
  9. data/lib/microsoft_kiota_abstractions/serialization/parse_node_proxy_factory.rb +39 -0
  10. data/lib/microsoft_kiota_abstractions/serialization/serialization_writer.rb +11 -0
  11. data/lib/microsoft_kiota_abstractions/serialization/serialization_writer_proxy_factory.rb +50 -0
  12. data/lib/microsoft_kiota_abstractions/store/backed_model.rb +8 -0
  13. data/lib/microsoft_kiota_abstractions/store/backing_store.rb +52 -0
  14. data/lib/microsoft_kiota_abstractions/store/backing_store_factory.rb +10 -0
  15. data/lib/microsoft_kiota_abstractions/store/backing_store_factory_singleton.rb +16 -0
  16. data/lib/microsoft_kiota_abstractions/store/backing_store_parse_node_factory.rb +15 -0
  17. data/lib/microsoft_kiota_abstractions/store/backing_store_serialization_writer_proxy_factory.rb +99 -0
  18. data/lib/microsoft_kiota_abstractions/store/in_memory_backing_store.rb +167 -0
  19. data/lib/microsoft_kiota_abstractions/store/in_memory_backing_store_factory.rb +15 -0
  20. data/lib/microsoft_kiota_abstractions/version.rb +1 -1
  21. data/lib/microsoft_kiota_abstractions.rb +11 -0
  22. metadata +12 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec82d13c67d7a99abb8072fc1bfb3e1b7926ae95462caca4f9260993e09583bb
4
- data.tar.gz: 910a8c047e114d477144c8769f27efe623a56486800422811cb772b612680ffd
3
+ metadata.gz: 513206169119988b7f24a1bf7e7dea92ecfb179111e71145b8e3dc4bcd38da81
4
+ data.tar.gz: a4fa14ef64cb4c64feb606852dd542d37eb7fb4650579de10f13263379b1763e
5
5
  SHA512:
6
- metadata.gz: ca47b5b57ae1e5df01a5921f36d85f231ca34d23365f91e0c7c785617e8b82017559e5e2f3bdae5558f19a8c428fe91b9b2e7c71c3e9c9b2a767d50f43d1516b
7
- data.tar.gz: 2d3485190a43513db40db1e6f0022982076552d5eb2162a7580c9f6551314feecc532ab20a1cd0aeef35c918d7f539df3b64e7517518188508d4343c38630904
6
+ metadata.gz: a76b0d9d11fa0d77d740de87eb29574e8ba070c06770472a590e2fa4d25f5588f0b6d502e86f06460117c4b373d9acd658581798a0a02bacad12161a4aa63b0d
7
+ data.tar.gz: daced43e53c41e8194540a9704da0d6be3f66c61429c2d7a8555c1c06ea8d6fe255e581476cc6732a2e16fb78a494edf04de17b42f5ce545dbd7f67474b2f0ae
@@ -4,6 +4,8 @@ require_relative 'serialization/parse_node_factory'
4
4
  require_relative 'serialization/parse_node_factory_registry'
5
5
  require_relative 'serialization/serialization_writer_factory'
6
6
  require_relative 'serialization/serialization_writer_factory_registry'
7
+ require_relative 'store/backing_store_parse_node_factory'
8
+ require_relative 'store/backing_store_serialization_writer_proxy_factory'
7
9
 
8
10
  module MicrosoftKiotaAbstractions
9
11
  class ApiClientBuilder
@@ -13,6 +15,41 @@ module MicrosoftKiotaAbstractions
13
15
  factory
14
16
  end
15
17
 
18
+ # wraps the factory, and every factory registered by default, so models write only their changed values
19
+ def self.enable_backing_store_for_serialization_writer_factory(original)
20
+ result = original
21
+ if original.is_a?(SerializationWriterFactoryRegistry)
22
+ enable_backing_store_for_registry(original, BackingStoreSerializationWriterProxyFactory)
23
+ elsif !original.is_a?(BackingStoreSerializationWriterProxyFactory)
24
+ result = BackingStoreSerializationWriterProxyFactory.new(original)
25
+ end
26
+ enable_backing_store_for_registry(SerializationWriterFactoryRegistry.default_instance,
27
+ BackingStoreSerializationWriterProxyFactory)
28
+ result
29
+ end
30
+
31
+ # wraps the factory, and every factory registered by default, so models start tracking changes once read
32
+ def self.enable_backing_store_for_parse_node_factory(original)
33
+ result = original
34
+ if original.is_a?(ParseNodeFactoryRegistry)
35
+ enable_backing_store_for_registry(original, BackingStoreParseNodeFactory)
36
+ elsif !original.is_a?(BackingStoreParseNodeFactory)
37
+ result = BackingStoreParseNodeFactory.new(original)
38
+ end
39
+ enable_backing_store_for_registry(ParseNodeFactoryRegistry.default_instance, BackingStoreParseNodeFactory)
40
+ result
41
+ end
42
+
43
+ def self.enable_backing_store_for_registry(registry, proxy_class)
44
+ factories = registry.content_type_associated_factories
45
+ factories.each do |content_type, factory|
46
+ next if factory.is_a?(proxy_class) || factory.is_a?(registry.class)
47
+
48
+ factories[content_type] = proxy_class.new(factory)
49
+ end
50
+ end
51
+ private_class_method :enable_backing_store_for_registry
52
+
16
53
  def self.register_default_deserializer(factory_class)
17
54
  factory = factory_class.new
18
55
  MicrosoftKiotaAbstractions::ParseNodeFactoryRegistry.default_instance.content_type_associated_factories[factory.get_valid_content_type] =
@@ -8,14 +8,18 @@ module MicrosoftKiotaAbstractions
8
8
  class AllowedHostsValidator
9
9
  # creates a new AllocatedHostsValidator with provided values
10
10
  def initialize(allowed_hosts)
11
- @allowed_hosts = {}
12
- allowed_hosts.each { |host| @allowed_hosts[host.downcase] = true }
11
+ self.allowed_hosts = allowed_hosts
13
12
  end
14
13
 
15
14
  # sets the list of valid hosts with provided value (val)
16
15
  def allowed_hosts=(val)
17
- @allowed_hosts = {}
18
- val.each { |host| @allowed_hosts[host.downcase] = true }
16
+ hosts = {}
17
+ val.each do |host|
18
+ raise ArgumentError, "host should not contain http or https prefix: #{host}" if host.match?(%r{\Ahttps?://}i)
19
+
20
+ hosts[host.downcase] = true
21
+ end
22
+ @allowed_hosts = hosts
19
23
  end
20
24
 
21
25
  # checks whether the provided host is valid
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require_relative 'serialization/parsable'
5
+
6
+ module MicrosoftKiotaAbstractions
7
+ class MultipartBody
8
+ include Parsable
9
+
10
+ Part = Struct.new(:name, :content_type, :value, :filename)
11
+ private_constant :Part
12
+
13
+ attr_reader :boundary
14
+ attr_accessor :request_adapter
15
+
16
+ def initialize
17
+ @boundary = SecureRandom.uuid
18
+ @parts = {}
19
+ end
20
+
21
+ def add_or_replace_part(part_name, content_type, part_value, filename = nil)
22
+ raise ArgumentError, 'Part name cannot be null' if blank?(part_name)
23
+ raise ArgumentError, 'Content type cannot be null' if blank?(content_type)
24
+ raise ArgumentError, 'Content type cannot contain line breaks' if content_type.to_s.match?(/[\r\n]/)
25
+ raise ArgumentError, 'Part value cannot be null' if part_value.nil?
26
+
27
+ @parts[normalize(part_name)] = Part.new(part_name, content_type, part_value, filename)
28
+ end
29
+
30
+ def get_part_value(part_name)
31
+ raise ArgumentError, 'Part name cannot be null' if blank?(part_name)
32
+
33
+ @parts[normalize(part_name)]&.value
34
+ end
35
+
36
+ def remove_part(part_name) # rubocop:disable Naming/PredicateMethod
37
+ raise ArgumentError, 'Part name cannot be null' if blank?(part_name)
38
+
39
+ !@parts.delete(normalize(part_name)).nil?
40
+ end
41
+
42
+ def serialize(writer)
43
+ raise ArgumentError, 'Serialization writer cannot be null' if writer.nil?
44
+ raise ArgumentError, 'Request adapter or serialization writer factory cannot be null' if request_adapter&.get_serialization_writer_factory.nil?
45
+ raise ArgumentError, 'No parts to serialize' if @parts.empty?
46
+
47
+ @parts.each_value.with_index do |part, index|
48
+ add_new_line(writer) unless index.zero?
49
+ writer.write_string_value('', "--#{boundary}")
50
+ writer.write_string_value('Content-Type', part.content_type)
51
+ writer.write_string_value('Content-Disposition', content_disposition(part))
52
+ add_new_line(writer)
53
+ write_part_value(writer, part)
54
+ end
55
+ add_new_line(writer)
56
+ writer.write_string_value('', "--#{boundary}--")
57
+ end
58
+
59
+ private
60
+
61
+ def blank?(value)
62
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
63
+ end
64
+
65
+ def normalize(part_name)
66
+ part_name.downcase
67
+ end
68
+
69
+ def add_new_line(writer)
70
+ writer.write_string_value('', '')
71
+ end
72
+
73
+ def content_disposition(part)
74
+ disposition = %(form-data; name="#{escape(part.name)}")
75
+ disposition += %(; filename="#{escape(part.filename)}") unless part.filename.nil?
76
+ disposition
77
+ end
78
+
79
+ # as browsers escape names and file names in multipart/form-data
80
+ def escape(value)
81
+ value.to_s.gsub(/["\r\n]/, '"' => '%22', "\r" => '%0D', "\n" => '%0A')
82
+ end
83
+
84
+ def write_part_value(writer, part)
85
+ value = part.value
86
+ if value.is_a?(Parsable)
87
+ writer.write_byte_array_value('', serialize_parsable(part))
88
+ elsif value.is_a?(String)
89
+ writer.write_byte_array_value('', value)
90
+ elsif value.respond_to?(:read)
91
+ rewind(value)
92
+ writer.write_byte_array_value('', value.read)
93
+ else
94
+ raise ArgumentError, "Unsupported type #{value.class} for part #{part.name}"
95
+ end
96
+ end
97
+
98
+ # read from the start each time the body is written, as .NET does for a seekable stream
99
+ def rewind(stream)
100
+ stream.rewind if stream.respond_to?(:rewind)
101
+ rescue SystemCallError
102
+ nil
103
+ end
104
+
105
+ def serialize_parsable(part)
106
+ part_writer = request_adapter.get_serialization_writer_factory.get_serialization_writer(part.content_type)
107
+ part_writer.write_object_value(nil, part.value)
108
+ content = part_writer.get_serialized_content
109
+ return content unless content.respond_to?(:read)
110
+
111
+ content.rewind if content.respond_to?(:rewind)
112
+ content.read
113
+ end
114
+ end
115
+ end
@@ -28,6 +28,11 @@ module MicrosoftKiotaAbstractions
28
28
  raise NotImplementedError
29
29
  end
30
30
 
31
+ # enables the backing store on the models this adapter reads and writes
32
+ def enable_backing_store(_backing_store_factory = nil)
33
+ raise NotImplementedError
34
+ end
35
+
31
36
  def set_base_url(_base_url)
32
37
  raise NotImplementedError
33
38
  end
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'date'
4
+ require 'time'
3
5
  require 'uri'
4
6
  require 'stduritemplate'
5
7
  require_relative 'http_method'
8
+ require_relative 'multipart_body'
6
9
  require_relative 'request_headers'
7
10
 
8
11
  module MicrosoftKiotaAbstractions
@@ -32,7 +35,8 @@ module MicrosoftKiotaAbstractions
32
35
  return @uri unless @uri.nil?
33
36
 
34
37
  if path_parameters[@@raw_url_key].nil?
35
- return URI(StdUriTemplate.expand(@url_template, path_parameters.merge(query_parameters)))
38
+ values = path_parameters.merge(query_parameters).transform_values { |v| template_value(v) }
39
+ return URI(StdUriTemplate.expand(@url_template, values))
36
40
  end
37
41
 
38
42
  self.uri = path_parameters[@@raw_url_key]
@@ -111,6 +115,11 @@ module MicrosoftKiotaAbstractions
111
115
 
112
116
  def set_content_from_parsable(request_adapter, content_type, values)
113
117
  writer = request_adapter.get_serialization_writer_factory.get_serialization_writer(content_type)
118
+ if values.is_a?(MultipartBody)
119
+ content_type = "#{content_type.gsub(/;\s*boundary=[^;]*/i, '')}; boundary=#{values.boundary}"
120
+ values.request_adapter = request_adapter
121
+ @headers.remove(@@content_type_header)
122
+ end
114
123
  @headers.try_add(@@content_type_header, content_type)
115
124
  if !values.nil? && values.is_a?(Array)
116
125
  writer.write_collection_of_object_values(nil, values)
@@ -144,5 +153,19 @@ module MicrosoftKiotaAbstractions
144
153
  query_parameters[key] = value unless value.nil?
145
154
  end
146
155
  end
156
+
157
+ private
158
+
159
+ # the template engine only takes strings, numbers, booleans and lists of them
160
+ def template_value(value)
161
+ case value
162
+ when nil, String, Integer, Float, true, false then value
163
+ when Array then value.map { |v| template_value(v) }
164
+ when Hash then value.transform_values { |v| template_value(v) }
165
+ when Date, Time then value.iso8601
166
+ when MicrosoftKiotaAbstractions::ISODuration then value.string
167
+ else value.to_s
168
+ end
169
+ end
147
170
  end
148
171
  end
@@ -49,7 +49,7 @@ module MicrosoftKiotaAbstractions
49
49
  end
50
50
  iso_str = iso_str.strip
51
51
  iso_str = iso_str.chomp('T') if iso_str[-1].eql? 'T'
52
- iso_str
52
+ iso_str == 'P' ? 'PT0S' : iso_str
53
53
  end
54
54
 
55
55
  def normalize
@@ -182,7 +182,7 @@ module MicrosoftKiotaAbstractions
182
182
  end
183
183
  iso_str = iso_str.strip
184
184
  iso_str = iso_str.chomp('T') if iso_str[-1].eql? 'T'
185
- ISO8601::Duration.new(iso_str)
185
+ ISO8601::Duration.new(iso_str == 'P' ? 'PT0S' : iso_str)
186
186
  end
187
187
 
188
188
  def update_member_variables
@@ -2,6 +2,9 @@
2
2
 
3
3
  module MicrosoftKiotaAbstractions
4
4
  module ParseNode
5
+ # called with the model before and after its fields are filled, see ParseNodeProxyFactory
6
+ attr_accessor :on_before_assign_field_values, :on_after_assign_field_values
7
+
5
8
  def get_string_value
6
9
  raise NotImplementedError
7
10
  end
@@ -34,6 +37,10 @@ module MicrosoftKiotaAbstractions
34
37
  raise NotImplementedError
35
38
  end
36
39
 
40
+ def get_byte_array_value
41
+ raise NotImplementedError
42
+ end
43
+
37
44
  def get_collection_of_primitive_values
38
45
  raise NotImplementedError
39
46
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'parse_node_factory'
4
+
5
+ module MicrosoftKiotaAbstractions
6
+ # Wraps a parse node factory to run callbacks before and after every model is filled
7
+ class ParseNodeProxyFactory
8
+ include ParseNodeFactory
9
+
10
+ def initialize(concrete, on_before, on_after)
11
+ raise ArgumentError, 'concrete factory cannot be nil' if concrete.nil?
12
+
13
+ @concrete = concrete
14
+ @on_before = on_before
15
+ @on_after = on_after
16
+ end
17
+
18
+ def get_valid_content_type
19
+ @concrete.get_valid_content_type
20
+ end
21
+
22
+ def get_parse_node(content_type, content)
23
+ node = @concrete.get_parse_node(content_type, content)
24
+ original_before = node.on_before_assign_field_values
25
+ original_after = node.on_after_assign_field_values
26
+ node.on_before_assign_field_values = lambda do |value|
27
+ @on_before&.call(value)
28
+ original_before&.call(value)
29
+ end
30
+ # after callbacks unwind the before ones, and run even when an inner one raised
31
+ node.on_after_assign_field_values = lambda do |value|
32
+ original_after&.call(value)
33
+ ensure
34
+ @on_after&.call(value)
35
+ end
36
+ node
37
+ end
38
+ end
39
+ end
@@ -2,6 +2,13 @@
2
2
 
3
3
  module MicrosoftKiotaAbstractions
4
4
  module SerializationWriter
5
+ # called with the model before and after it is written, and with the model and its writer when it starts
6
+ attr_accessor :on_before_object_serialization, :on_after_object_serialization, :on_start_object_serialization
7
+
8
+ def write_null_value(_key)
9
+ raise NotImplementedError
10
+ end
11
+
5
12
  def write_string_value(_key, _value)
6
13
  raise NotImplementedError
7
14
  end
@@ -42,6 +49,10 @@ module MicrosoftKiotaAbstractions
42
49
  raise NotImplementedError
43
50
  end
44
51
 
52
+ def write_byte_array_value(_key, _value)
53
+ raise NotImplementedError
54
+ end
55
+
45
56
  def write_collection_of_primitive_values(_key, _value)
46
57
  raise NotImplementedError
47
58
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'serialization_writer_factory'
4
+
5
+ module MicrosoftKiotaAbstractions
6
+ # Wraps a serialization writer factory to run callbacks around every model it writes
7
+ class SerializationWriterProxyFactory
8
+ include SerializationWriterFactory
9
+
10
+ def initialize(concrete, on_before, on_after, on_start)
11
+ raise ArgumentError, 'concrete factory cannot be nil' if concrete.nil?
12
+
13
+ @concrete = concrete
14
+ @on_before = on_before
15
+ @on_after = on_after
16
+ @on_start = on_start
17
+ end
18
+
19
+ def get_valid_content_type
20
+ @concrete.get_valid_content_type
21
+ end
22
+
23
+ def get_serialization_writer(content_type)
24
+ wrap(@concrete.get_serialization_writer(content_type), @on_before, @on_after, @on_start)
25
+ end
26
+
27
+ protected
28
+
29
+ def wrap(writer, on_before, on_after, on_start)
30
+ original_before = writer.on_before_object_serialization
31
+ original_after = writer.on_after_object_serialization
32
+ original_start = writer.on_start_object_serialization
33
+ writer.on_before_object_serialization = lambda do |value|
34
+ on_before&.call(value)
35
+ original_before&.call(value)
36
+ end
37
+ # after callbacks unwind the before ones, and run even when an inner one raised
38
+ writer.on_after_object_serialization = lambda do |value|
39
+ original_after&.call(value)
40
+ ensure
41
+ on_after&.call(value)
42
+ end
43
+ writer.on_start_object_serialization = lambda do |value, object_writer|
44
+ on_start&.call(value, object_writer)
45
+ original_start&.call(value, object_writer)
46
+ end
47
+ writer
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MicrosoftKiotaAbstractions
4
+ # A model that keeps its values in a backing store
5
+ module BackedModel
6
+ attr_reader :backing_store
7
+ end
8
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MicrosoftKiotaAbstractions
4
+ # Stores the values of a model and tracks which ones changed since it was read
5
+ module BackingStore
6
+ def get(_key)
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def set(_key, _value)
11
+ raise NotImplementedError
12
+ end
13
+
14
+ # the stored key and value pairs, or only the changed ones when return_only_changed_values is set
15
+ def enumerate
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def enumerate_keys_for_values_changed_to_nil
20
+ raise NotImplementedError
21
+ end
22
+
23
+ # registers a callback called with (key, old_value, new_value) on every set, returns its id
24
+ def subscribe(_callback, _subscription_id = nil)
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def unsubscribe(_subscription_id)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ def clear
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def initialization_completed
37
+ raise NotImplementedError
38
+ end
39
+
40
+ def initialization_completed=(_value)
41
+ raise NotImplementedError
42
+ end
43
+
44
+ def return_only_changed_values
45
+ raise NotImplementedError
46
+ end
47
+
48
+ def return_only_changed_values=(_value)
49
+ raise NotImplementedError
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MicrosoftKiotaAbstractions
4
+ # Creates the backing store of a new model
5
+ module BackingStoreFactory
6
+ def create_backing_store
7
+ raise NotImplementedError
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'in_memory_backing_store_factory'
4
+
5
+ module MicrosoftKiotaAbstractions
6
+ # Holds the backing store factory generated models use
7
+ module BackingStoreFactorySingleton
8
+ class << self
9
+ attr_writer :instance
10
+
11
+ def instance
12
+ @instance ||= InMemoryBackingStoreFactory.new
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../serialization/parse_node_proxy_factory'
4
+ require_relative 'backed_model'
5
+
6
+ module MicrosoftKiotaAbstractions
7
+ # Marks a model's values as not changed once it has been read
8
+ class BackingStoreParseNodeFactory < ParseNodeProxyFactory
9
+ def initialize(concrete)
10
+ super(concrete,
11
+ ->(value) { value.backing_store.initialization_completed = false if value.is_a?(BackedModel) && value.backing_store },
12
+ ->(value) { value.backing_store.initialization_completed = true if value.is_a?(BackedModel) && value.backing_store })
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require 'delegate'
5
+ require_relative '../serialization/serialization_writer_proxy_factory'
6
+ require_relative 'backed_model'
7
+
8
+ module MicrosoftKiotaAbstractions
9
+ # Writes only the values of a model that changed, including the ones set back to nil
10
+ class BackingStoreSerializationWriterProxyFactory < SerializationWriterProxyFactory
11
+ def initialize(concrete)
12
+ super(concrete, nil, nil, nil)
13
+ end
14
+
15
+ def get_serialization_writer(content_type)
16
+ session = Session.new
17
+ writer = wrap(@concrete.get_serialization_writer(content_type),
18
+ session.method(:before), session.method(:after), session.method(:start))
19
+ DocumentWriter.new(writer, session)
20
+ end
21
+
22
+ # the document is complete once its content is produced: only then do the written models become clean
23
+ class DocumentWriter < SimpleDelegator
24
+ def initialize(writer, session)
25
+ super(writer)
26
+ @session = session
27
+ end
28
+
29
+ def get_serialized_content
30
+ content = __getobj__.get_serialized_content
31
+ @session.finish
32
+ content
33
+ end
34
+ end
35
+
36
+ # one per writer: tracks the models written into the document
37
+ class Session
38
+ def initialize
39
+ @depth = 0
40
+ @filtering = {}.compare_by_identity
41
+ @written = {}.compare_by_identity
42
+ @failed = false
43
+ end
44
+
45
+ def before(value)
46
+ if @depth.zero?
47
+ # an exception already being handled around the write is not a failure of the write
48
+ @outer_error = $ERROR_INFO
49
+ open_documents.push(self)
50
+ end
51
+ @depth += 1
52
+ store = store_of(value)
53
+ return unless store
54
+
55
+ @filtering[store] = store.return_only_changed_values unless @filtering.key?(store)
56
+ store.return_only_changed_values = true
57
+ @written[store] = true
58
+ end
59
+
60
+ def start(value, writer)
61
+ store_of(value)&.enumerate_keys_for_values_changed_to_nil&.each { |key| writer.write_null_value(key) }
62
+ end
63
+
64
+ # runs even when writing the model raised: the store reads every value again, and keeps its changes
65
+ def after(value)
66
+ @depth -= 1
67
+ open_documents.pop if @depth.zero? && open_documents.last.equal?(self)
68
+ @failed ||= !$ERROR_INFO.nil? && !$ERROR_INFO.equal?(@outer_error)
69
+ store = store_of(value)
70
+ store.return_only_changed_values = @filtering.delete(store) if store && @filtering.key?(store)
71
+ end
72
+
73
+ # a document produced while another is written, such as a multipart part, leaves the models to that one
74
+ def finish
75
+ unless @failed
76
+ enclosing = open_documents.last
77
+ enclosing ? enclosing.adopt(@written) : @written.each_key { |store| store.initialization_completed = true }
78
+ end
79
+ @written.clear
80
+ @failed = false
81
+ end
82
+
83
+ def adopt(written)
84
+ @written.merge!(written)
85
+ end
86
+
87
+ private
88
+
89
+ def open_documents
90
+ Thread.current[:microsoft_kiota_open_documents] ||= []
91
+ end
92
+
93
+ def store_of(value)
94
+ value.backing_store if value.is_a?(BackedModel)
95
+ end
96
+ end
97
+ private_constant :DocumentWriter, :Session
98
+ end
99
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require_relative 'backing_store'
5
+ require_relative 'backed_model'
6
+
7
+ module MicrosoftKiotaAbstractions
8
+ # A backing store that keeps the values in memory
9
+ class InMemoryBackingStore
10
+ include BackingStore
11
+
12
+ # changed: set after initialization completed; snapshot: a copy of a mutable value when it was set
13
+ Entry = Struct.new(:changed, :value, :snapshot)
14
+
15
+ attr_reader :initialization_completed
16
+ attr_accessor :return_only_changed_values
17
+
18
+ def initialize
19
+ @store = {}
20
+ @subscriptions = {}
21
+ @member_subscriptions = {}
22
+ @walking = {}
23
+ @initialization_completed = true
24
+ @return_only_changed_values = false
25
+ end
26
+
27
+ def initialization_completed=(value)
28
+ @initialization_completed = value
29
+ walk_once(:initialization) do
30
+ @store.each_key do |key|
31
+ backed_models(@store[key].value).each { |model| model.backing_store.initialization_completed = value }
32
+ ensure_value_is_consistent(key)
33
+ @store[key].changed = !value
34
+ end
35
+ end
36
+ end
37
+
38
+ def get(key)
39
+ validate(key)
40
+ return unless @store.key?(key)
41
+
42
+ ensure_value_is_consistent(key)
43
+ entry = @store[key]
44
+ entry.value if !@return_only_changed_values || entry.changed
45
+ end
46
+
47
+ def set(key, value)
48
+ validate(key)
49
+ old_value = @store[key]&.value
50
+ mark_assigned(value) if @initialization_completed
51
+ @store[key] = Entry.new(@initialization_completed, value, snapshot_of(value))
52
+ follow_members(key, value)
53
+ notify(key, old_value, value)
54
+ end
55
+
56
+ def enumerate
57
+ @store.each_key { |key| ensure_value_is_consistent(key) } if @return_only_changed_values
58
+ @store.select { |_, entry| !@return_only_changed_values || entry.changed }.map { |key, entry| [key, entry.value] }
59
+ end
60
+
61
+ def enumerate_keys_for_values_changed_to_nil
62
+ @store.select { |_, entry| entry.changed && entry.value.nil? }.keys
63
+ end
64
+
65
+ def subscribe(callback, subscription_id = nil)
66
+ raise ArgumentError, 'callback cannot be nil' if callback.nil?
67
+
68
+ subscription_id ||= SecureRandom.uuid
69
+ @subscriptions[subscription_id] = callback
70
+ subscription_id
71
+ end
72
+
73
+ def unsubscribe(subscription_id)
74
+ @subscriptions.delete(subscription_id)
75
+ end
76
+
77
+ def clear
78
+ @member_subscriptions.each_key.to_a.each { |key| follow_members(key, nil) }
79
+ @store.clear
80
+ end
81
+
82
+ protected
83
+
84
+ def mark_all_changed
85
+ walk_once(:marking) do
86
+ @store.each_value do |entry|
87
+ entry.changed = true
88
+ mark_assigned(entry.value)
89
+ end
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ # a model assigned after initialization is written in full, as .NET does for the items of a collection
96
+ def mark_assigned(value)
97
+ backed_models(value).each { |model| model.backing_store.mark_all_changed if model.backing_store.is_a?(InMemoryBackingStore) }
98
+ end
99
+
100
+ def validate(key)
101
+ raise ArgumentError, 'key cannot be nil or empty' if key.nil? || key.empty?
102
+ end
103
+
104
+ def backed_models(value)
105
+ (value.is_a?(Array) ? value : [value]).select { |model| model.is_a?(BackedModel) && model.backing_store }
106
+ end
107
+
108
+ def snapshot_of(value)
109
+ case value
110
+ when Array then value.map { |item| snapshot_item(item) }
111
+ when Hash then value.transform_values { |item| snapshot_item(item) }
112
+ when String then value.dup unless value.frozen?
113
+ end
114
+ end
115
+
116
+ # copies what could be edited in place; a nested model is compared by identity and tracked by its own store
117
+ def snapshot_item(item)
118
+ case item
119
+ when Array, Hash then snapshot_of(item)
120
+ when String then item.frozen? ? item : item.dup
121
+ else item
122
+ end
123
+ end
124
+
125
+ # the nested models a property holds report their changes to it, for as long as they are in it
126
+ def follow_members(key, value)
127
+ followed = @member_subscriptions[key] ||= {}.compare_by_identity
128
+ members = backed_models(value).to_h { |model| [model, true] }.compare_by_identity
129
+ followed.keys.reject { |model| members.key?(model) }.each { |model| model.backing_store.unsubscribe(followed.delete(model)) }
130
+ members.each_key { |model| followed[model] ||= model.backing_store.subscribe(->(*) { member_changed(key) }) }
131
+ @member_subscriptions.delete(key) if followed.empty?
132
+ end
133
+
134
+ def member_changed(key)
135
+ entry = @store[key]
136
+ return if entry.nil?
137
+
138
+ entry.changed = true if @initialization_completed
139
+ notify(key, entry.value, entry.value)
140
+ end
141
+
142
+ def notify(key, old_value, new_value)
143
+ walk_once(:notification) { @subscriptions.each_value { |callback| callback.call(key, old_value, new_value) } }
144
+ end
145
+
146
+ # models can reference each other or themselves: a store reached again while it is being walked is skipped
147
+ def walk_once(walk)
148
+ return if @walking[walk]
149
+
150
+ @walking[walk] = true
151
+ begin
152
+ yield
153
+ ensure
154
+ @walking.delete(walk)
155
+ end
156
+ end
157
+
158
+ # a value changed in place since it was set, or holding a changed model, is marked as changed
159
+ def ensure_value_is_consistent(key)
160
+ entry = @store[key]
161
+ walk_once(:consistency) do
162
+ backed_models(entry.value).each { |model| model.backing_store.enumerate.map(&:first).each { |k| model.backing_store.get(k) } }
163
+ end
164
+ set(key, entry.value) unless entry.snapshot.nil? || entry.value == entry.snapshot
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'backing_store_factory'
4
+ require_relative 'in_memory_backing_store'
5
+
6
+ module MicrosoftKiotaAbstractions
7
+ # Creates in memory backing stores
8
+ class InMemoryBackingStoreFactory
9
+ include BackingStoreFactory
10
+
11
+ def create_backing_store
12
+ InMemoryBackingStore.new
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MicrosoftKiotaAbstractions
4
- VERSION = '0.24.0' # x-release-please-version
4
+ VERSION = '0.25.0' # x-release-please-version
5
5
  end
@@ -12,6 +12,7 @@ require_relative 'microsoft_kiota_abstractions/request_configuration'
12
12
  require_relative 'microsoft_kiota_abstractions/request_option'
13
13
  require_relative 'microsoft_kiota_abstractions/request_headers'
14
14
  require_relative 'microsoft_kiota_abstractions/request_information'
15
+ require_relative 'microsoft_kiota_abstractions/multipart_body'
15
16
  require_relative 'microsoft_kiota_abstractions/version'
16
17
  require_relative 'microsoft_kiota_abstractions/serialization/parsable'
17
18
  require_relative 'microsoft_kiota_abstractions/serialization/iso_duration'
@@ -24,6 +25,16 @@ require_relative 'microsoft_kiota_abstractions/serialization/serialization_write
24
25
  require_relative 'microsoft_kiota_abstractions/serialization/serialization_writer_factory_registry'
25
26
  require_relative 'microsoft_kiota_abstractions/serialization/composed_type_wrapper'
26
27
  require_relative 'microsoft_kiota_abstractions/serialization/parse_node_helper'
28
+ require_relative 'microsoft_kiota_abstractions/serialization/parse_node_proxy_factory'
29
+ require_relative 'microsoft_kiota_abstractions/serialization/serialization_writer_proxy_factory'
30
+ require_relative 'microsoft_kiota_abstractions/store/backing_store'
31
+ require_relative 'microsoft_kiota_abstractions/store/backed_model'
32
+ require_relative 'microsoft_kiota_abstractions/store/backing_store_factory'
33
+ require_relative 'microsoft_kiota_abstractions/store/in_memory_backing_store'
34
+ require_relative 'microsoft_kiota_abstractions/store/in_memory_backing_store_factory'
35
+ require_relative 'microsoft_kiota_abstractions/store/backing_store_factory_singleton'
36
+ require_relative 'microsoft_kiota_abstractions/store/backing_store_parse_node_factory'
37
+ require_relative 'microsoft_kiota_abstractions/store/backing_store_serialization_writer_proxy_factory'
27
38
 
28
39
  module MicrosoftKiotaAbstractions
29
40
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: microsoft_kiota_abstractions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.0
4
+ version: 0.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Microsoft Corporation
@@ -104,6 +104,7 @@ files:
104
104
  - lib/microsoft_kiota_abstractions/authentication/base_bearer_token_authentication_provider.rb
105
105
  - lib/microsoft_kiota_abstractions/base_request_builder.rb
106
106
  - lib/microsoft_kiota_abstractions/http_method.rb
107
+ - lib/microsoft_kiota_abstractions/multipart_body.rb
107
108
  - lib/microsoft_kiota_abstractions/request_adapter.rb
108
109
  - lib/microsoft_kiota_abstractions/request_configuration.rb
109
110
  - lib/microsoft_kiota_abstractions/request_headers.rb
@@ -117,9 +118,19 @@ files:
117
118
  - lib/microsoft_kiota_abstractions/serialization/parse_node_factory.rb
118
119
  - lib/microsoft_kiota_abstractions/serialization/parse_node_factory_registry.rb
119
120
  - lib/microsoft_kiota_abstractions/serialization/parse_node_helper.rb
121
+ - lib/microsoft_kiota_abstractions/serialization/parse_node_proxy_factory.rb
120
122
  - lib/microsoft_kiota_abstractions/serialization/serialization_writer.rb
121
123
  - lib/microsoft_kiota_abstractions/serialization/serialization_writer_factory.rb
122
124
  - lib/microsoft_kiota_abstractions/serialization/serialization_writer_factory_registry.rb
125
+ - lib/microsoft_kiota_abstractions/serialization/serialization_writer_proxy_factory.rb
126
+ - lib/microsoft_kiota_abstractions/store/backed_model.rb
127
+ - lib/microsoft_kiota_abstractions/store/backing_store.rb
128
+ - lib/microsoft_kiota_abstractions/store/backing_store_factory.rb
129
+ - lib/microsoft_kiota_abstractions/store/backing_store_factory_singleton.rb
130
+ - lib/microsoft_kiota_abstractions/store/backing_store_parse_node_factory.rb
131
+ - lib/microsoft_kiota_abstractions/store/backing_store_serialization_writer_proxy_factory.rb
132
+ - lib/microsoft_kiota_abstractions/store/in_memory_backing_store.rb
133
+ - lib/microsoft_kiota_abstractions/store/in_memory_backing_store_factory.rb
123
134
  - lib/microsoft_kiota_abstractions/version.rb
124
135
  - microsoft_kiota_abstractions.gemspec
125
136
  homepage: https://learn.microsoft.com/openapi/kiota/