diff --git a/pom.xml b/pom.xml index cd99ab0..aaa405f 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ 4.11.0.201803080745-r 5.2.0 3.10.0 - 0.4.0 + 0.8.0 2.18.3 0.9.0 diff --git a/src/main/java/net/kemitix/gitdb/GitDB.java b/src/main/java/net/kemitix/gitdb/GitDB.java index c458467..7a0fd4a 100644 --- a/src/main/java/net/kemitix/gitdb/GitDB.java +++ b/src/main/java/net/kemitix/gitdb/GitDB.java @@ -22,10 +22,10 @@ package net.kemitix.gitdb; import net.kemitix.gitdb.impl.LocalGitDB; +import net.kemitix.mon.maybe.Maybe; +import net.kemitix.mon.result.Result; -import java.io.IOException; import java.nio.file.Path; -import java.util.Optional; /** * Main API for connecting to a Git repo as a database. @@ -41,13 +41,12 @@ public interface GitDB { * @param userName the user name * @param userEmailAddress the user email address * @return a GitDB instance for the created local gitdb - * @throws IOException if there {@code dbDir} is a file or a non-empty directory */ - static GitDB initLocal( + static Result initLocal( final Path dbDir, final String userName, final String userEmailAddress - ) throws IOException { + ) { return LocalGitDB.init(dbDir, userName, userEmailAddress); } @@ -59,7 +58,7 @@ public interface GitDB { * @param userEmailAddress the user email address * @return a GitDB instance for the local gitdb */ - static GitDB openLocal(final Path dbDir, final String userName, final String userEmailAddress) { + static Result openLocal(final Path dbDir, final String userName, final String userEmailAddress) { return LocalGitDB.open(dbDir, userName, userEmailAddress); } @@ -67,9 +66,8 @@ public interface GitDB { * Select the named branch. * * @param name the branch to select - * @return an Optional containing the branch if it exists - * @throws IOException if there is an error accessing the branch name + * @return an Result Maybe containing the branch if it exists */ - Optional branch(String name) throws IOException; + Result> branch(String name); } diff --git a/src/main/java/net/kemitix/gitdb/GitDBBranch.java b/src/main/java/net/kemitix/gitdb/GitDBBranch.java index e1fa7cf..fe08fb9 100644 --- a/src/main/java/net/kemitix/gitdb/GitDBBranch.java +++ b/src/main/java/net/kemitix/gitdb/GitDBBranch.java @@ -22,9 +22,8 @@ package net.kemitix.gitdb; import com.github.zafarkhaja.semver.Version; - -import java.io.IOException; -import java.util.Optional; +import net.kemitix.mon.maybe.Maybe; +import net.kemitix.mon.result.Result; /** * API for interacting with a branch in a GirDB. @@ -38,9 +37,8 @@ public interface GitDBBranch { * * @param key the key to lookup * @return an Optional containing the value, if it exists, or empty if not - * @throws IOException if there was an error reading the value */ - Optional get(String key) throws IOException; + Result> get(String key); /** * Put a value into the store for the key. @@ -48,18 +46,16 @@ public interface GitDBBranch { * @param key the key to place the value under * @param value the value (must be Serializable) * @return an updated branch containing the new key/value - * @throws IOException if there was an error writing the key/value */ - GitDBBranch put(String key, String value) throws IOException; + Result put(String key, String value); /** * Removes a key and its value from the store. * * @param key the key to remove * @return an updated branch without the key, or the original if the key was not found - * @throws IOException if there was an error removing the key/value */ - GitDBBranch remove(String key) throws IOException; + Result remove(String key); /** * Returns the GitDB format for the current branch. @@ -67,8 +63,7 @@ public interface GitDBBranch { *

Different branches can have different versions.

* * @return the format as per semantic versioning, i.e. "x.y.z" within an Optional - * @throws IOException error reading version */ - Optional getFormatVersion() throws IOException; + Result> getFormatVersion(); } diff --git a/src/main/java/net/kemitix/gitdb/impl/CommitWriter.java b/src/main/java/net/kemitix/gitdb/impl/CommitWriter.java index 4f9cf11..9b2dd07 100644 --- a/src/main/java/net/kemitix/gitdb/impl/CommitWriter.java +++ b/src/main/java/net/kemitix/gitdb/impl/CommitWriter.java @@ -21,10 +21,9 @@ package net.kemitix.gitdb.impl; +import net.kemitix.mon.result.Result; import org.eclipse.jgit.lib.*; -import java.io.IOException; - /** * Commits Key/Value updates into the Git Repository. * @@ -52,15 +51,14 @@ class CommitWriter { * @param userName the user name * @param userEmailAddress the user email address * @return the id of the commit - * @throws IOException if there is an error writing the value */ - ObjectId write( + Result write( final ObjectId treeId, final ObjectId parentId, final String message, final String userName, final String userEmailAddress - ) throws IOException { + ) { final CommitBuilder commitBuilder = new CommitBuilder(); commitBuilder.setTreeId(treeId); commitBuilder.setMessage(message); @@ -68,7 +66,7 @@ class CommitWriter { commitBuilder.setAuthor(ident); commitBuilder.setCommitter(ident); commitBuilder.setParentId(parentId); - return objectInserter.insert(commitBuilder); + return Result.of(() -> objectInserter.insert(commitBuilder)); } /** @@ -82,15 +80,14 @@ class CommitWriter { * @param userName the user name * @param userEmailAddress the user email address * @return the id of the commit - * @throws IOException if there is an error writing the value */ - ObjectId write( + Result write( final ObjectId treeId, final Ref branchRef, final String message, final String userName, final String userEmailAddress - ) throws IOException { + ) { return write(treeId, branchRef.getObjectId(), message, userName, userEmailAddress); } } diff --git a/src/main/java/net/kemitix/gitdb/impl/GitDBBranchImpl.java b/src/main/java/net/kemitix/gitdb/impl/GitDBBranchImpl.java index 097c646..92b495b 100644 --- a/src/main/java/net/kemitix/gitdb/impl/GitDBBranchImpl.java +++ b/src/main/java/net/kemitix/gitdb/impl/GitDBBranchImpl.java @@ -25,12 +25,12 @@ import com.github.zafarkhaja.semver.Version; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import net.kemitix.gitdb.GitDBBranch; +import net.kemitix.mon.maybe.Maybe; +import net.kemitix.mon.result.Result; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; -import java.io.IOException; -import java.util.Optional; import java.util.function.Function; /** @@ -48,15 +48,6 @@ class GitDBBranchImpl implements GitDBBranch { private final String userEmailAddress; private final String name; - private static GitDBBranch select( - final Ref branchRef, - final GitDBRepo gitDBRepo, - final String userName, - final String userEmailAddress - ) { - return new GitDBBranchImpl(branchRef, gitDBRepo, userName, userEmailAddress, branchRef.getName()); - } - /** * Initialise the creation of new GitDBBranch instances. * @@ -65,7 +56,7 @@ class GitDBBranchImpl implements GitDBBranch { * @param userEmailAddress the user's email address to record against changes * @return a Function for creating a GitDBBranch when supplied with a Ref for a branch */ - static Function init( + static Function> init( final Repository repository, final String userName, final String userEmailAddress @@ -73,38 +64,52 @@ class GitDBBranchImpl implements GitDBBranch { return ref -> select(ref, new GitDBRepo(repository), userName, userEmailAddress); } + private static Result select( + final Ref branchRef, + final GitDBRepo gitDBRepo, + final String userName, + final String userEmailAddress + ) { + return Result.ok(new GitDBBranchImpl(branchRef, gitDBRepo, userName, userEmailAddress, branchRef.getName())); + } + @Override - public Optional get(final String key) throws IOException { + public Result> get(final String key) { return gitDBRepo.readValue(branchRef, KEY_PREFIX + key); } @Override - public GitDBBranch put(final String key, final String value) throws IOException { - final ObjectId newTree = gitDBRepo.writeValue(branchRef, KEY_PREFIX + key, value); + public Result put(final String key, final String value) { final String message = String.format("Add key [%s] = [%s]", key, value); - final Ref newBranch = gitDBRepo.writeCommit(branchRef, newTree, message, userName, userEmailAddress); - return select(newBranch, gitDBRepo, userName, userEmailAddress); + return gitDBRepo.writeValue(branchRef, KEY_PREFIX + key, value) + .flatMap(nt -> gitDBRepo.writeCommit(branchRef, nt, message, userName, userEmailAddress)) + .flatMap(nb -> select(nb, gitDBRepo, userName, userEmailAddress)); } @Override - public GitDBBranch remove(final String key) throws IOException { - final Optional newTree = gitDBRepo.removeKey(branchRef, KEY_PREFIX + key); - if (newTree.isPresent()) { - final Ref newBranch = - gitDBRepo.writeCommit( - branchRef, newTree.get(), - String.format("Remove Key [%s]", key), - userName, - userEmailAddress); - return select(newBranch, gitDBRepo, userName, userEmailAddress); - } - return this; + public Result remove(final String key) { + return gitDBRepo.removeKey(branchRef, KEY_PREFIX + key).flatMap(treeId -> + writeRemoveKeyCommit(key, treeId) + .map(selectUpdatedBranch()) + .orElse(Result.ok(this))); + } + + private Maybe> writeRemoveKeyCommit(final String key, final Maybe idMaybe) { + return idMaybe.map(objectId -> { + final String message = String.format("Remove Key [%s]", key); + return gitDBRepo.writeCommit(branchRef, objectId, message, userName, userEmailAddress); + }); + } + + private Function, Result> selectUpdatedBranch() { + return refResult -> refResult.flatMap(ref -> + select(ref, gitDBRepo, userName, userEmailAddress)); } @Override - public Optional getFormatVersion() throws IOException { + public Result> getFormatVersion() { return gitDBRepo.readValue(branchRef, "GitDB.Version") - .map(Version::valueOf); + .map(version -> version.map(Version::valueOf)); } } diff --git a/src/main/java/net/kemitix/gitdb/impl/GitDBRepo.java b/src/main/java/net/kemitix/gitdb/impl/GitDBRepo.java index 1efbe7c..8bdedce 100644 --- a/src/main/java/net/kemitix/gitdb/impl/GitDBRepo.java +++ b/src/main/java/net/kemitix/gitdb/impl/GitDBRepo.java @@ -21,14 +21,14 @@ package net.kemitix.gitdb.impl; -import lombok.val; +import net.kemitix.mon.maybe.Maybe; +import net.kemitix.mon.result.Result; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; -import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.Optional; +import java.util.stream.Stream; /** * Wrapper for interacting with the GitDB Repository. @@ -47,7 +47,7 @@ class GitDBRepo { /** * Creates a new instance of this class. * - * @param repository the Git Repository + * @param repository the Git Repository */ GitDBRepo(final Repository repository) { this.repository = repository; @@ -64,56 +64,35 @@ class GitDBRepo { * @param key the key to insert * @param valueId id of the value * @return the id of the inserted tree - * @throws IOException the tree could not be stored */ - ObjectId insertNewTree( + Result insertNewTree( final String key, final ObjectId valueId - ) throws IOException { + ) { return keyWriter.writeFirst(key, valueId); } - /** - * Insert a commit into the store, returning its unique id. - * - * @param treeId id of the tree - * @param branchRef the branch to commit to - * @param message the message - * @param userName the user name - * @param userEmailAddress the user email address - * @return the id of the commit - * @throws IOException the commit could not be stored - */ - ObjectId insertCommit( - final ObjectId treeId, - final String message, - final String userName, - final String userEmailAddress, - final Ref branchRef - ) throws IOException { - return commitWriter.write(treeId, branchRef, message, userName, userEmailAddress); - } - /** * Reads a value from the branch with the given key. * * @param branchRef the branch to select from * @param key the key to get the value for * @return an Optional containing the value if found, or empty - * @throws IOException if there was an error reading the value */ - Optional readValue( + Result> readValue( final Ref branchRef, final String key - ) throws IOException { - val blob = new GitTreeReader(repository) - .treeFilter(key) - .stream(branchRef) - .findFirst(); - if (blob.isPresent()) { - return Optional.of(blob.get().blobAsString()); - } - return Optional.empty(); + ) { + final GitTreeReader treeFilter = new GitTreeReader(repository).treeFilter(key); + return streamTree(branchRef, treeFilter).flatMap(s -> + Result.invert(s.findFirst() + .map(NamedRevBlob::blobAsString) + .map(Maybe::just) + .orElseGet(Maybe::nothing))); + } + + private Result> streamTree(final Ref branchRef, final GitTreeReader treeFilter) { + return treeFilter.stream(branchRef); } /** @@ -125,11 +104,10 @@ class GitDBRepo { * @param key the key to place the value under * @param value the value * @return the id of the updated tree containing the update - * @throws IOException if there was an error writing the value */ - ObjectId writeValue(final Ref branchRef, final String key, final String value) throws IOException { - final ObjectId blob = valueWriter.write(value.getBytes(StandardCharsets.UTF_8)); - return keyWriter.write(key, blob, branchRef); + Result writeValue(final Ref branchRef, final String key, final String value) { + return valueWriter.write(value.getBytes(StandardCharsets.UTF_8)) + .flatMap(b -> keyWriter.write(key, b, branchRef)); } /** @@ -141,17 +119,37 @@ class GitDBRepo { * @param userName the user name * @param userEmailAddress the user email address * @return the Ref of the updated branch - * @throws IOException if there was an error writing the branch */ - Ref writeCommit( + Result writeCommit( final Ref branchRef, final ObjectId tree, final String message, final String userName, final String userEmailAddress - ) throws IOException { - final ObjectId commitId = insertCommit(tree, message, userName, userEmailAddress, branchRef); - return headWriter.write(branchRef.getName(), commitId); + ) { + return insertCommit(tree, message, userName, userEmailAddress, branchRef) + .flatMap(cid -> Result.of(() -> + headWriter.write(branchRef.getName(), cid))); + } + + /** + * Insert a commit into the store, returning its unique id. + * + * @param treeId id of the tree + * @param branchRef the branch to commit to + * @param message the message + * @param userName the user name + * @param userEmailAddress the user email address + * @return the id of the commit + */ + Result insertCommit( + final ObjectId treeId, + final String message, + final String userName, + final String userEmailAddress, + final Ref branchRef + ) { + return commitWriter.write(treeId, branchRef, message, userName, userEmailAddress); } /** @@ -162,14 +160,13 @@ class GitDBRepo { * @param initUser the user name * @param initEmail the user email address * @return the id of the commit - * @throws IOException if there was an error writing the commit */ - ObjectId initialCommit( + Result initialCommit( final ObjectId treeId, final String initMessage, final String initUser, final String initEmail - ) throws IOException { + ) { return commitWriter.write(treeId, ObjectId.zeroId(), initMessage, initUser, initEmail); } @@ -182,9 +179,8 @@ class GitDBRepo { * @param key the key to place the value under * @return an Optional containing the id of the updated tree containing the update, if the key was found, or an * empty Optional if there key was not found, the there was no changes made - * @throws IOException if there was an error writing the value */ - Optional removeKey(final Ref branchRef, final String key) throws IOException { + Result> removeKey(final Ref branchRef, final String key) { return keyRemover.remove(branchRef, key); } diff --git a/src/main/java/net/kemitix/gitdb/impl/GitTreeReader.java b/src/main/java/net/kemitix/gitdb/impl/GitTreeReader.java index 5abd235..76c82f7 100644 --- a/src/main/java/net/kemitix/gitdb/impl/GitTreeReader.java +++ b/src/main/java/net/kemitix/gitdb/impl/GitTreeReader.java @@ -23,15 +23,20 @@ package net.kemitix.gitdb.impl; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; +import net.kemitix.mon.result.Result; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; -import java.io.IOException; import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Stream; /** @@ -51,23 +56,55 @@ class GitTreeReader { * * @param branchRef the branch to read * @return a stream of key/value pairs as NamedRevBlobs - * @throws IOException if there is an error reading the commit or walking the tree */ - Stream stream(final Ref branchRef) throws IOException { + Result> stream(final Ref branchRef) { final TreeWalk treeWalk = new TreeWalk(repository); final RevWalk revWalk = new RevWalk(repository); - treeWalk.addTree(revWalk.parseCommit(branchRef.getObjectId()).getTree()); - treeWalk.setRecursive(false); - Optional.ofNullable(treeFilter) - .ifPresent(treeWalk::setFilter); - final Stream.Builder builder = Stream.builder(); - while (treeWalk.next()) { - builder.add(new NamedRevBlob( - treeWalk.getNameString(), - revWalk.lookupBlob(treeWalk.getObjectId(0)), - repository)); - } - return builder.build(); + return Result.of(parseBranchCommit(branchRef, revWalk)) + .map(RevCommit::getTree) + .flatMap(addTreeTo(treeWalk)) + .peek(disableRecursion(treeWalk)) + .peek(setTreeFilter(treeWalk)) + .flatMap(streamMatching(treeWalk, revWalk)); + } + + private Function>> streamMatching( + final TreeWalk treeWalk, + final RevWalk revWalk + ) { + return x -> Result.of(() -> { + final Stream.Builder builder = Stream.builder(); + while (treeWalk.next()) { + builder.add(namedRevBlob(treeWalk, revWalk)); + } + return builder.build(); + }); + } + + private NamedRevBlob namedRevBlob(TreeWalk treeWalk, RevWalk revWalk) { + return new NamedRevBlob( + treeWalk.getNameString(), + revWalk.lookupBlob(treeWalk.getObjectId(0)), + repository); + } + + private Consumer setTreeFilter(TreeWalk treeWalk) { + return x -> Optional.ofNullable(treeFilter).ifPresent(treeWalk::setFilter); + } + + private Consumer disableRecursion(TreeWalk treeWalk) { + return x -> treeWalk.setRecursive(false); + } + + private Function> addTreeTo(TreeWalk treeWalk) { + return tree -> Result.of(() -> { + treeWalk.addTree(tree); + return null; + }); + } + + private Callable parseBranchCommit(Ref branchRef, RevWalk revWalk) { + return () -> revWalk.parseCommit(branchRef.getObjectId()); } /** @@ -77,7 +114,7 @@ class GitTreeReader { * @return the GitTreeReader */ GitTreeReader treeFilter(final String path) { - this.treeFilter = PathFilter.create(path); + treeFilter = PathFilter.create(path); return this; } diff --git a/src/main/java/net/kemitix/gitdb/impl/HeadWriter.java b/src/main/java/net/kemitix/gitdb/impl/HeadWriter.java index 39f2107..c9d6351 100644 --- a/src/main/java/net/kemitix/gitdb/impl/HeadWriter.java +++ b/src/main/java/net/kemitix/gitdb/impl/HeadWriter.java @@ -22,11 +22,11 @@ package net.kemitix.gitdb.impl; import lombok.RequiredArgsConstructor; +import net.kemitix.mon.result.Result; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; -import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -47,16 +47,17 @@ class HeadWriter { * @param branchName the branch name * @param commitId the commit to point the branch at * @return the Ref of the new branch - * @throws IOException error writing branch head */ - Ref write(final String branchName, final ObjectId commitId) throws IOException { + Result write(final String branchName, final ObjectId commitId) { final Path branchRefPath = repository .getDirectory() .toPath() .resolve(branchName) .toAbsolutePath(); final byte[] commitIdBytes = commitId.name().getBytes(StandardCharsets.UTF_8); - Files.write(branchRefPath, commitIdBytes); - return repository.findRef(branchName); + return Result.of(() -> { + Files.write(branchRefPath, commitIdBytes); + return repository.findRef(branchName); + }); } } diff --git a/src/main/java/net/kemitix/gitdb/impl/InitGitDBRepo.java b/src/main/java/net/kemitix/gitdb/impl/InitGitDBRepo.java index a2bd757..b2cc831 100644 --- a/src/main/java/net/kemitix/gitdb/impl/InitGitDBRepo.java +++ b/src/main/java/net/kemitix/gitdb/impl/InitGitDBRepo.java @@ -22,6 +22,7 @@ package net.kemitix.gitdb.impl; import net.kemitix.gitdb.FormatVersion; +import net.kemitix.mon.result.Result; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryCache; @@ -50,35 +51,73 @@ class InitGitDBRepo { * Initialise a new GitDB repo. * * @param dbDir the directory to initialise the repo in - * @throws IOException if there is an error in creating the repo files */ - static void create(final Path dbDir) throws IOException { + static Result create(final Path dbDir) { final InitGitDBRepo initRepo = new InitGitDBRepo(); - final File validDbDir = initRepo.validDbDir(dbDir.toFile()); - validDbDir.mkdirs(); - try (Repository repository = RepositoryCache.FileKey.exact(validDbDir, FS.DETECTED).open(false)) { - repository.create(true); - initRepo.createInitialBranchOnMaster(repository); - } + return initRepo.validDbDir(dbDir.toFile()) + .peek(File::mkdirs) + .flatMap(dir -> { + try (Repository repository = RepositoryCache.FileKey.exact(dir, FS.DETECTED).open(false)) { + repository.create(true); + initRepo.createInitialBranchOnMaster(repository); + } catch (IOException e) { + return Result.error(e); + } + return Result.ok(null); + }); } - private void createInitialBranchOnMaster(final Repository repository) throws IOException { + private Result validDbDir(final File dbDir) { + return Result.ok(dbDir) + .flatMap(this::verifyIsNotAFile) + .flatMap(this::isEmptyIfExists); + } + + private Result createInitialBranchOnMaster(final Repository repository) { final GitDBRepo repo = new GitDBRepo(repository); - final ValueWriter valueWriter = new ValueWriter(repository); - final ObjectId objectId = valueWriter.write(new FormatVersion().toBytes()); - final ObjectId treeId = repo.insertNewTree(GIT_DB_VERSION, objectId); - final ObjectId commitId = repo.initialCommit(treeId, INIT_MESSAGE, INIT_USER, INIT_EMAIL); - createBranch(repository, commitId, MASTER); + return new ValueWriter(repository) + .write(new FormatVersion().toBytes()) + .flatMap(oid -> repo.insertNewTree(GIT_DB_VERSION, oid)) + .flatMap(tid -> repo.initialCommit(tid, INIT_MESSAGE, INIT_USER, INIT_EMAIL)) + .flatMap(cid -> Result.of(() -> { + createBranch(repository, cid, MASTER); + return null; + })); } - private void createBranch( + private Result verifyIsNotAFile(final File dbDir) { + if (dbDir.isFile()) { + return Result.error(new NotDirectoryException(dbDir.toString())); + } + return Result.ok(dbDir); + } + + private Result isEmptyIfExists(final File dbDir) { + if (dbDir.exists()) { + return Result.of(() -> { + try (DirectoryStream directoryStream = Files.newDirectoryStream(dbDir.toPath())) { + if (directoryStream.iterator().hasNext()) { + throw new DirectoryNotEmptyException(dbDir.toString()); + } + } + return dbDir; + } + ); + } + return Result.ok(dbDir); + } + + private Result createBranch( final Repository repository, final ObjectId commitId, final String branchName - ) throws IOException { + ) { final Path branchRefPath = branchRefPath(repository, branchName); final byte[] commitIdBytes = commitId.name().getBytes(StandardCharsets.UTF_8); - Files.write(branchRefPath, commitIdBytes); + return Result.of(() -> { + Files.write(branchRefPath, commitIdBytes); + return null; + }); } private Path branchRefPath( @@ -90,26 +129,4 @@ class InitGitDBRepo { .resolve(String.format(REFS_HEADS_FORMAT, branchName)) .toAbsolutePath(); } - - private File validDbDir(final File dbDir) throws IOException { - verifyIsNotAFile(dbDir); - if (dbDir.exists()) { - verifyIsEmpty(dbDir); - } - return dbDir; - } - - private void verifyIsEmpty(final File dbDir) throws IOException { - try (DirectoryStream directoryStream = Files.newDirectoryStream(dbDir.toPath())) { - if (directoryStream.iterator().hasNext()) { - throw new DirectoryNotEmptyException(dbDir.toString()); - } - } - } - - private void verifyIsNotAFile(final File dbDir) throws NotDirectoryException { - if (dbDir.isFile()) { - throw new NotDirectoryException(dbDir.toString()); - } - } } diff --git a/src/main/java/net/kemitix/gitdb/impl/KeyRemover.java b/src/main/java/net/kemitix/gitdb/impl/KeyRemover.java index f8cd7d0..392e540 100644 --- a/src/main/java/net/kemitix/gitdb/impl/KeyRemover.java +++ b/src/main/java/net/kemitix/gitdb/impl/KeyRemover.java @@ -22,10 +22,11 @@ package net.kemitix.gitdb.impl; import lombok.RequiredArgsConstructor; +import net.kemitix.mon.maybe.Maybe; +import net.kemitix.mon.result.Result; import org.eclipse.jgit.lib.*; import java.io.IOException; -import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Predicate; @@ -40,6 +41,24 @@ class KeyRemover { private final Repository repository; + /** + * Remove a key from the repository. + * + * @param branchRef the branch to update + * @param key the key to remove + * @return the id of the updated tree + */ + Result> remove(final Ref branchRef, final String key) { + final TreeFormatter treeFormatter = new TreeFormatter(); + final AtomicBoolean removed = new AtomicBoolean(false); + new GitTreeReader(repository) + .stream(branchRef) + .peek(s -> s.peek(flagIfFound(key, removed)) + .filter(isNotKey(key)) + .forEach(addToTree(treeFormatter))); + return insertTree(treeFormatter).maybe(oi -> removed.get()); + } + /** * Sets the boolean to true if the key matches a NamedRevBlob's name. * @@ -75,38 +94,17 @@ class KeyRemover { return item -> treeFormatter.append(item.getName(), item.getRevBlob()); } - /** - * Remove a key from the repository. - * - * @param branchRef the branch to update - * @param key the key to remove - * @return the id of the updated tree - * @throws IOException if there is an error writing the value - */ - Optional remove(final Ref branchRef, final String key) throws IOException { - final TreeFormatter treeFormatter = new TreeFormatter(); - final AtomicBoolean removed = new AtomicBoolean(false); - new GitTreeReader(repository) - .stream(branchRef) - .peek(flagIfFound(key, removed)) - .filter(isNotKey(key)) - .forEach(addToTree(treeFormatter)); - if (removed.get()) { - return Optional.of(insertTree(treeFormatter)); - } - return Optional.empty(); - } - /** * Insert a tree into the repo, returning its id. * * @param treeFormatter the formatter containing the proposed tree's data. * @return the name of the tree object. - * @throws IOException the object could not be stored. */ - private ObjectId insertTree(final TreeFormatter treeFormatter) throws IOException { + private Result insertTree(final TreeFormatter treeFormatter) { try (ObjectInserter inserter = repository.getObjectDatabase().newInserter()) { - return inserter.insert(treeFormatter); + return Result.ok(inserter.insert(treeFormatter)); + } catch (IOException e) { + return Result.error(e); } } } diff --git a/src/main/java/net/kemitix/gitdb/impl/KeyWriter.java b/src/main/java/net/kemitix/gitdb/impl/KeyWriter.java index 1dc00c1..0c7364b 100644 --- a/src/main/java/net/kemitix/gitdb/impl/KeyWriter.java +++ b/src/main/java/net/kemitix/gitdb/impl/KeyWriter.java @@ -21,10 +21,9 @@ package net.kemitix.gitdb.impl; +import net.kemitix.mon.result.Result; import org.eclipse.jgit.lib.*; -import java.io.IOException; - /** * Writes Keys into the Git Repository. * @@ -48,42 +47,41 @@ class KeyWriter { /** * Write the first key into a new tree. * - * @param key the key + * @param key the key * @param valueId the id of the value * @return the id of the new tree - * @throws IOException if there is an error writing the key */ - ObjectId writeFirst(final String key, final ObjectId valueId) throws IOException { + Result writeFirst(final String key, final ObjectId valueId) { return writeTree(key, valueId, new TreeFormatter()); } /** * Write the key into a tree. * - * @param key the key - * @param valueId the id of the value + * @param key the key + * @param valueId the id of the value * @param branchRef the branch whose tree should be updated * @return the id of the updated tree - * @throws IOException if there is an error writing the key */ - ObjectId write(final String key, final ObjectId valueId, final Ref branchRef) throws IOException { - return writeTree(key, valueId, getTreeFormatter(branchRef)); + Result write(final String key, final ObjectId valueId, final Ref branchRef) { + return getTreeFormatter(branchRef) + .flatMap(f -> writeTree(key, valueId, f)); } - private TreeFormatter getTreeFormatter(final Ref branchRef) throws IOException { + private Result getTreeFormatter(final Ref branchRef) { final TreeFormatter treeFormatter = new TreeFormatter(); final GitTreeReader gitTreeReader = new GitTreeReader(repository); - gitTreeReader.stream(branchRef) - .forEach(item -> treeFormatter.append(item.getName(), item.getRevBlob())); - return treeFormatter; + return gitTreeReader.stream(branchRef) + .peek(s -> s.forEach(item -> treeFormatter.append(item.getName(), item.getRevBlob()))) + .map(x -> treeFormatter); } - private ObjectId writeTree( + private Result writeTree( final String key, final ObjectId valueId, final TreeFormatter treeFormatter - ) throws IOException { + ) { treeFormatter.append(key, FileMode.REGULAR_FILE, valueId); - return objectInserter.insert(treeFormatter); + return Result.of(() -> objectInserter.insert(treeFormatter)); } } diff --git a/src/main/java/net/kemitix/gitdb/impl/LocalGitDB.java b/src/main/java/net/kemitix/gitdb/impl/LocalGitDB.java index b6bbb09..0965c62 100644 --- a/src/main/java/net/kemitix/gitdb/impl/LocalGitDB.java +++ b/src/main/java/net/kemitix/gitdb/impl/LocalGitDB.java @@ -22,8 +22,8 @@ package net.kemitix.gitdb.impl; import net.kemitix.gitdb.GitDB; +import net.kemitix.mon.result.Result; -import java.io.IOException; import java.nio.file.Path; /** @@ -40,9 +40,8 @@ public interface LocalGitDB extends GitDB { * @param userName the user name * @param userEmailAddress the user email address * @return a GitDB instance for the created local gitdb - * @throws IOException if there {@code dbDir} is a file or a non-empty directory */ - static GitDB init(final Path dbDir, final String userName, final String userEmailAddress) throws IOException { + static Result init(final Path dbDir, final String userName, final String userEmailAddress) { return LocalGitDBImpl.init(dbDir, userName, userEmailAddress); } @@ -54,7 +53,7 @@ public interface LocalGitDB extends GitDB { * @param userEmailAddress the user email address * @return a GitDB instance for the created local gitdb */ - static GitDB open(final Path dbDir, final String userName, final String userEmailAddress) { + static Result open(final Path dbDir, final String userName, final String userEmailAddress) { return LocalGitDBImpl.open(dbDir, userName, userEmailAddress); } diff --git a/src/main/java/net/kemitix/gitdb/impl/LocalGitDBImpl.java b/src/main/java/net/kemitix/gitdb/impl/LocalGitDBImpl.java index 7d9f4d7..9164a96 100644 --- a/src/main/java/net/kemitix/gitdb/impl/LocalGitDBImpl.java +++ b/src/main/java/net/kemitix/gitdb/impl/LocalGitDBImpl.java @@ -24,12 +24,14 @@ package net.kemitix.gitdb.impl; import net.kemitix.gitdb.GitDB; import net.kemitix.gitdb.GitDBBranch; import net.kemitix.gitdb.InvalidRepositoryException; +import net.kemitix.mon.maybe.Maybe; +import net.kemitix.mon.result.Result; import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.lib.*; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; import java.io.IOException; import java.nio.file.Path; -import java.util.Optional; import java.util.function.Function; /** @@ -43,10 +45,8 @@ final class LocalGitDBImpl implements GitDB, LocalGitDB { private static final String ERROR_OPENING_REPOSITORY = "Error opening repository"; private final Repository repository; - private final String userName; - private final String userEmailAddress; - private final Function branchInit; + private final Function> branchInit; private LocalGitDBImpl( final Repository repository, @@ -54,9 +54,7 @@ final class LocalGitDBImpl implements GitDB, LocalGitDB { final String userEmailAddress ) { this.repository = repository; - this.userName = userName; - this.userEmailAddress = userEmailAddress; - branchInit = GitDBBranchImpl.init(this.repository, this.userName, this.userEmailAddress); + branchInit = GitDBBranchImpl.init(this.repository, userName, userEmailAddress); } /** @@ -66,15 +64,14 @@ final class LocalGitDBImpl implements GitDB, LocalGitDB { * @param userName the user name * @param userEmailAddress the user email address * @return a GitDB instance for the created local gitdb - * @throws IOException if there {@code dbDir} is a file or a non-empty directory */ - static GitDB init( + static Result init( final Path dbDir, final String userName, final String userEmailAddress - ) throws IOException { - InitGitDBRepo.create(dbDir); - return open(dbDir, userName, userEmailAddress); + ) { + return InitGitDBRepo.create(dbDir) + .flatMap(c -> open(dbDir, userName, userEmailAddress)); } /** @@ -85,25 +82,43 @@ final class LocalGitDBImpl implements GitDB, LocalGitDB { * @param userEmailAddress the user email address * @return a GitDB instance for the created local gitdb */ - static GitDB open( + static Result open( final Path dbDir, final String userName, final String userEmailAddress ) { + return gitOpen(dbDir) + .map(Git::getRepository) + .maybe(Repository::isBare) + .flatMap(asErrorIfNotBare(dbDir)) + .map(toLocalGitDB(userName, userEmailAddress)); + } + + private static Result gitOpen(Path dbDir) { try { - return Optional.of(Git.open(dbDir.toFile())) - .map(Git::getRepository) - .filter(Repository::isBare) - .map(repository -> new LocalGitDBImpl(repository, userName, userEmailAddress)) - .orElseThrow(() -> new InvalidRepositoryException(NOT_A_BARE_REPO, dbDir)); + return Result.ok(Git.open(dbDir.toFile())); } catch (IOException e) { - throw new InvalidRepositoryException(ERROR_OPENING_REPOSITORY, dbDir, e); + return Result.error(new InvalidRepositoryException(ERROR_OPENING_REPOSITORY, dbDir, e)); } } + private static Function, Result> asErrorIfNotBare(final Path dbDir) { + return maybe -> Result.fromMaybe(maybe, () -> new InvalidRepositoryException(NOT_A_BARE_REPO, dbDir)); + } + + private static Function toLocalGitDB(final String userName, final String userEmailAddress) { + return repository -> new LocalGitDBImpl(repository, userName, userEmailAddress); + } + @Override - public Optional branch(final String name) throws IOException { - return Optional.ofNullable(repository.findRef(name)).map(branchInit); + public Result> branch(final String name) { + try { + return Result.invert(Maybe.maybe( + repository.findRef(name)) + .map(branchInit::apply)); + } catch (IOException e) { + return Result.error(e); + } } } diff --git a/src/main/java/net/kemitix/gitdb/impl/NamedRevBlob.java b/src/main/java/net/kemitix/gitdb/impl/NamedRevBlob.java index 09a39ec..d8376b6 100644 --- a/src/main/java/net/kemitix/gitdb/impl/NamedRevBlob.java +++ b/src/main/java/net/kemitix/gitdb/impl/NamedRevBlob.java @@ -24,7 +24,9 @@ package net.kemitix.gitdb.impl; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; +import net.kemitix.mon.result.Result; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevBlob; @@ -48,10 +50,15 @@ class NamedRevBlob { * Converts the blob to a String. * * @return a string - * @throws IOException of there was an error reading the blob */ - String blobAsString() throws IOException { - return new String(repository.open(revBlob.getId(), Constants.OBJ_BLOB).getBytes()); + Result blobAsString() { + try { + return Result.ok(repository.open(revBlob.getId(), Constants.OBJ_BLOB)) + .map(ObjectLoader::getBytes) + .map(String::new); + } catch (IOException e) { + return Result.error(e); + } } } diff --git a/src/main/java/net/kemitix/gitdb/impl/ValueWriter.java b/src/main/java/net/kemitix/gitdb/impl/ValueWriter.java index f066551..7fa9b37 100644 --- a/src/main/java/net/kemitix/gitdb/impl/ValueWriter.java +++ b/src/main/java/net/kemitix/gitdb/impl/ValueWriter.java @@ -21,13 +21,12 @@ package net.kemitix.gitdb.impl; +import net.kemitix.mon.result.Result; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; -import java.io.IOException; - /** * Writes Values into the Git Repository. * @@ -51,9 +50,8 @@ class ValueWriter { * * @param blob the value blob * @return the id of the value object - * @throws IOException if there is an error writing the value */ - ObjectId write(final byte[] blob) throws IOException { - return objectInserter.insert(Constants.OBJ_BLOB, blob); + Result write(final byte[] blob) { + return Result.of(() -> objectInserter.insert(Constants.OBJ_BLOB, blob)); } } diff --git a/src/test/java/net/kemitix/gitdb/test/GitDBTest.java b/src/test/java/net/kemitix/gitdb/test/GitDBTest.java index c2736b7..fdf15b8 100644 --- a/src/test/java/net/kemitix/gitdb/test/GitDBTest.java +++ b/src/test/java/net/kemitix/gitdb/test/GitDBTest.java @@ -5,58 +5,53 @@ import net.kemitix.gitdb.FormatVersion; import net.kemitix.gitdb.GitDB; import net.kemitix.gitdb.GitDBBranch; import net.kemitix.gitdb.InvalidRepositoryException; +import net.kemitix.mon.maybe.Maybe; +import net.kemitix.mon.result.Result; import org.assertj.core.api.WithAssertions; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintStream; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.Files; import java.nio.file.NotDirectoryException; import java.nio.file.Path; -import java.util.Optional; import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; -import static org.assertj.core.api.Assumptions.assumeThat; - -@ExtendWith(MockitoExtension.class) class GitDBTest implements WithAssertions { private final Supplier stringSupplier = UUID.randomUUID()::toString; private final String userName = stringSupplier.get(); private final String userEmailAddress = stringSupplier.get(); - private static void tree(final Path dbDir, final PrintStream out) throws IOException { - final Process treeProcess = new ProcessBuilder("tree", dbDir.toString()).start(); - try (final BufferedReader reader = new BufferedReader(new InputStreamReader(treeProcess.getInputStream()))) { - String line; - while (null != (line = reader.readLine())) { - out.println("line = " + line); - } - } - } - // When initialising a repo in a dir that doesn't exist then a bare repo is created @Test void initRepo_whenDirNotExist_thenCreateBareRepo() throws IOException { //given final Path dir = dirDoesNotExist(); //when - final GitDB gitDB = GitDB.initLocal(dir, userName, userEmailAddress); + final Result gitDB = GitDB.initLocal(dir, userName, userEmailAddress); //then - assertThat(gitDB).isNotNull(); + assertThatResultIsOkay(gitDB); assertThatIsBareRepo(dir); } + private Path dirDoesNotExist() throws IOException { + final Path directory = Files.createTempDirectory("gitdb"); + Files.delete(directory); + return directory; + } + + private void assertThatResultIsOkay(final Result result) { + assertThat(result.isOkay()).isTrue(); + } + private void assertThatIsBareRepo(final Path dbDir) throws IOException { final Git git = Git.open(dbDir.toFile()); final Repository repository = git.getRepository(); @@ -65,56 +60,64 @@ class GitDBTest implements WithAssertions { assertThat(repository.getDirectory()).isEqualTo(dbDir.toFile()); } - private Path dirDoesNotExist() throws IOException { - final Path directory = Files.createTempDirectory("gitdb"); - Files.delete(directory); - return directory; - } - // When initialising a repo in a dir that is a file then an exception is thrown @Test void initRepo_whenDirIsFile_thenThrowException() throws IOException { //given final Path dir = fileExists(); + //when + final Result gitDBResult = GitDB.initLocal(dir, userName, userEmailAddress); //then - assertThatExceptionOfType(NotDirectoryException.class) - .isThrownBy(() -> GitDB.initLocal(dir, userName, userEmailAddress)) - .withMessageContaining(dir.toString()); + gitDBResult.match( + failOnSuccess("Is a file not a directory"), + error -> assertThat(error) + .isInstanceOf(NotDirectoryException.class) + .hasMessageContaining(dir.toString()) + ); } private Path fileExists() throws IOException { return Files.createTempFile("gitdb", "file"); } + private Consumer failOnSuccess(String message) { + return success -> fail(message); + } + // When initialising a repo in a non-empty dir then an exception is thrown @Test void initRepo_whenNotEmptyDir_thenThrowException() throws IOException { //given final Path dir = dirExists(); filesExistIn(dir); + //when + final Result gitDBResult = GitDB.initLocal(dir, userName, userEmailAddress); //then - assertThatExceptionOfType(DirectoryNotEmptyException.class) - .isThrownBy(() -> GitDB.initLocal(dir, userName, userEmailAddress)) - .withMessageContaining(dir.toString()); - } - - private void filesExistIn(final Path dir) throws IOException { - Files.createTempFile(dir, "gitdb", "file"); + gitDBResult.match( + failOnSuccess("Directory is not empty"), + error -> assertThat(error) + .isInstanceOf(DirectoryNotEmptyException.class) + .hasMessageContaining(dir.toString()) + ); } private Path dirExists() throws IOException { return Files.createTempDirectory("gitdb"); } + private void filesExistIn(final Path dir) throws IOException { + Files.createTempFile(dir, "gitdb", "file"); + } + // When initialising a repo in a empty dir then a bare repo is created @Test void initRepo_whenEmptyDir_thenCreateBareRepo() throws IOException { //given final Path dir = dirExists(); //when - final GitDB gitDB = GitDB.initLocal(dir, userName, userEmailAddress); + final Result gitDB = GitDB.initLocal(dir, userName, userEmailAddress); //then - assertThat(gitDB).isNotNull(); + assertThatResultIsOkay(gitDB); assertThatIsBareRepo(dir); } @@ -123,10 +126,16 @@ class GitDBTest implements WithAssertions { void openRepo_NotBareRepo_thenThrowException() throws IOException { //given final Path dir = dirExists(); + //when + final Result gitDBResult = GitDB.openLocal(dir, userName, userEmailAddress); //then - assertThatExceptionOfType(InvalidRepositoryException.class) - .isThrownBy(() -> GitDB.openLocal(dir, userName, userEmailAddress)) - .withMessageContaining(dir.toString()); + gitDBResult.match( + failOnSuccess("Not a bare repo"), + error -> assertThat(error) + .isInstanceOf(InvalidRepositoryException.class) + .hasMessageContaining(dir.toString()) + ); + } // When opening a repo in a dir that is a file then an exception is thrown @@ -134,10 +143,15 @@ class GitDBTest implements WithAssertions { void openRepo_whenDirIsFile_thenThrowException() throws IOException { //given final Path dir = fileExists(); + //when + final Result gitDBResult = GitDB.openLocal(dir, userName, userEmailAddress); //then - assertThatExceptionOfType(InvalidRepositoryException.class) - .isThrownBy(() -> GitDB.openLocal(dir, userName, userEmailAddress)) - .withMessageContaining(dir.toString()); + gitDBResult.match( + failOnSuccess("Directory is a file"), + error -> assertThat(error) + .isInstanceOf(InvalidRepositoryException.class) + .hasMessageContaining(dir.toString()) + ); } // When opening a repo in a dir that doesn't exist then an exception is thrown @@ -145,10 +159,15 @@ class GitDBTest implements WithAssertions { void openRepo_whenDirNotExist_thenThrowException() throws IOException { //given final Path dir = dirDoesNotExist(); + //when + final Result gitDBResult = GitDB.openLocal(dir, userName, userEmailAddress); //then - assertThatExceptionOfType(InvalidRepositoryException.class) - .isThrownBy(() -> GitDB.openLocal(dir, userName, userEmailAddress)) - .withMessageContaining(dir.toString()); + gitDBResult.match( + failOnSuccess("Directory does not exist"), + error -> assertThat(error) + .isInstanceOf(InvalidRepositoryException.class) + .hasMessageContaining(dir.toString()) + ); } // When opening a repo in a dir that is not a bare repo then an exception is thrown @@ -156,12 +175,17 @@ class GitDBTest implements WithAssertions { void openRepo_whenRepoNotBare_thenThrowException() throws IOException, GitAPIException { //given final Path dir = nonBareRepo(); + //when + final Result gitDBResult = GitDB.openLocal(dir, userName, userEmailAddress); //then - assertThatExceptionOfType(InvalidRepositoryException.class) - .isThrownBy(() -> GitDB.openLocal(dir, userName, userEmailAddress)) - .withMessageContaining("Invalid GitDB repo") - .withMessageContaining("Not a bare repo") - .withMessageContaining(dir.toString()); + gitDBResult.match( + failOnSuccess("Not a bare repo"), + error -> assertThat(error) + .isInstanceOf(InvalidRepositoryException.class) + .hasMessageContaining("Invalid GitDB repo") + .hasMessageContaining("Not a bare repo") + .hasMessageContaining(dir.toString()) + ); } private Path nonBareRepo() throws IOException, GitAPIException { @@ -176,9 +200,13 @@ class GitDBTest implements WithAssertions { //given final Path dir = gitDBRepoPath(); //when - final GitDB gitDB = GitDB.openLocal(dir, userName, userEmailAddress); + final Result gitDB = GitDB.openLocal(dir, userName, userEmailAddress); //then - assertThat(gitDB).isNotNull(); + assertThat(gitDB.isOkay()).isTrue(); + gitDB.match( + success -> assertThat(success).isNotNull(), + error -> fail("did not open local repo") + ); } private Path gitDBRepoPath() throws IOException { @@ -189,144 +217,179 @@ class GitDBTest implements WithAssertions { // Given a valid GitDb handle - private GitDB gitDB(final Path dbDir) throws IOException { + // When select a branch that doesn't exist then an empty Optional is returned + @Test + void selectBranch_whenBranchNotExist_thenEmptyOptional() throws Throwable { + //given + final Result gitDb = gitDB(dirDoesNotExist()); + //when + final Result> branch = gitDb.flatMap(selectBranch("unknown")); + //then + assertThat(branch.orElseThrow().toOptional()).isEmpty(); + } + + private Result gitDB(final Path dbDir) { return GitDB.initLocal(dbDir, userName, userEmailAddress); } - // When select a branch that doesn't exist then an empty Optional is returned - @Test - void selectBranch_whenBranchNotExist_thenEmptyOptional() throws IOException { - //given - final GitDB gitDb = gitDB(dirDoesNotExist()); - //when - final Optional branch = gitDb.branch("unknown"); - //then - assertThat(branch).isEmpty(); - } - - // When select a valid branch then a GitDbBranch is returned - @Test - void selectBranch_branchExists_thenReturnBranch() throws IOException { - //given - final Path dbDir = dirDoesNotExist(); - final GitDB gitDb = gitDB(dbDir); - //when - final Optional branch = gitDb.branch("master"); - //then - assertThat(branch).as("Branch master exists").isNotEmpty(); + private Function>> selectBranch(final String branchName) { + return db -> db.branch(branchName); } // Given a valid GitDbBranch handle - private GitDBBranch gitDBBranch() throws IOException { - final GitDB gitDB = gitDB(dirDoesNotExist()); - final Optional branchOptional = gitDB.branch("master"); - assumeThat(branchOptional).isNotEmpty(); - return branchOptional.get(); + // When select a valid branch then a GitDbBranch is returned + @Test + void selectBranch_branchExists_thenReturnBranch() throws Throwable { + //given + final Path dbDir = dirDoesNotExist(); + final Result gitDb = gitDB(dbDir); + //when + final Result> branch = gitDb.flatMap(selectBranch("master")); + //then + assertThat(branch.orElseThrow().toOptional()).as("Branch master exists").isNotEmpty(); } // When getting a key that does not exist then return an empty Optional @Test - void getKey_whenKeyNotExist_thenReturnEmptyOptional() throws IOException, ClassNotFoundException { + void getKey_whenKeyNotExist_thenReturnEmptyOptional() { //given final GitDBBranch branch = gitDBBranch(); //when - final Optional value = branch.get("unknown"); + final Result> value = branch.get("unknown"); //then - assertThat(value).isEmpty(); + value.match( + success -> assertThat(success.toOptional()).isEmpty(), + failOnError() + ); + } + + private GitDBBranch gitDBBranch() { + try { + return gitDB(dirDoesNotExist()) + .flatMap(selectBranch("master")) + .orElseThrow().orElse(null); + } catch (Throwable throwable) { + throw new RuntimeException("Couldn't create master branch", throwable); + } + } + + private Consumer failOnError() { + return error -> fail("Not an error"); } // When getting the format version it matches expected @Test - void getVersionFormat_thenFormatIsSet() throws IOException { + void getVersionFormat_thenFormatIsSet() { //given final GitDBBranch gitDBBranch = gitDBBranch(); - //when - final Optional formatVersion = gitDBBranch.getFormatVersion(); - //then final Version version = new FormatVersion().getVersion(); - assertThat(formatVersion).contains(version); - assertThat(formatVersion.get()).isNotSameAs(version); + //when + final Result> formatVersion = gitDBBranch.getFormatVersion(); + //then + formatVersion.match( + success -> success.peek(v -> assertThat(v).isEqualTo(version).isNotSameAs(version)), + failOnError() + ); } // When putting a key/value pair then a GitDbBranch is returned @Test - void putValue_thenReturnUpdatedGitDBBranch() throws IOException { + void putValue_thenReturnUpdatedGitDBBranch() { //given final GitDBBranch originalBranch = gitDBBranch(); //when - final GitDBBranch updatedBranch = originalBranch.put("key-name", "value"); + final Result updatedBranch = originalBranch.put("key-name", "value"); //then - assertThat(updatedBranch).isNotNull(); - assertThat(updatedBranch).isNotSameAs(originalBranch); + updatedBranch.match( + success -> assertThat(success).isNotNull().isNotSameAs(originalBranch), + failOnError() + ); } // When getting a key that does exist then the value is returned inside an Optional @Test - void getKey_whenExists_thenReturnValueInOptional() throws IOException, ClassNotFoundException { + void getKey_whenExists_thenReturnValueInOptional() { //given final String key = stringSupplier.get(); final String value = stringSupplier.get(); final GitDBBranch originalBranch = gitDBBranch(); - final GitDBBranch updatedBranch = originalBranch.put(key, value); + final Result updatedBranch = originalBranch.put(key, value); //when - final Optional result = updatedBranch.get(key); + final Result> result = updatedBranch.flatMap(b -> b.get(key)); //then - assertThat(result).contains(value); + result.match( + success -> success.map(v -> assertThat(v).contains(value)), + failOnError() + ); } // When removing a key that does not exist then the GitDbBranch is returned @Test - void removeKey_whenNotExist_thenReturnOriginal() throws IOException { + void removeKey_whenNotExist_thenReturnOriginal() { //given final GitDBBranch gitDBBranch = gitDBBranch(); //when - final GitDBBranch result = gitDBBranch.remove("unknown"); + final Result result = gitDBBranch.remove("unknown"); //then - assertThat(result).isSameAs(gitDBBranch); + result.match( + success -> assertThat(success).isSameAs(gitDBBranch), + failOnError() + ); } // When removing a key that does exist then a GitDbBranch is returned @Test - void removeKey_whenExists_thenReturnUpdatedBranch() throws IOException { + void removeKey_whenExists_thenReturnUpdatedBranch() { //given final String key = stringSupplier.get(); final String value = stringSupplier.get(); - final GitDBBranch originalBranch = gitDBBranchWithKeyValue(key, value); + final Result originalBranch = gitDBBranchWithKeyValue(key, value); //when - final GitDBBranch updatedBranch = originalBranch.remove(key); + final Result updatedBranch = originalBranch.flatMap(b -> b.remove(key)); //then - assertThat(updatedBranch).isNotSameAs(originalBranch); + updatedBranch.match( + success -> assertThat(success).isNotSameAs(originalBranch), + failOnError() + ); } - private GitDBBranch gitDBBranchWithKeyValue(final String key, final String value) throws IOException { + private Result gitDBBranchWithKeyValue(final String key, final String value) { return gitDBBranch().put(key, value); } // When removing a key that does exist then original GitDbBranch can still find it @Test - void removeKey_whenExists_thenOriginalCanStillFind() throws IOException { + void removeKey_whenExists_thenOriginalCanStillFind() { //given final String key = stringSupplier.get(); final String value = stringSupplier.get(); - final GitDBBranch originalBranch = gitDBBranchWithKeyValue(key, value); + final Result originalBranch = gitDBBranchWithKeyValue(key, value); //when - final GitDBBranch updatedBranch = originalBranch.remove(key); + final Result updatedBranch = originalBranch.flatMap(b -> b.remove(key)); //then - assertThat(originalBranch.get(key)).contains(value); + originalBranch.flatMap(b -> b.get(key)) + .match( + success -> assertThat(success.toOptional()).contains(value), + failOnError() + ); } // When removing a key that does exist then the updated GitDbBranch can't find it @Test - void removeKey_whenExists_thenUpdatedCanNotFind() throws IOException { + void removeKey_whenExists_thenUpdatedCanNotFind() { //given final String key = stringSupplier.get(); final String value = stringSupplier.get(); - final GitDBBranch originalBranch = gitDBBranchWithKeyValue(key, value); + final Result originalBranch = gitDBBranchWithKeyValue(key, value); //when - final GitDBBranch updatedBranch = originalBranch.remove(key); + final Result updatedBranch = originalBranch.flatMap(b -> b.remove(key)); //then - assertThat(updatedBranch.get(key)).isEmpty(); + updatedBranch.flatMap(b -> b.get(key)) + .match( + success -> assertThat(success.toOptional()).isEmpty(), + failOnError() + ); } }