Skip to content

feat: handle recursion for arguments containing input objects#1461

Merged
ysmolski merged 14 commits intomasterfrom
yury/eng-8731-cost-handle-recursion-for-arguments-containing-input-objects
Apr 8, 2026
Merged

feat: handle recursion for arguments containing input objects#1461
ysmolski merged 14 commits intomasterfrom
yury/eng-8731-cost-handle-recursion-for-arguments-containing-input-objects

Conversation

@ysmolski
Copy link
Copy Markdown
Contributor

@ysmolski ysmolski commented Mar 31, 2026

This PR adds support of input object fields passed as arguments.
It handles nested input objects, recursive types, list of input objects
and list-typed arguments.
Negative weights on input fields reduce the cost, but clamped to zero for each field.

I have simplified code by moving costConfigs and defaultListSize into the CostCalculator.

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 31, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8c50ba4e-bf3c-413a-b70f-aff64743ee93

📥 Commits

Reviewing files that changed from the base of the PR and between 9a1952c and e622a6e.

📒 Files selected for processing (2)
  • v2/pkg/engine/plan/cost.go
  • v2/pkg/engine/plan/cost_visitor.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • v2/pkg/engine/plan/cost_visitor.go

📝 Walkthrough

Walkthrough

Cost computation was refactored so CostCalculator is constructed with and caches Configuration; public cost APIs no longer accept Configuration. Input-object argument costs are computed recursively. Call sites, planner wiring, request APIs, and tests were updated; tests now use nullable expected-cost pointers.

Changes

Cohort / File(s) Summary
Cost calculator core
v2/pkg/engine/plan/cost.go, v2/pkg/engine/plan/cost_visitor.go
CostCalculator now constructed with Configuration and caches data-source configs/default list size; removed Configuration params from cost/validate/debug methods. Added recursive input-object field discovery and per-field input-object cost computation; changed argument-weight logic and skip behavior for missing DS configs.
Planner integration
v2/pkg/engine/plan/planner.go
Planner now calls NewCostCalculator(p.planningVisitor.Config) when cost computation is enabled and wires the calculator into the plan.
Execution & request call sites
execution/engine/execution_engine.go, execution/graphql/request.go
Updated call sites and signatures: ValidateSliceArguments, ComputeEstimatedCost, and ComputeActualCost no longer take plan.Configuration; callers pass CostCalculator, variables, and actual list sizes as applicable.
Tests — execution engine
execution/engine/execution_engine_test.go, execution/engine/execution_engine_cost_test.go
Test expectations changed from int sentinels to nullable *int via intPtr(...); assertions now conditional on non-nil expectations. Added new "input object cost" subtests and updated many expected cost values to pointer form.
AST utilities
v2/pkg/ast/ast_type.go
Added ResolveTypeNameBytes and ResolveTypeNameString helpers and clarified doc comments for type-unwrapping utilities.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client as Client
participant ExecutionEngine as ExecutionEngine
participant Planner as Planner
participant CostCalculator as CostCalculator
participant Request as Request

Client->>ExecutionEngine: send operation + variables
ExecutionEngine->>Planner: request plan (planning visitor + cost tree)
Planner->>CostCalculator: NewCostCalculator(config)
Planner-->>ExecutionEngine: return plan (with cost calculator wired)
ExecutionEngine->>Request: build request representation
ExecutionEngine->>CostCalculator: ValidateSliceArguments(variables, report)
ExecutionEngine->>CostCalculator: ComputeEstimatedCost(variables)
alt synchronous execution
    ExecutionEngine->>CostCalculator: ComputeActualCost(variables, actualListSizes)
end
CostCalculator-->>ExecutionEngine: return estimated/actual cost

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the main change: adding support for input object recursion in arguments, which is the core feature across multiple files.
Description check ✅ Passed The description accurately relates to the changeset, explaining the feature (input object fields in arguments), specific scenarios handled, and the refactoring (moving configs into CostCalculator).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch yury/eng-8731-cost-handle-recursion-for-arguments-containing-input-objects

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
v2/pkg/engine/plan/cost_visitor.go (1)

166-220: ⚠️ Potential issue | 🟠 Major

Inline input-object literals still bypass this recursion.

Line 179 only handles ast.ValueKindVariable, so Lines 210-212 and 223-264 are only reached after variable extraction. execution/engine.ExecutionEngine.Execute does that normalization first, but plan.NewPlanner(...).Plan(...) does not. Unless the caller pre-normalizes the operation, a direct planner call like field(arg: { nested: { ... } }) will miss input-object field costs and can underestimate the result. Either normalize literals before the cost walk when ComputeCosts is enabled, or add object/list-literal handling here.

Based on learnings: In wundergraph/graphql-go-tools, the execution engine converts all inline literal argument values into variables before cost validation runs.

Also applies to: 223-264

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/pkg/engine/plan/cost_visitor.go` around lines 166 - 220, The extractor in
CostVisitor.extractFieldArguments currently only handles ast.ValueKindVariable
and therefore ignores inline object/list literals, causing input-object field
costs to be missed; update extractFieldArguments (inside the switch on
argValue.Kind) to also handle ast.ValueKindObject and ast.ValueKindList by
detecting input-object literal shapes, marking argInfo.isInputObject (and
isSimple where appropriate), resolving the literal's underlying type like you do
for variables (use v.Operation.ResolveUnderlyingType /
v.Operation.TypeNameString and lookup with v.Definition.NodeByNameStr), and call
v.buildInputObjectFieldTypes to populate argInfo.inputObjectFieldTypes for
nested fields; alternatively, if you prefer normalization, ensure callers of
plan.NewPlanner(...).Plan(...) normalize inline argument literals into variables
the same way execution/engine.ExecutionEngine.Execute does before cost
computation so extractFieldArguments can rely on variables alone.
🧹 Nitpick comments (2)
v2/pkg/engine/plan/cost.go (1)

231-236: Honor the documented isList precedence here.

inputObjectField says isList takes priority, but inputObjectCost checks isInputObject first. If a list-of-input-objects ever sets both flags, the walker will call GetObject() on the array and skip its elements.

Small alignment fix
-		if typeInfo.isInputObject {
-			valueObj := value.GetObject()
-			if valueObj != nil {
-				cost += inputObjectCost(typeInfo.unwrappedTypeName, valueObj, weights, types)
-			}
-		} else if typeInfo.isList {
+		if typeInfo.isList {
 			valueArray := value.GetArray()
-			if valueArray != nil {
-				for _, item := range valueArray {
-					if item.Type() == astjson.TypeObject {
-						cost += inputObjectCost(typeInfo.unwrappedTypeName, item.GetObject(), weights, types)
-					}
-				}
+			for _, item := range valueArray {
+				if item.Type() == astjson.TypeObject {
+					cost += inputObjectCost(typeInfo.unwrappedTypeName, item.GetObject(), weights, types)
+				}
 			}
+		} else if typeInfo.isInputObject {
+			valueObj := value.GetObject()
+			if valueObj != nil {
+				cost += inputObjectCost(typeInfo.unwrappedTypeName, valueObj, weights, types)
+			}
 		}

Also applies to: 577-603

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/pkg/engine/plan/cost.go` around lines 231 - 236, The inputObjectField
struct documents that isList takes precedence over isInputObject, but
inputObjectCost currently checks isInputObject first and thus treats
list-of-input-objects as an input object (calling GetObject() on the array);
update inputObjectCost (and the similar logic around the other block noted
~577-603) to check isList first and, when true, iterate over list elements
(e.g., process each element or call GetArray/GetAt) rather than treating the
whole value as an input object; reference inputObjectField, inputObjectCost, and
any walker/GetObject/GetArray calls to locate and adjust the branching order so
lists win over input object handling.
execution/engine/execution_engine_cost_test.go (1)

4044-4127: Add one case where a nested negative subtotal goes below zero.

These tests cover whole-node clamping and an exact-zero nested subtotal, but they do not distinguish that from “only clamp at the outer field.” A positive sibling plus an over-discounted child would lock that behavior down.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@execution/engine/execution_engine_cost_test.go` around lines 4044 - 4127, Add
a new test case in execution_engine_cost_test.go that exercises a nested
subtotal that would go below zero but is combined with a positive sibling to
ensure clamping happens at the nested subtotal (not just the outer field). Copy
the pattern used by the surrounding t.Run cases: create an
ExecutionEngineTestCase with schema, operation returning a graphql.Request whose
input causes the nested child subtotal to be negative (e.g., an inner reduction
larger than its base so inner subtotal < 0) while the outer object includes a
positive field; use
mustGraphqlDataSourceConfiguration/mustFactory/testNetHttpClient and the same
CostConfig/fieldConfig, call computeCosts(), and set
expectedEstimatedCost/expectedActualCost to the sum where the negative nested
subtotal is floored to zero and only the positive sibling contributes. Ensure
the test name clearly indicates "nested negative subtotal clamped" and reference
the same data source metadata (RootNodes/CostConfig) as other cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@execution/graphql/request.go`:
- Around line 213-220: In ComputeActualCost on Request, restore the nil-reset
path: if actualListSizes == nil then set r.actualCost = 0 and return (do not
call calc.ActualCost); otherwise if calc == nil set r.actualCost = 0 as before,
else call calc.ActualCost(variables, actualListSizes). Update the
Request.ComputeActualCost function to check actualListSizes for nil before
invoking plan.CostCalculator.ActualCost so a reused Request won't retain a stale
non-zero actualCost.

---

Outside diff comments:
In `@v2/pkg/engine/plan/cost_visitor.go`:
- Around line 166-220: The extractor in CostVisitor.extractFieldArguments
currently only handles ast.ValueKindVariable and therefore ignores inline
object/list literals, causing input-object field costs to be missed; update
extractFieldArguments (inside the switch on argValue.Kind) to also handle
ast.ValueKindObject and ast.ValueKindList by detecting input-object literal
shapes, marking argInfo.isInputObject (and isSimple where appropriate),
resolving the literal's underlying type like you do for variables (use
v.Operation.ResolveUnderlyingType / v.Operation.TypeNameString and lookup with
v.Definition.NodeByNameStr), and call v.buildInputObjectFieldTypes to populate
argInfo.inputObjectFieldTypes for nested fields; alternatively, if you prefer
normalization, ensure callers of plan.NewPlanner(...).Plan(...) normalize inline
argument literals into variables the same way
execution/engine.ExecutionEngine.Execute does before cost computation so
extractFieldArguments can rely on variables alone.

---

Nitpick comments:
In `@execution/engine/execution_engine_cost_test.go`:
- Around line 4044-4127: Add a new test case in execution_engine_cost_test.go
that exercises a nested subtotal that would go below zero but is combined with a
positive sibling to ensure clamping happens at the nested subtotal (not just the
outer field). Copy the pattern used by the surrounding t.Run cases: create an
ExecutionEngineTestCase with schema, operation returning a graphql.Request whose
input causes the nested child subtotal to be negative (e.g., an inner reduction
larger than its base so inner subtotal < 0) while the outer object includes a
positive field; use
mustGraphqlDataSourceConfiguration/mustFactory/testNetHttpClient and the same
CostConfig/fieldConfig, call computeCosts(), and set
expectedEstimatedCost/expectedActualCost to the sum where the negative nested
subtotal is floored to zero and only the positive sibling contributes. Ensure
the test name clearly indicates "nested negative subtotal clamped" and reference
the same data source metadata (RootNodes/CostConfig) as other cases.

In `@v2/pkg/engine/plan/cost.go`:
- Around line 231-236: The inputObjectField struct documents that isList takes
precedence over isInputObject, but inputObjectCost currently checks
isInputObject first and thus treats list-of-input-objects as an input object
(calling GetObject() on the array); update inputObjectCost (and the similar
logic around the other block noted ~577-603) to check isList first and, when
true, iterate over list elements (e.g., process each element or call
GetArray/GetAt) rather than treating the whole value as an input object;
reference inputObjectField, inputObjectCost, and any walker/GetObject/GetArray
calls to locate and adjust the branching order so lists win over input object
handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 694a1c9a-fda3-4b65-a854-a60aa67fbde7

📥 Commits

Reviewing files that changed from the base of the PR and between f3974f6 and 3683af3.

📒 Files selected for processing (8)
  • execution/engine/execution_engine.go
  • execution/engine/execution_engine_cost_test.go
  • execution/engine/execution_engine_test.go
  • execution/graphql/request.go
  • v2/pkg/ast/ast_type.go
  • v2/pkg/engine/plan/cost.go
  • v2/pkg/engine/plan/cost_visitor.go
  • v2/pkg/engine/plan/planner.go

Comment thread execution/graphql/request.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
v2/pkg/engine/plan/cost_visitor.go (1)

239-244: Minor: Redundant type name resolution.

ResolveTypeNameString(fieldTypeRef) is called twice - once to set unwrappedTypeName and again inside NodeByNameStr. Consider reusing the already-computed value.

♻️ Suggested simplification
 fieldTypeRef := v.Definition.InputValueDefinitionType(inputFieldRef)
 unwrappedTypeName := v.Definition.ResolveTypeNameString(fieldTypeRef)

-fieldNode, exists := v.Definition.NodeByNameStr(v.Definition.ResolveTypeNameString(fieldTypeRef))
+fieldNode, exists := v.Definition.NodeByNameStr(unwrappedTypeName)
 if !exists {
     continue
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/pkg/engine/plan/cost_visitor.go` around lines 239 - 244, The code
redundantly calls ResolveTypeNameString(fieldTypeRef) twice; compute it once
into unwrappedTypeName and reuse that when fetching the node instead of calling
ResolveTypeNameString a second time. Update the NodeByNameStr call to use
unwrappedTypeName (e.g., fieldNode, exists :=
v.Definition.NodeByNameStr(unwrappedTypeName)) so only one resolution occurs,
leaving v.Definition, ResolveTypeNameString, unwrappedTypeName, NodeByNameStr,
and fieldTypeRef as the referenced symbols.
v2/pkg/engine/plan/cost.go (3)

592-598: Edge case: Nested arrays ([[InputObject]]) not fully handled.

When isList=true and the JSON contains nested arrays (e.g., [[{...}]]), the inner arrays are skipped since item.Type() == astjson.TypeObject is false for array items. If nested list support is needed, this would require additional handling.

This is likely acceptable for the current scope since nested list arguments are uncommon in GraphQL schemas. Consider documenting this limitation or adding support if use cases emerge.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/pkg/engine/plan/cost.go` around lines 592 - 598, The list-handling branch
in typeInfo.isList uses value.GetArray() and only processes items where
item.Type() == astjson.TypeObject, skipping nested arrays like [[InputObject]];
update the logic in the isList branch (around typeInfo.isList, value.GetArray(),
and inputObjectCost / typeInfo.unwrappedTypeName) to detect astjson.TypeArray
items and either recursively iterate into the inner arrays until you reach
objects (calling inputObjectCost for objects) or call a small helper that walks
nested arrays and invokes inputObjectCost for any encountered objects;
alternatively, if nested lists are intentionally unsupported, add a clear
comment documenting the limitation.

630-634: Redundant nil check after constructor change.

NewCostCalculator now always stores a non-nil DataSourceCostConfig (creating an empty one if GetCostConfig() returns nil). The check dsCostConfig == nil at line 407 will never be true for configs set by this constructor.

♻️ Option 1: Remove the nil assignment to skip empty configs entirely
 for _, ds := range config.DataSources {
     dsCostConfig := ds.GetCostConfig()
-    if dsCostConfig == nil {
-        dsCostConfig = &DataSourceCostConfig{}
+    if dsCostConfig == nil {
+        continue // Skip data sources without cost configuration
     }
     c.costConfigs[ds.Hash()] = dsCostConfig
 }

This would avoid storing empty configs and make the nil check at line 407 meaningful again, aligning with the PR summary which mentions "skipping data sources that have no cost configuration".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/pkg/engine/plan/cost.go` around lines 630 - 634, The nil-check is
redundant because NewCostCalculator currently creates and stores an empty
DataSourceCostConfig, so change the constructor to only store a config when the
source actually provides one: call ds.GetCostConfig(), and if it returns non-nil
assign it to c.costConfigs[ds.Hash()]; do not replace a nil with
&DataSourceCostConfig{} — this removes storing empty configs and makes later nil
checks (e.g., in cost calculation code) meaningful; update the code path around
NewCostCalculator and the use of DataSourceCostConfig/GetCostConfig so only real
configs are inserted.

456-476: Code correctly implements additive argument weights per IBM GraphQL Cost Specification.

Per the IBM GraphQL Cost Specification, explicit argument weights are intentionally additive with input object field-level costs. The @cost directive applies to both ARGUMENT_DEFINITION and INPUT_FIELD_DEFINITION, and cost calculation recursively sums the argument weight plus all input field costs. The current logic at lines 461 and 469 is spec-compliant. Consider adding a comment referencing the IBM spec to clarify this behavior for future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/pkg/engine/plan/cost.go` around lines 456 - 476, The argument-weight logic
in the node.arguments loop (using argumentWeightFound, arg.isInputObject,
arg.inputFieldsCost, dsCostConfig.EnumScalarTypeWeight, and
dsCostConfig.ObjectTypeWeight) is correct per the IBM GraphQL Cost Specification
but is unclear to future readers; add a concise inline comment above the block
explaining that explicit argument weights are intentionally additive with input
object field-level costs per the IBM spec (`@cost` applies to ARGUMENT_DEFINITION
and INPUT_FIELD_DEFINITION and should be summed recursively), and include a link
or reference to the IBM spec for maintainers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@v2/pkg/engine/plan/cost_visitor.go`:
- Around line 239-244: The code redundantly calls
ResolveTypeNameString(fieldTypeRef) twice; compute it once into
unwrappedTypeName and reuse that when fetching the node instead of calling
ResolveTypeNameString a second time. Update the NodeByNameStr call to use
unwrappedTypeName (e.g., fieldNode, exists :=
v.Definition.NodeByNameStr(unwrappedTypeName)) so only one resolution occurs,
leaving v.Definition, ResolveTypeNameString, unwrappedTypeName, NodeByNameStr,
and fieldTypeRef as the referenced symbols.

In `@v2/pkg/engine/plan/cost.go`:
- Around line 592-598: The list-handling branch in typeInfo.isList uses
value.GetArray() and only processes items where item.Type() ==
astjson.TypeObject, skipping nested arrays like [[InputObject]]; update the
logic in the isList branch (around typeInfo.isList, value.GetArray(), and
inputObjectCost / typeInfo.unwrappedTypeName) to detect astjson.TypeArray items
and either recursively iterate into the inner arrays until you reach objects
(calling inputObjectCost for objects) or call a small helper that walks nested
arrays and invokes inputObjectCost for any encountered objects; alternatively,
if nested lists are intentionally unsupported, add a clear comment documenting
the limitation.
- Around line 630-634: The nil-check is redundant because NewCostCalculator
currently creates and stores an empty DataSourceCostConfig, so change the
constructor to only store a config when the source actually provides one: call
ds.GetCostConfig(), and if it returns non-nil assign it to
c.costConfigs[ds.Hash()]; do not replace a nil with &DataSourceCostConfig{} —
this removes storing empty configs and makes later nil checks (e.g., in cost
calculation code) meaningful; update the code path around NewCostCalculator and
the use of DataSourceCostConfig/GetCostConfig so only real configs are inserted.
- Around line 456-476: The argument-weight logic in the node.arguments loop
(using argumentWeightFound, arg.isInputObject, arg.inputFieldsCost,
dsCostConfig.EnumScalarTypeWeight, and dsCostConfig.ObjectTypeWeight) is correct
per the IBM GraphQL Cost Specification but is unclear to future readers; add a
concise inline comment above the block explaining that explicit argument weights
are intentionally additive with input object field-level costs per the IBM spec
(`@cost` applies to ARGUMENT_DEFINITION and INPUT_FIELD_DEFINITION and should be
summed recursively), and include a link or reference to the IBM spec for
maintainers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f16ab907-603c-4d82-94f4-27688e812c87

📥 Commits

Reviewing files that changed from the base of the PR and between 3683af3 and 912dc17.

📒 Files selected for processing (2)
  • v2/pkg/engine/plan/cost.go
  • v2/pkg/engine/plan/cost_visitor.go

ysmolski added a commit to wundergraph/cosmo that referenced this pull request Mar 31, 2026
This PR adds support of input object fields passed as arguments.
It handles nested input objects, recursive types, list of input objects
and list-typed arguments.

Negative weights on input fields reduce the cost,
but clamped to zero for each field.

Engine PR: wundergraph/graphql-go-tools#1461
… yury/eng-8731-cost-handle-recursion-for-arguments-containing-input-objects
@ysmolski ysmolski merged commit ba21793 into master Apr 8, 2026
10 checks passed
@ysmolski ysmolski deleted the yury/eng-8731-cost-handle-recursion-for-arguments-containing-input-objects branch April 8, 2026 09:58
ysmolski pushed a commit that referenced this pull request Apr 8, 2026
🤖 I have created a release *beep* *boop*
---


##
[2.0.0-rc.269](v2.0.0-rc.268...v2.0.0-rc.269)
(2026-04-08)


### Features

* handle recursion for arguments containing input objects
([#1461](#1461))
([ba21793](ba21793))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
ysmolski pushed a commit that referenced this pull request Apr 8, 2026
##
[1.11.0](execution/v1.10.0...execution/v1.11.0)
(2026-04-08)


### Features

* check slicing Arguments passed when requireOneSlicingArgument
([#1456](#1456))
([72c181f](72c181f))
* handle recursion for arguments containing input objects
([#1461](#1461))
([ba21793](ba21793))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants