vaadin / hilla

Build better business applications, faster. No more juggling REST endpoints or deciphering GraphQL queries. Hilla seamlessly connects Spring Boot and React to accelerate application development.
https://hilla.dev
Apache License 2.0
868 stars 58 forks source link

Form validation does not work in Kotlin. #2343

Open inithink opened 2 months ago

inithink commented 2 months ago

Describe the bug

When I use Kotlin class for endpoint, validation does not work.

image

Expected-behavior

show errors in form

Reproduction

Repository

https://github.com/inithink/hilla-crm-tutorial.git

Kotlin

KotlinClass.kt

import jakarta.validation.constraints.Email
import jakarta.validation.constraints.NotNull

data class KotlinClass(var email: @Email @NotNull String)

KotlinClassForm.tsx

import {TextField} from "@hilla/react-components/TextField";
import {Button} from "@hilla/react-components/Button";
import {useForm} from "@hilla/react-form";
import KotlinClassModel from "Frontend/generated/com/example/application/services/KotlinClassModel";

export default function KotlinClassForm() {
  const {field, model, submit, reset} = useForm(KotlinClassModel,);

  return (
    <div className="flex flex-col gap-s items-start">
      <TextField label="Email" {...field(model.email)} />
      <div className="flex gap-m">
        <Button onClick={submit} theme="primary">Save</Button>
        <Button onClick={reset}>Reset</Button>
      </div>
    </div>
  )
}

Java

JavaClass.java

package com.example.application.services;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;

public class JavaClass {
  public JavaClass(String email) {
    this.email = email;
  }

  @NotNull
  @Email
  String email;

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }
}

JavaForm.tsx

import {TextField} from "@hilla/react-components/TextField";
import {EmailField} from "@hilla/react-components/EmailField";
import {Select, SelectItem} from "@hilla/react-components/Select";
import {Button} from "@hilla/react-components/Button";
import {useForm} from "@hilla/react-form";
import ContactRecordModel from "Frontend/generated/com/example/application/services/CRMService/ContactRecordModel";
import {CRMService, JavaService} from "Frontend/generated/endpoints";
import {useEffect, useState} from "react";
import ContactRecord from "Frontend/generated/com/example/application/services/CRMService/ContactRecord";
import {StringModel} from "@hilla/form";
import JavaClassModel from "Frontend/generated/com/example/application/services/JavaClassModel";
import JavaClass from "Frontend/generated/com/example/application/services/JavaClass";

export default function JavaClassForm() {
  const {field, model, submit, reset} = useForm(JavaClassModel);

  return (
    <div className="flex flex-col gap-s items-start">
      <TextField label="Email" {...field(model.email)} />
      <div className="flex gap-m">
        <Button onClick={submit} theme="primary">Save</Button>
        <Button onClick={reset}>Reset</Button>
      </div>
    </div>
  )
}

System Info

macOS 14.4.1(23E224), M1 Pro hilla version: 2.5.6 browser: chrome 123.0.6312.124

jody90 commented 1 month ago

Are there any updates for this Issue?

platosha commented 1 month ago

Hi, one possible known cause is that Kotlin compiler requires specifying a flag for emitting annotations in JVM bytecode. Could you, please, try with the -Xemit-jvm-type-annotations compiler flag? See also: https://kotlinlang.org/docs/whatsnew14.html#type-annotations-in-the-jvm-bytecode

Yue-plus commented 1 month ago

Hilla 2.5.7 also encountered this problem

inithink commented 1 month ago

Hi, one possible known cause is that Kotlin compiler requires specifying a flag for emitting annotations in JVM bytecode. Could you, please, try with the -Xemit-jvm-type-annotations compiler flag? See also: https://kotlinlang.org/docs/whatsnew14.html#type-annotations-in-the-jvm-bytecode

Oh. It works!

I think it would be amazing if this was documented!

This is a successful pom.xml file.

...
    <build>
        ...
        <plugins>
          <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <version>${kotlin.version}</version>
            <configuration>
              <args>
                <arg>-Xjsr305=strict</arg>
                <arg>-Xemit-jvm-type-annotations</arg>
              </args>
              <compilerPlugins>
                <plugin>spring</plugin>
                <plugin>jpa</plugin>
              </compilerPlugins>
            </configuration>
...
Yue-plus commented 1 month ago

Hi, one possible known cause is that Kotlin compiler requires specifying a flag for emitting annotations in JVM bytecode. Could you, please, try with the compiler flag? See also: https://kotlinlang.org/docs/whatsnew14.html#type-annotations-in-the-jvm-bytecode`-Xemit-jvm-type-annotations`

Oh. It works!

I think it would be amazing if this was documented!

This is a successful pom.xml file.

...
    <build>
        ...
        <plugins>
          <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <version>${kotlin.version}</version>
            <configuration>
              <args>
                <arg>-Xjsr305=strict</arg>
                <arg>-Xemit-jvm-type-annotations</arg>
              </args>
              <compilerPlugins>
                <plugin>spring</plugin>
                <plugin>jpa</plugin>
              </compilerPlugins>
            </configuration>
...

Successful!

This is an example of the 'build.gradle.kts' file available:

import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
    // Omit content
    kotlin("jvm") version "2.0.0"
}

// Omit content

java {
    sourceCompatibility = JavaVersion.VERSION_21
}

kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xjsr305=strict")
        freeCompilerArgs.add("-Xemit-jvm-type-annotations")
        jvmTarget = JvmTarget.JVM_21
    }
}

// Omit content

Maybe we can modify the Gradle plugin to add this compilation option by default.