Skip to content

Do not generate string literal defaults for non-string Java fields in input types#885

Merged
jiholee17 merged 2 commits intomasterfrom
fix/remove-non-string-defaults
Oct 23, 2025
Merged

Do not generate string literal defaults for non-string Java fields in input types#885
jiholee17 merged 2 commits intomasterfrom
fix/remove-non-string-defaults

Conversation

@jiholee17
Copy link
Copy Markdown
Collaborator

Closes #883

Changes

Currently, Codegen generates String literal default values in input types and assigns them to non-String Java types if such a mapping is defined (i.e. GraphQL Uri to java.net.URI), which causes the generated Java class to not compile. This PR checks whether a String value in GraphQL is being assigned to a String type in Java and only generates the default value if the type and value are both String.

The default value set in the schema is propagated by DGS at runtime so query/mutation behavior remains the same. This change just prevents generating code that does not compile.

is IntValue -> CodeBlock.of("\$L", value.value)
is StringValue -> CodeBlock.of("\$S", value.value)
is StringValue -> {
// Only generate string literal default values for string types to prevent invalid Java initialization
Copy link
Copy Markdown
Contributor

@iparadiso iparadiso Oct 22, 2025

Choose a reason for hiding this comment

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

Nit: I know we aligned on this, but I think we can do a bit better with a method to go through some common scalar types as well as primitives from strings. Anything we don't cover can just be null. So the call for stringValue can call this if it's not a string type to make a more best effort?

Something like....

private fun parseStringToType(value: String, type: JavaTypeName): CodeBlock? {
    return when (type) {
        // primitives -> String
        ClassName.get(Int::class.java) -> CodeBlock.of("\$T.valueOf(\$S)", Integer::class.java, value)
        ClassName.get(Long::class.java) -> CodeBlock.of("\$T.valueOf(\$S)", java.lang.Long::class.java, value)
        ClassName.get(Float::class.java) -> CodeBlock.of("\$T.valueOf(\$S)", java.lang.Float::class.java, value)
        ClassName.get(Double::class.java) -> CodeBlock.of("\$T.valueOf(\$S)", java.lang.Double::class.java, value)
        ClassName.get(Boolean::class.java) -> CodeBlock.of("\$T.valueOf(\$S)", java.lang.Boolean::class.java, value)
        // Example for custom scalar
        ClassName.get(Uri::class.java) -> CodeBlock.of("\$T.parse(\$S)", Uri::class.java, value)
        // Add more custom scalar handlers here
        else -> null // fallback or throw error
    }
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I like that idea and it would definitely make generated types easier to work with. I think supporting primitives would be simple enough but I'm curious about which other Java types you think should be automatically handled (java.net.Uri, java.math.BigDecimal, etc. would probably be commonly mapped to). I also wonder if this might make it inconsistent if only some default types are being generated.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ultimately, the list of possible types that would find their way here would be only GraphQL scalar types (Int, Float, String, Boolean) and custom scalar types right? So while we could have BigDecimal registered as a custom schema type, it would have to be plugged in?

Perhaps we should just start with default scalar types and log an enhancement to consider a way to support default code blocks for custom types? I think there could be other ways to plug in initializing custom types so we may find that trying to deduce it while generating the code may not be the final solution anyway.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think I'm thinking about it the other way around because the Java types are generated from the typeMapping the user provides. Looking at DataTypeGenerator I did see there's this block that checks for a few common Java types which does what Isaiah mentioned, so adding URI here would extend that flexibility.

private fun generateCode(
    value: Value<out Value<*>>,
    type: JavaTypeName,
    inputTypeDefinitions: List<InputObjectTypeDefinition>,
): CodeBlock? =
    when (type) {
        BIG_DECIMAL -> bigDecimalCodeBlock(value, type)
        CURRENCY -> currencyCodeBlock(value, type)
        LOCALE -> localeCodeBlock(value, type)
        ClassName.LONG.box() -> longCodeBlock(value, type)
        else -> defaultCodeBlock(value, type, inputTypeDefinitions)
    }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ok, we might be talking about more than one thing then. I think I'd just be in favor of applying the fix you have here and then logging a ticket to investigate refactoring the mappings to be more robust and support more cases, including Uri (for example).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Sounds good! Agree that I would want to look more into how to support URI (using URI.create() vs. new URI() where one wraps the exception of malformed URIs and the other doesn't), thanks both for your input!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm a bit late to the party since this was already merged, but what do folks think about handling URI as a special case? On the one hand we should not, because why handle this one type specially, but on the other hand, it's a very common case.

Something like this works:

is StringValue -> {
                // Only generate string literal default values for string types to prevent invalid Java initialization
                if (type is ClassName && type == ClassName.get(String::class.java)) {
                    CodeBlock.of("\$S", value.value)
                } else if(type == ClassName.get(URI::class.java)) {
                    CodeBlock.of("URI.create(\$S)", value.value)
                } else {
                    null
                }
            }

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think this makes sense, I can add it in a follow-up PR.

Copy link
Copy Markdown
Contributor

@iparadiso iparadiso left a comment

Choose a reason for hiding this comment

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

The current impl looks fine to unblock breaking code. I left some thoughts on making a best case effort to parse more strings to complex types we could support in this PR in another one if we want.

@jiholee17 jiholee17 force-pushed the fix/remove-non-string-defaults branch from a59e94d to 21c6b05 Compare October 23, 2025 18:29
@jiholee17 jiholee17 merged commit 0f4ae1d into master Oct 23, 2025
6 checks passed
@sjohnr sjohnr deleted the fix/remove-non-string-defaults branch October 24, 2025 17:32
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.

Remove string literal default value from non-string types in generated types

5 participants