plainrouter-sdk 0.5.0 → 0.6.1

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 (149) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -0
  3. data/lib/plainrouter/client.rb +343 -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 +4 -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 +103 -0
  104. data/lib/plainrouter/openapi/models/get_account_inventory404_response_any_of.rb +188 -0
  105. data/lib/plainrouter/openapi/models/get_event200_response_deliveries_inner.rb +25 -1
  106. data/lib/plainrouter/openapi/models/get_event200_response_event_deliveries_inner.rb +25 -1
  107. data/lib/plainrouter/openapi/models/get_inventory_metrics200_response.rb +26 -5
  108. data/lib/plainrouter/openapi/models/get_inventory_metrics200_response_pagination_value.rb +202 -0
  109. data/lib/plainrouter/openapi/models/launch_creatives_index200_response.rb +164 -0
  110. data/lib/plainrouter/openapi/models/launch_creatives_index200_response_creatives.rb +196 -0
  111. data/lib/plainrouter/openapi/models/{creative_intake_read_creative.rb → launch_creatives_index200_response_creatives_data_inner.rb} +4 -4
  112. data/lib/plainrouter/openapi/models/{creative_intake_read_creative_tags.rb → launch_creatives_index200_response_creatives_data_inner_tags.rb} +1 -1
  113. data/lib/plainrouter/openapi/models/launch_creatives_status200_response.rb +166 -0
  114. data/lib/plainrouter/openapi/models/launch_plans_copy422_response.rb +103 -0
  115. data/lib/plainrouter/openapi/models/launch_plans_index200_response_plans.rb +107 -4
  116. data/lib/plainrouter/openapi/models/launch_plans_index200_response_plans_next_page_arguments.rb +104 -0
  117. data/lib/plainrouter/openapi/models/launch_plans_index200_response_plans_next_page_arguments_any_of.rb +190 -0
  118. data/lib/plainrouter/openapi/models/launch_plans_index200_response_plans_next_page_arguments_any_of1.rb +190 -0
  119. data/lib/plainrouter/openapi/models/launch_plans_store201_response.rb +166 -0
  120. data/lib/plainrouter/openapi/models/launch_plans_validate422_response.rb +103 -0
  121. data/lib/plainrouter/openapi/models/launch_policies_index200_response.rb +164 -0
  122. data/lib/plainrouter/openapi/models/launch_policies_index200_response_policies.rb +453 -0
  123. data/lib/plainrouter/openapi/models/{list_events200_response_events_links_inner.rb → launch_policies_index200_response_policies_links_inner.rb} +3 -3
  124. data/lib/plainrouter/openapi/models/launch_policies_store201_response.rb +166 -0
  125. data/lib/plainrouter/openapi/models/launch_policy.rb +346 -0
  126. data/lib/plainrouter/openapi/models/launcher_file_import403_response.rb +216 -0
  127. data/lib/plainrouter/openapi/models/launcher_file_import422_response.rb +103 -0
  128. data/lib/plainrouter/openapi/models/launcher_file_import422_response_any_of.rb +242 -0
  129. data/lib/plainrouter/openapi/models/launcher_file_import422_response_any_of_syntactic_row_failures_inner.rb +190 -0
  130. data/lib/plainrouter/openapi/models/list_events200_response_events.rb +1 -1
  131. data/lib/plainrouter/openapi/models/plan_execute_rejected_error.rb +2 -2
  132. data/lib/plainrouter/openapi/models/plan_validate_rejected.rb +164 -0
  133. data/lib/plainrouter/openapi/models/plan_validate_rejected_error.rb +191 -0
  134. data/lib/plainrouter/openapi/models/proposal_unavailable.rb +164 -0
  135. data/lib/plainrouter/openapi/models/proposal_unavailable_error.rb +214 -0
  136. data/lib/plainrouter/openapi/models/set_destination_test_mode200_response_destination.rb +67 -1
  137. data/lib/plainrouter/openapi/models/{deployment_plan.rb → store_deployment_plan_request.rb} +185 -208
  138. data/lib/plainrouter/openapi/models/store_deployment_plan_request_ad_set_spec.rb +220 -0
  139. data/lib/plainrouter/openapi/models/store_deployment_plan_request_copy.rb +266 -0
  140. data/lib/plainrouter/openapi/models/store_deployment_plan_request_creative_overrides_inner.rb +217 -0
  141. data/lib/plainrouter/openapi/models/store_launch_policy_request.rb +260 -0
  142. data/lib/plainrouter/openapi/models/update_creative_status_request.rb +188 -0
  143. data/lib/plainrouter/openapi/models/update_deployment_plan_request.rb +511 -0
  144. data/lib/plainrouter/openapi/version.rb +1 -1
  145. data/lib/plainrouter/openapi.rb +88 -5
  146. data/lib/plainrouter/proposal_errors.rb +31 -0
  147. data/lib/plainrouter/version.rb +1 -1
  148. data/lib/plainrouter.rb +1 -0
  149. metadata +94 -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: a5520d3b56ecefc97c7aad741a1e8491e25c71f643cc7661a88360e659eba0e8
4
+ data.tar.gz: e3f6836fc2fb695602513a2cc77c1ea06c59325635a6976e96f8f3594336c43d
5
5
  SHA512:
6
- metadata.gz: 60ce380c81df2427e5f7a9ae893fd8a3ff98799c7dc03b2287938d5f16a07809300a7a698bbbd8cc8c60738f23a806fdf090313ace52f83b357b5fce2bfd85bb
7
- data.tar.gz: 8860b378192926ceb32774bf5e9a03fdb3ecd0d744e511a32bc0e0485ec9841b6c39396ac70083b6e0cde6e4cde75ad188a7cb7ccf669fdc419906f40348b2d4
6
+ metadata.gz: fc2ada06b63f54534980b71b367c77c96a9c3471c18391b6404d6aeed3b118234bd25d7ef23b12c65fdc0ea90336d19c621da2dc530faf6431aca87d625ccacb
7
+ data.tar.gz: 8d936747bea708f07a5472a5a228660271cf3f9ff1d6962e7eef444dbda4c5a3bcbe55de5b6af673b7e7e27f4b725724ba8fce899b8d3c9dfc050d33698a6ec0
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,358 @@ 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
+ def build_request_url(path, opts = {})
130
+ return super unless path == "/mcp/launcher/import"
131
+
132
+ @config.base_url(opts[:operation]).sub(%r{/+\z}, "").delete_suffix("/api/v1") + path
133
+ end
134
+
135
+ private
136
+
137
+ def validate_integer_inputs(http_method, path, opts)
138
+ method_path, metadata = integer_input_operation(http_method, path)
139
+ return unless metadata
140
+
141
+ request_bodies = [opts[:body], opts[:form_params]].map { |body| parsed_request_body(body) }
142
+ metadata.fetch("body").each do |segments|
143
+ request_bodies.each do |request_body|
144
+ field_path = boolean_integer_body_path(request_body, segments)
145
+ refuse_boolean_integer(field_path) if field_path
146
+ end
147
+ end
148
+
149
+ query_params = opts[:query_params] || {}
150
+ metadata.fetch("query").each do |name|
151
+ present, value = hash_value(query_params, name)
152
+ next unless present
153
+
154
+ field_path = boolean_integer_value_path(value, name)
155
+ refuse_boolean_integer(field_path) if field_path
156
+ end
157
+
158
+ integer_path_names = metadata.fetch("path")
159
+ return if integer_path_names.empty?
160
+
161
+ template = method_path.split(" ", 2).last
162
+ path_values = integer_path_values(template, path)
163
+ integer_path_names.each do |name|
164
+ value = path_values[name]
165
+ next unless value == "true" || value == "false"
166
+
167
+ refuse_boolean_integer(name)
168
+ end
169
+ end
170
+
171
+ def integer_input_operation(http_method, path)
172
+ method = http_method.to_s.upcase
173
+ path = path.to_s
174
+ INTEGER_INPUT_FIELDS.each do |method_path, metadata|
175
+ entry_method, template = method_path.split(" ", 2)
176
+ next unless entry_method == method
177
+ next unless path_matches_template?(template, path)
178
+
179
+ return [method_path, metadata]
180
+ end
181
+ [nil, nil]
182
+ end
183
+
184
+ def path_matches_template?(template, path)
185
+ pattern = Regexp.escape(template).gsub(/\\\{[^}]+\\\}/, "[^/]+")
186
+ Regexp.new("\\A#{pattern}\\z").match?(path)
187
+ end
188
+
189
+ def integer_path_values(template, path)
190
+ template_parts = template.split("/")
191
+ path_parts = path.split("/")
192
+ return {} unless template_parts.length == path_parts.length
80
193
 
81
- def initialize(token: nil, base_url: DEFAULT_BASE_URL, timeout: 30, user_agent: nil)
194
+ template_parts.zip(path_parts).each_with_object({}) do |(template_part, path_part), values|
195
+ match = /\A\{([^}]+)\}\z/.match(template_part)
196
+ values[match[1]] = URI::DEFAULT_PARSER.unescape(path_part) if match
197
+ end
198
+ end
199
+
200
+ def parsed_request_body(body)
201
+ value = body.respond_to?(:to_hash) ? body.to_hash : body
202
+ return value unless value.is_a?(String)
203
+
204
+ JSON.parse(value)
205
+ rescue JSON::ParserError
206
+ nil
207
+ end
208
+
209
+ def boolean_integer_body_path(value, segments, field_path = "")
210
+ return field_path if segments.empty? && boolean_value?(value)
211
+ return nil if segments.empty?
212
+
213
+ segment, *rest = segments
214
+ if segment == "*"
215
+ entries = if value.is_a?(Array)
216
+ value.each_with_index.map { |item, index| ["#{field_path}[#{index}]", item] }
217
+ elsif value.is_a?(Hash)
218
+ value.map do |key, item|
219
+ item_path = field_path.empty? ? key.to_s : "#{field_path}.#{key}"
220
+ [item_path, item]
221
+ end
222
+ else
223
+ return nil
224
+ end
225
+ entries.each do |item_path, item|
226
+ found = boolean_integer_body_path(item, rest, item_path)
227
+ return found if found
228
+ end
229
+ return nil
230
+ end
231
+
232
+ return nil unless value.is_a?(Hash)
233
+
234
+ present, child = hash_value(value, segment)
235
+ return nil unless present
236
+
237
+ child_path = field_path.empty? ? segment : "#{field_path}.#{segment}"
238
+ boolean_integer_body_path(child, rest, child_path)
239
+ end
240
+
241
+ def boolean_integer_value_path(value, field_path)
242
+ return field_path if boolean_value?(value)
243
+
244
+ if value.is_a?(Array)
245
+ value.each_with_index do |item, index|
246
+ found = boolean_integer_value_path(item, "#{field_path}[#{index}]")
247
+ return found if found
248
+ end
249
+ end
250
+ nil
251
+ end
252
+
253
+ def hash_value(hash, name)
254
+ return [true, hash[name]] if hash.key?(name)
255
+
256
+ symbol = name.to_sym
257
+ return [true, hash[symbol]] if hash.key?(symbol)
258
+
259
+ [false, nil]
260
+ end
261
+
262
+ def boolean_value?(value)
263
+ value.equal?(true) || value.equal?(false)
264
+ end
265
+
266
+ def refuse_boolean_integer(field_path)
267
+ raise ArgumentError,
268
+ "#{field_path} must not be a boolean; expected an integer or numeric string."
269
+ end
270
+
271
+ def normalize_generated_operation_options(method_path, options)
272
+ return options unless options.is_a?(Hash)
273
+
274
+ metadata = INTEGER_INPUT_FIELDS.fetch(method_path)
275
+ option_fields = metadata.fetch("query") + metadata.fetch("body").filter_map do |segments|
276
+ segments.first if segments.length == 1
277
+ end
278
+ normalized = options.dup
279
+ option_fields.uniq.each do |name|
280
+ present, value = hash_value(normalized, name)
281
+ next unless present && value.is_a?(String) && /\A[+-]?\d+\z/.match?(value)
282
+
283
+ key = normalized.key?(name) ? name : name.to_sym
284
+ normalized[key] = value.to_i
285
+ end
286
+ normalized
287
+ end
288
+
289
+ def validate_generated_operation_inputs(method_path, argument_names, argument_values)
290
+ metadata = INTEGER_INPUT_FIELDS.fetch(method_path)
291
+ arguments = argument_names.zip(argument_values).to_h
292
+ options = arguments["opts"] || arguments["options"] || {}
293
+
294
+ metadata.fetch("body").each do |segments|
295
+ body_candidates = arguments.filter_map do |name, value|
296
+ next if %w[opts options].include?(name)
297
+ next unless value.is_a?(Hash) || value.is_a?(Array) || value.respond_to?(:to_hash) || value.is_a?(String)
298
+
299
+ parsed_request_body(value)
300
+ end
301
+ body_candidates.concat(
302
+ [options, options["form_params"], options[:form_params], options["body"], options[:body], options["debug_body"], options[:debug_body]]
303
+ .map { |body| parsed_request_body(body) }
304
+ )
305
+ body_candidates.each do |request_body|
306
+ field_path = boolean_integer_body_path(request_body, segments)
307
+ refuse_boolean_integer(field_path) if field_path
308
+ end
309
+ end
310
+
311
+ metadata.fetch("query").each do |name|
312
+ present, value = hash_value(options, name)
313
+ unless present
314
+ query_params = options["query_params"] || options[:query_params] || {}
315
+ present, value = hash_value(query_params, name)
316
+ end
317
+ next unless present
318
+
319
+ field_path = boolean_integer_value_path(value, name)
320
+ refuse_boolean_integer(field_path) if field_path
321
+ end
322
+
323
+ metadata.fetch("path").each do |name|
324
+ value = arguments[name]
325
+ refuse_boolean_integer(name) if boolean_value?(value)
326
+ end
327
+ end
328
+ end
329
+
330
+ attr_reader :actions, :action_dry_run, :action_reads, :action_rules, :agent_context,
331
+ :ad_tests, :cookie_declarations, :creatives, :events, :file_imports, :inventory,
332
+ :launch_policies, :operations, :plans, :sandbox
333
+
334
+ def initialize(token: nil, base_url: DEFAULT_BASE_URL, timeout: 30, user_agent: nil, accept: nil)
82
335
  configuration = OpenAPI::Configuration.new
83
336
  configure_base_url(configuration, base_url)
84
337
  configuration.access_token = token
85
338
  configuration.timeout = timeout
86
339
  yield configuration if block_given?
340
+ configuration.configure_faraday_connection { |connection| connection.use RejectRedirects }
87
341
 
88
- api_client = OpenAPI::ApiClient.new(configuration)
342
+ api_client = JsonApiClient.new(configuration, accept: accept)
89
343
  api_client.user_agent = user_agent || "plainrouter-ruby/#{VERSION}"
90
344
 
345
+ @actions = OpenAPI::ActionProposalApiApi.new(api_client)
346
+ @action_dry_run = OpenAPI::ActionDryRunApiApi.new(api_client)
347
+ @action_reads = OpenAPI::ActionReadApiApi.new(api_client)
348
+ @action_rules = OpenAPI::ActionRuleApiApi.new(api_client)
349
+ @agent_context = OpenAPI::AgentContextApi.new(api_client)
350
+ @cookie_declarations = OpenAPI::CookieDeclarationApi.new(api_client)
351
+ @creatives = OpenAPI::CreativeApi.new(api_client)
91
352
  @events = OpenAPI::EventApi.new(api_client)
92
353
  @events.singleton_class.prepend(EventsValidation)
354
+ @file_imports = OpenAPI::DraftBatchFileImportApi.new(api_client)
355
+ @inventory = OpenAPI::MetaInventoryExposureApi.new(api_client)
356
+ @launch_policies = OpenAPI::LaunchPolicyApi.new(api_client)
93
357
  @operations = OpenAPI::OperationsApi.new(api_client)
94
358
  @sandbox = OpenAPI::SandboxApi.new(api_client)
95
359
  @plans = OpenAPI::DeploymentPlanApi.new(api_client)
360
+ @ad_tests = OpenAPI::AdTestApiApi.new(api_client)
361
+
362
+ [
363
+ @actions, @action_dry_run, @action_reads, @action_rules, @ad_tests, @agent_context,
364
+ @cookie_declarations, @creatives, @events, @file_imports, @inventory,
365
+ @launch_policies, @operations, @plans, @sandbox
366
+ ].each { |api_group| install_integer_input_guards(api_group) }
96
367
  end
97
368
 
98
369
  private
99
370
 
371
+ def install_integer_input_guards(api_group)
372
+ return if api_group.instance_variable_defined?(:@plainrouter_integer_input_guards)
373
+
374
+ guard_module = INTEGER_INPUT_GUARD_CACHE_LOCK.synchronize do
375
+ INTEGER_INPUT_GUARD_CACHE[api_group.class] ||= build_integer_input_guard_module(api_group.class)
376
+ end
377
+ api_group.singleton_class.prepend(guard_module)
378
+ api_group.instance_variable_set(:@plainrouter_integer_input_guards, true)
379
+ end
380
+
381
+ def build_integer_input_guard_module(api_class)
382
+ operation_entries = INTEGER_INPUT_FIELDS.each_with_object({}) do |(method_path, metadata), entries|
383
+ entries[ruby_operation_name(metadata.fetch("operationId"))] = method_path
384
+ end
385
+ guard_module = Module.new
386
+ api_class.instance_methods(false).grep(/_with_http_info\z/).each do |http_info_method|
387
+ operation_method = http_info_method.to_s.delete_suffix("_with_http_info")
388
+ method_path = operation_entries[operation_method]
389
+ next unless method_path
390
+
391
+ [operation_method.to_sym, http_info_method].each do |method_name|
392
+ method = api_class.instance_method(method_name)
393
+ signature = ruby_method_signature(method)
394
+ argument_names = method.parameters.map { |_kind, name| name.to_s }
395
+ argument_values = argument_names.join(", ")
396
+ options_name = argument_names.find { |name| %w[opts options].include?(name) }
397
+ normalize_options = options_name ? "#{options_name} = @api_client.__send__(:normalize_generated_operation_options, #{method_path.inspect}, #{options_name})" : nil
398
+ source = <<~RUBY
399
+ def #{method_name}(#{signature})
400
+ #{normalize_options}
401
+ @api_client.__send__(:validate_generated_operation_inputs, #{method_path.inspect}, #{argument_names.inspect}, [#{argument_values}])
402
+ super
403
+ end
404
+ RUBY
405
+ guard_module.module_eval(source, __FILE__, __LINE__)
406
+ end
407
+ end
408
+ guard_module
409
+ end
410
+
411
+ def ruby_method_signature(method)
412
+ source_path, line_number = method.source_location
413
+ definition = File.foreach(source_path).lazy.drop(line_number - 1).first
414
+ match = definition && /\A\s*def\s+#{Regexp.escape(method.name.to_s)}\((.*)\)\s*\z/.match(definition)
415
+ return match[1] if match
416
+
417
+ method.parameters.map do |kind, name|
418
+ case kind
419
+ when :req then name.to_s
420
+ when :opt then "#{name} = {}"
421
+ when :rest then "*#{name}"
422
+ when :keyreq then "#{name}:"
423
+ when :key then "#{name}: {}"
424
+ when :keyrest then "**#{name}"
425
+ when :block then "&#{name}"
426
+ else raise ArgumentError, "Unsupported generated parameter: #{kind}"
427
+ end
428
+ end.join(", ")
429
+ end
430
+
431
+ def ruby_operation_name(operation_id)
432
+ operation_id
433
+ .gsub(/([A-Z]+)([A-Z][a-z])/, "\\1_\\2")
434
+ .gsub(/([a-z0-9])([A-Z])/, "\\1_\\2")
435
+ .gsub(/[^A-Za-z0-9]+/, "_")
436
+ .gsub(/\A_+|_+\z/, "")
437
+ .downcase
438
+ end
439
+
100
440
  def configure_base_url(configuration, base_url)
101
441
  uri = URI.parse(base_url)
102
442
  unless uri.is_a?(URI::HTTP) && uri.host && !uri.query && !uri.fragment