Skip to content

Use placeholder-based messages in ConstraintViolation #173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 44 commits into from
Dec 27, 2024

Conversation

yevhenii-nadtochii
Copy link
Contributor

@yevhenii-nadtochii yevhenii-nadtochii commented Dec 20, 2024

This PR deprecates printf error message string in ConstraintViolation in favor of TemplateString. The newly created type expects a string template with ${...} placeholder, and a map of placeholder values. This matches more to what we define for options within Proto definitions.

Please note, the full support of recently introduced format of message placeholders is not fully implemented in this PR. This is the first step towards it.

(set_once) was migrated completely because it stands separately from the rule framework. Runtime validation was updated in a compatible way, though, as for now, we have no intention to fully support the introduced message templates. The codegen-based validation is to be migrated in the upcoming PRs.

@yevhenii-nadtochii yevhenii-nadtochii self-assigned this Dec 20, 2024
Copy link

codecov bot commented Dec 23, 2024

Codecov Report

Attention: Patch coverage is 42.69231% with 149 lines in your changes missing coverage. Please review.

Project coverage is 33.55%. Comparing base (d6299a8) to head (3841eda).
Report is 45 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master     #173      +/-   ##
============================================
+ Coverage     33.08%   33.55%   +0.47%     
+ Complexity      350      349       -1     
============================================
  Files           139      142       +3     
  Lines          2805     2834      +29     
  Branches        228      234       +6     
============================================
+ Hits            928      951      +23     
- Misses         1808     1814       +6     
  Partials         69       69              

@yevhenii-nadtochii yevhenii-nadtochii marked this pull request as ready for review December 26, 2024 09:38
@yevhenii-nadtochii
Copy link
Contributor Author

@armiol @alexander-yevsyukov PTAL

Copy link
Collaborator

@armiol armiol left a comment

Choose a reason for hiding this comment

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

@yevhenii-nadtochii please see my comments.

@@ -46,6 +47,7 @@ internal class ErrorMessageSpec {
val error = exception.asMessage()
error.constraintViolationList.size shouldBe 1
val violation = error.getConstraintViolation(0)
violation.msgFormat shouldBe "Expected less than 100 Cents per one Dollars, but got 101."
val message = violation.message.format()
message shouldBe "Expected less than 100 Cents per one Dollars, but got 101."
Copy link
Collaborator

Choose a reason for hiding this comment

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

"Dollar".

paramList shouldBe expectedParams
message.withPlaceholders shouldBe template(field.index + 1)
message.placeholderValueMap shouldContainExactly mapOf(
"field.name" to fieldName,
Copy link
Collaborator

Choose a reason for hiding this comment

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

As discussed vocally, let's have something less prone to typos for operating the placeholder names than just string literals. E.g. constants, and probably some API on top of them, such as extension functions for MessageTemplate.Builder (for Kotlin API, at least).

* Creates a new [TemplateString] with the given [value], which doesn't
* contain placeholders.
*/
internal fun withoutPlaceholders(value: String): TemplateString = templateString {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would shorten to plainString.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@yevhenii-nadtochii,

doesn't

Let's use full form in the documentation: "does not". This is a general recommendation for technical docs because this way it's more visible.

Copy link
Collaborator

@alexander-yevsyukov alexander-yevsyukov left a comment

Choose a reason for hiding this comment

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

Please see my comments.

val formatted = ViolationText.of(violation).toString()

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please remove these leading space characters.

"field.type" to fieldType,
"field.value" to "$value1",
"field.proposed_value" to "$value2",
"parent.type" to parentType
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please introduce constants for the placeholder names. We'll need them for finding usages.

@yevhenii-nadtochii
Copy link
Contributor Author

@armiol @alexander-yevsyukov PTAL

As for now, we don't need TemplateString extensions for ErrorPlaceholder. This type is not composed anywhere in Kotlin now.

Copy link
Collaborator

@armiol armiol left a comment

Choose a reason for hiding this comment

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

@yevhenii-nadtochii LGTM in general. See my comments though.

Let's try one more thing. How about converting MessageValidator.java into Kotlin, and creating the convenient extensions, as per my past proposal?

For instance, an extension function

TemplateString.Builder.withField(field: FieldDeclaration)

would allow us to write the following code:

    var field = constraint.field();
    // ...
    
    violation.message
        .toBuilder()
        .withField(field)
    //  ^^^ Sets all three values:
    //    FIELD_NAME("field.name"),
    //    FIELD_VALUE("field.value"),
    //    FIELD_TYPE("field.type"),

It is both shorter, and less prone to errors, as it accepts FieldDeclarations, not Strings.

* placeholder_value = { "dog.name": "Fido" }
* ```
*
* This method will return "My dog's name is Fido".
Copy link
Collaborator

Choose a reason for hiding this comment

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

"My dog's name is Fido."

*/
public fun TemplateString.format(): String {
checkPlaceholdersHasValue(withPlaceholders, placeholderValueMap) {
"Can not format the given `TemplateString`: `$withPlaceholders`. " +
Copy link
Collaborator

Choose a reason for hiding this comment

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

"Cannot"

@yevhenii-nadtochii
Copy link
Contributor Author

@armiol PTAL

Copy link
Collaborator

@armiol armiol left a comment

Choose a reason for hiding this comment

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

Copy link
Collaborator

@alexander-yevsyukov alexander-yevsyukov left a comment

Choose a reason for hiding this comment

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

LGTM

@yevhenii-nadtochii yevhenii-nadtochii merged commit 932e2ca into master Dec 27, 2024
7 checks passed
@yevhenii-nadtochii yevhenii-nadtochii deleted the deprecate-printf-message branch December 27, 2024 10:53
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.

3 participants