I'm thrilled to release my new open source library...
# feed
j
I'm thrilled to release my new open source library: Akkurate, a validation library taking advantage of the expressive power of Kotlin. πŸŽ‰ Its role is to help you write qualitative and maintainable validation code for complex business logic. The library is still under heavy development, I’m open to any criticism to help improve it. πŸ™ https://github.com/nesk/akkurate
πŸ‘πŸΌ 1
πŸ‘πŸΎ 1
πŸŽ‰ 6
πŸ‘ 7
πŸ‘πŸ» 1
πŸš€ 1
For those remembering, this library is the consequence of this survey, thank you to those who helped!
❀️ 1
d
I wonder if this could used as a nice assertion library! The only minor detail is that not everybody would want to include the annotation in the production code unless being used there too...
j
It can be used for assertions too yes! You can throw the validation errors if you want, which allows you to do domain model validation. I will write a tutorial about this, it's already planned πŸ˜‰
❀️ 1
Concerning the annotation, I'm working on two options: β€’ Getting rid of the annotation and directly provide a list of classes to the compiler plugin. It already exists but is not stable yet. β€’ Getting rid of the whole compiler plugin, for those who really have to keep their compiler as barebone as possible!
(the latter option obviously implies a syntax a little more cumbersome, but it could work for some people)
d
Or (3) just generate them for all data classes in the project on a ksp setting (of course in my case, only in the test module code...)
j
This could work too, I will think about it and the implications. Thank you for the suggestion!
d
Currently we're using Strikt for assertions, maybe even having an integration that uses your validators to check domain objects... currently strikt needs get(SomeObject::someField).isXXX() for each field, your dsl seems so much nicer... I could see something like
expectThat(result).isValid(validator)
would be Soo much nicer, but then the isValid should try to produce similar output as Strikt does for it's soft assertion failures...
I don't think Strikt can be completely replaced, since there'll always be results that aren't data classes that are internal to the project, like 3rd party classes, or just primitives...
Also, it might be nice to have some kind of integration with Arrow?
j
I'm already planning an Arrow integration πŸ˜„
πŸ‘πŸΌ 1
I really have to write a public roadmap πŸ˜›
d
I'm also really curious about how you'll handle the clean testing of those validations... just repeating the validation code in the test code is typical to such things πŸ˜›!
j
It's still in the thinking process, I did not find the perfect solution yet. Would you care to share your ideas/hopes with me?
d
I'm not really sure, I really test such things when I need regression tests -- to make sure I don't change my model in ways that might break things even though the code itself compiles... Usually ends up that I do:
Copy code
expectThat(result).and {
  get(User::id).isXXX...
  get(User::name).isXXX...
  ...
}
in the first test, and then for other tests I check just a certain field to make sure that the right record was returned.
Because it's a bit tedious to put all that code in all the test cases... (and sometimes I need to ignore certain fields, so I can't check data class equality...)
And making an extension function for each one isn't nice either...
That's really why I wonder if the validator IS in itself clear enough to be used in the tests
And doesn't really need to be tested, just to make sure that results are being validated
There's no point in testing that YOUR code works...
Since you already have tests...
j
yep, you want to test your code πŸ˜„
maybe Akkurate could get an integration to write nice assertion messages in your test output?
if the validation fails
d
You saw how strikt prints out theirs?
Copy code
β–Ό Expect that "The Enlightened take things Lightly":
  βœ— has length 5
         found 35
  βœ— matches the regular expression /\d+/
                             found "The Enlightened take things Lightly"
  βœ“ starts with "T"
j
not yet, I don't have the time right now but I will definitely look at it!
Nice!
I must admit this is beautiful πŸ˜„
d
That's the output for this:
Copy code
val subject = "The Enlightened take things Lightly"
expectThat(subject) {
  hasLength(5)           // fails
  matches(Regex("\\d+")) // fails
  startsWith("T")        // still evaluated and passes
}
Which looks a bit like your library... but your's I think does a better job for checking object hierarchies.
A little thought, maybe testing could be driven by something like: https://kotest.io/docs/next/proptest/property-based-testing.html
But it's still quite a bit of work to make the test cases...
And it's again could touch on "testing the framework" if not strictly used for regression.
j
maybe snapshot testing could be a solution πŸ€”
d
j
yes, for example
snapshot testing is great you're starting to (quoting you) "repeating the validation code in the test code"
I'm not sure this is the solution here, but it could be one of the possibilities
d
How would it go through all the possible constraints?
j
like snapshot testing everywhere else: with multiple tests πŸ˜„ however, I was also thinking about a configuration option forcing all the constraints to fail
d
like snapshot testing everywhere else: with multiple tests πŸ™ˆ
πŸ˜„ 1
however, I was also thinking about a configuration option forcing all the constraints to fail (edited)
How would that help?
j
Say you have a property containing a nullable string, you constrain it to be not null and not empty. Those two constrains cannot fail at the same time, either the validation fails because the string is null, or either because its empty. Without this config option, you would have to run multiple tests. With it, you can make all the constraints fail at the same time, even if its not possible in production. What do you think?
d
I think that it's a bit funny to allow to check if not empty if the value is null, no? Shouldn't there be
isNotNull { ... not null validation }
?
j
see the last paragraph about nullable values: https://akkurate.dev/docs/extend.html#named-constraints
by using vacuous truth, you reduce the cognitive overload and the boilerplate code
d
True, but in certain situations, you might forget to write isNotNull(), and shoot yourself in the foot... like when asserting in unit tests...
j
Copy code
Validator<String?> {
    isNotNull()
    isNotEmpty()
}
is easier to write and more readable than:
Copy code
Validator<String?> {
    isNotNull {
        isNotEmpty()
    }
}
also it means you can write:
Copy code
Validator<String?> {
    isNotEmpty()
}
which is a valid use case: you allow the string to be null, but if present you want it to be filled
True, but in certain situations, you might forget to write isNotNull(), and shoot yourself in the foot... like when asserting in unit tests...
This is something I have in mind, maybe with an Intellij plugin
d
I hear, then that could be better... and for the option of letting them fail with a config option... how would you put that option if the validator is in production code...? Usually I'd say dependency injection, but here you'd have to allow currying for configs before the context currying to be able to do that, and it would still be a problem in most DI frameworks to inject into lambdas... the other option is maybe putting the lambda straight in the DI's module and replacing that... which also might only work with Kotlin Inject...
j
I thought maybe I could create a factory to help with configurable injection, nothing really settled there but it is one of my options
(also, thank you for all those messages, you're helping me shaping the library there πŸ™ )
πŸ‘πŸΌ 1
d
Property testing would make that option superfluous, but is sometimes a pain to configure properly... but it would also check all the permutations and edge cases in just a few tests...
If there could be a DSL/ksp plugin to simplify that, it might be an option
j
Oh I will definitely provide property testing! But I will think about snapshot testing too, not sure if I will do it but I find this pretty interesting for this use case πŸ˜„
d
BTW, you can try asking on #meta for a channel in slack... I don't know if they'll open one so early on in the life of this library, but you could try... Also, the Arrow integration will provide an
Either<R, Nel<ConstraintViolation>>
instead of
Success
and
Failure
?
It's better than piling up this whole discussion in one big thread, and might allow others to get involved easier...
πŸ‘ 1
j
Also, the Arrow integration will provide an
Either<R, Nel<ConstraintViolation>>
instead of
Success
and
Failure
?
Yes, otherwise it would be a bad integration πŸ˜›
πŸ‘πŸΌ 1
d
Also, another little point (thinking more practically while looking a my test code):
Copy code
// In the tests code base... not for validation, only for test assertions on the object hierarchy returned.
val validator = Validator<Book, Book> { expected ->
    // First the property, then the constraint, finally the message.
    title.isNotEmpty().isEqualsTo(expected.title)

    releaseDate.isEqualsTo(expected.releaseDate)

    authors.startsWith(expected.authors.take(3)) // This would be more useful for urls that's ports change in tests when mock servers go up with different random port...

   // Skip checking unimportant or volatile fields, or check inside the object hierarchy which wouldn't be possible with a normal assertEquals(...)
}

expectThat(result).isValid(validator, expectedBook)
Maybe there could be a more simplified syntax for a validator for this kind of use case?
j
Are you talking about the
isValid
method? Or about the content of the validator? If so, what part exactly?
d
Before in the thread I wrote
expectThat(result).isValid(validator)
which isn't right... since a validator by itself won't check the expected state of the resulting object... and the validator isn't the same as one that would validate an object's state in production, because in production you have certain requirements on the data of the object, whereas in tests you're just comparing a resulting state to an expected one (ignoring certain fields, or treating others in special ways for the comparison you're interested in for that particular set of tests...)
j
I understand you see some potential in Akkurate to help you write your assertions, and I'm really glad about that; but let's put this use case on hold for a moment, this is clearly not the first focus for Akkurate. I like to discuss about it but I don't want to go down the rabbit hole now, there are plenty of other things to do before. πŸ˜… Until I get a dedicated channel for akkurate, which is too soon at the moment, maybe you can open a discussion on GitHub so this use case doesn't get lost and we can come back to it later? πŸ™‚
j
Thank you so much πŸ™‡β€β™‚οΈ
d
I guess this library made me realise that what's currently a pain point for me is "validation" in my tests... in my production code, I try to be a bit careful about what comes in w/o a special framework for now... πŸ™‚. I guess with more advanced needs like an api for UIs that need form validation, it could be useful, but that's not currently my area... also, a bit of another side-note, is that for i8n, you might not be able to use simple strings for errors, like in Android it would be string res ids which are Ints, and others might be using enums or sealed classes for errors... is that supported? Or can it only be strings?
j
Currently its only strings, but same here: I have plans to open that πŸ˜„
I really have to write that public roadmap πŸ˜›
πŸ‘ŒπŸΌ 1
p
This week published and already a first user experience: Akkurate: New library for validation of your domain models https://medium.com/@inzuael/akkurate-new-library-for-validation-of-your-domain-models-19ab3144dc6b
πŸ‘πŸ» 1
πŸ‘πŸΎ 2
K 9