plainrouter-sdk 0.5.0 → 0.6.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 (148) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -0
  3. data/lib/plainrouter/client.rb +337 -3
  4. data/lib/plainrouter/closed_input_objects.json +3896 -0
  5. data/lib/plainrouter/closed_input_validator.rb +246 -0
  6. data/lib/plainrouter/integer_input_fields.json +579 -0
  7. data/lib/plainrouter/openapi/api/action_read_api_api.rb +71 -31
  8. data/lib/plainrouter/openapi/api/action_rule_api_api.rb +350 -0
  9. data/lib/plainrouter/openapi/api/ad_test_api_api.rb +13 -9
  10. data/lib/plainrouter/openapi/api/agent_context_api.rb +93 -0
  11. data/lib/plainrouter/openapi/api/creative_api.rb +241 -0
  12. data/lib/plainrouter/openapi/api/deployment_plan_api.rb +257 -2
  13. data/lib/plainrouter/openapi/api/draft_batch_file_import_api.rb +142 -0
  14. data/lib/plainrouter/openapi/api/launch_policy_api.rb +242 -0
  15. data/lib/plainrouter/openapi/api/meta_inventory_exposure_api.rb +133 -0
  16. data/lib/plainrouter/openapi/api_client.rb +3 -0
  17. data/lib/plainrouter/openapi/api_model_base.rb +2 -0
  18. data/lib/plainrouter/openapi/configuration.rb +34 -0
  19. data/lib/plainrouter/openapi/models/action_batch_read_data.rb +42 -4
  20. data/lib/plainrouter/openapi/models/action_dry_run_read_dry_run.rb +42 -4
  21. data/lib/plainrouter/openapi/models/action_list_read_meta.rb +33 -4
  22. data/lib/plainrouter/openapi/models/action_proposal_input.rb +72 -1
  23. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner.rb +1 -0
  24. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of.rb +2 -0
  25. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of1.rb +2 -0
  26. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of10.rb +2 -0
  27. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of10_params.rb +2 -0
  28. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of11.rb +3 -1
  29. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of12.rb +2 -0
  30. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of12_params.rb +2 -0
  31. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of13.rb +2 -0
  32. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of13_params.rb +2 -0
  33. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of2.rb +2 -0
  34. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of3.rb +2 -0
  35. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of4.rb +2 -0
  36. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of4_target_entity.rb +2 -0
  37. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of5.rb +2 -0
  38. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of6.rb +2 -0
  39. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of7.rb +2 -0
  40. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of8.rb +2 -0
  41. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of9.rb +2 -0
  42. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of_params.rb +2 -0
  43. data/lib/plainrouter/openapi/models/action_proposal_input_actions_inner_any_of_target_entity.rb +2 -0
  44. data/lib/plainrouter/openapi/models/action_proposal_input_evidence_inner.rb +4 -2
  45. data/lib/plainrouter/openapi/models/action_proposal_read_proposal.rb +42 -4
  46. data/lib/plainrouter/openapi/models/action_read_item.rb +11 -1
  47. data/lib/plainrouter/openapi/models/action_read_item_counted_evidence.rb +319 -0
  48. data/lib/plainrouter/openapi/models/action_read_item_counted_evidence_counted.rb +174 -0
  49. data/lib/plainrouter/openapi/models/action_read_item_counted_evidence_coverage.rb +162 -0
  50. data/lib/plainrouter/openapi/models/action_read_item_counted_evidence_meta_reported.rb +198 -0
  51. data/lib/plainrouter/openapi/models/{action_proposal_input_actions_inner_any_of11_params.rb → action_read_item_counted_evidence_target.rb} +41 -24
  52. data/lib/plainrouter/openapi/models/action_read_item_counted_evidence_window.rb +225 -0
  53. data/lib/plainrouter/openapi/models/action_receipt_unavailable.rb +164 -0
  54. data/lib/plainrouter/openapi/models/action_receipt_unavailable_error.rb +214 -0
  55. data/lib/plainrouter/openapi/models/action_rule_detail_read.rb +164 -0
  56. data/lib/plainrouter/openapi/models/action_rule_detail_read_rule.rb +402 -0
  57. data/lib/plainrouter/openapi/models/action_rule_detail_read_rule_current_revision.rb +388 -0
  58. data/lib/plainrouter/openapi/models/action_rule_detail_read_rule_runs_inner.rb +396 -0
  59. data/lib/plainrouter/openapi/models/action_rule_detail_read_rule_runs_inner_decisions_inner.rb +382 -0
  60. data/lib/plainrouter/openapi/models/action_rule_detail_read_rule_window.rb +242 -0
  61. data/lib/plainrouter/openapi/models/action_rule_write_read.rb +164 -0
  62. data/lib/plainrouter/openapi/models/action_rule_write_read_rule.rb +373 -0
  63. data/lib/plainrouter/openapi/models/actions_api_decision_receipt404_response.rb +103 -0
  64. data/lib/plainrouter/openapi/models/actions_rules_index200_response.rb +164 -0
  65. data/lib/plainrouter/openapi/models/actions_rules_index200_response_rules.rb +196 -0
  66. data/lib/plainrouter/openapi/models/actions_rules_index200_response_rules_data_inner.rb +344 -0
  67. data/lib/plainrouter/openapi/models/actions_rules_index200_response_rules_data_inner_account.rb +157 -0
  68. data/lib/plainrouter/openapi/models/actions_rules_index200_response_rules_data_inner_counts.rb +156 -0
  69. data/lib/plainrouter/openapi/models/actions_rules_index403_response.rb +103 -0
  70. data/lib/plainrouter/openapi/models/actions_rules_index403_response_any_of.rb +214 -0
  71. data/lib/plainrouter/openapi/models/actions_rules_store_request.rb +317 -0
  72. data/lib/plainrouter/openapi/models/actions_rules_store_request_definition.rb +105 -0
  73. data/lib/plainrouter/openapi/models/actions_rules_store_request_definition_any_of.rb +317 -0
  74. data/lib/plainrouter/openapi/models/actions_rules_store_request_definition_any_of1.rb +361 -0
  75. data/lib/plainrouter/openapi/models/actions_rules_store_request_definition_any_of2.rb +361 -0
  76. data/lib/plainrouter/openapi/models/actions_rules_store_request_definition_any_of_cost_ratio.rb +104 -0
  77. data/lib/plainrouter/openapi/models/actions_rules_store_request_window.rb +103 -0
  78. data/lib/plainrouter/openapi/models/actions_rules_update_request.rb +267 -0
  79. data/lib/plainrouter/openapi/models/actions_rules_update_request_definition.rb +105 -0
  80. data/lib/plainrouter/openapi/models/actions_rules_update_request_definition_any_of.rb +317 -0
  81. data/lib/plainrouter/openapi/models/actions_rules_update_request_definition_any_of1.rb +361 -0
  82. data/lib/plainrouter/openapi/models/actions_rules_update_request_definition_any_of2.rb +361 -0
  83. data/lib/plainrouter/openapi/models/ad_test_list_read_tests.rb +1 -1
  84. data/lib/plainrouter/openapi/models/agent_context200_response.rb +214 -0
  85. data/lib/plainrouter/openapi/models/agent_context200_response_agent.rb +268 -0
  86. data/lib/plainrouter/openapi/models/agent_context200_response_platform_ad_account.rb +292 -0
  87. data/lib/plainrouter/openapi/models/agent_context200_response_platform_ad_account_workspace.rb +190 -0
  88. data/lib/plainrouter/openapi/models/agent_context401_response.rb +103 -0
  89. data/lib/plainrouter/openapi/models/agent_context_auth_error.rb +190 -0
  90. data/lib/plainrouter/openapi/models/creative_intake_read.rb +1 -1
  91. data/lib/plainrouter/openapi/models/creative_mcp_grant_operation.rb +44 -0
  92. data/lib/plainrouter/openapi/models/creative_mcp_preview.rb +283 -0
  93. data/lib/plainrouter/openapi/models/creative_mcp_refusal.rb +45 -0
  94. data/lib/plainrouter/openapi/models/creative_mcp_tool.rb +44 -0
  95. data/lib/plainrouter/openapi/models/delivery_status.rb +2 -1
  96. data/lib/plainrouter/openapi/models/deployment_plan_read.rb +42 -4
  97. data/lib/plainrouter/openapi/models/destination.rb +73 -9
  98. data/lib/plainrouter/openapi/models/destination_config.rb +103 -0
  99. data/lib/plainrouter/openapi/models/destination_status.rb +2 -1
  100. data/lib/plainrouter/openapi/models/draft_batch_mode.rb +41 -0
  101. data/lib/plainrouter/openapi/models/get_account_inventory200_response.rb +341 -0
  102. data/lib/plainrouter/openapi/models/get_account_inventory200_response_summary.rb +158 -0
  103. data/lib/plainrouter/openapi/models/get_account_inventory404_response.rb +188 -0
  104. data/lib/plainrouter/openapi/models/get_event200_response_deliveries_inner.rb +25 -1
  105. data/lib/plainrouter/openapi/models/get_event200_response_event_deliveries_inner.rb +25 -1
  106. data/lib/plainrouter/openapi/models/get_inventory_metrics200_response.rb +26 -5
  107. data/lib/plainrouter/openapi/models/get_inventory_metrics200_response_pagination_value.rb +202 -0
  108. data/lib/plainrouter/openapi/models/launch_creatives_index200_response.rb +164 -0
  109. data/lib/plainrouter/openapi/models/launch_creatives_index200_response_creatives.rb +196 -0
  110. data/lib/plainrouter/openapi/models/{creative_intake_read_creative.rb → launch_creatives_index200_response_creatives_data_inner.rb} +4 -4
  111. data/lib/plainrouter/openapi/models/{creative_intake_read_creative_tags.rb → launch_creatives_index200_response_creatives_data_inner_tags.rb} +1 -1
  112. data/lib/plainrouter/openapi/models/launch_creatives_status200_response.rb +166 -0
  113. data/lib/plainrouter/openapi/models/launch_plans_copy422_response.rb +103 -0
  114. data/lib/plainrouter/openapi/models/launch_plans_index200_response_plans.rb +107 -4
  115. data/lib/plainrouter/openapi/models/launch_plans_index200_response_plans_next_page_arguments.rb +104 -0
  116. data/lib/plainrouter/openapi/models/launch_plans_index200_response_plans_next_page_arguments_any_of.rb +190 -0
  117. data/lib/plainrouter/openapi/models/launch_plans_index200_response_plans_next_page_arguments_any_of1.rb +190 -0
  118. data/lib/plainrouter/openapi/models/launch_plans_store201_response.rb +166 -0
  119. data/lib/plainrouter/openapi/models/launch_plans_validate422_response.rb +103 -0
  120. data/lib/plainrouter/openapi/models/launch_policies_index200_response.rb +164 -0
  121. data/lib/plainrouter/openapi/models/launch_policies_index200_response_policies.rb +453 -0
  122. data/lib/plainrouter/openapi/models/{list_events200_response_events_links_inner.rb → launch_policies_index200_response_policies_links_inner.rb} +3 -3
  123. data/lib/plainrouter/openapi/models/launch_policies_store201_response.rb +166 -0
  124. data/lib/plainrouter/openapi/models/launch_policy.rb +346 -0
  125. data/lib/plainrouter/openapi/models/launcher_file_import403_response.rb +216 -0
  126. data/lib/plainrouter/openapi/models/launcher_file_import422_response.rb +103 -0
  127. data/lib/plainrouter/openapi/models/launcher_file_import422_response_any_of.rb +242 -0
  128. data/lib/plainrouter/openapi/models/launcher_file_import422_response_any_of_syntactic_row_failures_inner.rb +190 -0
  129. data/lib/plainrouter/openapi/models/list_events200_response_events.rb +1 -1
  130. data/lib/plainrouter/openapi/models/plan_execute_rejected_error.rb +2 -2
  131. data/lib/plainrouter/openapi/models/plan_validate_rejected.rb +164 -0
  132. data/lib/plainrouter/openapi/models/plan_validate_rejected_error.rb +191 -0
  133. data/lib/plainrouter/openapi/models/proposal_unavailable.rb +164 -0
  134. data/lib/plainrouter/openapi/models/proposal_unavailable_error.rb +214 -0
  135. data/lib/plainrouter/openapi/models/set_destination_test_mode200_response_destination.rb +67 -1
  136. data/lib/plainrouter/openapi/models/{deployment_plan.rb → store_deployment_plan_request.rb} +185 -208
  137. data/lib/plainrouter/openapi/models/store_deployment_plan_request_ad_set_spec.rb +220 -0
  138. data/lib/plainrouter/openapi/models/store_deployment_plan_request_copy.rb +266 -0
  139. data/lib/plainrouter/openapi/models/store_deployment_plan_request_creative_overrides_inner.rb +217 -0
  140. data/lib/plainrouter/openapi/models/store_launch_policy_request.rb +260 -0
  141. data/lib/plainrouter/openapi/models/update_creative_status_request.rb +188 -0
  142. data/lib/plainrouter/openapi/models/update_deployment_plan_request.rb +511 -0
  143. data/lib/plainrouter/openapi/version.rb +1 -1
  144. data/lib/plainrouter/openapi.rb +87 -5
  145. data/lib/plainrouter/proposal_errors.rb +31 -0
  146. data/lib/plainrouter/version.rb +1 -1
  147. data/lib/plainrouter.rb +1 -0
  148. metadata +93 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df224f0dc73b39e6a6a2858032da5d51e03c22f577a80685a0369fbb9b7bc2de
4
- data.tar.gz: 3b2b161e563636a585b6f465ad6c911f865716a918829c508ed520648c7b45b0
3
+ metadata.gz: 0d1302d07cfa93ec0f86d445bde27cc160ecf0eb84c1acb612d52975ba9e7af4
4
+ data.tar.gz: 59adbb3ccfa96fdcf0cabce017469a7d304b3f6ca9e705945311ed34631a1a1d
5
5
  SHA512:
6
- metadata.gz: 60ce380c81df2427e5f7a9ae893fd8a3ff98799c7dc03b2287938d5f16a07809300a7a698bbbd8cc8c60738f23a806fdf090313ace52f83b357b5fce2bfd85bb
7
- data.tar.gz: 8860b378192926ceb32774bf5e9a03fdb3ecd0d744e511a32bc0e0485ec9841b6c39396ac70083b6e0cde6e4cde75ad188a7cb7ccf669fdc419906f40348b2d4
6
+ metadata.gz: d6d0c69e034706877defeb06c69ff69c9eb92205c48a8158becac5ba2c9b6ddf13e39fe7204f710293d2a57e3d990bdb5cde4d1a34f912a22cad7947c8c688e8
7
+ data.tar.gz: af6667e2681e9c344f0ea9034930c627c5c1455b121aace73a3fd48502dbe31c18db25454427e183e679743b3c3a0ecc8f7fae57687cd16f904341cf34e6c93d
data/README.md CHANGED
@@ -45,6 +45,8 @@ example = sandbox.sandbox.get_sandbox
45
45
 
46
46
  For response metadata, append `_with_http_info` to a generated operation. Models, API errors, configuration, and every generated operation are available in the deliberately separate `PlainRouter::OpenAPI` namespace.
47
47
 
48
+ `PlainRouter::Client` defaults to `Accept: application/json`; override with `accept: "..."`. API redirects raise `OpenAPI::ApiError` with `code` and `response_headers`, without following. Every generated operation is available through the curated groups: `actions`, `action_dry_run`, `action_reads`, `action_rules`, `ad_tests`, `agent_context`, `cookie_declarations`, `creatives`, `events`, `inventory`, `operations`, `plans`, and `sandbox`. The generated namespace remains available for direct access without this policy.
49
+
48
50
  ## Development
49
51
 
50
52
  From `packages/ruby`:
@@ -64,3 +66,8 @@ scripts/check-ruby-generated.sh
64
66
  Releases use `ruby-v<version>` tags so they remain separate from the npm and Python package tags in this repository. RubyGems publication is performed only by the trusted `publish-ruby.yml` workflow.
65
67
 
66
68
  Licensed under Apache-2.0.
69
+
70
+ Per-call `header_params`, including `Accept`, remain supported and merge names
71
+ case-insensitively, e.g. `client.operations.list_events(header_params: { "accept" => "text/html" })`.
72
+ They override `accept:`. A caller Faraday connection's configured Accept is kept
73
+ when `accept:` is omitted; an exact `*/*` counts as unset at that boundary.
@@ -2,10 +2,19 @@
2
2
 
3
3
  require "uri"
4
4
  require "date"
5
+ require "json"
6
+ require "thread"
5
7
 
6
8
  module PlainRouter
7
9
  # Compact entry point for the Plainrouter API.
8
10
  class Client
11
+ INTEGER_INPUT_FIELDS = JSON.parse(File.read(File.join(__dir__, "integer_input_fields.json")))
12
+ INTEGER_INPUT_GUARD_CACHE = {}
13
+ INTEGER_INPUT_GUARD_CACHE_LOCK = Mutex.new
14
+ private_constant :INTEGER_INPUT_FIELDS
15
+ private_constant :INTEGER_INPUT_GUARD_CACHE
16
+ private_constant :INTEGER_INPUT_GUARD_CACHE_LOCK
17
+
9
18
  module EventsValidation
10
19
  # Keep this literal identical to app/Domains/Signals/Data/ConsentDecision.php::CAPTURED_AT_PATTERN.
11
20
  CAPTURED_AT_PATTERN = '/\A(?<datetime>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.(?<fraction>\d{1,6}))?(?<offset>Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)\z/'
@@ -76,27 +85,352 @@ module PlainRouter
76
85
  end
77
86
  end
78
87
 
79
- attr_reader :events, :operations, :sandbox, :plans
88
+ class RejectRedirects < Faraday::Middleware
89
+ def on_complete(env)
90
+ return unless (300...400).cover?(env.status)
91
+
92
+ raise OpenAPI::ApiError.new(code: env.status, response_headers: env.response_headers, response_body: env.body)
93
+ end
94
+ end
95
+
96
+ # Distinguish generator defaults from caller values without changing operation signatures.
97
+ DefaultAccept = Class.new(String)
98
+ private_constant :DefaultAccept
99
+
100
+ class JsonApiClient < OpenAPI::ApiClient
101
+ def initialize(configuration, accept:)
102
+ super(configuration)
103
+ @accept = accept
104
+ @default_headers = Faraday::Utils::Headers.new(@default_headers)
105
+ @default_headers["Accept"] = accept || "application/json"
106
+ end
107
+
108
+ def select_header_accept(accepts)
109
+ DefaultAccept.new(super(accepts) || "application/json")
110
+ end
111
+
112
+ def build_request(http_method, path, request, opts = {})
113
+ validate_integer_inputs(http_method, path, opts)
114
+
115
+ headers = Faraday::Utils::Headers.new
116
+ (opts[:header_params] || {}).each do |name, value|
117
+ next if name.casecmp?("Accept") && value.is_a?(DefaultAccept)
118
+
119
+ headers[name] = value
120
+ end
121
+ unless headers.key?("Accept")
122
+ caller_accept = request.headers["Accept"]
123
+ caller_accept = nil if caller_accept == "*/*"
124
+ headers["Accept"] = @accept || caller_accept || @default_headers["Accept"] || "application/json"
125
+ end
126
+ super(http_method, path, request, opts.merge(header_params: headers))
127
+ end
128
+
129
+ private
130
+
131
+ def validate_integer_inputs(http_method, path, opts)
132
+ method_path, metadata = integer_input_operation(http_method, path)
133
+ return unless metadata
134
+
135
+ request_bodies = [opts[:body], opts[:form_params]].map { |body| parsed_request_body(body) }
136
+ metadata.fetch("body").each do |segments|
137
+ request_bodies.each do |request_body|
138
+ field_path = boolean_integer_body_path(request_body, segments)
139
+ refuse_boolean_integer(field_path) if field_path
140
+ end
141
+ end
142
+
143
+ query_params = opts[:query_params] || {}
144
+ metadata.fetch("query").each do |name|
145
+ present, value = hash_value(query_params, name)
146
+ next unless present
147
+
148
+ field_path = boolean_integer_value_path(value, name)
149
+ refuse_boolean_integer(field_path) if field_path
150
+ end
151
+
152
+ integer_path_names = metadata.fetch("path")
153
+ return if integer_path_names.empty?
154
+
155
+ template = method_path.split(" ", 2).last
156
+ path_values = integer_path_values(template, path)
157
+ integer_path_names.each do |name|
158
+ value = path_values[name]
159
+ next unless value == "true" || value == "false"
160
+
161
+ refuse_boolean_integer(name)
162
+ end
163
+ end
164
+
165
+ def integer_input_operation(http_method, path)
166
+ method = http_method.to_s.upcase
167
+ path = path.to_s
168
+ INTEGER_INPUT_FIELDS.each do |method_path, metadata|
169
+ entry_method, template = method_path.split(" ", 2)
170
+ next unless entry_method == method
171
+ next unless path_matches_template?(template, path)
172
+
173
+ return [method_path, metadata]
174
+ end
175
+ [nil, nil]
176
+ end
177
+
178
+ def path_matches_template?(template, path)
179
+ pattern = Regexp.escape(template).gsub(/\\\{[^}]+\\\}/, "[^/]+")
180
+ Regexp.new("\\A#{pattern}\\z").match?(path)
181
+ end
182
+
183
+ def integer_path_values(template, path)
184
+ template_parts = template.split("/")
185
+ path_parts = path.split("/")
186
+ return {} unless template_parts.length == path_parts.length
187
+
188
+ template_parts.zip(path_parts).each_with_object({}) do |(template_part, path_part), values|
189
+ match = /\A\{([^}]+)\}\z/.match(template_part)
190
+ values[match[1]] = URI::DEFAULT_PARSER.unescape(path_part) if match
191
+ end
192
+ end
193
+
194
+ def parsed_request_body(body)
195
+ value = body.respond_to?(:to_hash) ? body.to_hash : body
196
+ return value unless value.is_a?(String)
197
+
198
+ JSON.parse(value)
199
+ rescue JSON::ParserError
200
+ nil
201
+ end
202
+
203
+ def boolean_integer_body_path(value, segments, field_path = "")
204
+ return field_path if segments.empty? && boolean_value?(value)
205
+ return nil if segments.empty?
206
+
207
+ segment, *rest = segments
208
+ if segment == "*"
209
+ entries = if value.is_a?(Array)
210
+ value.each_with_index.map { |item, index| ["#{field_path}[#{index}]", item] }
211
+ elsif value.is_a?(Hash)
212
+ value.map do |key, item|
213
+ item_path = field_path.empty? ? key.to_s : "#{field_path}.#{key}"
214
+ [item_path, item]
215
+ end
216
+ else
217
+ return nil
218
+ end
219
+ entries.each do |item_path, item|
220
+ found = boolean_integer_body_path(item, rest, item_path)
221
+ return found if found
222
+ end
223
+ return nil
224
+ end
225
+
226
+ return nil unless value.is_a?(Hash)
227
+
228
+ present, child = hash_value(value, segment)
229
+ return nil unless present
230
+
231
+ child_path = field_path.empty? ? segment : "#{field_path}.#{segment}"
232
+ boolean_integer_body_path(child, rest, child_path)
233
+ end
234
+
235
+ def boolean_integer_value_path(value, field_path)
236
+ return field_path if boolean_value?(value)
237
+
238
+ if value.is_a?(Array)
239
+ value.each_with_index do |item, index|
240
+ found = boolean_integer_value_path(item, "#{field_path}[#{index}]")
241
+ return found if found
242
+ end
243
+ end
244
+ nil
245
+ end
246
+
247
+ def hash_value(hash, name)
248
+ return [true, hash[name]] if hash.key?(name)
249
+
250
+ symbol = name.to_sym
251
+ return [true, hash[symbol]] if hash.key?(symbol)
252
+
253
+ [false, nil]
254
+ end
255
+
256
+ def boolean_value?(value)
257
+ value.equal?(true) || value.equal?(false)
258
+ end
259
+
260
+ def refuse_boolean_integer(field_path)
261
+ raise ArgumentError,
262
+ "#{field_path} must not be a boolean; expected an integer or numeric string."
263
+ end
264
+
265
+ def normalize_generated_operation_options(method_path, options)
266
+ return options unless options.is_a?(Hash)
267
+
268
+ metadata = INTEGER_INPUT_FIELDS.fetch(method_path)
269
+ option_fields = metadata.fetch("query") + metadata.fetch("body").filter_map do |segments|
270
+ segments.first if segments.length == 1
271
+ end
272
+ normalized = options.dup
273
+ option_fields.uniq.each do |name|
274
+ present, value = hash_value(normalized, name)
275
+ next unless present && value.is_a?(String) && /\A[+-]?\d+\z/.match?(value)
276
+
277
+ key = normalized.key?(name) ? name : name.to_sym
278
+ normalized[key] = value.to_i
279
+ end
280
+ normalized
281
+ end
282
+
283
+ def validate_generated_operation_inputs(method_path, argument_names, argument_values)
284
+ metadata = INTEGER_INPUT_FIELDS.fetch(method_path)
285
+ arguments = argument_names.zip(argument_values).to_h
286
+ options = arguments["opts"] || arguments["options"] || {}
287
+
288
+ metadata.fetch("body").each do |segments|
289
+ body_candidates = arguments.filter_map do |name, value|
290
+ next if %w[opts options].include?(name)
291
+ next unless value.is_a?(Hash) || value.is_a?(Array) || value.respond_to?(:to_hash) || value.is_a?(String)
292
+
293
+ parsed_request_body(value)
294
+ end
295
+ body_candidates.concat(
296
+ [options, options["form_params"], options[:form_params], options["body"], options[:body], options["debug_body"], options[:debug_body]]
297
+ .map { |body| parsed_request_body(body) }
298
+ )
299
+ body_candidates.each do |request_body|
300
+ field_path = boolean_integer_body_path(request_body, segments)
301
+ refuse_boolean_integer(field_path) if field_path
302
+ end
303
+ end
304
+
305
+ metadata.fetch("query").each do |name|
306
+ present, value = hash_value(options, name)
307
+ unless present
308
+ query_params = options["query_params"] || options[:query_params] || {}
309
+ present, value = hash_value(query_params, name)
310
+ end
311
+ next unless present
312
+
313
+ field_path = boolean_integer_value_path(value, name)
314
+ refuse_boolean_integer(field_path) if field_path
315
+ end
316
+
317
+ metadata.fetch("path").each do |name|
318
+ value = arguments[name]
319
+ refuse_boolean_integer(name) if boolean_value?(value)
320
+ end
321
+ end
322
+ end
323
+
324
+ attr_reader :actions, :action_dry_run, :action_reads, :action_rules, :agent_context,
325
+ :ad_tests, :cookie_declarations, :creatives, :events, :file_imports, :inventory,
326
+ :launch_policies, :operations, :plans, :sandbox
80
327
 
81
- def initialize(token: nil, base_url: DEFAULT_BASE_URL, timeout: 30, user_agent: nil)
328
+ def initialize(token: nil, base_url: DEFAULT_BASE_URL, timeout: 30, user_agent: nil, accept: nil)
82
329
  configuration = OpenAPI::Configuration.new
83
330
  configure_base_url(configuration, base_url)
84
331
  configuration.access_token = token
85
332
  configuration.timeout = timeout
86
333
  yield configuration if block_given?
334
+ configuration.configure_faraday_connection { |connection| connection.use RejectRedirects }
87
335
 
88
- api_client = OpenAPI::ApiClient.new(configuration)
336
+ api_client = JsonApiClient.new(configuration, accept: accept)
89
337
  api_client.user_agent = user_agent || "plainrouter-ruby/#{VERSION}"
90
338
 
339
+ @actions = OpenAPI::ActionProposalApiApi.new(api_client)
340
+ @action_dry_run = OpenAPI::ActionDryRunApiApi.new(api_client)
341
+ @action_reads = OpenAPI::ActionReadApiApi.new(api_client)
342
+ @action_rules = OpenAPI::ActionRuleApiApi.new(api_client)
343
+ @agent_context = OpenAPI::AgentContextApi.new(api_client)
344
+ @cookie_declarations = OpenAPI::CookieDeclarationApi.new(api_client)
345
+ @creatives = OpenAPI::CreativeApi.new(api_client)
91
346
  @events = OpenAPI::EventApi.new(api_client)
92
347
  @events.singleton_class.prepend(EventsValidation)
348
+ @file_imports = OpenAPI::DraftBatchFileImportApi.new(api_client)
349
+ @inventory = OpenAPI::MetaInventoryExposureApi.new(api_client)
350
+ @launch_policies = OpenAPI::LaunchPolicyApi.new(api_client)
93
351
  @operations = OpenAPI::OperationsApi.new(api_client)
94
352
  @sandbox = OpenAPI::SandboxApi.new(api_client)
95
353
  @plans = OpenAPI::DeploymentPlanApi.new(api_client)
354
+ @ad_tests = OpenAPI::AdTestApiApi.new(api_client)
355
+
356
+ [
357
+ @actions, @action_dry_run, @action_reads, @action_rules, @ad_tests, @agent_context,
358
+ @cookie_declarations, @creatives, @events, @file_imports, @inventory,
359
+ @launch_policies, @operations, @plans, @sandbox
360
+ ].each { |api_group| install_integer_input_guards(api_group) }
96
361
  end
97
362
 
98
363
  private
99
364
 
365
+ def install_integer_input_guards(api_group)
366
+ return if api_group.instance_variable_defined?(:@plainrouter_integer_input_guards)
367
+
368
+ guard_module = INTEGER_INPUT_GUARD_CACHE_LOCK.synchronize do
369
+ INTEGER_INPUT_GUARD_CACHE[api_group.class] ||= build_integer_input_guard_module(api_group.class)
370
+ end
371
+ api_group.singleton_class.prepend(guard_module)
372
+ api_group.instance_variable_set(:@plainrouter_integer_input_guards, true)
373
+ end
374
+
375
+ def build_integer_input_guard_module(api_class)
376
+ operation_entries = INTEGER_INPUT_FIELDS.each_with_object({}) do |(method_path, metadata), entries|
377
+ entries[ruby_operation_name(metadata.fetch("operationId"))] = method_path
378
+ end
379
+ guard_module = Module.new
380
+ api_class.instance_methods(false).grep(/_with_http_info\z/).each do |http_info_method|
381
+ operation_method = http_info_method.to_s.delete_suffix("_with_http_info")
382
+ method_path = operation_entries[operation_method]
383
+ next unless method_path
384
+
385
+ [operation_method.to_sym, http_info_method].each do |method_name|
386
+ method = api_class.instance_method(method_name)
387
+ signature = ruby_method_signature(method)
388
+ argument_names = method.parameters.map { |_kind, name| name.to_s }
389
+ argument_values = argument_names.join(", ")
390
+ options_name = argument_names.find { |name| %w[opts options].include?(name) }
391
+ normalize_options = options_name ? "#{options_name} = @api_client.__send__(:normalize_generated_operation_options, #{method_path.inspect}, #{options_name})" : nil
392
+ source = <<~RUBY
393
+ def #{method_name}(#{signature})
394
+ #{normalize_options}
395
+ @api_client.__send__(:validate_generated_operation_inputs, #{method_path.inspect}, #{argument_names.inspect}, [#{argument_values}])
396
+ super
397
+ end
398
+ RUBY
399
+ guard_module.module_eval(source, __FILE__, __LINE__)
400
+ end
401
+ end
402
+ guard_module
403
+ end
404
+
405
+ def ruby_method_signature(method)
406
+ source_path, line_number = method.source_location
407
+ definition = File.foreach(source_path).lazy.drop(line_number - 1).first
408
+ match = definition && /\A\s*def\s+#{Regexp.escape(method.name.to_s)}\((.*)\)\s*\z/.match(definition)
409
+ return match[1] if match
410
+
411
+ method.parameters.map do |kind, name|
412
+ case kind
413
+ when :req then name.to_s
414
+ when :opt then "#{name} = {}"
415
+ when :rest then "*#{name}"
416
+ when :keyreq then "#{name}:"
417
+ when :key then "#{name}: {}"
418
+ when :keyrest then "**#{name}"
419
+ when :block then "&#{name}"
420
+ else raise ArgumentError, "Unsupported generated parameter: #{kind}"
421
+ end
422
+ end.join(", ")
423
+ end
424
+
425
+ def ruby_operation_name(operation_id)
426
+ operation_id
427
+ .gsub(/([A-Z]+)([A-Z][a-z])/, "\\1_\\2")
428
+ .gsub(/([a-z0-9])([A-Z])/, "\\1_\\2")
429
+ .gsub(/[^A-Za-z0-9]+/, "_")
430
+ .gsub(/\A_+|_+\z/, "")
431
+ .downcase
432
+ end
433
+
100
434
  def configure_base_url(configuration, base_url)
101
435
  uri = URI.parse(base_url)
102
436
  unless uri.is_a?(URI::HTTP) && uri.host && !uri.query && !uri.fragment