The usual way in idiomatic Rust would be to use type safety for this purpose: have the function accept special types for its input, and provide the caller secondary interfaces to construct these types. The constructors would then be responsible for inspecting and rejecting invalid input. This way, the caller can continue pushing the construction, and thus the if/else statements for validation errors, upward to the ultimate source of the possibly-invalid values.
(This is also possible in C/C++/Java/C#/..., if not so idiomatic.)
In cases where a function’s precondition can’t be expressed in the type system, the function should check at the start and bale. For example, it’s reasonable in Java to check if parameters are null (since the compiler cannot do this).
For more on this, Google “parse, don’t validate”.
And if we keep pushing (thus pending the decision making) up, wouldn't the top most function become a lot more complicated having a lot more logic pushed up from far down below?
That's bad and impractical advice but now will pollute many pull requests with needless arguments.