Exceptions don’t really have anything to do with OOP so I’m not sure why you think they’re bad because of that?
Exceptions are perfectly idiomatic in Clojure (and ClojureScript) but, in my opinion, should be used for “exceptional” situations – things you either don’t expect or can’t handle – and not for flow of control (and they shouldn’t be used for flow of control in any language, OO or not, as far as I’m concerned).
So what are you left with? You’ll mostly see either some sort of conditional branching or a monadic approach where the error data and success data are carried along together. Some people like the latter but I generally don’t, except in limited situations where the traditional conditional branching would just be too distracting.
For the example above, my decisions would be based around what kinds of “failure” each step could produce and how (or even “if”) it should be reported to the caller. Some things to consider are i18n/l10n if failures have to be reported to humans, whether something like GitHub - cognitect-labs/anomalies might be appropriate, whether data needs to be conveyed back to the caller in terms of a failure, and so on.
For “validate input”, it might be acceptable to just throw an exception if the function has no sensible behavior when given invalid input – or perhaps you need to communicate to the caller what was wrong at some level.
For the “data read”, perhaps it will throw an exception (which you probably can’t handle either) or perhaps it might return nil
/()
and you need to take action based on that (which may also involve communicating failure to the caller).
The same applies to the business rules: how can they fail, what can you do about those failures, what should you communicate to the caller about those failures.
And so on.
Every situation is likely to be different so I would expect lots of different approaches depending on the answers to all of the above.