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.
- checksums.yaml +4 -4
- data/lib/microsoft_kiota_abstractions/api_client_builder.rb +37 -0
- data/lib/microsoft_kiota_abstractions/authentication/allowed_hosts_validator.rb +8 -4
- data/lib/microsoft_kiota_abstractions/multipart_body.rb +115 -0
- data/lib/microsoft_kiota_abstractions/request_adapter.rb +5 -0
- data/lib/microsoft_kiota_abstractions/request_information.rb +24 -1
- data/lib/microsoft_kiota_abstractions/serialization/iso_duration.rb +2 -2
- data/lib/microsoft_kiota_abstractions/serialization/parse_node.rb +7 -0
- data/lib/microsoft_kiota_abstractions/serialization/parse_node_proxy_factory.rb +39 -0
- data/lib/microsoft_kiota_abstractions/serialization/serialization_writer.rb +11 -0
- data/lib/microsoft_kiota_abstractions/serialization/serialization_writer_proxy_factory.rb +50 -0
- data/lib/microsoft_kiota_abstractions/store/backed_model.rb +8 -0
- data/lib/microsoft_kiota_abstractions/store/backing_store.rb +52 -0
- data/lib/microsoft_kiota_abstractions/store/backing_store_factory.rb +10 -0
- data/lib/microsoft_kiota_abstractions/store/backing_store_factory_singleton.rb +16 -0
- data/lib/microsoft_kiota_abstractions/store/backing_store_parse_node_factory.rb +15 -0
- data/lib/microsoft_kiota_abstractions/store/backing_store_serialization_writer_proxy_factory.rb +99 -0
- data/lib/microsoft_kiota_abstractions/store/in_memory_backing_store.rb +167 -0
- data/lib/microsoft_kiota_abstractions/store/in_memory_backing_store_factory.rb +15 -0
- data/lib/microsoft_kiota_abstractions/version.rb +1 -1
- data/lib/microsoft_kiota_abstractions.rb +11 -0
- metadata +12 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 513206169119988b7f24a1bf7e7dea92ecfb179111e71145b8e3dc4bcd38da81
|
|
4
|
+
data.tar.gz: a4fa14ef64cb4c64feb606852dd542d37eb7fb4650579de10f13263379b1763e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
val.each
|
|
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
|
-
|
|
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,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,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
|
data/lib/microsoft_kiota_abstractions/store/backing_store_serialization_writer_proxy_factory.rb
ADDED
|
@@ -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
|
|
@@ -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.
|
|
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/
|