ermadmi78 / kobby

Kobby is a codegen plugin of Kotlin DSL Client by GraphQL schema. The generated DSL supports execution of complex GraphQL queries, mutation and subscriptions in Kotlin with syntax similar to native GraphQL syntax.
Apache License 2.0
83 stars 4 forks source link

Generate extension functions toBuilder, toDto, and toInput for DTO classes #37

Closed ermadmi78 closed 1 year ago

ermadmi78 commented 1 year ago

The DTO and Input classes generated by Kobby contain helper builder classes that make it easy to create instances of these classes manually. The purpose of this issue is to extend the use of builder classes.

DTO classes refactoring

For GraphQL type Actor:

type Actor {
    id: ID!
    firstName: String!
    lastName: String
}

Kobby generates a DTO class that looks like this:

public data class ActorDto(
  public val id: Long? = null,
  public val firstName: String? = null,
  public val lastName: String? = null,
)

public fun ActorDto(block: ActorDtoBuilder.() -> Unit): ActorDto {
  // ActorDto builder DSL
  return ActorDtoBuilder().apply(block).let {
    ActorDto(
          it.id,
          it.firstName,
          it.lastName
        )
  }
}

public fun ActorDto.copy(block: ActorDtoBuilder.() -> Unit): ActorDto {
  // ActorDto copy DSL
  return ActorDtoBuilder().also {
    it.id = this.id
    it.firstName = this.firstName
    it.lastName = this.lastName
  }
  .apply(block).let {
    ActorDto(
          it.id,
          it.firstName,
          it.lastName
        )
  }
}

public class ActorDtoBuilder {
  public var id: Long? = null

  public var firstName: String? = null

  public var lastName: String? = null
}

Let's add the toBuilder and toDto extension functions to the generated DTO class, which will make the builder easier to use:

public data class ActorDto(
  public val id: Long? = null,
  public val firstName: String? = null,
  public val lastName: String? = null,
)

public fun ActorDto.toBuilder(): ActorDtoBuilder = ActorDtoBuilder().also {
  it.id = this.id
  it.firstName = this.firstName
  it.lastName = this.lastName
}

public fun ActorDtoBuilder.toDto(): ActorDto = ActorDto(
  id,
  firstName,
  lastName
)

public fun ActorDto(block: ActorDtoBuilder.() -> Unit): ActorDto =
    ActorDtoBuilder().apply(block).toDto()

public fun ActorDto.copy(block: ActorDtoBuilder.() -> Unit): ActorDto =
    toBuilder().apply(block).toDto()

public class ActorDtoBuilder {
  public var id: Long? = null

  public var firstName: String? = null

  public var lastName: String? = null
}

Input classes refactoring

For GraphQL input ActorInput:

input ActorInput {
    id: ID!
    firstName: String!
    lastName: String
}

Kobby generates an Input class that looks like this:

public data class ActorInput(
  public val id: Long,
  public val firstName: String,
  public val lastName: String? = null,
)

public fun ActorInput(block: ActorInputBuilder.() -> Unit): ActorInput {
  // ActorInput builder DSL
  return ActorInputBuilder().apply(block).let {
    ActorInput(
          it.id ?: error("ActorInput: 'id' must not be null"),
          it.firstName ?: error("ActorInput: 'firstName' must not be null"),
          it.lastName
        )
  }
}

public fun ActorInput.copy(block: ActorInputBuilder.() -> Unit): ActorInput {
  // ActorInput copy DSL
  return ActorInputBuilder().also {
    it.id = this.id
    it.firstName = this.firstName
    it.lastName = this.lastName
  }
  .apply(block).let {
    ActorInput(
          it.id ?: error("ActorInput: 'id' must not be null"),
          it.firstName ?: error("ActorInput: 'firstName' must not be null"),
          it.lastName
        )
  }
}

public class ActorInputBuilder {
  public var id: Long? = null

  public var firstName: String? = null

  public var lastName: String? = null
}

Let's add the toBuilder and toInput extension functions to the generated Input class, which will make the builder easier to use:

public data class ActorInput(
  public val id: Long,
  public val firstName: String,
  public val lastName: String? = null,
)

public fun ActorInput.toBuilder(): ActorInputBuilder = ActorInputBuilder().also {
  it.id = this.id
  it.firstName = this.firstName
  it.lastName = this.lastName
}

public fun ActorInputBuilder.toInput(): ActorInput = ActorInput(
  id ?: error("ActorInput: 'id' must not be null"),
  firstName ?: error("ActorInput: 'firstName' must not be null"),
  lastName
)

public fun ActorInput(block: ActorInputBuilder.() -> Unit): ActorInput =
    ActorInputBuilder().apply(block).toInput()

public fun ActorInput.copy(block: ActorInputBuilder.() -> Unit): ActorInput =
    toBuilder().apply(block).toInput()

public class ActorInputBuilder {
  public var id: Long? = null

  public var firstName: String? = null

  public var lastName: String? = null
}

Customization

Use the following build script blocks to customize extension function names.

Gradle:

plugins {
    id("io.github.ermadmi78.kobby")
}

kobby {
    kotlin {
        dto {
            builder {
                // Name of DTO based "toBuilder" function for DTO classes
                toBuilderFun = "toBuilder"

                // Name of builder based "toDto" function for DTO classes
                toDtoFun = "toDto"

                // Name of builder based "toInput" function for DTO input classes
                toInputFun = "toInput"
            }
        }
    }
}

Maven:

<build>
    <plugins>
        <plugin>
            <groupId>io.github.ermadmi78</groupId>
            <artifactId>kobby-maven-plugin</artifactId>
            <version>${kobby.version}</version>
            <executions>
                <execution>
                    <phase>generate-sources</phase>
                    <goals>
                        <goal>generate-kotlin</goal>
                    </goals>
                    <configuration>
                        <kotlin>
                            <dto>
                                <builder>
                                    <!-- Name of DTO based "toBuilder" function for DTO classes -->
                                    <toBuilderFun>toBuilder</toBuilderFun>

                                    <!-- Name of builder based "toDto" function for DTO classes -->
                                    <toDtoFun>toDto</toDtoFun>

                                    <!-- Name of builder based "toInput" function for DTO input classes -->
                                    <toInputFun>toInput</toInputFun>
                                </builder>
                            </dto>
                        </kotlin>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
ermadmi78 commented 1 year ago

Available since release 3.1.0