Skip to content

Perform additional validation & fail earlier for incorrect arguments#87

Closed
Marcono1234 wants to merge 6 commits intopalantir:developfrom
Marcono1234:builder-method-validation
Closed

Perform additional validation & fail earlier for incorrect arguments#87
Marcono1234 wants to merge 6 commits intopalantir:developfrom
Marcono1234:builder-method-validation

Conversation

@Marcono1234
Copy link
Contributor

@Marcono1234 Marcono1234 commented Sep 26, 2024

Resolves #342

Before this PR

  • Builder methods were not consistently checking for null
    This would probably often have lead to delayed NullPointerExceptions, or might have even caused different behavior in case null had internally a special meaning.
  • Thrown exception types were not consistent
    Sometimes IllegalArgumentException was thrown for null; in two cases UnsupportedOperationException was thrown instead of IllegalStateException.
  • Validation was missing for adding record constructors
  • Some validation only happened during build(), and not already when calling builder methods
    Note: I only changed a few cases; maybe would be worth to investigate this more in depth in the future. But this applies mainly to immutable state of a builder, e.g. a TypeSpec.Builder for class will keep that kind and it cannot be changed to enum. For other validation is probably fine and better to do this in build().

After this PR

  • NullPointerException is thrown for unexpected null
  • IllegalStateException is thrown when methods are called on wrong type, e.g. adding record constructor to regular class
  • Exceptions are thrown for incorrect record constructor usage
  • Added addModifier(Iterable) overloads, see Fix incomplete toBuilder implementations and add tests #85 (comment)

Possible downsides?

I hope these changes do not accidentally disallow arguments which should be allowed.

@palantirtech
Copy link
Member

Thanks for your interest in palantir/javapoet, @Marcono1234! Before we can accept your pull request, you need to sign our contributor license agreement - just visit https://cla.palantir.com/ and follow the instructions. Once you sign, I'll automatically update this pull request.

@changelog-app
Copy link

changelog-app bot commented Sep 26, 2024

Generate changelog in changelog/@unreleased

What do the change types mean?
  • feature: A new feature of the service.
  • improvement: An incremental improvement in the functionality or operation of the service.
  • fix: Remedies the incorrect behaviour of a component of the service in a backwards-compatible way.
  • break: Has the potential to break consumers of this service's API, inclusive of both Palantir services
    and external consumers of the service's API (e.g. customer-written software or integrations).
  • deprecation: Advertises the intention to remove service functionality without any change to the
    operation of the service itself.
  • manualTask: Requires the possibility of manual intervention (running a script, eyeballing configuration,
    performing database surgery, ...) at the time of upgrade for it to succeed.
  • migration: A fully automatic upgrade migration task with no engineer input required.

Note: only one type should be chosen.

How are new versions calculated?
  • ❗The break and manual task changelog types will result in a major release!
  • 🐛 The fix changelog type will result in a minor release in most cases, and a patch release version for patch branches. This behaviour is configurable in autorelease.
  • ✨ All others will result in a minor version release.

Type

  • Feature
  • Improvement
  • Fix
  • Break
  • Deprecation
  • Manual task
  • Migration

Description

Perform additional validation & fail earlier for incorrect arguments

Check the box to generate changelog(s)

  • Generate changelog entry

Comment on lines +72 to +73
checkState(enclosingClassName == null || enclosingClassName.packageName.equals(packageName),
"enclosing class is in wrong package");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is impossible with the public API currently, but I added this check nonetheless to be safe.

Comment on lines -122 to +125
protected final List<AnnotationSpec> concatAnnotations(List<AnnotationSpec> annotations) {
final List<AnnotationSpec> concatAnnotations(List<AnnotationSpec> annotations) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed this because if I understand it correctly TypeName cannot be subclassed by users, so it makes no sense that they see this method if they cannot use it anyway.

Comment on lines 102 to +103
static ArrayTypeName get(ArrayType mirror, Map<TypeParameterElement, TypeVariableName> typeVariables) {
checkNotNull(mirror, "mirror == null");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For all these get(... mirror, Map typeVariables) methods in the ...TypeName classes I wasn't completely sure if typeVariables can be null either. So for now I did not add a null check.

Comment on lines -434 to +445
// Compact constructor.
// Canonical record constructor.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This just writes the canonical constructor, whether it is also "compact" depends on whether MethodSpec.compactConstructorBuilder() was used.

throw new UnsupportedOperationException(kind + " can't have record constructor");
}
checkState(this.kind == Kind.RECORD, "%s can't have record constructor", this.kind);
checkState(this.recordConstructor == null, "record constructor already set to %s", this.recordConstructor);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This "record constructor already set" is probably consistent with most other builder methods, which don't allow overwriting values:

  • TypeSpec.Builder#superclass
  • MethodSpec.Builder#defaultValue
  • FieldSpec.Builder#initializer

(but there are some exceptions)

public Builder addEnumConstant(String name, TypeSpec typeSpec) {
checkNotNull(name, "name == null");
checkNotNull(typeSpec, "typeSpec == null");
checkState(kind == Kind.ENUM, "only enums can have enum constants");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Side note: This "only enums can have enum constants" is currently also checked in build() (with message "%s is not enum" there)

Comment on lines +865 to +866
checkArgument(methodSpec.defaultValue() == null || kind == Kind.ANNOTATION,
"method with default value is only allowed for annotation type");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Side note: This defaultValue() check already exists in build() as well

Comment on lines -60 to +61
a = ParameterSpec.builder(int.class, "i").addModifiers(Modifier.STATIC).build();
b = ParameterSpec.builder(int.class, "i").addModifiers(Modifier.STATIC).build();
a = ParameterSpec.builder(int.class, "i").addModifiers(Modifier.FINAL).build();
b = ParameterSpec.builder(int.class, "i").addModifiers(Modifier.FINAL).build();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This includes the fix from #85 (comment), where it was previously not delegating the call, and this addModifiers overload did not validate the modifiers.

@Marcono1234
Copy link
Contributor Author

Currently still waiting for an answer to my questions regarding the CLA, see #84 (comment).

@stale
Copy link

stale bot commented Jun 27, 2025

This PR has been automatically marked as stale because it has not been touched in the last 14 days. If you'd like to keep it open, please leave a comment or add the 'long-lived' label, otherwise it'll be closed in 7 days.

@Marcono1234
Copy link
Contributor Author

Have created #342 now to track this feature request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature request] Fail fast for incorrect builder usage

2 participants