Skip to content

RFC: Input Object Builders, should non optional arguments be in the constructor?  #5375

Open
@BoD

Description

@BoD

In v4, a codegen option generateInputBuilders has been added to generate builders for Input Objects.

For this input object:

input UserInput {
  firstName: String!
  lastName: String!
  email: String
} 

the generated class will look like this:

data class UserInput(
  val firstName: String,
  val lastName: String,
  val email: Optional<String?> = Optional.Absent,
) {
  class Builder {
    private var firstName: String? = null
    private var lastName: String? = null
    private var email: Optional<String?> = Optional.Absent

    fun firstName(firstName: String): Builder {
      this.firstName = firstName
      return this
    }

    fun lastName(lastName: String): Builder {
      this.lastName = lastName
      return this
    }

    fun email(email: String?): Builder {
      this.email = Optional.Present(email)
      return this
    }

    fun build(): UserInput = UserInput(
      firstName = firstName ?: error("missing value for firstName"),
      lastName = lastName ?: error("missing value for lastName"),
      email = email,
    )
  }
}

The check for non optional arguments is done at runtime in the build() method.

Usage:

UserInput.Builder()
  .firstName("John") // Could forget
  .lastName("Doe") // Could forget
  .email("[email protected]")
  .build()

Putting them in the builder's constructor would make this a build time check:

data class UserInput(
  val firstName: String,
  val lastName: String,
  val email: Optional<String?> = Optional.Absent,
) {
  class Builder(
    private val firstName: String,
    private val lastName: String,
  ) {
    private var email: Optional<String?> = Optional.Absent

    fun email(email: String?): Builder {
      this.email = Optional.Present(email)
      return this
    }

    fun build(): UserInput = UserInput(
      firstName = firstName,
      lastName = lastName,
      email = email,
    )
  }
}

Usage:

UserInput.Builder(
  firstName = "John", // Can't forget to supply it
  lastName = "Doe", // Can't forget to supply it
)
  .email("[email protected]")
  .build()

However:

  • this makes the builder's API inconsistent (some arguments are setters / some are constructor arguments)
  • for Input types that only have non optional arguments, the builder's constructor will be the same as the input type's constructor itself, but with the disadvantage of more verbosity of the builder pattern
  • this is prone to the issue of the arguments that can change order with a new version of a schema (issue Arguments order for generated Kotlin/Java code #4659).

Other considerations:

  • making this change later will probably be difficult
  • this Kotlin issue discusses a feature request to enforce named arguments
  • in case of @oneOf input objects, we would generate only builder constructors for each input field.

Metadata

Metadata

Assignees

Labels

⚙️ Codegen📜 Feedback WantedThis feature/API needs more use cases and details before it can be implemented.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions