Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package org.tkareine.validation_example;
- import java.sql.SQLException;
- import java.time.LocalDate;
- import java.util.LinkedHashMap;
- import java.util.Map;
- import java.util.Optional;
- /**
- * Design considerations:
- *
- * <ul>
- * <li>validate all user input, showing all failures</li>
- * <li>integrate with legacy backend code that uses nulls and (checked)
- * exceptions</li>
- * </ul>
- *
- * Usage:
- *
- * <pre>
- * $ mkdir -p target &&
- * javac -d target src/org/tkareine/validation_example/BuilderExample.java &&
- * java -ea -cp target org.tkareine.validation_example.BuilderExample
- * </pre>
- */
- public class BuilderExample {
- public static void main(String[] args) {
- // demonstrates dependency injection
- UserInputValidation userInputValidation = new UserInputValidation(new SafeLegacyBackend(new LegacyBackend("jdbc:oracle:thin:@sunspark:1521:monolithic")));
- testSuccess(userInputValidation);
- testFailureWhenEmpty(userInputValidation);
- testFailureWhenInvalid(userInputValidation);
- testFailureWhenRoleNotFound(userInputValidation);
- System.out.println("ok");
- }
- private static void testSuccess(UserInputValidation userInputValidation) {
- assert(userInputValidation.builder()
- .birthYear("1942")
- .role("scientist")
- .build()
- .isSuccess());
- }
- private static void testFailureWhenEmpty(UserInputValidation userInputValidation) {
- UserInputValidation.Result validationResult = userInputValidation.builder()
- .birthYear("")
- .role("")
- .build();
- assert(!validationResult.isSuccess());
- assert(validationResult.getValidationFailures().size() == 2);
- assert(validationResult.getValidationFailures().get("birthYear").equals("empty"));
- assert(validationResult.getValidationFailures().get("role").equals("empty"));
- }
- private static void testFailureWhenInvalid(UserInputValidation userInputValidation) {
- UserInputValidation.Result validationResult = userInputValidation.builder()
- .birthYear("-1981")
- .role("en")
- .build();
- assert(!validationResult.isSuccess());
- assert(validationResult.getValidationFailures().size() == 2);
- assert(validationResult.getValidationFailures().get("birthYear").equals("not a year: \"-1981\""));
- assert(validationResult.getValidationFailures().get("role").equals("no such role: \"en\""));
- }
- private static void testFailureWhenRoleNotFound(UserInputValidation userInputValidation) {
- UserInputValidation.Result validationResult = userInputValidation.builder()
- .birthYear("1981")
- .role("engineer")
- .build();
- assert(!validationResult.isSuccess());
- assert(validationResult.getValidationFailures().size() == 1);
- assert(validationResult.getValidationFailures().get("role").equals("no such role: \"engineer\""));
- }
- public static class UserInputValidation {
- private final SafeLegacyBackend safeLegacyBackend;
- public UserInputValidation(SafeLegacyBackend safeLegacyBackend) {
- this.safeLegacyBackend = safeLegacyBackend;
- }
- public Builder builder() {
- return new Builder();
- }
- public class Builder {
- private final Map<String, String> failures = new LinkedHashMap<>();
- public Builder birthYear(String input) {
- if (!checkEmptyInput("birthYear", input)) {
- return this;
- }
- int year;
- try {
- year = Integer.parseUnsignedInt(input);
- } catch (NumberFormatException e) {
- return putFailure("birthYear", "not a year: \"" + input + "\"");
- }
- if (year < 1900 || year > LocalDate.now().getYear()) {
- return putFailure("birthYear", "impossible: " + input);
- }
- return this;
- }
- public Builder role(String input) {
- if (!checkEmptyInput("role", input)) {
- return this;
- }
- if (!safeLegacyBackend.parseRole(input).isPresent()) {
- return putFailure("role", "no such role: \"" + input + "\"");
- }
- return this;
- }
- public Result build() {
- return failures.isEmpty()
- ? Result.success()
- : Result.failure(failures);
- }
- private boolean checkEmptyInput(String fieldName, String input) {
- if (input.isEmpty()) {
- putFailure(fieldName, "empty");
- return false;
- } else {
- return true;
- }
- }
- private Builder putFailure(String fieldName, String failure) {
- failures.put(fieldName, failure);
- return this;
- }
- }
- interface Result {
- boolean isSuccess();
- Map<String, String> getValidationFailures();
- static Result success() {
- return Success.INSTANCE;
- }
- static Result failure(Map<String, String> validationFailures) {
- return new Failure(validationFailures);
- }
- }
- public final static class Success implements Result {
- private static Success INSTANCE = new Success();
- private Success() {} // hide
- @Override
- public boolean isSuccess() {
- return true;
- }
- @Override
- public Map<String, String> getValidationFailures() {
- // if you call me, you have a bug in the calling side
- throw new UnsupportedOperationException("getValidationFailures for success result");
- }
- }
- public final static class Failure implements Result {
- private final Map<String, String> failures;
- private Failure(Map<String, String> failures) {
- this.failures = failures;
- }
- @Override
- public boolean isSuccess() {
- return false;
- }
- @Override
- public Map<String, String> getValidationFailures() {
- return failures;
- }
- }
- }
- /**
- * More reasonable interface to {@link LegacyBackend} which you design and
- * implement to suit your needs.
- */
- public static class SafeLegacyBackend {
- private LegacyBackend legacyBackend;
- public SafeLegacyBackend(LegacyBackend legacyBackend) {
- this.legacyBackend = legacyBackend;
- }
- public Optional<LegacyBackend.Role> parseRole(String name) {
- LegacyBackend.Role role;
- try {
- role = legacyBackend.getRole(name);
- } catch (IllegalArgumentException ignored) {
- // Catch only the exceptions that are due to input. Let severe
- // exceptions, such as SQLException, to propagate up the call
- // stack. Severe exceptions might be due to system failure or
- // programming error -- usually there's nothing you can do about
- // them in the app's logic, anyway.
- //
- // This is known as the Fail Fast Principle.
- return Optional.empty();
- } catch (SQLException e) {
- // Wrap checked exception inside an unchecked exception in
- // order to avoid tainting new code with legacy design choices.
- throw new RuntimeException(e);
- }
- // Consider designing new code so that you avoid using `null`. That
- // way you avoid null checks and you can be sure that a method
- // signature always returns a value of the specified return type.
- return Optional.ofNullable(role);
- }
- }
- public static class LegacyBackend {
- final Database database;
- public LegacyBackend(String dbURL) {
- database = Database.connect(dbURL);
- }
- public Role getRole(String name) throws SQLException {
- if (name == null || name.length() < 3) {
- throw new IllegalArgumentException("invalid role name");
- }
- return database.getRoleByName(name);
- }
- static class Database {
- private Database() {} // hide
- static Database connect(String dbURL) {
- return new Database(/* DriverManager.getConnection(dbURL) */);
- }
- Role getRoleByName(String name) throws SQLException {
- // fake implementation
- return name.equals("scientist")
- ? new Role(1L, "scientist", 42)
- : null; // not found
- }
- }
- static class Role {
- private final long id;
- private final String name;
- private final int karma;
- public Role(long id, String name, int karma) {
- this.id = id;
- this.name = name;
- this.karma = karma;
- }
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement