-
-
Notifications
You must be signed in to change notification settings - Fork 98
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
implement requireSourceFieldsUsedExcept #608
base: master
Are you sure you want to change the base?
implement requireSourceFieldsUsedExcept #608
Conversation
151adba
to
99f2e7b
Compare
to accommodate settings that are validation in nature, the first one being RequireAllSourceFieldsUsed(except)
to be able to track source field usage across the processing workflow without polluting the derivation code
8c71303
to
25eabf2
Compare
25eabf2
to
13ebbaf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello, thank you for your PR!
I think it's really nice first iteration, showing that you really looked around the codebase 😄
In general, for the sake of backward compatibility and limiting the amount of new types, and flexibility I would like to suggest several things:
- separation of the general policy (
IgnoreUnusedFields
/FailOnUnusedFields
/FailOnUnusedFieldStrict
/etc) from its exceptions (IgnoreField[Path, Cfg]
,FailOnUnusedField[Path, Cfg]
) both in DSL, type-level config and config within macros - that would allow changing the policy globally or in the whole scope
- allowed/rejected fields could be appended one by one, matching current conventions for other customizations
- if needed there could be 3 possible overrides: ignore field, reject unused field, remove override for field
So that would mean:
- storing field policy as a
TransformerFlag
similar toImplicitTransformerPreference
(allowing us to add more policiies in the future)- e.g.
.unusedFieldPolicy(FailOnUnused)
- e.g.
- adding exceptions to list... maybe requiring non-empty list of overrides
- e.g.
def ignoreUnusedField(selector, selectors*)
- e.g.
- things would be stored in a slightly different way internally, mostly using
runtimeOverrides: Vector[(Path, TransformerOverride)]
which is already managing propagation of configs based on paths
Such DSL would require a but more keystrokes but it would better fit Chimney's existing conventions.
A.asCtor[runtime.PathList.List[?, ?]] | ||
.map(A0 => A0.param_<[runtime.Path](0) -> A0.param_<[runtime.PathList](1)) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A list of paths would not be needed if instead this would apply a config, as many times as many paths there is in vararg. This would make less code to maintain in DSL and macros.
case _: Success[?] => | ||
updateState(_.appendToUsedSourceFields(field)).logSuccess(_ => s"source field $field usage registered") | ||
case _: Failure => this | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't like to store the result of the fields check in DerivationResult
- this should be handled by ProductToProduct
rule only.
final case class RequireAllSourceFieldsUsedExcept(sourceFields: Set[String]) extends Verification { | ||
override def toString: String = s"RequireAllSourceFieldsUsedExcept(sourceFields=${sourceFields.mkString(", ")})" | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't introduce a new type parameter the DSL - while users should not rely on it, and MiMa should not complain about it (type erasure) it would break source compatibility for all people who could accidentally save the inferred type somewhere.
Instead:
- the field policy should be a separate flag, working similar to
ImplicitTransformerPreference
- this would allow to change the policy globally (via
-Xmacro-settings
) or for all derivations in scope with implicitTransformerConfiguration
- then explicit allowance/rejection of a field would be a normal
TransformerOverride
following existing overrides conventions - it would work similarly to withFieldRenamed in that there would not be any runtime data related to it
- then only ProductToProductRule would be modified to check what is the current policy and trace which fields were used, generating error if needed
object PathList { | ||
final class Empty extends PathList | ||
final class List[Head <: Path, Tail <: PathList] extends PathList | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed when the Path
is applied to Overrides for every selector in vararg
private val preventImplicitSummoningForTypes: Option[(??, ??)] = None | ||
private val preventImplicitSummoningForTypes: Option[(??, ??)] = None, | ||
/** Stores all verification settings provided by user */ | ||
verifications: Vector[Verification] = Vector.empty |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of having a separate value, it should be stored in runtimeOverrides
under the Path
key, but with a new subtype e.g. sealed trait ForFieldPolicy extends TransformerOverride
@@ -76,6 +76,11 @@ final case class NotSupportedTransformerDerivation( | |||
)(val fromType: String, val toType: String) | |||
extends TransformerDerivationError | |||
|
|||
final case class UnusedButRequiredToUseSourceFields( | |||
unused: Set[String] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused: Set[String]
-> unusedFields: ListSet[String]
@@ -108,6 +113,8 @@ object TransformerDerivationError { | |||
| Please eliminate total/partial ambiguity from implicit scope or use ${MAGENTA}enableImplicitConflictResolution$RESET/${MAGENTA}withFieldComputed$RESET/${MAGENTA}withFieldComputedPartial$RESET to decide which one should be used.""".stripMargin | |||
case NotSupportedTransformerDerivation(exprPrettyPrint) => | |||
s" derivation from $exprPrettyPrint: $fromType to $toType is not supported in Chimney!" | |||
case UnusedButRequiredToUseSourceFields(unusedFields) => | |||
s" field(s) $MAGENTA${unusedFields.mkString(", ")}$RESET of $MAGENTA${fromType}$RESET are required to be used in the transformation but are not used!" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe are required to be used in the transformation but are not used
-> were required to be used in the transformation but are not used
?
* @param selectorFrom | ||
* exception fields that are not required to be used in the transformation | ||
* @return | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* @return
* [[io.scalaland.chimney.dsl.TransformerInto]]
*
* @since 1.5.0
// expected output: | ||
// Target(a = "value") | ||
``` | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Snippet cannot at the same time check successful output and fail compilation, it would have to be 2 snippets:
- one with
// expected error:
- one with
expected output:
without it, snipped checking will fail.
My pleasure! Thank you so much for the quick review and good ideas. I initially had a similar thought in #601, it's nice to hear it wasn't too far off. I'll start developing the API change 🤓🚧 |
This reverts commit 333f105.
… `.withIgnoreUnusedField(_.field)` and a few review comments addressed
Hey @MateuszKubuszok, I addressed the architectural change you proposed (and almost all your comments). Namely I split the API into a flag and a setting to override configuration per field. // implementation:done -> tracks all fields and forces compile error if any left unused
.enableUnusedFieldPolicy(FailOnUnused)
// implementation:done -> untracks a single field
// TODO: how do I implement the vararg version without introducing PathList type? could use your direction here
.withIgnoreUnusedField(_.b) To avoid a API change in the future and preserve backward compatibility, it's probably best to fix the API in this PR. So I tried to formulate your vision. Here's the domain terms and phrases I imagined makes sense for this feature. Please share if you have any suggestion.
In that case I imagined the flag to be parameterized on two aspects fail/warn on unused and whether to track all fields or none of the fields by default. // TODO: does this make sense? isn't it too verbose?
.enableUnusedFieldPolicy(FailOnUnused, TrackAll)
.enableUnusedFieldPolicy(WarnOnUnused, TrackNone)
// TODO: to manually track a bunch of fields
.withTrackUnusedFields(_.b, _.c) |
Hmm, the way I see it we have 2 possible policies for now:
and both would additionally fail the compilation if someone marked a field as unused but it was required. In some more-or-less distant future, after #115, we might have additional policies:
Additionally, we have to keep in mind that:
Currently I think the API what would work the best would be something like: // sealed abstract class UnusedFieldPolicy
// object FailOnIgnoredSourceVal extends UnusedFieldPolicy
src.into[Target]
.enableUnusedFieldPolicy(FailOnIgnoredSourceVal) // 1 value for now
.disableUnusedFieldPolicy // ignores unused fields, the default
.withFieldUnused(_.foo.bar.baz) // expect that field is unused:
// - fail compilation if it is needed
// - do NOT treat field as ignored when UnusedFieldPolicy is enabled
// - be na noop when UnusedFieldPolicy is disabled and field is not used
.transform Then we could test it similarly to other flags:
Adding:
would be a job for another PR, and not necessarily something to do soon. I think that even this change would require some refactoring to how |
Thanks for sharing your insights Mateus, I skimmed through your comment twice and we are aligned. I need to carve up sometime on the weekend to hack it. 🚀 |
1ebf9a0
to
eb2ddcb
Compare
An implementation of
requireSourceFieldsUsedExcept(_.field1, _.field2..)
as described in #248TODO/Future Work:
ProductToProduct
classdef ignoreUnusedField(selector, selectors*)
as Mateus suggestedFailOnSpecifiedUnused
subclass forActionOnUnused
def requireUsedField(selector, selectors*)
as Mateus suggestedRequireUsedField
toTransformerOverrides
to add to list of fields to be checked ifFailOnSpecifiedUnused
is enabled