From 165664947e6b5f7a60aa8c5c85a11fecb64d5dea Mon Sep 17 00:00:00 2001
From: Volker Schukai <volker.schukai@schukai.com>
Date: Tue, 6 Sep 2022 20:13:22 +0200
Subject: [PATCH] initial version

---
 .gitignore                                    |  144 ++
 .idea/.gitignore                              |    8 +
 .idea/aws.xml                                 |   17 +
 .idea/markdown.xml                            |    9 +
 .idea/misc.xml                                |    6 +
 .idea/modules.xml                             |    9 +
 .idea/vcs.xml                                 |    6 +
 Makefile                                      |  131 ++
 README.md                                     |  383 +++++-
 api.go                                        |   53 +
 api_test.go                                   |   66 +
 benchmark_test.go                             |   22 +
 change.go                                     |   73 ++
 change_test.go                                |  102 ++
 configuration.iml                             |   10 +
 constants.go                                  |    7 +
 env.go                                        |   50 +
 env_test.go                                   |   75 ++
 error.go                                      |   78 ++
 export.go                                     |   91 ++
 export_test.go                                |   54 +
 file.go                                       |  185 +++
 file_test.go                                  |  604 +++++++++
 filesystem.go                                 |   12 +
 filesystem_test.go                            |   13 +
 flags.go                                      |   71 +
 flags_test.go                                 |  122 ++
 format.go                                     |   31 +
 format_test.go                                |   19 +
 go.mod                                        |   27 +
 go.sum                                        |   61 +
 http-handler.go                               |  107 ++
 http-handler_test.go                          |  316 +++++
 import.go                                     |  151 +++
 import_test.go                                |   28 +
 integration_test.go                           |   46 +
 licenses/github.com/imdario/mergo/LICENSE     |   28 +
 .../magiconair/properties/LICENSE.md          |   24 +
 .../github.com/pelletier/go-toml/v2/LICENSE   |   21 +
 licenses/github.com/r3labs/diff/v3/.gitignore |   17 +
 .../github.com/r3labs/diff/v3/.travis.yml     |    8 +
 .../github.com/r3labs/diff/v3/CONTRIBUTING.md |   80 ++
 licenses/github.com/r3labs/diff/v3/LICENSE    |  373 ++++++
 licenses/github.com/r3labs/diff/v3/Makefile   |   11 +
 licenses/github.com/r3labs/diff/v3/README.md  |  327 +++++
 .../github.com/r3labs/diff/v3/change_value.go |  209 +++
 .../github.com/r3labs/diff/v3/comparative.go  |   44 +
 licenses/github.com/r3labs/diff/v3/diff.go    |  417 ++++++
 .../github.com/r3labs/diff/v3/diff_bool.go    |   29 +
 .../r3labs/diff/v3/diff_comparative.go        |   62 +
 .../r3labs/diff/v3/diff_examples_test.go      |  611 +++++++++
 .../github.com/r3labs/diff/v3/diff_float.go   |   35 +
 .../github.com/r3labs/diff/v3/diff_int.go     |   35 +
 .../r3labs/diff/v3/diff_interface.go          |   39 +
 .../github.com/r3labs/diff/v3/diff_map.go     |   81 ++
 .../github.com/r3labs/diff/v3/diff_pointer.go |   60 +
 .../github.com/r3labs/diff/v3/diff_slice.go   |  141 ++
 .../github.com/r3labs/diff/v3/diff_string.go  |   34 +
 .../github.com/r3labs/diff/v3/diff_struct.go  |  123 ++
 .../github.com/r3labs/diff/v3/diff_test.go    | 1156 +++++++++++++++++
 .../github.com/r3labs/diff/v3/diff_time.go    |   36 +
 .../github.com/r3labs/diff/v3/diff_uint.go    |   35 +
 licenses/github.com/r3labs/diff/v3/error.go   |   74 ++
 licenses/github.com/r3labs/diff/v3/filter.go  |   22 +
 licenses/github.com/r3labs/diff/v3/go.mod     |    9 +
 licenses/github.com/r3labs/diff/v3/go.sum     |   24 +
 licenses/github.com/r3labs/diff/v3/options.go |   93 ++
 licenses/github.com/r3labs/diff/v3/patch.go   |  237 ++++
 .../github.com/r3labs/diff/v3/patch_map.go    |  105 ++
 .../github.com/r3labs/diff/v3/patch_slice.go  |   78 ++
 .../github.com/r3labs/diff/v3/patch_struct.go |   66 +
 .../github.com/r3labs/diff/v3/patch_test.go   |  351 +++++
 .../github.com/vmihailenco/msgpack/LICENSE    |   25 +
 licenses/golang.org/x/exp/LICENSE             |   27 +
 licenses/gopkg.in/yaml.v3/LICENSE             |   50 +
 licenses/gopkg.in/yaml.v3/NOTICE              |   13 +
 properties.go                                 |   35 +
 properties_test.go                            |   29 +
 release.json                                  |    1 +
 settings.go                                   |   70 +
 settings_test.go                              |   51 +
 stream.go                                     |   41 +
 stream_test.go                                |   95 ++
 tags.go                                       |   51 +
 tags_test.go                                  |   81 ++
 watch.go                                      |  122 ++
 watch_test.go                                 |  143 ++
 87 files changed, 9060 insertions(+), 56 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 .idea/.gitignore
 create mode 100644 .idea/aws.xml
 create mode 100644 .idea/markdown.xml
 create mode 100644 .idea/misc.xml
 create mode 100644 .idea/modules.xml
 create mode 100644 .idea/vcs.xml
 create mode 100644 Makefile
 create mode 100644 api.go
 create mode 100644 api_test.go
 create mode 100644 benchmark_test.go
 create mode 100644 change.go
 create mode 100644 change_test.go
 create mode 100644 configuration.iml
 create mode 100644 constants.go
 create mode 100644 env.go
 create mode 100644 env_test.go
 create mode 100644 error.go
 create mode 100644 export.go
 create mode 100644 export_test.go
 create mode 100644 file.go
 create mode 100644 file_test.go
 create mode 100644 filesystem.go
 create mode 100644 filesystem_test.go
 create mode 100644 flags.go
 create mode 100644 flags_test.go
 create mode 100644 format.go
 create mode 100644 format_test.go
 create mode 100644 go.mod
 create mode 100644 go.sum
 create mode 100644 http-handler.go
 create mode 100644 http-handler_test.go
 create mode 100644 import.go
 create mode 100644 import_test.go
 create mode 100644 integration_test.go
 create mode 100644 licenses/github.com/imdario/mergo/LICENSE
 create mode 100644 licenses/github.com/magiconair/properties/LICENSE.md
 create mode 100644 licenses/github.com/pelletier/go-toml/v2/LICENSE
 create mode 100644 licenses/github.com/r3labs/diff/v3/.gitignore
 create mode 100644 licenses/github.com/r3labs/diff/v3/.travis.yml
 create mode 100644 licenses/github.com/r3labs/diff/v3/CONTRIBUTING.md
 create mode 100644 licenses/github.com/r3labs/diff/v3/LICENSE
 create mode 100644 licenses/github.com/r3labs/diff/v3/Makefile
 create mode 100644 licenses/github.com/r3labs/diff/v3/README.md
 create mode 100644 licenses/github.com/r3labs/diff/v3/change_value.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/comparative.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/diff.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/diff_bool.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/diff_comparative.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/diff_examples_test.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/diff_float.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/diff_int.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/diff_interface.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/diff_map.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/diff_pointer.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/diff_slice.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/diff_string.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/diff_struct.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/diff_test.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/diff_time.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/diff_uint.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/error.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/filter.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/go.mod
 create mode 100644 licenses/github.com/r3labs/diff/v3/go.sum
 create mode 100644 licenses/github.com/r3labs/diff/v3/options.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/patch.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/patch_map.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/patch_slice.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/patch_struct.go
 create mode 100644 licenses/github.com/r3labs/diff/v3/patch_test.go
 create mode 100644 licenses/github.com/vmihailenco/msgpack/LICENSE
 create mode 100644 licenses/golang.org/x/exp/LICENSE
 create mode 100644 licenses/gopkg.in/yaml.v3/LICENSE
 create mode 100644 licenses/gopkg.in/yaml.v3/NOTICE
 create mode 100644 properties.go
 create mode 100644 properties_test.go
 create mode 100644 release.json
 create mode 100644 settings.go
 create mode 100644 settings_test.go
 create mode 100644 stream.go
 create mode 100644 stream_test.go
 create mode 100644 tags.go
 create mode 100644 tags_test.go
 create mode 100644 watch.go
 create mode 100644 watch_test.go

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..207bbd6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,144 @@
+# Created by https://www.toptal.com/developers/gitignore/api/intellij,go
+# Edit at https://www.toptal.com/developers/gitignore?templates=intellij,go
+
+### Go ###
+# If you prefer the allow list template instead of the deny list, see community template:
+# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
+#
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+
+# Go workspace file
+go.work
+
+### Go Patch ###
+/vendor/
+/Godeps/
+
+### Intellij ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Intellij Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
+
+# Azure Toolkit for IntelliJ plugin
+# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
+.idea/**/azureSettings.xml
+
+# End of https://www.toptal.com/developers/gitignore/api/intellij,go
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/aws.xml b/.idea/aws.xml
new file mode 100644
index 0000000..ec328d0
--- /dev/null
+++ b/.idea/aws.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="accountSettings">
+    <option name="activeProfile" value="profile:default" />
+    <option name="activeRegion" value="eu-west-1" />
+    <option name="recentlyUsedProfiles">
+      <list>
+        <option value="profile:default" />
+      </list>
+    </option>
+    <option name="recentlyUsedRegions">
+      <list>
+        <option value="eu-west-1" />
+      </list>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/markdown.xml b/.idea/markdown.xml
new file mode 100644
index 0000000..ec0b30f
--- /dev/null
+++ b/.idea/markdown.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="MarkdownSettings">
+    <enabledExtensions>
+      <entry key="MermaidLanguageExtension" value="false" />
+      <entry key="PlantUMLLanguageExtension" value="true" />
+    </enabledExtensions>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..639900d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectRootManager">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..ee399e3
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/configuration.iml" filepath="$PROJECT_DIR$/configuration.iml" />
+      <module fileurl="file://$PROJECT_DIR$/../http-negotiation/http-negotiation.iml" filepath="$PROJECT_DIR$/../http-negotiation/http-negotiation.iml" />
+    </modules>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0e828da
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,131 @@
+PROJECT_ROOT:=$(dir $(realpath $(lastword $(MAKEFILE_LIST))))
+THIS_MAKEFILE:=$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
+THIS_MAKEFILE_PATH:=$(PROJECT_ROOT)$(THIS_MAKEFILE) 
+
+# @see .PHONY https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html#Phony-Targets
+.DEFAULT_GOAL := help
+
+.PHONY: print
+## Print Path	
+print:
+	@echo "THIS_MAKEFILE:      $(THIS_MAKEFILE)"
+	@echo "THIS_MAKEFILE_PATH: $(THIS_MAKEFILE_PATH)"
+	@echo "PROJECT_ROOT:       $(PROJECT_ROOT)"
+
+# Add a comment to the public targets so that it appears
+# in this help Use two # characters for a help comment
+.PHONY: help
+help:
+	@printf "${COMMENT}Usage:${RESET}\n"
+	@printf " make [target] [arg=\"val\"...]\n\n"
+	@printf "${COMMENT}Available targets:${RESET}\n"
+	@awk '/^[a-zA-Z\-\\_0-9\.@]+:/ { \
+		helpMessage = match(lastLine, /^## (.*)/); \
+		if (helpMessage) { \
+			helpCommand = substr($$1, 0, index($$1, ":")); \
+			helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \
+			printf " ${INFO}%-22s${RESET} %s\n", helpCommand, helpMessage; \
+		} \
+	} \
+	{ lastLine = $$0 }' $(MAKEFILE_LIST)
+	@printf "\n${COMMENT}Available arguments:${RESET}\n\n"
+	@awk '/^(([a-zA-Z\-\\_0-9\.@]+)\s[?:]?=)/ { \
+		helpMessage = match(lastLine, /^## (.*)/); \
+		if (helpMessage) { \
+			helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \
+			printf " ${INFO}%-22s${RESET} %s (Default: %s)\n", $$1, helpMessage, $$3; \
+		} \
+	} \
+	{ lastLine = $$0 }' $(MAKEFILE_LIST)
+
+
+## run tests
+test:
+	echo "Running tests"
+	go test -cover -v ./...
+
+## run tests with fuzzing
+test-fuzz:
+	echo "Running fuzz tests"
+	go test -v -fuzztime=30s -fuzz=Fuzz ./...
+
+#### VERSION
+BIN_DIR ?= $(shell echo $$HOME)/.local/bin/
+VERSION_NAME 	     := version
+EXECUTABLES = $(EXECUTABLES:-) $(VERSION_NAME)
+VERSION_BIN_PATH := $(BIN_DIR)$(VERSION_NAME)
+
+VERSION_BIN := $(shell command -v $(VERSION_NAME) 2> /dev/null)
+
+ifndef VERSION_BIN
+    $(shell curl -o $(VERSION_BIN_PATH) http://download.schukai.com/tools/version/version-$(shell uname -s | tr [:upper:] [:lower:])-$(shell echo `uname -m | sed s/aarch64/arm64/ | sed s/x86_64/amd64/`))
+    $(shell chmod +x $(VERSION_BIN_PATH))
+endif
+     
+RELEASE_FILE ?= $(PROJECT_ROOT)release.json
+ 
+ifeq ("$(wildcard $(RELEASE_FILE))","")
+  $(shell echo '{"version":"0.1.0"}' > $(RELEASE_FILE))
+endif
+
+PROJECT_VERSION ?= $(shell cat $(RELEASE_FILE) | jq -r .version)
+PROJECT_BUILD_DATE ?= $(shell $(VERSION_BIN) date)
+
+.PHONY: next-patch-version
+## create next patch version
+next-patch-version: check-clean-repo
+	echo "Creating next version"
+	$(VERSION_BIN) patch --path $(RELEASE_FILE) --selector "version"
+	git add $(RELEASE_FILE) && git commit -m "Bump version to $$(cat $(RELEASE_FILE) | jq -r .version)"
+
+.PHONY: next-minor-version
+## create next minor version
+next-minor-version: check-clean-repo
+	echo  "Creating next minor version"
+	$(VERSION_BIN) minor --path $(RELEASE_FILE) --selector "version"
+	git add $(RELEASE_FILE) && git commit -m "Bump version to $$( cat $(RELEASE_FILE) | jq -r .version)"
+
+.PHONY: next-major-version
+## create next major version
+next-major-version: check-clean-repo
+	echo "Creating next minor version"
+	$(VERSION_BIN) major --path $(RELEASE_FILE) --selector "version"
+	git add $(RELEASE_FILE) && git commit -m "Bump version to $$(cat $(RELEASE_FILE) | jq -r .version)"
+
+.PHONY: check-clean-repo
+check-clean-repo:
+	git diff-index --quiet HEAD || (echo "There are uncommitted changes after running make. Please commit or stash them before running make."; exit 1)
+	
+## tag repository with next patch version
+tag-patch-version: next-patch-version 
+	echo "Tagging patch version"
+	$(eval PROJECT_VERSION := $(shell cat $(RELEASE_FILE) | jq -r .version))
+	git tag -a v$(PROJECT_VERSION) -m "Version $(PROJECT_VERSION)"
+
+## tag repository with next minor version
+tag-minor-version: next-minor-version 
+	echo "Tagging minor version"
+	$(eval PROJECT_VERSION := $(shell cat $(RELEASE_FILE) | jq -r .version))
+	git tag -a v$(PROJECT_VERSION) -m "Version $(PROJECT_VERSION)"
+
+## tag repository with next major version
+tag-major-version: next-major-version 
+	echo "Tagging major version"
+	$(eval PROJECT_VERSION := $(shell cat $(RELEASE_FILE) | jq -r .version))
+	git tag -a v$(PROJECT_VERSION) -m "Version $(PROJECT_VERSION)"
+
+GO_MOD_FILE := $(SOURCE_PATH)go.mod
+
+ifeq ($(shell test -e $(GO_MOD_FILE) && echo -n yes),yes)
+    GO_CURRENT_MODULE := $(shell cat $(GO_MOD_FILE) | head -n1 | cut -d" " -f2)
+	# go install github.com/google/go-licenses@latest
+	EXECUTABLES = $(EXECUTABLES:-) go-licenses;    
+endif
+
+.PHONY: fetch-licenses
+## Fetch licenses for all modules
+fetch-licenses:
+	go-licenses save $(GO_CURRENT_MODULE) --ignore gitlab.schukai.com --force --save_path $(PROJECT_ROOT)licenses/ ; cd -
+
+
+
diff --git a/README.md b/README.md
index ca5bf8d..2b7a26e 100644
--- a/README.md
+++ b/README.md
@@ -1,92 +1,363 @@
-# Configuration
+## Configuration
 
+## Installation
+
+```shell
+go get gitlab.schukai.com/oss/libraries/go/application/configuration
+```
+
+**Note:** This library uses [Go Modules](https://github.com/golang/go/wiki/Modules) to manage dependencies.
+
+## What do this library?
+
+This library provides a simple way to load configuration from different sources.
+
+It supports:
+
+* [x] Environment variables
+* [x] Command line flags
+* [x] Configuration files
+  * [x] JSON
+  * [x] YAML
+  * [x] TOML
+  * [x] Properties
+* [x] Configuration from a struct
+* [x] HTTP API (get and set configuration)
+
+## Usage
+
+### Initialize
+
+A new configuration is created using the `configuration.New()` function. The passed
+structure is used type for the configuration. The values are taken as default values
+of the configuration.
+
+```go
+package main
+
+import (
+    "fmt"
+    "os"
+    "gitlab.schukai.com/oss/libraries/go/application/configuration"
+)
+
+func main(){
+  config := struct {
+    Host string
+    Port int
+  }{
+    Host: "localhost",
+    Port: 8080,
+  }
+
+  c := configuration.New(config)
+
+  fmt.Println(c.Config().Host)
+  fmt.Println(c.Config().Port)
+
+}
+
+```
+
+Configuration values can come from different sources. The order of
+sources is important.
+
+#### Environment variables
+
+With the `Environment` function, you can load configuration from environment variables.
+
+```go
+package main
 
+import (
+    "fmt"
+    "os"
+    "gitlab.schukai.com/oss/libraries/go/application/configuration"
+)
 
-## Getting started
+func main(){
+  config := struct {
+    Host string `env:"HOST"`
+  }{
+    Host: "localhost",
+  }
 
-To make it easy for you to get started with GitLab, here's a list of recommended next steps.
+  // Set value
+  os.Setenv("HOST", "www.example.com")
 
-Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
+  s := configuration.New(config)
+  fmt.Println(s.Config().Host) // localhost
 
-## Add your files
+  s.InitFromEnv("") // no prefix
+  fmt.Println(s.Config().Host) // www.example.com
 
-- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
-- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
+}
 
 ```
-cd existing_repo
-git remote add origin https://gitlab.schukai.com/oss/libraries/go/application/configuration.git
-git branch -M master
-git push -uf origin master
+
+#### Command line flags
+
+Obviously, you can also load configuration from command line flags. This library supports
+the standard library [flag](https://golang.org/pkg/flag/) package.
+
+```go
+package main
+
+import (
+    "fmt"
+    "os"
+   "flag"
+    "gitlab.schukai.com/oss/libraries/go/application/configuration"
+)
+
+func main(){
+  config := struct {
+    Host string `flag:"host"`
+  }{
+    Host: "localhost",
+  }
+
+  // Set value
+  flag.String("host", "www.example.com", "help message for host flag")
+  flag.Parse()
+
+  s := configuration.New(config)
+  s.InitFromFlagSet(flag.CommandLine)
+
+  fmt.Println(s.Config().Host) // www.example.com
+
+}
+
 ```
 
-## Integrate with your tools
+Do you want to allow the user to specify a configuration via the command line,
+so you can use the `AddFileFromFlagSet` function. This function expects a `flag.FlagSet`.
 
-- [ ] [Set up project integrations](https://gitlab.schukai.com/oss/libraries/go/application/configuration/-/settings/integrations)
 
-## Collaborate with your team
+### Import files and streams
 
-- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
-- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
-- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
-- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
-- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
+You can load configuration from files and streams. With the `Import()` function you
+import files and streams. The specified files are loaded first, and then the
+streams.
 
-## Test and Deploy
+The configurations are merged. If a value is already set, it is overwritten by the
+specified value. So if a value is set in `etc` and in the more specific
+File in the user home directory, the value is taken from the user home directory.
 
-Use the built-in continuous integration in GitLab.
+```go
+package main
 
-- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
-- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
-- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
-- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
-- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
+import (
+   "fmt"
+   "os"
+   "flag"
+   "gitlab.schukai.com/oss/libraries/go/application/configuration"
+)
 
-***
+func main() {
 
-# Editing this README
+   config := struct {
+      Host string
+   }{
+      Host: "localhost",
+   }
 
-When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
+   c := configuration.New(config)
 
-## Suggestions for a good README
-Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
+   c.SetMnemonic("my-app")
+   c.SetDefaultDirectories()
+   c.Import()
 
-## Name
-Choose a self-explaining name for your project.
+   fmt.Println(c.Config().Host)
 
-## Description
-Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
+}
 
-## Badges
-On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
 
-## Visuals
-Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
+```
 
-## Installation
-Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
+The configuration would then be looked for in the following places:
 
-## Usage
-Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
+* ~/config.yaml   (working directory)
+* ~/.config/my-app/config.yaml
+* /etc/my-app/config.yaml
 
-## Support
-Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
+#### Configuration files
 
-## Roadmap
-If you have ideas for releases in the future, it is a good idea to list them in the README.
+Configuration files are certainly the most common way to define configurations.
+These can be in different formats. The following formats are currently supported:
+JSON, YAML, TOML and Properties.
 
-## Contributing
-State if you are open to contributions and what your requirements are for accepting them.
+With files, the approach is slightly different. Here the function `AddDirectory()`
+first, specify directories in which to search for the file. The file
+is then searched for in the order of the directories. If the file is found
+is loaded.
+
+The helper function `AddWorkingDirectory()` adds the current working directory.
+
+With `AddEtcDirectory()` the directory `etc` is added under Unix.
+
+`AddUserConfigDirectory()` adds the directory `~.config` on Unix. It will
+uses the `os.UserConfigDir()` method. Thus, it is possible to use the directory
+can also be used on other operating systems.
+
+The `SetDefaultDirectories()` method sets the paths to the default values. Become internal
+the paths with `AddWorkingDirectory()`, `AddEtcDirectory()` and `AddUserConfigDirectory()`
+set.
+
+The directory structure can be specified directly with the `SetDirectories()` function.
+This overwrites the directory structure specified with `AddDirectory()`.
+
+The filename of the configuration file is specified with `SetFileName()`. The file name
+is searched for with the extension `.json`, `.yaml`, `.yml`, `.toml` or `.properties`.
+
+If a format is specified with the `SetFileFormat()` method, only files with
+searched for this ending.
+
+As an extra, you can specify your own file system with `SetFilesystem()`.
+This is useful for testing.
+
+Furthermore, the `AddFile()` function can be used to add a file directly. 
+This makes sense if you are given a file as a parameter
+or if you don't want to have a multi-level structure.
+
+**Watch files**
 
-For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
+If you want to watch configuration files for changes, you can use the `Watch()` function.
+With the function `StopWatch()` you can stop watching the files. The function
+returns a bool channel on which the status of the watch is reported. 
+
+If the watch has ended, a true is sent over the channel. If an error occurs, a false is sent.
+
+```go
+
+
+#### Streams
+
+The configuration can also be loaded from a stream. This is useful if you want to
+load the configuration from other sources. For example, from a database.
+
+With the `AddReader()` function, you can add a stream to the configuration. The
+stream is then searched for in the order of the streams. If the stream is found
+is loaded.
+
+### HTTP API
+
+The configuration can also be changed via HTTP. This is useful if you want to
+change the configuration at runtime. For example, if you would like to change the
+configuration of a service.
+
+With a Get request, the configuration is returned as the format specified in the
+`Accept` header. If no format is specified, the configuration is returned in JSON format.
+
+With a Post request, the configuration can be changed. The configuration is then
+taken from the body of the request. The format is determined by the `Content-Type`
+header. If no format is specified, the configuration is taken from the JSON format.
+
+
+```go
+config := struct {
+   Host string
+}
+s := New(config)
+mux := http.NewServeMux()
+mux.HandleFunc("/config", s.ServeHTTP)
+
+```
+
+There is also a middleware to get access to the configuration.
+
+```go
+config := struct {
+    Host string
+}
+s := New(config)
+mux := http.NewServeMux()
+mux.Use(s.Middleware)
+```
+
+The configuration can then be accessed via the `config` context.
+
+```go
+func handler(w http.ResponseWriter, r *http.Request) {
+	config := r.Context().Value("config").(struct {
+		Host string
+	})
+	fmt.Println(config.Host)
+}
+
+
+```
+
+### On change
+
+If you want to be notified when the configuration changes, you can use the
+`OnChange()` function. This function takes a callback function as a parameter.
+
+The following program gives the following output `Change from localhost to www.example.com`.
+
+```go
+package main
+
+import (
+   "fmt"
+   "os"
+   "flag"
+   "gitlab.schukai.com/oss/libraries/go/application/configuration"
+)
+
+func main() {
+   config := struct {
+      Host string
+   }{
+      Host: "localhost",
+   }
+
+   s := configuration.New(config)
+
+   closeChan := make(chan bool)
+
+   s.OnChange(func(event configuration.ChangeEvent) {
+      log := event.Changlog
+      msg := fmt.Sprintf("Change from %s to %s", log[0].From, log[0].To)
+      fmt.Println(msg)
+      closeChan <- true
+   })
+
+   c := s.Config()
+   c.Host = "www.example.com"
+
+   s.SetConfig(c)
+
+   // Wait for change
+   select {
+   case <-closeChan:
+   }
+
+}
+
+
+```
+
+### Error handling
+
+If an error occurs, it is returned by the function `Errors()`. The errors can be handled as usual.
+
+The `HasErrors()` function can be used to check whether errors have occurred.
+
+
+## Change Log
+
+### 1.0.0
+
+* Initial release
+
+## Contributing
 
-You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
+Merge requests are welcome. For major changes, please open an issue first to discuss what
+you would like to change.
 
-## Authors and acknowledgment
-Show your appreciation to those who have contributed to the project.
+Please make sure to update tests as appropriate.
 
 ## License
-For open source projects, say how it is licensed.
 
-## Project status
-If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
+[AGPL-3.0](https://choosealicense.com/licenses/agpl-3.0/)
\ No newline at end of file
diff --git a/api.go b/api.go
new file mode 100644
index 0000000..e57f55e
--- /dev/null
+++ b/api.go
@@ -0,0 +1,53 @@
+package configuration
+
+import "github.com/imdario/mergo"
+
+// NewSetting creates a new configuration setting
+// with the given defaults.
+func New[C any](defaults C) *setting[C] {
+
+	s := &setting[C]{}
+	s.initDefaults()
+
+	if err := mergo.Merge(&defaults, s.config); err != nil {
+		s.errors = append(s.errors, err)
+	}
+
+	s.config = defaults
+
+	err := validateConfig[C](defaults)
+	if err != nil {
+		s.errors = append(s.errors, err)
+		var r C
+		s.config = r
+	}
+
+	initFileBackend(&s.files)
+	return s
+}
+
+// Set the mnemonic
+// The mnemonic is used to identify the configuration in the configuration file
+func (s *setting[C]) SetMnemonic(mnemonic string) *setting[C] {
+	if mnemonic == "" {
+		s.errors = append(s.errors, MnemonicEmptyError)
+	} else {
+		s.mnemonic = mnemonic
+	}
+	return s
+}
+
+// Check if the setting contains errors
+func (s *setting[C]) HasError() bool {
+	return len(s.errors) > 0
+}
+
+// Get all errors
+func (s *setting[C]) Errors() []error {
+	return s.errors
+}
+
+// Config() returns the configuration
+func (s *setting[C]) Config() C {
+	return s.config
+}
diff --git a/api_test.go b/api_test.go
new file mode 100644
index 0000000..9a0c4ee
--- /dev/null
+++ b/api_test.go
@@ -0,0 +1,66 @@
+package configuration
+
+import (
+	"github.com/magiconair/properties/assert"
+	"testing"
+)
+
+// Example for README
+// Print are commented out because they are only for demonstration
+func TestReadExample1(t *testing.T) {
+
+	config := struct {
+		Host string
+		Port int
+	}{
+		Host: "localhost",
+		Port: 8080,
+	}
+
+	c := New(config)
+
+	//fmt.Println(c.Config().Host)
+	//fmt.Println(c.Config().Port)
+
+	assert.Equal(t, c.Config().Host, "localhost")
+	assert.Equal(t, c.Config().Port, 8080)
+
+}
+
+func TestNew(t *testing.T) {
+	var config ConfigStruct2
+	s := New(config)
+
+	if s == nil {
+		t.Error("Expected not nil")
+	}
+
+}
+
+func TestNewWithDefaults(t *testing.T) {
+	var config ConfigStruct2
+	s := New(config)
+
+	if s == nil {
+		t.Error("Expected not nil")
+	}
+
+	// use external api to read the value
+	if s.Config().H.HA != 1 {
+		t.Error("Expected 1")
+	}
+
+	// use internal api to read the value
+	if s.config.H.HB != "yes" {
+		t.Error("Expected yes")
+	}
+
+	if s.config.H.HC != true {
+		t.Error("Expected true")
+	}
+
+	if s.config.H.HD != false {
+		t.Error("Expected false")
+	}
+
+}
diff --git a/benchmark_test.go b/benchmark_test.go
new file mode 100644
index 0000000..9e55e52
--- /dev/null
+++ b/benchmark_test.go
@@ -0,0 +1,22 @@
+package configuration
+
+import (
+	"bytes"
+	"testing"
+)
+
+func BenchmarkCreateConfig(b *testing.B) {
+	// run the Fib function b.N times
+	for n := 0; n < b.N; n++ {
+		config := ConfigStruct2{
+			A: "1",
+			B: true,
+			F: 2,
+		}
+
+		s := New(config)
+
+		buffer := bytes.NewBufferString("")
+		s.Write(buffer, Yaml)
+	}
+}
diff --git a/change.go b/change.go
new file mode 100644
index 0000000..c7dd5da
--- /dev/null
+++ b/change.go
@@ -0,0 +1,73 @@
+package configuration
+
+import (
+	"github.com/r3labs/diff/v3"
+)
+
+type ChangeEvent struct {
+	Changlog diff.Changelog
+}
+
+type EventHook func(event ChangeEvent)
+
+func (s *setting[C]) OnChange(hook EventHook) *setting[C] {
+	s.hooks.change = append(s.hooks.change, hook)
+	return s
+}
+
+func (s *setting[C]) notifyChangeHooks(changelog diff.Changelog) *setting[C] {
+	for _, h := range s.hooks.change {
+		h(ChangeEvent{Changlog: changelog})
+	}
+	return s
+}
+
+func (s *setting[C]) setConfigInternal(config C, lock bool) *setting[C] {
+
+	var (
+		changelog diff.Changelog
+		err       error
+	)
+
+	if lock {
+		s.Lock()
+	}
+
+	defer func() {
+		if lock {
+			s.Unlock()
+		}
+
+		if len(changelog) > 0 {
+			go s.notifyChangeHooks(changelog)
+		}
+
+	}()
+
+	if err := validateConfig[C](config); err != nil {
+		s.errors = append(s.errors, err)
+		return s
+	}
+
+	d, err := diff.NewDiffer()
+	if err != nil {
+		s.errors = append(s.errors, err)
+		return s
+	}
+
+	d.ConvertCompatibleTypes = true
+	d.AllowTypeMismatch = true
+
+	changelog, err = d.Diff(s.config, config)
+	if err != nil {
+		s.errors = append(s.errors, err)
+		return s
+	}
+
+	s.config = config
+	return s
+}
+
+func (s *setting[C]) SetConfig(config C) *setting[C] {
+	return s.setConfigInternal(config, true)
+}
diff --git a/change_test.go b/change_test.go
new file mode 100644
index 0000000..8494069
--- /dev/null
+++ b/change_test.go
@@ -0,0 +1,102 @@
+package configuration
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"testing"
+	"time"
+)
+
+func TestReadmeExample(t *testing.T) {
+
+	config := struct {
+		Host string
+	}{
+		Host: "localhost",
+	}
+
+	s := New(config)
+
+	closeChan := make(chan bool)
+
+	msg := ""
+	s.OnChange(func(event ChangeEvent) {
+		log := event.Changlog
+		msg = fmt.Sprintf("Change from %s to %s", log[0].From, log[0].To)
+		// for Readme
+		//fmt.Println(msg)
+		closeChan <- true
+	})
+
+	c := s.Config()
+	c.Host = "www.example.com"
+
+	s.SetConfig(c)
+
+	// Wait for change
+	select {
+	case <-closeChan:
+	case <-time.After(time.Second):
+		t.Fatalf("Timeout")
+	}
+
+	if msg != "Change from localhost to www.example.com" {
+		t.Error("Expected \"Change from localhost to www.example.com\" got ", msg)
+	}
+
+}
+
+func TestCangeOnChange(t *testing.T) {
+
+	defaults := ConfigStruct2{
+		A: "",
+		B: false,
+		C: nil,
+		D: nil,
+		F: 0,
+		G: "",
+	}
+
+	s := New(defaults)
+	s.SetMnemonic("test")
+
+	var buf bytes.Buffer
+	buf.WriteString("A: a\n")
+	r := (io.Reader)(&buf)
+
+	s.AddReader(r, Yaml)
+
+	s.Import()
+
+	if s.HasError() {
+		t.Error("Expected not error", s.Errors())
+	}
+
+	c := s.Config()
+	if c.A != "a" {
+		t.Error("Expected \"a\" got ", c)
+	}
+
+	closeChan := make(chan bool)
+
+	counter := 0
+	s.OnChange(func(event ChangeEvent) {
+		counter++
+		closeChan <- true
+	})
+
+	c.A = "b"
+	s.SetConfig(c)
+
+	select {
+	case <-closeChan:
+	case <-time.After(time.Second):
+		t.Fatalf("Timeout")
+	}
+
+	if counter != 1 {
+		t.Error("Expected 1 got ", counter)
+	}
+
+}
diff --git a/configuration.iml b/configuration.iml
new file mode 100644
index 0000000..49df094
--- /dev/null
+++ b/configuration.iml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="Go" enabled="true" />
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/constants.go b/constants.go
new file mode 100644
index 0000000..eb3eaff
--- /dev/null
+++ b/constants.go
@@ -0,0 +1,7 @@
+package configuration
+
+const fileName = "config"
+const contextKey = "config"
+
+const envTagKey = "env"
+const flagTagKey = "flag"
diff --git a/env.go b/env.go
new file mode 100644
index 0000000..b656b66
--- /dev/null
+++ b/env.go
@@ -0,0 +1,50 @@
+package configuration
+
+import (
+	"os"
+	"reflect"
+	"strconv"
+)
+
+func (s *setting[C]) InitFromEnv(prefix string) *setting[C] {
+
+	s.Lock()
+	defer s.Unlock()
+
+	err := runOnTags(&s.config, []string{envTagKey}, func(k string, field reflect.Value) {
+
+		if !field.CanSet() {
+			return
+		}
+
+		v, found := os.LookupEnv(k)
+		if !found {
+			return // env not found
+		}
+
+		t := reflect.TypeOf(v)
+
+		switch field.Kind() {
+		case reflect.String:
+			field.SetString(v)
+		case reflect.Int:
+			intVar, err := strconv.Atoi(v)
+			if err != nil {
+				s.errors = append(s.errors, err)
+				return
+			}
+			field.SetInt(int64(intVar))
+		case reflect.Bool:
+			field.SetBool(v == "true")
+		default:
+			s.errors = append(s.errors, newMismatchedTypeError(t, field.Type()))
+		}
+	})
+
+	if err != nil {
+		s.errors = append(s.errors, err)
+	}
+
+	return s
+
+}
diff --git a/env_test.go b/env_test.go
new file mode 100644
index 0000000..7847f14
--- /dev/null
+++ b/env_test.go
@@ -0,0 +1,75 @@
+package configuration
+
+import (
+	"github.com/magiconair/properties/assert"
+	"os"
+	"testing"
+)
+
+// Example for README
+// Print are commented out because they are only for demonstration
+func TestReadmeExample2(t *testing.T) {
+	config := struct {
+		Host string `env:"HOST"`
+	}{
+		Host: "localhost",
+	}
+
+	// Set value
+	os.Setenv("HOST", "www.example.com")
+
+	s := New(config)
+	//fmt.Println(s.Config().Host) // localhost
+	assert.Equal(t, s.Config().Host, "localhost")
+
+	s.InitFromEnv("") // no prefix
+	//fmt.Println(s.Config().Host) // www.example.com
+	assert.Equal(t, s.Config().Host, "www.example.com")
+
+}
+
+func TestFromEnvNotSet(t *testing.T) {
+	config := ConfigStruct6{
+		IA: 1234,
+		IB: "no",
+		IC: false,
+		ID: true,
+	}
+
+	s := New(config)
+
+	// Value not set
+	s.InitFromEnv("VALUE_IB")
+
+	if s.Config().IA != 1234 {
+		t.Errorf("Expected 1234, got %d", s.Config().IA)
+	}
+
+}
+
+func TestFromEnv(t *testing.T) {
+	config := ConfigStruct6{
+		IA: 1234,
+		IB: "no",
+		IC: false,
+		ID: true,
+	}
+
+	s := New(config)
+
+	// Value not set
+	s.InitFromEnv("VALUE_IB")
+
+	if s.Config().IA != 1234 {
+		t.Errorf("Expected 1234, got %d", s.Config().IA)
+	}
+
+	// Set value
+	os.Setenv("VALUE_IB", "i-am-b")
+	s.InitFromEnv("VALUE_IB")
+
+	if s.Config().IB != "i-am-b" {
+		t.Errorf("Expected i-am-b, got %s", s.Config().IB)
+	}
+
+}
diff --git a/error.go b/error.go
new file mode 100644
index 0000000..e8434d6
--- /dev/null
+++ b/error.go
@@ -0,0 +1,78 @@
+package configuration
+
+import (
+	"errors"
+	"reflect"
+)
+
+var FileNameEmptyError = errors.New("file name is empty")
+var MnemonicEmptyError = errors.New("mnemonic is empty")
+var NoFileImportedError = errors.New("no file imported")
+var NoFilesToWatchError = errors.New("no files to watch")
+var WatchAlreadyInitializedError = errors.New("watch already initialized")
+var WatchAlreadyRunningError = errors.New("watch already running")
+var WatchNotInitializedError = errors.New("watch not initialized")
+var WatchNotRunningError = errors.New("watch not running")
+var WatchListNotInitializedError = errors.New("watch list not initialized")
+
+type FlagDoesNotExistError error
+
+func newFlagDoesNotExistError(flag string) FlagDoesNotExistError {
+	return FlagDoesNotExistError(errors.New("flag " + flag + " does not exist"))
+}
+
+// PathAlreadyExistsError is returned when a files already exists
+type PathAlreadyExistsError error
+
+func newPathAlreadyExistsError(path string) PathAlreadyExistsError {
+	return PathAlreadyExistsError(errors.New("files " + path + " already exists"))
+}
+
+// PathDoesNotExistError is returned when a files does not exist
+type PathDoesNotExistError error
+
+func newPathDoesNotExistError(path string) PathDoesNotExistError {
+	return PathDoesNotExistError(errors.New("files " + path + " does not exist"))
+}
+
+// FormatNotSupportedError is returned when the format is not supported
+type FormatNotSupportedError error
+
+func newFormatNotSupportedError(format Format) FormatNotSupportedError {
+	return FormatNotSupportedError(errors.New("format " + format.String() + " is not supported"))
+}
+
+// UnsupportedTypeError is returned when the type is not supported
+type UnsupportedTypeError error
+
+func newUnsupportedTypeError(t reflect.Type) UnsupportedTypeError {
+	return UnsupportedTypeError(errors.New("type " + t.String() + " is not supported"))
+}
+
+// ResetError is used to reset the error to nil
+// After calling this function, the call HasError() will return false
+func (s *setting[C]) ResetErrors() *setting[C] {
+	s.errors = []error{}
+	return s
+}
+
+// At the reflect level, some types are not supported
+type UnsupportedReflectKindError error
+
+func newUnsupportedReflectKindError(t reflect.Type) UnsupportedTypeError {
+	return UnsupportedReflectKindError(errors.New("type " + t.String() + " is not supported"))
+}
+
+// This error indicates that the flag is not found
+type FlagNotFoundError error
+
+func newFlagNotFoundError(name string) FlagNotFoundError {
+	return FlagNotFoundError(errors.New("flag " + name + " not found"))
+}
+
+// MismatchedTypeError is used to indicate that the type is not matching
+type MismatchedTypeError error
+
+func newMismatchedTypeError(expected, actual reflect.Type) MismatchedTypeError {
+	return MismatchedTypeError(errors.New("expected type " + expected.String() + " but got " + actual.String()))
+}
diff --git a/export.go b/export.go
new file mode 100644
index 0000000..c62cf59
--- /dev/null
+++ b/export.go
@@ -0,0 +1,91 @@
+package configuration
+
+import (
+	"encoding/json"
+	"github.com/magiconair/properties"
+	"io"
+	"os"
+
+	"github.com/pelletier/go-toml/v2"
+	"gopkg.in/yaml.v3"
+)
+
+func (s *setting[C]) Export() *setting[C] {
+	return s
+}
+
+func (s *setting[C]) writeJson(writer io.Writer) error {
+	encoder := json.NewEncoder(writer)
+	return encoder.Encode(s.config)
+}
+
+func (s *setting[C]) writeYaml(writer io.Writer) error {
+	encoder := yaml.NewEncoder(writer)
+	return encoder.Encode(s.config)
+}
+func (s *setting[C]) writeToml(writer io.Writer) error {
+	encoder := toml.NewEncoder(writer)
+	return encoder.Encode(s.config)
+}
+func (s *setting[C]) writeProperties(writer io.Writer) error {
+
+	m, errors := getMapForProperties[C](s.config)
+
+	if len(errors) > 0 {
+		for _, err := range errors {
+			s.errors = append(s.errors, err)
+		}
+	}
+
+	p := properties.LoadMap(m)
+
+	_, err := p.Write(writer, properties.UTF8)
+	return err
+
+}
+
+func (s *setting[C]) WriteFile(fn string, format Format) *setting[C] {
+
+	var err error
+
+	var file *os.File
+
+	if fn == "" {
+		file = os.Stdout
+	} else {
+		file, err = os.Create(fn)
+		if err != nil {
+			s.errors = append(s.errors, err)
+			return s
+		}
+	}
+
+	defer file.Close()
+	s.Write(io.Writer(file), format)
+	return s
+
+}
+
+func (s *setting[C]) Write(writer io.Writer, format Format) *setting[C] {
+
+	var err error
+
+	switch format {
+	case Json:
+		err = s.writeJson(writer)
+	case Yaml:
+		err = s.writeYaml(writer)
+	case Toml:
+		err = s.writeToml(writer)
+	case Properties:
+		err = s.writeProperties(writer)
+	default:
+		err = newFormatNotSupportedError(format)
+	}
+
+	if err != nil {
+		s.errors = append(s.errors, err)
+	}
+
+	return s
+}
diff --git a/export_test.go b/export_test.go
new file mode 100644
index 0000000..6709cb3
--- /dev/null
+++ b/export_test.go
@@ -0,0 +1,54 @@
+package configuration
+
+import (
+	"io"
+	"strings"
+	"testing"
+)
+
+func TestWrite(t *testing.T) {
+
+	config := ConfigStruct2{
+		A: "AXXX",
+		F: 99,
+	}
+	s := New(config)
+
+	// not implemented , Toml, Properties
+	for _, f := range []Format{Yaml, Json} {
+		w := strings.Builder{}
+		x := io.Writer(&w)
+
+		s.Write(x, f)
+
+		if s.HasError() {
+			t.Error(s.Errors())
+			s.ResetErrors()
+			continue
+		}
+
+		if w.String() == "" {
+			t.Error("Expected not empty")
+		}
+
+		content := w.String()
+
+		if f == Yaml {
+
+			if strings.Contains(content, "AXXX") == false {
+				t.Error("Expected AXXX")
+			}
+
+			if strings.Contains(content, "B: false") == false {
+				t.Error("Expected not B: false got ", w.String())
+			}
+
+			if strings.Contains(content, "F: 99") == false {
+				t.Error("Expected not F: 99 got ", w.String())
+			}
+		} else if f == Json {
+
+		}
+	}
+
+}
diff --git a/file.go b/file.go
new file mode 100644
index 0000000..2a1dd26
--- /dev/null
+++ b/file.go
@@ -0,0 +1,185 @@
+package configuration
+
+import (
+	"golang.org/x/exp/slices"
+	"io/fs"
+	"os"
+	"path"
+)
+
+type files struct {
+	path   string
+	format Format
+}
+
+type fileBackend struct {
+	directories []string
+	files       []files
+	format      Format
+	name        string
+	fs          fs.FS
+}
+
+// AddFiles adds a file to the list of files to import
+func (s *setting[C]) AddFile(file string, format Format) *setting[C] {
+	s.files.files = append(s.files.files, files{file, format})
+	return s
+}
+
+func initFileBackend(files *fileBackend) {
+	files.format = Yaml
+	files.name = fileName
+	files.fs = fs.FS(internalFS{})
+}
+
+// Path returns the configuration directory
+func (s *setting[C]) Directories() []string {
+	return s.files.directories
+}
+
+// AddPath adds a directory to the configuration directory
+func (s *setting[C]) AddDirectory(d string) *setting[C] {
+	s.Lock()
+	defer s.Unlock()
+	s.files.directories = append(s.files.directories, d)
+	s.sanitizeDirectories()
+	return s
+}
+
+func (s *setting[C]) sanitizeDirectories() {
+
+	wd, err := os.Getwd()
+	if err != nil {
+		s.errors = append(s.errors, err)
+	}
+
+	visited := make(map[string]bool, 0)
+	for i := 0; i < len(s.files.directories); i++ {
+		d := s.files.directories[i]
+
+		if !path.IsAbs(d) {
+			d = path.Join(wd, d)
+		}
+
+		if visited[d] == true {
+			s.errors = append(s.errors, newPathAlreadyExistsError(d))
+		} else {
+			visited[d] = true
+		}
+
+		if _, err := os.Stat(d); os.IsNotExist(err) {
+			s.errors = append(s.errors, newPathDoesNotExistError(d))
+		}
+
+	}
+
+}
+
+// Set all configuration directories
+func (s *setting[C]) SetDirectories(d []string) *setting[C] {
+	s.Lock()
+	defer s.Unlock()
+
+	s.files.directories = d
+	s.sanitizeDirectories()
+	return s
+}
+
+// Add the current working directory to the configuration directory
+func (s *setting[C]) AddWorkingDirectory() *setting[C] {
+	s.Lock()
+	defer s.Unlock()
+
+	current, err := os.Getwd()
+	if err != nil {
+		s.errors = append(s.errors, err)
+		return s
+	}
+	s.files.directories = append(s.files.directories, current)
+	s.sanitizeDirectories()
+	return s
+}
+
+// Add the Unix etc directory to the configuration directory
+func (s *setting[C]) AddEtcDirectory() *setting[C] {
+	s.Lock()
+	defer s.Unlock()
+
+	if s.mnemonic == "" {
+		s.errors = append(s.errors, MnemonicEmptyError)
+		return s
+	}
+
+	p := "/etc"
+	if _, err := os.Stat(p); os.IsNotExist(err) {
+		s.errors = append(s.errors, newPathDoesNotExistError(p))
+		return s
+	}
+
+	p = path.Join(p, s.mnemonic)
+
+	s.files.directories = append(s.files.directories, p)
+	s.sanitizeDirectories()
+	return s
+}
+
+// Add the user configuration directory to the configuration directory
+// The mnemonic must be set for this function
+func (s *setting[C]) AddUserConfigDirectory() *setting[C] {
+
+	if s.mnemonic == "" {
+		s.errors = append(s.errors, MnemonicEmptyError)
+		return s
+	}
+
+	s.Lock()
+	defer s.Unlock()
+
+	current, err := os.UserConfigDir()
+	if err != nil {
+		s.errors = append(s.errors, err)
+		return s
+	}
+
+	current = path.Join(current, s.mnemonic)
+	s.files.directories = append(s.files.directories, current)
+	s.sanitizeDirectories()
+	return s
+}
+
+// Add the current working directory, the user configuration directory
+// and the Unix etc directory to the configuration directory
+func (s *setting[C]) SetDefaultDirectories() *setting[C] {
+	s.AddWorkingDirectory().
+		AddUserConfigDirectory().
+		AddEtcDirectory()
+	return s
+}
+
+func (s *setting[C]) SetFileFormat(format Format) *setting[C] {
+
+	if slices.Contains(availableFormats, format) {
+		s.files.format = format
+	} else {
+		s.errors = append(s.errors, newFormatNotSupportedError(format))
+	}
+
+	return s
+}
+
+// Set the file name without extension
+func (s *setting[C]) SetFileName(name string) *setting[C] {
+
+	if name == "" {
+		s.errors = append(s.errors, FileNameEmptyError)
+	} else {
+		s.files.name = name
+	}
+
+	return s
+}
+
+func (s *setting[C]) SetFilesystem(f fs.FS) *setting[C] {
+	s.files.fs = f
+	return s
+}
diff --git a/file_test.go b/file_test.go
new file mode 100644
index 0000000..29241d1
--- /dev/null
+++ b/file_test.go
@@ -0,0 +1,604 @@
+package configuration
+
+import (
+	"bytes"
+	"github.com/magiconair/properties/assert"
+	"io"
+	"io/fs"
+	"os"
+	"testing"
+	"time"
+)
+
+type mockFS struct{}
+
+type mockFile struct {
+	fs.FileInfo
+	buffer io.Reader
+}
+
+type mockFileInfo struct {
+	name string
+	size int64
+	mode fs.FileMode
+	mod  time.Time
+	dir  bool
+	sys  interface{}
+}
+
+func (m mockFileInfo) Name() string {
+	return m.name
+}
+
+func (m mockFileInfo) Size() int64 {
+	return m.size
+}
+
+func (m mockFileInfo) Mode() fs.FileMode {
+	return m.mode
+}
+
+func (m mockFileInfo) ModTime() time.Time {
+	return m.mod
+}
+
+func (m mockFileInfo) IsDir() bool {
+	return m.dir
+}
+
+func (m mockFileInfo) Sys() any {
+	return m.sys
+}
+
+func (f mockFile) Stat() (fs.FileInfo, error) {
+	fi := mockFileInfo{}
+	return fi, nil
+}
+
+func (f mockFile) Read(b []byte) (int, error) {
+	return f.buffer.Read(b)
+}
+
+func (f mockFile) Close() error {
+	return nil
+}
+
+type fileSample struct {
+	file *mockFile
+	err  error
+}
+
+var fileSamples []fileSample
+
+func (mockFS) Open(name string) (fs.File, error) {
+
+	if len(fileSamples) > 0 {
+
+		s := fileSamples[0]
+		fileSamples = fileSamples[1:]
+
+		if s.err != nil {
+			return nil, s.err
+		}
+
+		return s.file, nil
+
+	}
+
+	return os.Open(name)
+}
+
+func TestDirectories(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+	c := New(cfg)
+	c.AddDirectory("/a")
+
+	if c.Directories() == nil {
+		t.Error("Expected not nil")
+	}
+
+	if len(c.Directories()) != 1 {
+		t.Error("Expected 1")
+	}
+
+}
+
+func TestSetDirectories(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(&cfg)
+	c.SetDirectories([]string{"/a", "/b"})
+
+	if c.Directories() == nil {
+		t.Error("Expected not nil")
+	}
+
+	if len(c.Directories()) != 2 {
+		t.Error("Expected 2")
+	}
+
+}
+
+func TestAddWorkingDirectory(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(&cfg)
+	c.AddWorkingDirectory()
+
+	if c.Directories() == nil {
+		t.Error("Expected not nil")
+	}
+
+	if len(c.Directories()) != 1 {
+		t.Error("Expected 1")
+	}
+
+}
+
+func TestAddWorkingDirectoryTwice(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(&cfg)
+	c.AddWorkingDirectory()
+	c.AddWorkingDirectory()
+
+	if c.Directories() == nil {
+		t.Error("Expected not nil")
+	}
+
+	if len(c.Directories()) != 2 {
+		t.Error("Expected 2 got ", len(c.Directories()))
+	}
+
+	if c.Errors() == nil {
+		t.Error("Expected not nil")
+	}
+
+}
+
+func TestAddDirectoryMissingMnemonic(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(cfg)
+	c.AddEtcDirectory()
+
+	if !c.HasError() {
+		t.Error("Expected error")
+	}
+
+	if c.Errors()[0] != MnemonicEmptyError {
+		t.Error("Expected error")
+	}
+
+}
+func TestAddDirectory(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(&cfg)
+	c.SetMnemonic("test")
+	c.AddEtcDirectory()
+
+	if c.Directories() == nil {
+		t.Error("Expected not nil")
+	}
+
+	if len(c.Directories()) != 1 {
+		t.Error("Expected 1")
+	}
+
+}
+
+func TestAddDirectoryTwice(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(&cfg)
+	c.SetMnemonic("test")
+	c.AddEtcDirectory()
+	c.AddEtcDirectory()
+
+	if c.Directories() == nil {
+		t.Error("Expected not nil")
+	}
+
+	if len(c.Directories()) != 2 {
+		t.Error("Expected 1 got ", len(c.Directories()))
+	}
+
+	if c.Errors() == nil {
+		t.Error("Expected not nil")
+	}
+
+}
+
+func TestAddUserDirectory(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(&cfg)
+	c.SetMnemonic("test")
+	c.AddUserConfigDirectory()
+
+	if c.Directories() == nil {
+		t.Error("Expected not nil")
+	}
+
+	if len(c.Directories()) != 1 {
+		t.Error("Expected 1")
+	}
+
+}
+
+func TestSetDefaultPath(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(&cfg)
+	c.SetMnemonic("test")
+	c.SetDefaultDirectories()
+
+	if c.Directories() == nil {
+		t.Error("Expected not nil")
+	}
+
+	if len(c.Directories()) != 3 {
+		t.Error("Expected 3 got ", len(c.Directories()))
+	}
+
+}
+
+func TestSetFileFormat(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(cfg)
+	c.SetFileFormat(Yaml)
+
+	if c.HasError() {
+		t.Error("Expected not error")
+	}
+
+}
+
+func TestSetFileFormatError(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(cfg)
+	c.SetFileFormat(Format(9999))
+
+	if !c.HasError() {
+		t.Error("Expected error")
+	}
+
+}
+
+func TestImport(t *testing.T) {
+
+	fileSamples = []fileSample{}
+	defer func() { fileSamples = nil }()
+
+	addSample1()
+
+	defaults := ConfigStruct2{
+		A: "Hello!",
+		D: map[string]string{"A": "1", "B": "2"},
+		F: 99,
+	}
+
+	mockFs := mockFS{}
+
+	c := New(defaults)
+	c.SetMnemonic("test")
+	c.SetFileFormat(Yaml)
+	c.SetFilesystem(mockFs)
+	// setDefaultdirectories can be returned errors
+	c.SetDefaultDirectories().ResetErrors()
+	c.Import()
+
+	if c.HasError() {
+		t.Error("Expected not error", c.Errors())
+	}
+
+	if c.Config().A != "from file" {
+		t.Error("Expected \"from file\" got ", c.Config().A)
+	}
+
+	m := c.Config().D.(map[string]interface{})
+
+	if m["A"] != "x" {
+		t.Error("Expected x got ", m["A"])
+	}
+
+	if m["B"] != "2" {
+		t.Error("Expected 2 got ", m["B"])
+	}
+
+}
+
+func addSample1() {
+
+	e := &mockFile{}
+
+	content := []byte(
+		`---
+A: "from file"
+F: 3
+D: 
+  A: x
+C:
+  - CA: 
+      CAA: 1
+  - CB: 2
+
+...
+
+`)
+
+	e.buffer = bytes.NewBuffer(content)
+
+	e.FileInfo = mockFileInfo{
+		name: "test",
+		size: int64(len(content)),
+		mode: 0,
+		mod:  time.Now(),
+		dir:  false,
+		sys:  nil,
+	}
+
+	fileSamples = append(fileSamples, fileSample{
+		file: e,
+	})
+
+}
+func addSample2() {
+
+	e := &mockFile{}
+
+	content := []byte(
+		`---
+F: 5
+C:
+  - CA: 
+      CAA: 1
+  - CB: 2
+...
+
+`)
+
+	e.buffer = bytes.NewBuffer(content)
+
+	e.FileInfo = mockFileInfo{
+		name: "test",
+		size: int64(len(content)),
+		mode: 0,
+		mod:  time.Now(),
+		dir:  false,
+		sys:  nil,
+	}
+
+	fileSamples = append(fileSamples, fileSample{
+		file: e,
+	})
+
+}
+
+// Import two files. The second file should overwrite the first file.
+func TestImport2(t *testing.T) {
+
+	fileSamples = []fileSample{}
+	defer func() { fileSamples = nil }()
+
+	addSample2()
+	addSample1()
+
+	defaults := ConfigStruct2{
+		A: "Hello!",
+		D: map[string]string{"A": "1", "B": "2"},
+		F: 99,
+	}
+
+	mockFs := mockFS{}
+
+	c := New(defaults)
+	c.SetMnemonic("test")
+	c.SetFileFormat(Yaml)
+	c.SetFilesystem(mockFs)
+	c.SetDefaultDirectories().ResetErrors() // errors not important here
+	c.Import()
+
+	if c.HasError() {
+		t.Error("Expected not error", c.Errors())
+	}
+
+	if c.Config().A != "from file" {
+		t.Error("Expected \"from file\" got ", c.Config().A)
+	}
+
+	m := c.Config().D.(map[string]interface{})
+
+	if m["A"] != "x" {
+		t.Error("Expected x got ", m["A"])
+	}
+
+	if m["B"] != "2" {
+		t.Error("Expected 2 got ", m["B"])
+	}
+
+	if c.Config().F != 5 {
+		t.Error("Expected 5 got ", c.Config().F)
+	}
+
+}
+
+// Import two files. The second file should overwrite the first file.
+func TestImport3(t *testing.T) {
+
+	fileSamples = []fileSample{}
+	defer func() { fileSamples = nil }()
+
+	addSample1()
+	addSample2()
+
+	defaults := ConfigStruct2{
+		A: "Hello!",
+		D: map[string]string{"A": "1", "B": "2"},
+		F: 99,
+	}
+
+	mockFs := mockFS{}
+
+	c := New(defaults)
+	c.SetMnemonic("test")
+	c.SetFileFormat(Yaml)
+	c.SetFilesystem(mockFs)
+	c.SetDefaultDirectories().ResetErrors() // errors not important here
+	c.Import()
+
+	if c.HasError() {
+		t.Error("Expected not error", c.Errors())
+	}
+
+	if c.Config().A != "from file" {
+		t.Error("Expected \"from file\" got ", c.Config().A)
+	}
+
+	m := c.Config().D.(map[string]interface{})
+
+	if m["A"] != "x" {
+		t.Error("Expected x got ", m["A"])
+	}
+
+	if m["B"] != "2" {
+		t.Error("Expected 2 got ", m["B"])
+	}
+
+	if c.Config().F != 3 {
+		t.Error("Expected 3 got ", c.Config().F)
+	}
+
+}
+
+func TestSettingAddFile(t *testing.T) {
+	cfg := ConfigStruct2{}
+
+	f, err := os.CreateTemp("", "watch_test")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	defer os.Remove(f.Name())
+	f.WriteString("A: \"from file\"")
+
+	c := New(cfg)
+	c.AddFile(f.Name(), Yaml)
+
+	assert.Equal(t, c.Config().A, "")
+
+	c.Import()
+	assert.Equal(t, c.Config().A, "from file")
+
+}
+
+func TestSetConfigName(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(cfg)
+	c.SetFileName("test")
+
+	if c.HasError() {
+		t.Error("Expected not error")
+	}
+
+}
+
+func TestConfigSetFileName(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(cfg)
+	c.SetFileName("test")
+
+	if c.HasError() {
+		t.Error("Expected not error")
+	}
+
+	c.SetFileName("")
+
+	if !c.HasError() {
+		t.Error("Expected error")
+	}
+
+}
+
+func TestConfigSetFileFormat(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(cfg)
+	c.SetFileFormat(99)
+
+	if !c.HasError() {
+		t.Error("Expected not error")
+	}
+
+}
+
+func TestConfigSetDirectories(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(cfg)
+	c.SetDirectories([]string{"a", "b"})
+
+	if !c.HasError() {
+		t.Error("Expected not error")
+	}
+
+}
+
+func TestConfigDirectories(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(cfg)
+	c.SetDirectories([]string{"a", "b"})
+
+	if !c.HasError() {
+		t.Error("Expected not error")
+	}
+
+	dirs := c.Directories()
+
+	if len(dirs) != 2 {
+		t.Error("Expected 2 got ", len(dirs))
+	}
+
+}
+
+func TestConfigAddDirectory(t *testing.T) {
+
+	cfg := ConfigStruct2{}
+
+	c := New(cfg)
+	c.AddDirectory("a")
+
+	if !c.HasError() {
+		t.Error("Expected not error")
+	}
+
+	dirs := c.Directories()
+
+	if len(dirs) != 1 {
+		t.Error("Expected 1 got ", len(dirs))
+	}
+
+}
diff --git a/filesystem.go b/filesystem.go
new file mode 100644
index 0000000..8e8d4cb
--- /dev/null
+++ b/filesystem.go
@@ -0,0 +1,12 @@
+package configuration
+
+import (
+	"io/fs"
+	"os"
+)
+
+type internalFS struct{}
+
+func (internalFS) Open(name string) (fs.File, error) {
+	return os.Open(name)
+}
diff --git a/filesystem_test.go b/filesystem_test.go
new file mode 100644
index 0000000..f0a061c
--- /dev/null
+++ b/filesystem_test.go
@@ -0,0 +1,13 @@
+package configuration
+
+import "testing"
+
+func TestFilesystemOpen(t *testing.T) {
+
+	f := internalFS{}
+	h, _ := f.Open("test-not-exists-unknown-file")
+
+	if h == nil {
+		t.Error("Expected nil")
+	}
+}
diff --git a/flags.go b/flags.go
new file mode 100644
index 0000000..8609b4f
--- /dev/null
+++ b/flags.go
@@ -0,0 +1,71 @@
+package configuration
+
+import (
+	"flag"
+	"reflect"
+	"strconv"
+)
+
+// AddFileFromFlags adds a file to the configuration
+// The file is read from the flag specified by the name
+func (s *setting[C]) AddFileFromFlagSet(flagset *flag.FlagSet, name string, format Format) *setting[C] {
+
+	flag := flagset.Lookup(name)
+	if flag == nil {
+		s.errors = append(s.errors, newFlagDoesNotExistError(name))
+		return s
+	}
+
+	path := flag.Value.String()
+	if path == "" {
+		s.errors = append(s.errors, FileNameEmptyError)
+		return s
+	}
+
+	return s.AddFile(path, format)
+}
+
+// InitFromFlags initializes the configuration from the command line flags.
+func (s *setting[C]) InitFromFlagSet(flagset *flag.FlagSet) *setting[C] {
+	s.Lock()
+	defer s.Unlock()
+
+	err := runOnTags(&s.config, []string{flagTagKey}, func(k string, field reflect.Value) {
+
+		flag := flagset.Lookup(k)
+		if flag == nil {
+			s.errors = append(s.errors, newFlagNotFoundError(k))
+			return // flag not found
+		}
+
+		v := flag.Value
+		t := reflect.TypeOf(v)
+
+		if !field.CanSet() {
+			return
+		}
+
+		switch field.Kind() {
+		case reflect.String:
+			field.SetString(v.String())
+		case reflect.Int:
+			intVar, err := strconv.Atoi(v.String())
+			if err != nil {
+				s.errors = append(s.errors, err)
+				return
+			}
+			field.SetInt(int64(intVar))
+		case reflect.Bool:
+			field.SetBool(v.String() == "true")
+		default:
+			s.errors = append(s.errors, newMismatchedTypeError(t, field.Type()))
+		}
+
+	})
+
+	if err != nil {
+		s.errors = append(s.errors, err)
+	}
+
+	return s
+}
diff --git a/flags_test.go b/flags_test.go
new file mode 100644
index 0000000..435bd79
--- /dev/null
+++ b/flags_test.go
@@ -0,0 +1,122 @@
+package configuration
+
+import (
+	"flag"
+	"os"
+	"testing"
+
+	"github.com/magiconair/properties/assert"
+)
+
+// Example for README
+// Print are commented out because they are only for demonstration
+func TestReadmeExample3(t *testing.T) {
+	config := struct {
+		Host string `flag:"host"`
+	}{
+		Host: "localhost",
+	}
+
+	// Set value
+	flag.String("host", "www.example.com", "help message for flagname")
+	flag.Parse()
+
+	s := New(config)
+	s.InitFromFlagSet(flag.CommandLine)
+
+	//fmt.Println(s.Config().Host) // www.example.com
+	assert.Equal(t, s.Config().Host, "www.example.com")
+
+}
+
+func TestSettingFromFlagWithDefault(t *testing.T) {
+	config := ConfigStruct6{}
+
+	s := New(config)
+	assert.Equal(t, s.Config().IB, "yes")
+
+	cmd := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
+	cmd.String("config", "", "help message for flagname")
+
+	tmp, err := os.CreateTemp("", "config")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	defer os.Remove(tmp.Name())
+
+	tmp.WriteString("IB: i-am-b")
+	cmd.Parse([]string{"--config", tmp.Name()})
+	s.AddFileFromFlagSet(cmd, "config", Yaml)
+
+	s.Import()
+
+	assert.Equal(t, s.Config().IB, "i-am-b")
+
+}
+
+func TestSettingFromFlag2(t *testing.T) {
+
+	config := ConfigStruct6{
+		IA: 1234,
+		IB: "no",
+		IC: false,
+		ID: true,
+	}
+
+	s := New(config)
+
+	cmd := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
+	cmd.Int("value-ia", 1234, "help message for flagname")
+	cmd.String("value-ib", "no", "help message for flagname")
+	cmd.Bool("value-ic", false, "help message for flagname")
+	cmd.Bool("value-id", true, "help message for flagname")
+
+	cmd.Parse([]string{"--value-ia=1423"})
+
+	s.InitFromFlagSet(cmd)
+
+	if s.Config().IA != 1423 {
+		t.Errorf("Expected 1423, got %d", s.Config().IA)
+	}
+
+}
+
+func TestSettingFromFlag(t *testing.T) {
+	config := ConfigStruct6{
+		IA: 34567,
+		ID: true,
+	}
+
+	s := New(config)
+
+	cmd := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
+	cmd.Int("value-ia", 1234, "help message for flagname")
+	cmd.String("value-ib", "no", "help message for flagname")
+	cmd.Bool("value-ic", false, "help message for flagname")
+	cmd.Bool("value-id", true, "help message for flagname")
+
+	cmd.Parse([]string{"--value-ia=8900",
+		"--value-ib=i-am-b",
+		"--value-ic=true",
+		"--value-id=false"})
+
+	s.InitFromFlagSet(cmd)
+
+	if s.Config().IA != 8900 {
+		t.Errorf("Expected 8900, got %d", s.Config().IA)
+	}
+
+	if s.Config().IB != "i-am-b" {
+		t.Errorf("Expected i-am-b, got %s", s.Config().IB)
+	}
+
+	if s.Config().IC != true {
+		t.Errorf("Expected true, got %t", s.Config().IC)
+	}
+
+	if s.Config().ID != false {
+		t.Errorf("Expected false, got %t", s.Config().ID)
+	}
+
+}
diff --git a/format.go b/format.go
new file mode 100644
index 0000000..b7f10a4
--- /dev/null
+++ b/format.go
@@ -0,0 +1,31 @@
+package configuration
+
+type Format int
+
+const (
+	Yaml Format = iota
+	Json
+	Toml
+	Properties
+)
+
+var availableFormats = []Format{Yaml, Json, Toml, Properties}
+
+func (f Format) String() string {
+	switch f {
+	case Yaml:
+		return "yaml"
+	case Json:
+		return "json"
+	case Toml:
+		return "toml"
+	case Properties:
+		return "properties"
+	default:
+		return "unknown"
+	}
+}
+
+func (f Format) Extension() string {
+	return "." + f.String()
+}
diff --git a/format_test.go b/format_test.go
new file mode 100644
index 0000000..aa2a876
--- /dev/null
+++ b/format_test.go
@@ -0,0 +1,19 @@
+package configuration
+
+import "testing"
+
+func TestFileFormat(t *testing.T) {
+
+	var f Format
+
+	for _, v := range availableFormats {
+		f = v
+		if f.String() == "unknown" {
+			t.Error("Expected not unknown")
+		}
+		if f.Extension() == ".unknown" {
+			t.Error("Expected not .unknown")
+		}
+	}
+
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..2de0bf4
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,27 @@
+module gitlab.schukai.com/oss/libraries/go/application/configuration
+
+go 1.19
+
+require (
+	github.com/fsnotify/fsnotify v1.5.4
+	github.com/imdario/mergo v0.3.13
+	github.com/kinbiko/jsonassert v1.1.1
+	github.com/magiconair/properties v1.8.6
+	github.com/pelletier/go-toml/v2 v2.0.5
+	github.com/r3labs/diff/v3 v3.0.0
+	github.com/stretchr/testify v1.8.0
+	gitlab.schukai.com/oss/libraries/go/network/http-negotiation v1.1.0
+	golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b
+	gopkg.in/yaml.v3 v3.0.1
+)
+
+require (
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
+	golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
+	golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect
+	google.golang.org/appengine v1.6.7 // indirect
+	google.golang.org/protobuf v1.28.1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..cf0b463
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,61 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
+github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
+github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
+github.com/kinbiko/jsonassert v1.1.1 h1:DB12divY+YB+cVpHULLuKePSi6+ui4M/shHSzJISkSE=
+github.com/kinbiko/jsonassert v1.1.1/go.mod h1:NO4lzrogohtIdNUNzx8sdzB55M4R4Q1bsrWVdqQ7C+A=
+github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
+github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
+github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/r3labs/diff/v3 v3.0.0 h1:ZhPwNxn9gW5WLPBV9GCYaVbMdLOSmJ0DeKdCiSbOLUI=
+github.com/r3labs/diff/v3 v3.0.0/go.mod h1:wCkTySAiDnZao1sZrVTDIzuzgLZ+cNPGn3LC8DlIg5g=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
+github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
+gitlab.schukai.com/oss/libraries/go/network/http-negotiation v1.1.0 h1:4sZ1sQ9JU2KfLWwhpM3rgOa4zG7CNJc+ntWKmya2vl4=
+gitlab.schukai.com/oss/libraries/go/network/http-negotiation v1.1.0/go.mod h1:RS2rKf5O+rmSBshHLOgjG7dxg5N2MhNYokZOBcuXdX8=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b h1:SCE/18RnFsLrjydh/R/s5EVvHoZprqEQUuoxK8q2Pc4=
+golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
+golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc=
+golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/http-handler.go b/http-handler.go
new file mode 100644
index 0000000..a025595
--- /dev/null
+++ b/http-handler.go
@@ -0,0 +1,107 @@
+package configuration
+
+import (
+	"bytes"
+	"context"
+	negotiation "gitlab.schukai.com/oss/libraries/go/network/http-negotiation"
+	"net/http"
+)
+
+// ContextKey is the key used to store the configuration in the request context
+// This is used by the middleware
+func (s *setting[C]) Middleware(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		ctx := r.Context()
+		ctx = context.WithValue(ctx, contextKey, s.config)
+		r = r.WithContext(ctx)
+		next.ServeHTTP(w, r)
+	})
+}
+
+// serveGet handles GET requests
+func (s *setting[C]) serveGet(w http.ResponseWriter, r *http.Request) {
+
+	n := negotiation.New(r.Header)
+	m := n.Type("application/json", "text/json", "application/yaml", "text/yaml", "application/toml", "text/toml", "application/properties", "text/properties", "text/x-java-properties", "text/x-properties")
+
+	if m == "" {
+		w.WriteHeader(http.StatusNotAcceptable)
+		return
+	}
+
+	buf := new(bytes.Buffer)
+
+	switch m {
+	case "application/json", "text/json":
+		w.Header().Set("Content-Type", "application/json")
+		s.Write(buf, Json)
+	case "application/yaml", "text/yaml":
+		w.Header().Set("Content-Type", "application/yaml")
+		s.Write(buf, Yaml)
+	case "application/toml", "text/toml":
+		w.Header().Set("Content-Type", "application/toml")
+		s.Write(buf, Toml)
+	case "application/properties", "text/properties", "text/x-java-properties", "text/x-properties":
+		w.Header().Set("Content-Type", "application/properties")
+		s.Write(buf, Properties)
+
+	}
+
+	if s.HasError() {
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	w.Write(buf.Bytes())
+
+}
+
+func (s *setting[C]) servePost(w http.ResponseWriter, r *http.Request) {
+
+	n := negotiation.New(r.Header)
+	m := n.ContentType("application/json", "text/json", "application/yaml", "text/yaml", "application/toml", "text/toml", "application/properties", "text/properties", "text/x-java-properties", "text/x-properties")
+
+	rs := reader{
+		reader: r.Body,
+	}
+
+	switch m {
+	case "application/json", "text/json":
+		rs.format = Json
+	case "application/yaml", "text/yaml":
+		rs.format = Yaml
+	case "application/toml", "text/toml":
+		rs.format = Toml
+	case "application/properties", "text/properties", "text/x-java-properties", "text/x-properties":
+		rs.format = Properties
+	default:
+		w.WriteHeader(http.StatusUnsupportedMediaType)
+		return
+	}
+
+	s.Lock()
+	defer s.Unlock()
+
+	b := s.config
+	s.importStream(rs)
+
+	x := s.config
+	s.config = b
+
+	s.setConfigInternal(x, false)
+
+}
+
+// ServeHTTP implements the http.Handler interface
+func (s *setting[C]) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+
+	switch r.Method {
+	case http.MethodGet:
+		s.serveGet(w, r)
+	case http.MethodPost:
+		s.servePost(w, r)
+	default:
+		w.WriteHeader(http.StatusMethodNotAllowed)
+	}
+
+}
diff --git a/http-handler_test.go b/http-handler_test.go
new file mode 100644
index 0000000..9e65107
--- /dev/null
+++ b/http-handler_test.go
@@ -0,0 +1,316 @@
+package configuration
+
+import (
+	"github.com/kinbiko/jsonassert"
+	"github.com/stretchr/testify/assert"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+	"time"
+)
+
+type ConfigHttpStruct1 struct {
+	A string `yaml:"A" json:"A" toml:"A" properties:"A"`
+	B bool   `yaml:"B" json:"B" toml:"B" properties:"B"`
+	C int    `yaml:"C" json:"C" toml:"C" properties:"C"`
+}
+
+func TestReadMeHttp(t *testing.T) {
+
+	var config ConfigHttpStruct1
+	s := New(config)
+
+	req, err := http.NewRequest("GET", "/config", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	req.Header.Set("Accept", "application/json")
+
+	rr := httptest.NewRecorder()
+	handler := http.HandlerFunc(s.ServeHTTP)
+	handler.ServeHTTP(rr, req)
+
+	jsonassert.New(t).Assertf(rr.Body.String(),
+		`{
+  "A": "",
+  "B": false,
+  "C": 0
+}
+`)
+
+}
+
+func TestConfigurationMiddleware(t *testing.T) {
+
+	req, err := http.NewRequest("GET", "/config", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		val := r.Context().Value(contextKey)
+		if val == nil {
+			t.Error("config not present")
+			return
+		}
+
+		config, ok := val.(ConfigHttpStruct1)
+		if !ok {
+			t.Error("config not of correct type")
+		}
+
+		assert.Equal(t, config.A, "a")
+
+	})
+
+	var config ConfigHttpStruct1
+	config.A = "a"
+	s := New(config)
+
+	s.Middleware(nextHandler).ServeHTTP(httptest.NewRecorder(), req)
+
+}
+
+func TestSettingServeHTTPJson(t *testing.T) {
+	// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
+	// pass 'nil' as the third parameter.
+	req, err := http.NewRequest("GET", "/config", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var config ConfigHttpStruct1
+	s := New(config)
+
+	req.Header.Set("Accept", "application/json")
+
+	// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
+	rr := httptest.NewRecorder()
+	handler := http.HandlerFunc(s.ServeHTTP)
+
+	// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
+	// directly and pass in our Request and ResponseRecorder.
+	handler.ServeHTTP(rr, req)
+
+	// Check the status code is what we expect.
+	if status := rr.Code; status != http.StatusOK {
+		t.Errorf("handler returned wrong status code: got %v want %v",
+			status, http.StatusOK)
+	}
+
+	// Check the response body is what we expect.
+	jsonassert.New(t).Assertf(rr.Body.String(),
+		`{
+  "A": "",
+  "B": false,
+  "C": 0
+}
+`)
+
+}
+func TestSettingServeHTTPYaml(t *testing.T) {
+	// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
+	// pass 'nil' as the third parameter.
+	req, err := http.NewRequest("GET", "/config", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	req.Header.Set("Accept", "application/yaml")
+
+	var config ConfigHttpStruct1
+	s := New(config)
+
+	// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
+	rr := httptest.NewRecorder()
+	handler := http.HandlerFunc(s.ServeHTTP)
+
+	// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
+	// directly and pass in our Request and ResponseRecorder.
+	handler.ServeHTTP(rr, req)
+
+	// Check the status code is what we expect.
+	if status := rr.Code; status != http.StatusOK {
+		t.Errorf("handler returned wrong status code: got %v want %v",
+			status, http.StatusOK)
+	}
+
+	// Check the response body is what we expect.
+	yaml := `A: ""
+B: false
+C: 0
+`
+	if rr.Body.String() != yaml {
+		t.Errorf("handler returned unexpected body: got %v want %v",
+			rr.Body.String(), yaml)
+	}
+
+}
+
+func TestSettingServeHTTPToml(t *testing.T) {
+
+	// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
+	// pass 'nil' as the third parameter.
+	req, err := http.NewRequest("GET", "/config", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	req.Header.Set("Accept", "application/toml")
+
+	var config ConfigHttpStruct1
+	s := New(config)
+
+	// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
+	rr := httptest.NewRecorder()
+	handler := http.HandlerFunc(s.ServeHTTP)
+
+	// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
+	// directly and pass in our Request and ResponseRecorder.
+	handler.ServeHTTP(rr, req)
+
+	// Check the status code is what we expect.
+	if status := rr.Code; status != http.StatusOK {
+		t.Errorf("handler returned wrong status code: got %v want %v",
+			status, http.StatusOK)
+	}
+
+	// Check the response body is what we expect.
+	toml := `A = ''
+B = false
+C = 0
+`
+	if rr.Body.String() != toml {
+		t.Errorf("handler returned unexpected body: got %v want %v",
+			rr.Body.String(), toml)
+	}
+
+}
+
+func TestSettingsHttpStatusNotAcceptable(t *testing.T) {
+	req, err := http.NewRequest("GET", "/config", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	req.Header.Set("Accept", "text/html")
+
+	var config ConfigHttpStruct1
+
+	s := New(config)
+
+	// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
+	rr := httptest.NewRecorder()
+	handler := http.HandlerFunc(s.ServeHTTP)
+
+	// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
+	// directly and pass in our Request and ResponseRecorder.
+	handler.ServeHTTP(rr, req)
+
+	// Check the status code is what we expect.
+	if status := rr.Code; status != http.StatusNotAcceptable {
+		t.Errorf("handler returned wrong status code: got %v want %v",
+			status, http.StatusNotAcceptable)
+	}
+}
+
+func TestSettingServeHTTPProperties(t *testing.T) {
+
+	// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
+	// pass 'nil' as the third parameter.
+	req, err := http.NewRequest("GET", "/config", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	req.Header.Set("Accept", "application/properties")
+
+	var config ConfigHttpStruct1
+
+	s := New(config)
+
+	// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
+	rr := httptest.NewRecorder()
+	handler := http.HandlerFunc(s.ServeHTTP)
+
+	// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
+	// directly and pass in our Request and ResponseRecorder.
+	handler.ServeHTTP(rr, req)
+
+	// Check the status code is what we expect.
+	if status := rr.Code; status != http.StatusOK {
+		t.Errorf("handler returned wrong status code: got %v want %v",
+			status, http.StatusOK)
+	}
+
+	// Check the response body is what we expect.
+	// https://en.wikipedia.org/wiki/.properties
+	// Each line in a .properties file normally stores a single property.
+	// Several formats are possible for each line, including key=value,
+	// key = value, key:value, and key value. Single-quotes or double-quotes
+	// are considered part of the string. Trailing space is significant and
+	// presumed to be trimmed as required by the consumer.
+
+	r := rr.Body.String()
+	// the order of the properties is not guaranteed
+	assert.Contains(t, r, "A = ")
+	assert.Contains(t, r, "B = false")
+	assert.Contains(t, r, "C = 0")
+
+}
+
+func TestConfigurationServePostJson(t *testing.T) {
+
+	var config ConfigHttpStruct1
+	s := New(config)
+
+	closeChan := make(chan bool)
+	counter := 0
+	s.OnChange(func(event ChangeEvent) {
+		counter++
+		closeChan <- true
+	})
+
+	// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
+	rr := httptest.NewRecorder()
+	handler := http.HandlerFunc(s.ServeHTTP)
+
+	// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
+	// directly and pass in our Request and ResponseRecorder.
+	req, err := http.NewRequest("POST", "/config", strings.NewReader(`{
+  "A": "hello",
+  "B": true,
+  "C": 1
+}`))
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	req.Header.Set("Content-Type", "application/json")
+	handler.ServeHTTP(rr, req)
+
+	// Check the status code is what we expect.
+	if status := rr.Code; status != http.StatusOK {
+		t.Errorf("handler returned wrong status code: got %v want %v",
+			status, http.StatusOK)
+	}
+
+	config = s.Config()
+	assert.Equal(t, "hello", config.A)
+	assert.Equal(t, true, config.B)
+	assert.Equal(t, 1, config.C)
+
+	select {
+	case <-closeChan:
+	case <-time.After(time.Second * 5):
+		t.Fatalf("Timeout")
+	}
+
+	if counter != 1 {
+		t.Error("Expected 1 got ", counter)
+	}
+
+}
diff --git a/import.go b/import.go
new file mode 100644
index 0000000..64140cb
--- /dev/null
+++ b/import.go
@@ -0,0 +1,151 @@
+package configuration
+
+import (
+	"bytes"
+	"encoding/json"
+	"github.com/imdario/mergo"
+	"github.com/magiconair/properties"
+	"github.com/pelletier/go-toml/v2"
+	"gopkg.in/yaml.v3"
+	"io"
+	"os"
+	"path"
+)
+
+func importJson[C any](config *C, reader io.Reader) error {
+	decoder := json.NewDecoder(reader)
+	return decoder.Decode(config)
+}
+
+func importYaml[C any](config *C, reader io.Reader) error {
+	decoder := yaml.NewDecoder(reader)
+	return decoder.Decode(config)
+}
+
+func importToml[C any](config *C, reader io.Reader) error {
+	decoder := toml.NewDecoder(reader)
+	return decoder.Decode(config)
+}
+
+func importProperties[C any](config *C, reader io.Reader) error {
+
+	buf := &bytes.Buffer{}
+	buf.ReadFrom(reader)
+	b := buf.Bytes()
+
+	p, err := properties.Load(b, properties.UTF8)
+	if err != nil {
+		return err
+	}
+
+	return p.Decode(config)
+
+}
+
+func (s *setting[C]) importStream(r reader) {
+	var c C
+	var err error
+
+	err = nil // reset error
+
+	x := r.reader
+	switch r.format {
+	case Json:
+		err = importJson(&c, x)
+	case Yaml:
+		err = importYaml(&c, x)
+	case Toml:
+		err = importToml(&c, x)
+	case Properties:
+		err = importProperties(&c, x)
+	default:
+		err = newFormatNotSupportedError(r.format)
+	}
+
+	if err == nil {
+		s.importCounter++
+	} else {
+		s.errors = append(s.errors, err)
+	}
+
+	if err := mergo.Merge(&s.config, c); err != nil {
+		s.errors = append(s.errors, err)
+	}
+}
+
+func (s *setting[C]) importStreams() {
+	for _, r := range s.stream.readers {
+		s.importStream(r)
+	}
+}
+
+func (s *setting[C]) importFiles() {
+
+	s.fileWatch.Lock()
+	defer s.fileWatch.Unlock()
+	
+	// new files may have been added
+	s.fileWatch.watchList = make(map[string]string)
+
+	for _, d := range s.files.directories {
+		fn := path.Join(d, s.files.name+s.files.format.Extension())
+
+		f, err := s.files.fs.Open(fn)
+
+		if os.IsNotExist(err) {
+			f.Close()
+			continue
+		}
+
+		r := (io.Reader)(f)
+		s.importStream(reader{s.files.format, r})
+		f.Close()
+
+		s.fileWatch.watchList[fn] = fn
+
+	}
+
+	for _, f := range s.files.files {
+		r, err := s.files.fs.Open(f.path)
+
+		if err != nil {
+			s.errors = append(s.errors, err)
+			r.Close()
+			continue
+		}
+		s.importStream(reader{f.format, r})
+		r.Close()
+		s.fileWatch.watchList[f.path] = f.path
+	}
+
+}
+
+func (s *setting[C]) Import() *setting[C] {
+
+	s.Lock()
+	defer s.Unlock()
+
+	defaults := s.config
+
+	var n C
+	s.config = n
+
+	s.importCounter = 0
+
+	s.importFiles()
+	s.importStreams()
+
+	if err := mergo.Merge(&s.config, defaults); err != nil {
+		s.errors = append(s.errors, err)
+	}
+
+	if s.importCounter == 0 {
+		s.errors = append(s.errors, NoFileImportedError)
+	}
+
+	x := s.config
+	s.config = defaults
+	s.setConfigInternal(x, false)
+
+	return s
+}
diff --git a/import_test.go b/import_test.go
new file mode 100644
index 0000000..f908f49
--- /dev/null
+++ b/import_test.go
@@ -0,0 +1,28 @@
+package configuration
+
+import (
+	"github.com/magiconair/properties/assert"
+	"testing"
+)
+
+// Example for README
+// Print are commented out because they are only for demonstration
+func TestReadExample3(t *testing.T) {
+
+	config := struct {
+		Host string
+	}{
+		Host: "localhost",
+	}
+
+	c := New(config)
+
+	c.SetMnemonic("my-app")
+	c.SetDefaultDirectories()
+	c.Import()
+
+	//fmt.Println(c.Config().Host)
+
+	assert.Equal(t, c.Config().Host, "localhost")
+
+}
diff --git a/integration_test.go b/integration_test.go
new file mode 100644
index 0000000..e1b10bd
--- /dev/null
+++ b/integration_test.go
@@ -0,0 +1,46 @@
+package configuration
+
+import (
+	"fmt"
+	"os"
+	"testing"
+)
+
+func FuzzTest(f *testing.F) {
+
+	f.Fuzz(func(t *testing.T, a string, b bool, f int) {
+
+		config := ConfigStruct2{
+			A: a,
+			B: b,
+			F: f,
+		}
+
+		s := New(config)
+
+		fmt.Println("A:", a, "B:", b, "F:", f)
+
+		if s == nil {
+			t.Error("Expected not nil")
+		}
+
+		if s.Config().A != a {
+			t.Error("Expected %i got %i", a, s.Config().A)
+		}
+
+		if s.Config().B != b {
+			t.Error("Expected %V got %V", b, s.Config().B)
+		}
+
+		if s.Config().F != f {
+			t.Error("Expected %i got %i", f, s.Config().F)
+		}
+
+		s.Write(os.Stdout, Yaml)
+		if s.HasError() {
+			t.Error(s.Errors())
+			s.ResetErrors()
+		}
+
+	})
+}
diff --git a/licenses/github.com/imdario/mergo/LICENSE b/licenses/github.com/imdario/mergo/LICENSE
new file mode 100644
index 0000000..6866802
--- /dev/null
+++ b/licenses/github.com/imdario/mergo/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2013 Dario Castañé. All rights reserved.
+Copyright (c) 2012 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/licenses/github.com/magiconair/properties/LICENSE.md b/licenses/github.com/magiconair/properties/LICENSE.md
new file mode 100644
index 0000000..79c87e3
--- /dev/null
+++ b/licenses/github.com/magiconair/properties/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright (c) 2013-2020, Frank Schroeder
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/licenses/github.com/pelletier/go-toml/v2/LICENSE b/licenses/github.com/pelletier/go-toml/v2/LICENSE
new file mode 100644
index 0000000..6839d51
--- /dev/null
+++ b/licenses/github.com/pelletier/go-toml/v2/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 - 2022 Thomas Pelletier, Eric Anderton
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/licenses/github.com/r3labs/diff/v3/.gitignore b/licenses/github.com/r3labs/diff/v3/.gitignore
new file mode 100644
index 0000000..e04e61e
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/.gitignore
@@ -0,0 +1,17 @@
+# Binaries for programs and plugins
+*.exe
+*.dll
+*.so
+*.dylib
+
+# Test binary, build with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
+.glide/
+.idea/
+
+patchflags_string.go
diff --git a/licenses/github.com/r3labs/diff/v3/.travis.yml b/licenses/github.com/r3labs/diff/v3/.travis.yml
new file mode 100644
index 0000000..17952b0
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/.travis.yml
@@ -0,0 +1,8 @@
+language: go
+
+go:
+  - "1.14"
+  - master
+
+before_script:
+  - make deps
diff --git a/licenses/github.com/r3labs/diff/v3/CONTRIBUTING.md b/licenses/github.com/r3labs/diff/v3/CONTRIBUTING.md
new file mode 100644
index 0000000..3c00b27
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/CONTRIBUTING.md
@@ -0,0 +1,80 @@
+# Contributing guidelines
+
+Looking to contribute something to this project? Here's how you can help:
+
+Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved.
+
+Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue or assessing patches and features.
+
+We also have a [code of conduct](https://ernest.io/conduct).
+
+## Using the issue tracker
+
+The issue tracker is the preferred channel for [bug reports](#bug-reports), [features requests](#feature-requests) and [submitting pull requests](#pull-requests), but please respect the following restrictions:
+
+* Please **do not** use the issue tracker for personal support requests.
+
+* Please **do not** derail issues. Keep the discussion on topic and
+  respect the opinions of others.
+
+<a name="bugs"></a>
+## Bug reports
+
+A bug is a _demonstrable problem_ that is caused by the code in the repository.
+Good bug reports are extremely helpful - thank you!
+
+Guidelines for bug reports:
+
+1. **Use the GitHub issue search** &mdash; check if the issue has already been
+   reported.
+
+2. **Check if the issue has been fixed** &mdash; try to reproduce it using the
+   latest `master` or `develop` branch in the repository.
+
+3. **Isolate the problem** &mdash; create a reduced test case and a live example.
+
+A good bug report shouldn't leave others needing to chase you up for more
+information. Please try to be as detailed as possible in your report. What is
+your environment? What steps will reproduce the issue? Which environment experience the problem? What would you expect to be the outcome? All these
+details will help people to fix any potential bugs.
+
+Example:
+
+> Short and descriptive example bug report title
+>
+> A summary of the issue and the environment in which it occurs. If
+> suitable, include the steps required to reproduce the bug.
+>
+> 1. This is the first step
+> 2. This is the second step
+> 3. Further steps, etc.
+>
+> `<url>` - a link to the reduced test case
+>
+> Any other information you want to share that is relevant to the issue being
+> reported. This might include the lines of code that you have identified as
+> causing the bug, and potential solutions (and your opinions on their
+> merits).
+
+<a name="features"></a>
+## Feature requests
+
+Feature requests are welcome. But take a moment to find out whether your idea
+fits with the scope and aims of the project. It's up to *you* to make a strong
+case to convince the project's developers of the merits of this feature. Please
+provide as much detail and context as possible.
+
+<a name="pull-requests"></a>
+## Pull requests
+
+Good pull requests - patches, improvements, new features - are a fantastic
+help. They should remain focused in scope and avoid containing unrelated
+commits.
+
+[**Please ask first**](https://ernest.io/community) before embarking on any significant pull request (e.g.
+implementing features, refactoring code, porting to a different language),
+otherwise you risk spending a lot of time working on something that the
+project's developers might not want to merge into the project.
+
+Please adhere to the coding conventions used throughout a project (indentation,
+accurate comments, etc.) and any other requirements (such as test coverage).
diff --git a/licenses/github.com/r3labs/diff/v3/LICENSE b/licenses/github.com/r3labs/diff/v3/LICENSE
new file mode 100644
index 0000000..a612ad9
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
diff --git a/licenses/github.com/r3labs/diff/v3/Makefile b/licenses/github.com/r3labs/diff/v3/Makefile
new file mode 100644
index 0000000..f119f8c
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/Makefile
@@ -0,0 +1,11 @@
+install:
+	go install -v ${LDFLAGS}
+
+deps:
+	go get github.com/stretchr/testify
+
+test:
+	@go test -v -cover ./...
+
+cover:
+	@go test -coverprofile cover.out
diff --git a/licenses/github.com/r3labs/diff/v3/README.md b/licenses/github.com/r3labs/diff/v3/README.md
new file mode 100644
index 0000000..97aee65
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/README.md
@@ -0,0 +1,327 @@
+# Diff [![PkgGoDev](https://pkg.go.dev/badge/github.com/r3labs/diff)](https://pkg.go.dev/github.com/r3labs/diff) [![Go Report Card](https://goreportcard.com/badge/github.com/r3labs/diff)](https://goreportcard.com/report/github.com/r3labs/diff) [![Build Status](https://travis-ci.com/r3labs/diff.svg?branch=master)](https://travis-ci.com/r3labs/diff)
+
+A library for diffing golang structures and values.
+
+Utilizing field tags and reflection, it is able to compare two structures of the same type and create a changelog of all modified values. The produced changelog can easily be serialized to json.
+
+NOTE: All active development now takes place on the v3 branch.
+
+## Installation
+
+For version 3:
+```
+go get github.com/r3labs/diff/v3
+```
+
+## Changelog Format
+
+When diffing two structures using `Diff`, a changelog will be produced. Any detected changes will populate the changelog array with a Change type:
+
+```go
+type Change struct {
+	Type string      // The type of change detected; can be one of create, update or delete
+	Path []string    // The path of the detected change; will contain any field name or array index that was part of the traversal
+	From interface{} // The original value that was present in the "from" structure
+	To   interface{} // The new value that was detected as a change in the "to" structure
+}
+```
+
+Given the example below, we are diffing two slices where the third element has been removed:
+
+```go
+from := []int{1, 2, 3, 4}
+to := []int{1, 2, 4}
+
+changelog, _ := diff.Diff(from, to)
+```
+
+The resultant changelog should contain one change:
+
+```go
+Change{
+    Type: "delete",
+    Path: ["2"],
+    From: 3,
+    To:   nil,
+}
+```
+
+## Supported Types
+
+A diffable value can be/contain any of the following types:
+
+
+| Type         | Supported |
+| ------------ | --------- |
+| struct       | ✔         |
+| slice        | ✔         |
+| string       | ✔         |
+| int          | ✔         |
+| bool         | ✔         |
+| map          | ✔         |
+| pointer      | ✔         |
+| custom types | ✔         |
+
+
+Please see the docs for more supported types, options and features.
+
+### Tags
+
+In order for struct fields to be compared, they must be tagged with a given name. All tag values are prefixed with `diff`. i.e. `diff:"items"`.
+
+| Tag           | Usage                                                                                                                                                                                                                                                                                           |
+| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `-`           | Excludes a value from being diffed                                                                                                                                                                                                                                                              |
+| `identifier`  | If you need to compare arrays by a matching identifier and not based on order, you can specify the `identifier` tag. If an identifiable element is found in both the from and to structures, they will be directly compared. i.e. `diff:"name, identifier"`                                     |
+| `immutable`   | Will omit this struct field from diffing. When using `diff.StructValues()` these values will be added to the returned changelog. It's use case is for when we have nothing to compare a struct to and want to show all of its relevant values.                                                  |
+| `nocreate`    | The default patch action is to allocate instances in the target strut, map or slice should they not exist. Adding this flag will tell patch to skip elements that it would otherwise need to allocate. This is separate from immutable, which is also honored while patching.                   |
+| `omitunequal` | Patching is a 'best effort' operation, and will by default attempt to update the 'correct' member of the target even if the underlying value has already changed to something other than the value in the change log 'from'. This tag will selectively ignore values that are not a 100% match. |
+
+## Usage
+
+### Basic Example
+
+Diffing a basic set of values can be accomplished using the diff functions. Any items that specify a "diff" tag using a name will be compared.
+
+```go
+import "github.com/r3labs/diff/v3"
+
+type Order struct {
+    ID    string `diff:"id"`
+    Items []int  `diff:"items"`
+}
+
+func main() {
+    a := Order{
+        ID: "1234",
+        Items: []int{1, 2, 3, 4},
+    }
+
+    b := Order{
+        ID: "1234",
+        Items: []int{1, 2, 4},
+    }
+
+    changelog, err := diff.Diff(a, b)
+    ...
+}
+```
+
+In this example, the output generated in the changelog will indicate that the third element with a value of '3' was removed from items.
+When marshalling the changelog to json, the output will look like:
+
+```json
+[
+    {
+        "type": "delete",
+        "path": ["items", "2"],
+        "from": 3,
+        "to": null
+    }
+]
+```
+
+### Options and Configuration
+
+Options can be set on the differ at call time which effect how diff acts when building the change log.
+```go
+import "github.com/r3labs/diff/v3"
+
+type Order struct {
+    ID    string `diff:"id"`
+    Items []int  `diff:"items"`
+}
+
+func main() {
+    a := Order{
+        ID: "1234",
+        Items: []int{1, 2, 3, 4},
+    }
+
+    b := Order{
+        ID: "1234",
+        Items: []int{1, 2, 4},
+    }
+
+    changelog, err := diff.Diff(a, b, diff.DisableStructValues(), diff.AllowTypeMismatch(true))
+    ...
+}
+```
+
+You can also create a new instance of a differ that allows options to be set.
+
+```go
+import "github.com/r3labs/diff/v3"
+
+type Order struct {
+    ID    string `diff:"id"`
+    Items []int  `diff:"items"`
+}
+
+func main() {
+    a := Order{
+        ID: "1234",
+        Items: []int{1, 2, 3, 4},
+    }
+
+    b := Order{
+        ID: "1234",
+        Items: []int{1, 2, 4},
+    }
+
+    d, err := diff.NewDiffer(diff.SliceOrdering(true))
+    if err != nil {
+        panic(err)
+    }
+
+    changelog, err := d.Diff(a, b)
+    ...
+}
+```
+
+Supported options are:
+
+`SliceOrdering` ensures that the ordering of items in a slice is taken into account
+
+`DiscardComplexOrigin` is a directive to diff to omit additional origin information about structs. This alters the behavior of patch and can lead to some pitfalls and non-intuitive behavior if used. On the other hand, it can significantly reduce the memory footprint of large complex diffs.
+
+`AllowTypeMismatch` is a global directive to either allow (true) or not to allow (false) patch apply the changes if 'from' is not equal. This is effectively a global version of the omitunequal tag.
+
+`Filter` provides a callback that allows you to determine which fields the differ descends into
+
+`DisableStructValues` disables populating a separate change for each item in a struct, where the struct is being compared to a nil Value.
+
+`TagName` sets the tag name to use when getting field names and options.
+
+### Patch and merge support
+Diff additionally supports merge and patch. Similar in concept to text patching / merging the Patch function, given 
+a change log and a target instance will make a _best effort_ to apply the changes in the change log to the variable
+pointed to. The intention is that the target pointer is of the same type however, that doesn't necessarily have to be 
+true. For example, two slices of differing structs may be similar enough to apply changes to in a polymorphic way, and 
+patch will certainly try.
+
+The patch function doesn't actually fail, and even if there are errors, it may succeed sufficiently for the task at hand.
+To accommodate this patch keeps track of each change log option it attempts to apply and reports the details of what 
+happened for further scrutiny.
+
+```go
+import "github.com/r3labs/diff/v3"
+
+type Order struct {
+    ID    string `diff:"id"`
+    Items []int  `diff:"items"`
+}
+
+func main() {
+    a := Order{
+        ID: "1234",
+        Items: []int{1, 2, 3, 4},
+    }
+
+    b := Order{
+        ID: "1234",
+        Items: []int{1, 2, 4},
+    }
+
+    c := Order{}
+    changelog, err := diff.Diff(a, b)
+
+    patchlog := diff.Patch(changelog, &c)
+    //Note the lack of an error. Patch is best effort and uses flags to indicate actions taken
+    //and keeps any errors encountered along the way for review
+    fmt.Printf("Encountered %d errors while patching", patchlog.ErrorCount())
+    ...
+}
+```
+
+Instances of differ with options set can also be used when patching.
+
+```go
+package main
+
+import "github.com/r3labs/diff/v3"
+
+type Order struct {
+	ID    string `json:"id"`
+	Items []int  `json:"items"`
+}
+
+func main() {
+    a := Order{
+        ID:    "1234",
+        Items: []int{1, 2, 3, 4},
+        }
+
+    b := Order{
+        ID:    "1234",
+        Items: []int{1, 2, 4},
+    }
+
+    d, _ := diff.NewDiffer(diff.TagName("json"))
+
+    changelog, _ := d.Diff(a, b)
+
+    d.Patch(changelog, &a)
+    // reflect.DeepEqual(a, b) == true
+}
+
+```
+
+As a convenience, there is a Merge function that allows one to take three interfaces and perform all the tasks at the same
+time.
+
+```go
+import "github.com/r3labs/diff/v3"
+
+type Order struct {
+    ID    string `diff:"id"`
+    Items []int  `diff:"items"`
+}
+
+func main() {
+    a := Order{
+        ID: "1234",
+        Items: []int{1, 2, 3, 4},
+    }
+
+    b := Order{
+        ID: "1234",
+        Items: []int{1, 2, 4},
+    }
+
+    c := Order{}
+    patchlog, err := diff.Merge(a, b, &c)
+    if err != nil {
+        fmt.Printf("Error encountered while diffing a & b")
+    }
+    fmt.Printf("Encountered %d errors while patching", patchlog.ErrorCount())
+    ...
+}
+```
+## Running Tests
+
+```
+make test
+```
+
+## Contributing
+
+Please read through our
+[contributing guidelines](CONTRIBUTING.md).
+Included are directions for opening issues, coding standards, and notes on
+development.
+
+Moreover, if your pull request contains patches or features, you must include
+relevant unit tests.
+
+## Versioning
+
+For transparency into our release cycle and in striving to maintain backward
+compatibility, this project is maintained under [the Semantic Versioning guidelines](http://semver.org/).
+
+## Copyright and License
+
+Code and documentation copyright since 2015 r3labs.io authors.
+
+Code released under
+[the Mozilla Public License Version 2.0](LICENSE).
diff --git a/licenses/github.com/r3labs/diff/v3/change_value.go b/licenses/github.com/r3labs/diff/v3/change_value.go
new file mode 100644
index 0000000..b0f3a73
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/change_value.go
@@ -0,0 +1,209 @@
+package diff
+
+import (
+	"fmt"
+	"reflect"
+)
+
+//ChangeValue is a specialized struct for monitoring patching
+type ChangeValue struct {
+	parent *reflect.Value
+	target *reflect.Value
+	flags  PatchFlags
+	change *Change
+	err    error
+	pos    int
+	index  int
+	key    reflect.Value
+}
+
+//swap swaps out the target as we move down the path. Note that a nil
+//     check is foregone here due to the fact we control usage.
+func (c *ChangeValue) swap(newTarget *reflect.Value) {
+	if newTarget.IsValid() {
+		c.ClearFlag(FlagInvalidTarget)
+		c.parent = c.target
+		c.target = newTarget
+		c.pos++
+	}
+}
+
+// Sets a flag on the node and saves the change
+func (c *ChangeValue) SetFlag(flag PatchFlags) {
+	if c != nil {
+		c.flags = c.flags | flag
+	}
+}
+
+//ClearFlag removes just a single flag
+func (c *ChangeValue) ClearFlag(flag PatchFlags) {
+	if c != nil {
+		c.flags = c.flags &^ flag
+	}
+}
+
+//HasFlag indicates if a flag is set on the node. returns false if node is bad
+func (c *ChangeValue) HasFlag(flag PatchFlags) bool {
+	return (c.flags & flag) != 0
+}
+
+//IsValid echo for is valid
+func (c *ChangeValue) IsValid() bool {
+	if c != nil {
+		return c.target.IsValid() || !c.HasFlag(FlagInvalidTarget)
+	}
+	return false
+}
+
+//ParentKind - helps keep us nil safe
+func (c ChangeValue) ParentKind() reflect.Kind {
+	if c.parent != nil {
+		return c.parent.Kind()
+	}
+	return reflect.Invalid
+}
+
+//ParentLen is a nil safe parent length check
+func (c ChangeValue) ParentLen() (ret int) {
+	if c.parent != nil &&
+		(c.parent.Kind() == reflect.Slice ||
+			c.parent.Kind() == reflect.Map) {
+		ret = c.parent.Len()
+	}
+	return
+}
+
+//ParentSet - nil safe parent set
+func (c *ChangeValue) ParentSet(value reflect.Value, convertCompatibleTypes bool) {
+	if c != nil && c.parent != nil {
+		defer func() {
+			if r := recover(); r != nil {
+				c.SetFlag(FlagParentSetFailed)
+			}
+		}()
+
+		if convertCompatibleTypes {
+			if !value.Type().ConvertibleTo(c.parent.Type()) {
+				c.AddError(fmt.Errorf("Value of type %s is not convertible to %s", value.Type().String(), c.parent.Type().String()))
+				c.SetFlag(FlagParentSetFailed)
+				return
+			}
+			c.parent.Set(value.Convert(c.parent.Type()))
+		} else {
+			c.parent.Set(value)
+		}
+		c.SetFlag(FlagParentSetApplied)
+	}
+}
+
+//Len echo for len
+func (c ChangeValue) Len() int {
+	return c.target.Len()
+}
+
+//Set echos reflect set
+func (c *ChangeValue) Set(value reflect.Value, convertCompatibleTypes bool) {
+	if c == nil {
+		return
+	}
+
+	defer func() {
+		if r := recover(); r != nil {
+			switch e := r.(type) {
+			case string:
+				c.AddError(NewError(e))
+			case *reflect.ValueError:
+				c.AddError(NewError(e.Error()))
+			}
+
+			c.SetFlag(FlagFailed)
+		}
+	}()
+
+	if c.HasFlag(OptionImmutable) {
+		c.SetFlag(FlagIgnored)
+		return
+	}
+
+	if convertCompatibleTypes {
+		if c.target.Kind() == reflect.Ptr && value.Kind() != reflect.Ptr {
+			if !value.IsValid() {
+				c.target.Set(reflect.Zero(c.target.Type()))
+				c.SetFlag(FlagApplied)
+				return
+			} else if !value.Type().ConvertibleTo(c.target.Elem().Type()) {
+				c.AddError(fmt.Errorf("Value of type %s is not convertible to %s", value.Type().String(), c.target.Type().String()))
+				c.SetFlag(FlagFailed)
+				return
+			}
+
+			tv := reflect.New(c.target.Elem().Type())
+			tv.Elem().Set(value.Convert(c.target.Elem().Type()))
+			c.target.Set(tv)
+		} else {
+			if !value.Type().ConvertibleTo(c.target.Type()) {
+				c.AddError(fmt.Errorf("Value of type %s is not convertible to %s", value.Type().String(), c.target.Type().String()))
+				c.SetFlag(FlagFailed)
+				return
+			}
+
+			c.target.Set(value.Convert(c.target.Type()))
+		}
+	} else {
+		if value.IsValid() {
+			if c.target.Kind() == reflect.Ptr && value.Kind() != reflect.Ptr {
+				tv := reflect.New(value.Type())
+				tv.Elem().Set(value)
+				c.target.Set(tv)
+			} else {
+				c.target.Set(value)
+			}
+		} else if c.target.Kind() == reflect.Ptr {
+			c.target.Set(reflect.Zero(c.target.Type()))
+		} else if !c.target.IsZero() {
+			t := c.target.Elem()
+			t.Set(reflect.Zero(t.Type()))
+		}
+	}
+	c.SetFlag(FlagApplied)
+}
+
+//Index echo for index
+func (c ChangeValue) Index(i int) reflect.Value {
+	return c.target.Index(i)
+}
+
+//ParentIndex - get us the parent version, nil safe
+func (c ChangeValue) ParentIndex(i int) (ret reflect.Value) {
+	if c.parent != nil {
+		ret = c.parent.Index(i)
+	}
+	return
+}
+
+//Instance a new element of type for target. Taking the
+//copy of the complex origin avoids the 'lack of data' issue
+//present when allocating complex structs with slices and
+//arrays
+func (c ChangeValue) NewElement() reflect.Value {
+	ret := c.change.parent
+	if ret != nil {
+		return reflect.ValueOf(ret)
+	}
+	return reflect.New(c.target.Type().Elem()).Elem()
+}
+
+//NewArrayElement gives us a dynamically typed new element
+func (c ChangeValue) NewArrayElement() reflect.Value {
+	c.target.Set(reflect.Append(*c.target, c.NewElement()))
+	c.SetFlag(FlagCreated)
+	return c.Index(c.Len() - 1)
+}
+
+//AddError appends errors to this change value
+func (c *ChangeValue) AddError(err error) *ChangeValue {
+	if c != nil {
+		c.err = err
+	}
+	return c
+}
diff --git a/licenses/github.com/r3labs/diff/v3/comparative.go b/licenses/github.com/r3labs/diff/v3/comparative.go
new file mode 100644
index 0000000..f92ff6b
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/comparative.go
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff
+
+import (
+	"reflect"
+)
+
+// Comparative ...
+type Comparative struct {
+	A, B *reflect.Value
+}
+
+// ComparativeList : stores indexed comparative
+type ComparativeList struct {
+	m    map[interface{}]*Comparative
+	keys []interface{}
+}
+
+// NewComparativeList : returns a new comparative list
+func NewComparativeList() *ComparativeList {
+	return &ComparativeList{
+		m:    make(map[interface{}]*Comparative),
+		keys: make([]interface{}, 0),
+	}
+}
+
+func (cl *ComparativeList) addA(k interface{}, v *reflect.Value) {
+	if (*cl).m[k] == nil {
+		(*cl).m[k] = &Comparative{}
+		(*cl).keys = append((*cl).keys, k)
+	}
+	(*cl).m[k].A = v
+}
+
+func (cl *ComparativeList) addB(k interface{}, v *reflect.Value) {
+	if (*cl).m[k] == nil {
+		(*cl).m[k] = &Comparative{}
+		(*cl).keys = append((*cl).keys, k)
+	}
+	(*cl).m[k].B = v
+}
diff --git a/licenses/github.com/r3labs/diff/v3/diff.go b/licenses/github.com/r3labs/diff/v3/diff.go
new file mode 100644
index 0000000..64e3dcd
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/diff.go
@@ -0,0 +1,417 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"strconv"
+	"strings"
+
+	"github.com/vmihailenco/msgpack"
+)
+
+const (
+	// CREATE represents when an element has been added
+	CREATE = "create"
+	// UPDATE represents when an element has been updated
+	UPDATE = "update"
+	// DELETE represents when an element has been removed
+	DELETE = "delete"
+)
+
+// DiffType represents an enum with all the supported diff types
+type DiffType uint8
+
+const (
+	UNSUPPORTED DiffType = iota
+	STRUCT
+	SLICE
+	ARRAY
+	STRING
+	BOOL
+	INT
+	UINT
+	FLOAT
+	MAP
+	PTR
+	INTERFACE
+)
+
+func (t DiffType) String() string {
+	switch t {
+	case STRUCT:
+		return "STRUCT"
+	case SLICE:
+		return "SLICE"
+	case ARRAY:
+		return "ARRAY"
+	case STRING:
+		return "STRING"
+	case BOOL:
+		return "BOOL"
+	case INT:
+		return "INT"
+	case UINT:
+		return "UINT"
+	case FLOAT:
+		return "FLOAT"
+	case MAP:
+		return "MAP"
+	case PTR:
+		return "PTR"
+	case INTERFACE:
+		return "INTERFACE"
+	default:
+		return "UNSUPPORTED"
+	}
+}
+
+// DiffFunc represents the built-in diff functions
+type DiffFunc func([]string, reflect.Value, reflect.Value, interface{}) error
+
+// Differ a configurable diff instance
+type Differ struct {
+	TagName                string
+	SliceOrdering          bool
+	DisableStructValues    bool
+	customValueDiffers     []ValueDiffer
+	cl                     Changelog
+	AllowTypeMismatch      bool
+	DiscardParent          bool
+	StructMapKeys          bool
+	FlattenEmbeddedStructs bool
+	ConvertCompatibleTypes bool
+	Filter                 FilterFunc
+}
+
+// Changelog stores a list of changed items
+type Changelog []Change
+
+// Change stores information about a changed item
+type Change struct {
+	Type   string      `json:"type"`
+	Path   []string    `json:"path"`
+	From   interface{} `json:"from"`
+	To     interface{} `json:"to"`
+	parent interface{} `json:"parent"`
+}
+
+// ValueDiffer is an interface for custom differs
+type ValueDiffer interface {
+	Match(a, b reflect.Value) bool
+	Diff(dt DiffType, df DiffFunc, cl *Changelog, path []string, a, b reflect.Value, parent interface{}) error
+	InsertParentDiffer(dfunc func(path []string, a, b reflect.Value, p interface{}) error)
+}
+
+// Changed returns true if both values differ
+func Changed(a, b interface{}) bool {
+	cl, _ := Diff(a, b)
+	return len(cl) > 0
+}
+
+// Diff returns a changelog of all mutated values from both
+func Diff(a, b interface{}, opts ...func(d *Differ) error) (Changelog, error) {
+	d, err := NewDiffer(opts...)
+	if err != nil {
+		return nil, err
+	}
+	return d.Diff(a, b)
+}
+
+// NewDiffer creates a new configurable diffing object
+func NewDiffer(opts ...func(d *Differ) error) (*Differ, error) {
+	d := Differ{
+		TagName:       "diff",
+		DiscardParent: false,
+	}
+
+	for _, opt := range opts {
+		err := opt(&d)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return &d, nil
+}
+
+// FilterFunc is a function that determines whether to descend into a struct field.
+// parent is the struct being examined and field is a field on that struct. path
+// is the path to the field from the root of the diff.
+type FilterFunc func(path []string, parent reflect.Type, field reflect.StructField) bool
+
+// StructValues gets all values from a struct
+// values are stored as "created" or "deleted" entries in the changelog,
+// depending on the change type specified
+func StructValues(t string, path []string, s interface{}) (Changelog, error) {
+	d := Differ{
+		TagName:       "diff",
+		DiscardParent: false,
+	}
+
+	v := reflect.ValueOf(s)
+
+	return d.cl, d.structValues(t, path, v)
+}
+
+// FilterOut filter out the changes based on path. Paths may contain valid regexp to match items
+func (cl *Changelog) FilterOut(path []string) Changelog {
+	var ncl Changelog
+
+	for _, c := range *cl {
+		if !pathmatch(path, c.Path) {
+			ncl = append(ncl, c)
+		}
+	}
+
+	return ncl
+}
+
+// Filter filter changes based on path. Paths may contain valid regexp to match items
+func (cl *Changelog) Filter(path []string) Changelog {
+	var ncl Changelog
+
+	for _, c := range *cl {
+		if pathmatch(path, c.Path) {
+			ncl = append(ncl, c)
+		}
+	}
+
+	return ncl
+}
+
+func (d *Differ) getDiffType(a, b reflect.Value) (DiffType, DiffFunc) {
+	switch {
+	case are(a, b, reflect.Struct, reflect.Invalid):
+		return STRUCT, d.diffStruct
+	case are(a, b, reflect.Slice, reflect.Invalid):
+		return SLICE, d.diffSlice
+	case are(a, b, reflect.Array, reflect.Invalid):
+		return ARRAY, d.diffSlice
+	case are(a, b, reflect.String, reflect.Invalid):
+		return STRING, d.diffString
+	case are(a, b, reflect.Bool, reflect.Invalid):
+		return BOOL, d.diffBool
+	case are(a, b, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Invalid):
+		return INT, d.diffInt
+	case are(a, b, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Invalid):
+		return UINT, d.diffUint
+	case are(a, b, reflect.Float32, reflect.Float64, reflect.Invalid):
+		return FLOAT, d.diffFloat
+	case are(a, b, reflect.Map, reflect.Invalid):
+		return MAP, d.diffMap
+	case are(a, b, reflect.Ptr, reflect.Invalid):
+		return PTR, d.diffPtr
+	case are(a, b, reflect.Interface, reflect.Invalid):
+		return INTERFACE, d.diffInterface
+	default:
+		return UNSUPPORTED, nil
+	}
+}
+
+// Diff returns a changelog of all mutated values from both
+func (d *Differ) Diff(a, b interface{}) (Changelog, error) {
+	// reset the state of the diff
+	d.cl = Changelog{}
+
+	return d.cl, d.diff([]string{}, reflect.ValueOf(a), reflect.ValueOf(b), nil)
+}
+
+func (d *Differ) diff(path []string, a, b reflect.Value, parent interface{}) error {
+
+	//look and see if we need to discard the parent
+	if parent != nil {
+		if d.DiscardParent || reflect.TypeOf(parent).Kind() != reflect.Struct {
+			parent = nil
+		}
+	}
+
+	// check if types match or are
+	if invalid(a, b) {
+		if d.AllowTypeMismatch {
+			d.cl.Add(UPDATE, path, a.Interface(), b.Interface())
+			return nil
+		}
+		return ErrTypeMismatch
+	}
+
+	// get the diff type and the corresponding built-int diff function to handle this type
+	diffType, diffFunc := d.getDiffType(a, b)
+
+	// first go through custom diff functions
+	if len(d.customValueDiffers) > 0 {
+		for _, vd := range d.customValueDiffers {
+			if vd.Match(a, b) {
+				err := vd.Diff(diffType, diffFunc, &d.cl, path, a, b, parent)
+				if err != nil {
+					return err
+				}
+				return nil
+			}
+		}
+	}
+
+	// then built-in diff functions
+	if diffType == UNSUPPORTED {
+		return errors.New("unsupported type: " + a.Kind().String())
+	}
+
+	return diffFunc(path, a, b, parent)
+}
+
+func (cl *Changelog) Add(t string, path []string, ftco ...interface{}) {
+	change := Change{
+		Type: t,
+		Path: path,
+		From: ftco[0],
+		To:   ftco[1],
+	}
+	if len(ftco) > 2 {
+		change.parent = ftco[2]
+	}
+	(*cl) = append((*cl), change)
+}
+
+func tagName(tag string, f reflect.StructField) string {
+	t := f.Tag.Get(tag)
+
+	parts := strings.Split(t, ",")
+	if len(parts) < 1 {
+		return "-"
+	}
+
+	return parts[0]
+}
+
+func identifier(tag string, v reflect.Value) interface{} {
+	if v.Kind() != reflect.Struct {
+		return nil
+	}
+
+	for i := 0; i < v.NumField(); i++ {
+		if hasTagOption(tag, v.Type().Field(i), "identifier") {
+			return v.Field(i).Interface()
+		}
+	}
+
+	return nil
+}
+
+func hasTagOption(tag string, f reflect.StructField, opt string) bool {
+	parts := strings.Split(f.Tag.Get(tag), ",")
+	if len(parts) < 2 {
+		return false
+	}
+
+	for _, option := range parts[1:] {
+		if option == opt {
+			return true
+		}
+	}
+
+	return false
+}
+
+func swapChange(t string, c Change) Change {
+	nc := Change{
+		Type: t,
+		Path: c.Path,
+	}
+
+	switch t {
+	case CREATE:
+		nc.To = c.To
+	case DELETE:
+		nc.From = c.To
+	}
+
+	return nc
+}
+
+func idComplex(v interface{}) string {
+	switch v := v.(type) {
+	case string:
+		return v
+	case int:
+		return strconv.Itoa(v)
+	default:
+		b, err := msgpack.Marshal(v)
+		if err != nil {
+			panic(err)
+		}
+		return string(b)
+	}
+
+}
+func idstring(v interface{}) string {
+	switch v := v.(type) {
+	case string:
+		return v
+	case int:
+		return strconv.Itoa(v)
+	default:
+		return fmt.Sprint(v)
+	}
+}
+
+func invalid(a, b reflect.Value) bool {
+	if a.Kind() == b.Kind() {
+		return false
+	}
+
+	if a.Kind() == reflect.Invalid {
+		return false
+	}
+	if b.Kind() == reflect.Invalid {
+		return false
+	}
+
+	return true
+}
+
+func are(a, b reflect.Value, kinds ...reflect.Kind) bool {
+	var amatch, bmatch bool
+
+	for _, k := range kinds {
+		if a.Kind() == k {
+			amatch = true
+		}
+		if b.Kind() == k {
+			bmatch = true
+		}
+	}
+
+	return amatch && bmatch
+}
+
+func AreType(a, b reflect.Value, types ...reflect.Type) bool {
+	var amatch, bmatch bool
+
+	for _, t := range types {
+		if a.Kind() != reflect.Invalid {
+			if a.Type() == t {
+				amatch = true
+			}
+		}
+		if b.Kind() != reflect.Invalid {
+			if b.Type() == t {
+				bmatch = true
+			}
+		}
+	}
+
+	return amatch && bmatch
+}
+
+func copyAppend(src []string, elems ...string) []string {
+	dst := make([]string, len(src)+len(elems))
+	copy(dst, src)
+	for i := len(src); i < len(src)+len(elems); i++ {
+		dst[i] = elems[i-len(src)]
+	}
+	return dst
+}
diff --git a/licenses/github.com/r3labs/diff/v3/diff_bool.go b/licenses/github.com/r3labs/diff/v3/diff_bool.go
new file mode 100644
index 0000000..0e30503
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/diff_bool.go
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff
+
+import "reflect"
+
+func (d *Differ) diffBool(path []string, a, b reflect.Value, parent interface{}) error {
+	if a.Kind() == reflect.Invalid {
+		d.cl.Add(CREATE, path, nil, exportInterface(b))
+		return nil
+	}
+
+	if b.Kind() == reflect.Invalid {
+		d.cl.Add(DELETE, path, exportInterface(a), nil)
+		return nil
+	}
+
+	if a.Kind() != b.Kind() {
+		return ErrTypeMismatch
+	}
+
+	if a.Bool() != b.Bool() {
+		d.cl.Add(UPDATE, path, exportInterface(a), exportInterface(b), parent)
+	}
+
+	return nil
+}
diff --git a/licenses/github.com/r3labs/diff/v3/diff_comparative.go b/licenses/github.com/r3labs/diff/v3/diff_comparative.go
new file mode 100644
index 0000000..7359d17
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/diff_comparative.go
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff
+
+import (
+	"reflect"
+)
+
+func (d *Differ) diffComparative(path []string, c *ComparativeList, parent interface{}) error {
+	for _, k := range c.keys {
+		id := idstring(k)
+		if d.StructMapKeys {
+			id = idComplex(k)
+		}
+
+		fpath := copyAppend(path, id)
+		nv := reflect.ValueOf(nil)
+
+		if c.m[k].A == nil {
+			c.m[k].A = &nv
+		}
+
+		if c.m[k].B == nil {
+			c.m[k].B = &nv
+		}
+
+		err := d.diff(fpath, *c.m[k].A, *c.m[k].B, parent)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (d *Differ) comparative(a, b reflect.Value) bool {
+	if a.Len() > 0 {
+		ae := a.Index(0)
+		ak := getFinalValue(ae)
+
+		if ak.Kind() == reflect.Struct {
+			if identifier(d.TagName, ak) != nil {
+				return true
+			}
+		}
+	}
+
+	if b.Len() > 0 {
+		be := b.Index(0)
+		bk := getFinalValue(be)
+
+		if bk.Kind() == reflect.Struct {
+			if identifier(d.TagName, bk) != nil {
+				return true
+			}
+		}
+	}
+
+	return false
+}
diff --git a/licenses/github.com/r3labs/diff/v3/diff_examples_test.go b/licenses/github.com/r3labs/diff/v3/diff_examples_test.go
new file mode 100644
index 0000000..e9d6e8c
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/diff_examples_test.go
@@ -0,0 +1,611 @@
+package diff_test
+
+import (
+	"fmt"
+	"math/big"
+	"reflect"
+
+	"github.com/r3labs/diff/v3"
+)
+
+//Try to do a bunch of stuff that will result in some or all failures
+//when trying to apply either a valid or invalid changelog
+func ExamplePatchWithErrors() {
+
+	type Fruit struct {
+		ID        int            `diff:"ID" json:"Identifier"`
+		Name      string         `diff:"name"`
+		Healthy   bool           `diff:"-"`
+		Nutrients []string       `diff:"nutrients"`
+		Labels    map[string]int `diff:"labs"`
+	}
+
+	type Bat struct {
+		ID   string `diff:"ID"`
+		Name string `diff:"-"`
+	}
+
+	a := Fruit{
+		ID:      1,
+		Name:    "Green Apple",
+		Healthy: true,
+		Nutrients: []string{
+			"vitamin a",
+			"vitamin b",
+			"vitamin c",
+			"vitamin d",
+		},
+		Labels: make(map[string]int),
+	}
+	a.Labels["likes"] = 10
+	a.Labels["colors"] = 2
+
+	b := Fruit{
+		ID:      2,
+		Name:    "Red Apple",
+		Healthy: true,
+		Nutrients: []string{
+			"vitamin c",
+			"vitamin d",
+			"vitamin e",
+		},
+		Labels: make(map[string]int),
+	}
+	b.Labels["forests"] = 1223
+	b.Labels["colors"] = 1222
+
+	c := Fruit{
+		Labels: make(map[string]int),
+		Nutrients: []string{
+			"vitamin c",
+			"vitamin d",
+			"vitamin a",
+		},
+	}
+	c.Labels["likes"] = 21
+	c.Labels["colors"] = 42
+
+	d := Bat{
+		ID:   "first",
+		Name: "second",
+	}
+
+	changelog, err := diff.Diff(a, b)
+	if err != nil {
+		panic(err)
+	}
+
+	//This fails in total because c is not assignable (passed by Value)
+	patchLog := diff.Patch(changelog, c)
+
+	//this also demonstrated the nested errors with 'next'
+
+	errors := patchLog[0].Errors.(*diff.DiffError)
+
+	//we can also continue to nest errors if we like
+	message := errors.WithCause(diff.NewError("This is a custom message")).
+		WithCause(fmt.Errorf("this is an error from somewhere else but still compatible")).
+		Error()
+
+	//invoke a few failures, i.e. bad changelog
+	changelog[2].Path[1] = "bad index"
+	changelog[3].Path[0] = "bad struct field"
+
+	patchLog = diff.Patch(changelog, &c)
+
+	patchLog, _ = diff.Merge(a, nil, &c)
+
+	patchLog, _ = diff.Merge(a, d, &c)
+
+	//try patching a string
+	patchLog = diff.Patch(changelog, message)
+
+	//test an invalid change Value
+	var bad *diff.ChangeValue
+	if bad.IsValid() {
+		fmt.Print("this should never happen")
+	}
+
+	//Output:
+}
+
+//ExampleMerge demonstrates how to use the Merge function
+func ExampleMerge() {
+	type Fruit struct {
+		ID        int            `diff:"ID" json:"Identifier"`
+		Name      string         `diff:"name"`
+		Healthy   bool           `diff:"healthy"`
+		Nutrients []string       `diff:"nutrients,create,omitunequal"`
+		Labels    map[string]int `diff:"labs,create"`
+	}
+
+	a := Fruit{
+		ID:      1,
+		Name:    "Green Apple",
+		Healthy: true,
+		Nutrients: []string{
+			"vitamin a",
+			"vitamin b",
+			"vitamin c",
+			"vitamin d",
+		},
+		Labels: make(map[string]int),
+	}
+	a.Labels["likes"] = 10
+	a.Labels["colors"] = 2
+
+	b := Fruit{
+		ID:      2,
+		Name:    "Red Apple",
+		Healthy: true,
+		Nutrients: []string{
+			"vitamin c",
+			"vitamin d",
+			"vitamin e",
+		},
+		Labels: make(map[string]int),
+	}
+	b.Labels["forests"] = 1223
+	b.Labels["colors"] = 1222
+
+	c := Fruit{
+		Labels: make(map[string]int),
+		Nutrients: []string{
+			"vitamin a",
+			"vitamin c",
+			"vitamin d",
+		},
+	}
+	c.Labels["likes"] = 21
+	c.Labels["colors"] = 42
+
+	//the only error that can happen here comes from the diff step
+	patchLog, _ := diff.Merge(a, b, &c)
+
+	//Note that unlike our patch version we've not included 'create' in the
+	//tag for nutrients. This will omit "vitamin e" from ending up in c
+	fmt.Printf("%#v", len(patchLog))
+
+	//Output: 8
+}
+
+//ExamplePrimitiveSlice demonstrates working with arrays and primitive values
+func ExamplePrimitiveSlice() {
+	sla := []string{
+		"this",
+		"is",
+		"a",
+		"simple",
+	}
+
+	slb := []string{
+		"slice",
+		"That",
+		"can",
+		"be",
+		"diff'ed",
+	}
+
+	slc := []string{
+		"ok",
+	}
+
+	patch, err := diff.Diff(sla, slb, diff.StructMapKeySupport())
+	if err != nil {
+		fmt.Print("failed to diff sla and slb")
+	}
+	cl := diff.Patch(patch, &slc)
+
+	//now the other way, round
+	sla = []string{
+		"slice",
+		"That",
+		"can",
+		"be",
+		"diff'ed",
+	}
+	slb = []string{
+		"this",
+		"is",
+		"a",
+		"simple",
+	}
+
+	patch, err = diff.Diff(sla, slb)
+	if err != nil {
+		fmt.Print("failed to diff sla and slb")
+	}
+	cl = diff.Patch(patch, &slc)
+
+	//and finally a clean view
+	sla = []string{
+		"slice",
+		"That",
+		"can",
+		"be",
+		"diff'ed",
+	}
+	slb = []string{}
+
+	patch, err = diff.Diff(sla, slb)
+	if err != nil {
+		fmt.Print("failed to diff sla and slb")
+	}
+	cl = diff.Patch(patch, &slc)
+
+	fmt.Printf("%d changes made to string array; %v", len(cl), slc)
+
+	//Output: 5 changes made to string array; [simple a]
+}
+
+//ExampleComplexMapPatch demonstrates how to use the Patch function for complex slices
+//NOTE: There is a potential pitfall here, take a close look at b[2]. If patching the
+//      original, the operation will work intuitively however, in a merge situation we
+//      may not get everything we expect because it's a true diff between a and b and
+//      the diff log will not contain enough information to fully recreate b from an
+//      empty slice. This is exemplified in that the test "colors" is dropped in element
+//      3 of c. Change "colors" to "color" and see what happens. Keep in mind this only
+//      happens when we need to allocate a new complex element. In normal operations we
+//      fix for this by keeping a copy of said element in the diff log (as parent) and
+//      allocate such an element as a whole copy prior to applying any updates?
+//
+//      The new default is to carry this information forward, we invoke this pitfall
+//      by creating such a situation and explicitly telling diff to discard the parent
+//      In memory constrained environments if the developer is careful, they can use
+//      the discard feature but unless you REALLY understand what's happening here, use
+//      the default.
+func ExampleComplexSlicePatch() {
+
+	type Content struct {
+		Text   string `diff:",create"`
+		Number int    `diff:",create"`
+	}
+	type Attributes struct {
+		Labels []Content `diff:",create"`
+	}
+
+	a := Attributes{
+		Labels: []Content{
+			{
+				Text:   "likes",
+				Number: 10,
+			},
+			{
+				Text:   "forests",
+				Number: 10,
+			},
+			{
+				Text:   "colors",
+				Number: 2,
+			},
+		},
+	}
+
+	b := Attributes{
+		Labels: []Content{
+			{
+				Text:   "forests",
+				Number: 14,
+			},
+			{
+				Text:   "location",
+				Number: 0x32,
+			},
+			{
+				Text:   "colors",
+				Number: 1222,
+			},
+			{
+				Text:   "trees",
+				Number: 34,
+			},
+		},
+	}
+	c := Attributes{}
+
+	changelog, err := diff.Diff(a, b, diff.DiscardComplexOrigin(), diff.StructMapKeySupport())
+	if err != nil {
+		panic(err)
+	}
+
+	patchLog := diff.Patch(changelog, &c)
+
+	fmt.Printf("Patched %d entries and encountered %d errors", len(patchLog), patchLog.ErrorCount())
+
+	//Output: Patched 7 entries and encountered 3 errors
+}
+
+//ExampleComplexMapPatch demonstrates how to use the Patch function for complex slices.
+func ExampleComplexMapPatch() {
+
+	type Key struct {
+		Value  string
+		weight int
+	}
+	type Content struct {
+		Text        string
+		Number      float64
+		WholeNumber int
+	}
+	type Attributes struct {
+		Labels map[Key]Content
+	}
+
+	a := Attributes{
+		Labels: make(map[Key]Content),
+	}
+	a.Labels[Key{Value: "likes"}] = Content{
+		WholeNumber: 10,
+		Number:      23.4,
+	}
+
+	a.Labels[Key{Value: "colors"}] = Content{
+		WholeNumber: 2,
+	}
+
+	b := Attributes{
+		Labels: make(map[Key]Content),
+	}
+	b.Labels[Key{Value: "forests"}] = Content{
+		Text: "Sherwood",
+	}
+	b.Labels[Key{Value: "colors"}] = Content{
+		Number: 1222,
+	}
+	b.Labels[Key{Value: "latitude"}] = Content{
+		Number: 38.978797,
+	}
+	b.Labels[Key{Value: "longitude"}] = Content{
+		Number: -76.490986,
+	}
+
+	//c := Attributes{}
+	c := Attributes{
+		Labels: make(map[Key]Content),
+	}
+	c.Labels[Key{Value: "likes"}] = Content{
+		WholeNumber: 210,
+		Number:      23.4453,
+	}
+
+	changelog, err := diff.Diff(a, b)
+	if err != nil {
+		panic(err)
+	}
+
+	patchLog := diff.Patch(changelog, &c)
+
+	fmt.Printf("%#v", len(patchLog))
+
+	//Output: 7
+}
+
+//ExamplePatch demonstrates how to use the Patch function
+func ExamplePatch() {
+
+	type Key struct {
+		value  string
+		weight int
+	}
+	type Cycle struct {
+		Name  string `diff:"name,create"`
+		Count int    `diff:"count,create"`
+	}
+	type Fruit struct {
+		ID        int           `diff:"ID" json:"Identifier"`
+		Name      string        `diff:"name"`
+		Healthy   bool          `diff:"healthy"`
+		Nutrients []string      `diff:"nutrients,create,omitunequal"`
+		Labels    map[Key]Cycle `diff:"labs,create"`
+		Cycles    []Cycle       `diff:"cycles,immutable"`
+		Weights   []int
+	}
+
+	a := Fruit{
+		ID:      1,
+		Name:    "Green Apple",
+		Healthy: true,
+		Nutrients: []string{
+			"vitamin a",
+			"vitamin b",
+			"vitamin c",
+			"vitamin d",
+		},
+		Labels: make(map[Key]Cycle),
+	}
+	a.Labels[Key{value: "likes"}] = Cycle{
+		Count: 10,
+	}
+	a.Labels[Key{value: "colors"}] = Cycle{
+		Count: 2,
+	}
+
+	b := Fruit{
+		ID:      2,
+		Name:    "Red Apple",
+		Healthy: true,
+		Nutrients: []string{
+			"vitamin c",
+			"vitamin d",
+			"vitamin e",
+		},
+		Labels: make(map[Key]Cycle),
+		Weights: []int{
+			1,
+			2,
+			3,
+			4,
+		},
+	}
+	b.Labels[Key{value: "forests"}] = Cycle{
+		Count: 1223,
+	}
+	b.Labels[Key{value: "colors"}] = Cycle{
+		Count: 1222,
+	}
+
+	c := Fruit{
+		//Labels: make(map[string]int),
+		Nutrients: []string{
+			"vitamin a",
+			"vitamin c",
+			"vitamin d",
+		},
+	}
+	//c.Labels["likes"] = 21
+
+	d := a
+	d.Cycles = []Cycle{
+		Cycle{
+			Name:  "First",
+			Count: 45,
+		},
+		Cycle{
+			Name:  "Third",
+			Count: 4,
+		},
+	}
+	d.Nutrients = append(d.Nutrients, "minerals")
+
+	changelog, err := diff.Diff(a, b)
+	if err != nil {
+		panic(err)
+	}
+
+	patchLog := diff.Patch(changelog, &c)
+
+	changelog, _ = diff.Diff(a, d)
+	patchLog = diff.Patch(changelog, &c)
+
+	fmt.Printf("%#v", len(patchLog))
+
+	//Output: 1
+}
+
+func ExampleDiff() {
+	type Tag struct {
+		Name  string `diff:"name,identifier"`
+		Value string `diff:"value"`
+	}
+
+	type Fruit struct {
+		ID        int      `diff:"id"`
+		Name      string   `diff:"name"`
+		Healthy   bool     `diff:"healthy"`
+		Nutrients []string `diff:"nutrients"`
+		Tags      []Tag    `diff:"tags"`
+	}
+
+	a := Fruit{
+		ID:      1,
+		Name:    "Green Apple",
+		Healthy: true,
+		Nutrients: []string{
+			"vitamin c",
+			"vitamin d",
+		},
+		Tags: []Tag{
+			{
+				Name:  "kind",
+				Value: "fruit",
+			},
+		},
+	}
+
+	b := Fruit{
+		ID:      2,
+		Name:    "Red Apple",
+		Healthy: true,
+		Nutrients: []string{
+			"vitamin c",
+			"vitamin d",
+			"vitamin e",
+		},
+		Tags: []Tag{
+			{
+				Name:  "popularity",
+				Value: "high",
+			},
+			{
+				Name:  "kind",
+				Value: "fruit",
+			},
+		},
+	}
+
+	changelog, err := diff.Diff(a, b)
+	if err != nil {
+		panic(err)
+	}
+
+	fmt.Printf("%#v", changelog)
+	// Produces: diff.Changelog{diff.Change{Type:"update", Path:[]string{"id"}, From:1, To:2}, diff.Change{Type:"update", Path:[]string{"name"}, From:"Green Apple", To:"Red Apple"}, diff.Change{Type:"create", Path:[]string{"nutrients", "2"}, From:interface {}(nil), To:"vitamin e"}, diff.Change{Type:"create", Path:[]string{"tags", "popularity"}, From:interface {}(nil), To:main.Tag{Name:"popularity", Value:"high"}}}
+}
+
+func ExampleFilter() {
+	type Tag struct {
+		Name  string `diff:"name,identifier"`
+		Value string `diff:"value"`
+	}
+
+	type Fruit struct {
+		ID        int      `diff:"id"`
+		Name      string   `diff:"name"`
+		Healthy   bool     `diff:"healthy"`
+		Nutrients []string `diff:"nutrients"`
+		Tags      []Tag    `diff:"tags"`
+	}
+
+	a := Fruit{
+		ID:      1,
+		Name:    "Green Apple",
+		Healthy: true,
+		Nutrients: []string{
+			"vitamin c",
+			"vitamin d",
+		},
+	}
+
+	b := Fruit{
+		ID:      2,
+		Name:    "Red Apple",
+		Healthy: true,
+		Nutrients: []string{
+			"vitamin c",
+			"vitamin d",
+			"vitamin e",
+		},
+	}
+
+	d, err := diff.NewDiffer(diff.Filter(func(path []string, parent reflect.Type, field reflect.StructField) bool {
+		return field.Name != "Name"
+	}))
+	if err != nil {
+		panic(err)
+	}
+
+	changelog, err := d.Diff(a, b)
+	if err != nil {
+		panic(err)
+	}
+
+	fmt.Printf("%#v", changelog)
+	// Output: diff.Changelog{diff.Change{Type:"update", Path:[]string{"id"}, From:1, To:2, parent:diff_test.Fruit{ID:1, Name:"Green Apple", Healthy:true, Nutrients:[]string{"vitamin c", "vitamin d"}, Tags:[]diff_test.Tag(nil)}}, diff.Change{Type:"create", Path:[]string{"nutrients", "2"}, From:interface {}(nil), To:"vitamin e", parent:interface {}(nil)}}
+}
+
+func ExamplePrivatePtr() {
+	type number struct {
+		value *big.Int
+		exp   int32
+	}
+	a := number{}
+	b := number{value: big.NewInt(111)}
+
+	changelog, err := diff.Diff(a, b)
+	if err != nil {
+		panic(err)
+	}
+
+	fmt.Printf("%#v", changelog)
+	// Output: diff.Changelog{diff.Change{Type:"update", Path:[]string{"value"}, From:interface {}(nil), To:111, parent:diff_test.number{value:(*big.Int)(nil), exp:0}}}
+}
diff --git a/licenses/github.com/r3labs/diff/v3/diff_float.go b/licenses/github.com/r3labs/diff/v3/diff_float.go
new file mode 100644
index 0000000..9494365
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/diff_float.go
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff
+
+import (
+	"reflect"
+)
+
+func (d *Differ) diffFloat(path []string, a, b reflect.Value, parent interface{}) error {
+	if a.Kind() == reflect.Invalid {
+		d.cl.Add(CREATE, path, nil, exportInterface(b))
+		return nil
+	}
+
+	if b.Kind() == reflect.Invalid {
+		d.cl.Add(DELETE, path, exportInterface(a), nil)
+		return nil
+	}
+
+	if a.Kind() != b.Kind() {
+		return ErrTypeMismatch
+	}
+
+	if a.Float() != b.Float() {
+		if a.CanInterface() {
+			d.cl.Add(UPDATE, path, exportInterface(a), exportInterface(b), parent)
+		} else {
+			d.cl.Add(UPDATE, path, a.Float(), b.Float(), parent)
+		}
+	}
+
+	return nil
+}
diff --git a/licenses/github.com/r3labs/diff/v3/diff_int.go b/licenses/github.com/r3labs/diff/v3/diff_int.go
new file mode 100644
index 0000000..3658bf7
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/diff_int.go
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff
+
+import (
+	"reflect"
+)
+
+func (d *Differ) diffInt(path []string, a, b reflect.Value, parent interface{}) error {
+	if a.Kind() == reflect.Invalid {
+		d.cl.Add(CREATE, path, nil, exportInterface(b))
+		return nil
+	}
+
+	if b.Kind() == reflect.Invalid {
+		d.cl.Add(DELETE, path, exportInterface(a), nil)
+		return nil
+	}
+
+	if a.Kind() != b.Kind() {
+		return ErrTypeMismatch
+	}
+
+	if a.Int() != b.Int() {
+		if a.CanInterface() {
+			d.cl.Add(UPDATE, path, exportInterface(a), exportInterface(b), parent)
+		} else {
+			d.cl.Add(UPDATE, path, a.Int(), b.Int(), parent)
+		}
+	}
+
+	return nil
+}
diff --git a/licenses/github.com/r3labs/diff/v3/diff_interface.go b/licenses/github.com/r3labs/diff/v3/diff_interface.go
new file mode 100644
index 0000000..ef6cde8
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/diff_interface.go
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff
+
+import "reflect"
+
+func (d *Differ) diffInterface(path []string, a, b reflect.Value, parent interface{}) error {
+	if a.Kind() == reflect.Invalid {
+		d.cl.Add(CREATE, path, nil, exportInterface(b))
+		return nil
+	}
+
+	if b.Kind() == reflect.Invalid {
+		d.cl.Add(DELETE, path, exportInterface(a), nil)
+		return nil
+	}
+
+	if a.Kind() != b.Kind() {
+		return ErrTypeMismatch
+	}
+
+	if a.IsNil() && b.IsNil() {
+		return nil
+	}
+
+	if a.IsNil() {
+		d.cl.Add(UPDATE, path, nil, exportInterface(b), parent)
+		return nil
+	}
+
+	if b.IsNil() {
+		d.cl.Add(UPDATE, path, exportInterface(a), nil, parent)
+		return nil
+	}
+
+	return d.diff(path, a.Elem(), b.Elem(), parent)
+}
diff --git a/licenses/github.com/r3labs/diff/v3/diff_map.go b/licenses/github.com/r3labs/diff/v3/diff_map.go
new file mode 100644
index 0000000..02b1c1b
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/diff_map.go
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff
+
+import (
+	"fmt"
+	"reflect"
+
+	"github.com/vmihailenco/msgpack"
+)
+
+func (d *Differ) diffMap(path []string, a, b reflect.Value, parent interface{}) error {
+	if a.Kind() == reflect.Invalid {
+		return d.mapValues(CREATE, path, b)
+	}
+
+	if b.Kind() == reflect.Invalid {
+		return d.mapValues(DELETE, path, a)
+	}
+
+	c := NewComparativeList()
+
+	for _, k := range a.MapKeys() {
+		ae := a.MapIndex(k)
+		c.addA(exportInterface(k), &ae)
+	}
+
+	for _, k := range b.MapKeys() {
+		be := b.MapIndex(k)
+		c.addB(exportInterface(k), &be)
+	}
+
+	return d.diffComparative(path, c, exportInterface(a))
+}
+
+func (d *Differ) mapValues(t string, path []string, a reflect.Value) error {
+	if t != CREATE && t != DELETE {
+		return ErrInvalidChangeType
+	}
+
+	if a.Kind() == reflect.Ptr {
+		a = reflect.Indirect(a)
+	}
+
+	if a.Kind() != reflect.Map {
+		return ErrTypeMismatch
+	}
+
+	x := reflect.New(a.Type()).Elem()
+
+	for _, k := range a.MapKeys() {
+		ae := a.MapIndex(k)
+		xe := x.MapIndex(k)
+
+		var err error
+		if d.StructMapKeys {
+			//it's not enough to turn k to a string, we need to able to  marshal a type when
+			//we apply it in patch so... we'll marshal it to JSON
+			var b []byte
+			if b, err = msgpack.Marshal(k.Interface()); err == nil {
+				err = d.diff(append(path, string(b)), xe, ae, a.Interface())
+			}
+		} else {
+			err = d.diff(append(path, fmt.Sprint(k.Interface())), xe, ae, a.Interface())
+		}
+		if err != nil {
+			return err
+		}
+	}
+
+	for i := 0; i < len(d.cl); i++ {
+		// only swap changes on the relevant map
+		if pathmatch(path, d.cl[i].Path) {
+			d.cl[i] = swapChange(t, d.cl[i])
+		}
+	}
+
+	return nil
+}
diff --git a/licenses/github.com/r3labs/diff/v3/diff_pointer.go b/licenses/github.com/r3labs/diff/v3/diff_pointer.go
new file mode 100644
index 0000000..7c9d875
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/diff_pointer.go
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff
+
+import (
+	"reflect"
+	"unsafe"
+)
+
+var isExportFlag uintptr = (1 << 5) | (1 << 6)
+
+func (d *Differ) diffPtr(path []string, a, b reflect.Value, parent interface{}) error {
+	if a.Kind() != b.Kind() {
+		if a.Kind() == reflect.Invalid {
+			if !b.IsNil() {
+				return d.diff(path, reflect.ValueOf(nil), reflect.Indirect(b), parent)
+			}
+
+			d.cl.Add(CREATE, path, nil, exportInterface(b), parent)
+			return nil
+		}
+
+		if b.Kind() == reflect.Invalid {
+			if !a.IsNil() {
+				return d.diff(path, reflect.Indirect(a), reflect.ValueOf(nil), parent)
+			}
+
+			d.cl.Add(DELETE, path, exportInterface(a), nil, parent)
+			return nil
+		}
+
+		return ErrTypeMismatch
+	}
+
+	if a.IsNil() && b.IsNil() {
+		return nil
+	}
+
+	if a.IsNil() {
+		d.cl.Add(UPDATE, path, nil, exportInterface(b), parent)
+		return nil
+	}
+
+	if b.IsNil() {
+		d.cl.Add(UPDATE, path, exportInterface(a), nil, parent)
+		return nil
+	}
+
+	return d.diff(path, reflect.Indirect(a), reflect.Indirect(b), parent)
+}
+
+func exportInterface(v reflect.Value) interface{} {
+	if !v.CanInterface() {
+		flagTmp := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + 2*unsafe.Sizeof(uintptr(0))))
+		*flagTmp = (*flagTmp) & (^isExportFlag)
+	}
+	return v.Interface()
+}
diff --git a/licenses/github.com/r3labs/diff/v3/diff_slice.go b/licenses/github.com/r3labs/diff/v3/diff_slice.go
new file mode 100644
index 0000000..3fd281b
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/diff_slice.go
@@ -0,0 +1,141 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff
+
+import (
+	"reflect"
+)
+
+func (d *Differ) diffSlice(path []string, a, b reflect.Value, parent interface{}) error {
+	if a.Kind() == reflect.Invalid {
+		d.cl.Add(CREATE, path, nil, exportInterface(b))
+		return nil
+	}
+
+	if b.Kind() == reflect.Invalid {
+		d.cl.Add(DELETE, path, exportInterface(a), nil)
+		return nil
+	}
+
+	if a.Kind() != b.Kind() {
+		return ErrTypeMismatch
+	}
+
+	if d.comparative(a, b) {
+		return d.diffSliceComparative(path, a, b)
+	}
+
+	return d.diffSliceGeneric(path, a, b)
+}
+
+func (d *Differ) diffSliceGeneric(path []string, a, b reflect.Value) error {
+	missing := NewComparativeList()
+
+	slice := sliceTracker{}
+	for i := 0; i < a.Len(); i++ {
+		ae := a.Index(i)
+
+		if (d.SliceOrdering && !hasAtSameIndex(b, ae, i)) || (!d.SliceOrdering && !slice.has(b, ae, d)) {
+			missing.addA(i, &ae)
+		}
+	}
+
+	slice = sliceTracker{}
+	for i := 0; i < b.Len(); i++ {
+		be := b.Index(i)
+
+		if (d.SliceOrdering && !hasAtSameIndex(a, be, i)) || (!d.SliceOrdering && !slice.has(a, be, d)) {
+			missing.addB(i, &be)
+		}
+	}
+
+	// fallback to comparing based on order in slice if item is missing
+	if len(missing.keys) == 0 {
+		return nil
+	}
+
+	return d.diffComparative(path, missing, exportInterface(a))
+}
+
+func (d *Differ) diffSliceComparative(path []string, a, b reflect.Value) error {
+	c := NewComparativeList()
+
+	for i := 0; i < a.Len(); i++ {
+		ae := a.Index(i)
+		ak := getFinalValue(ae)
+
+		id := identifier(d.TagName, ak)
+		if id != nil {
+			c.addA(id, &ae)
+		}
+	}
+
+	for i := 0; i < b.Len(); i++ {
+		be := b.Index(i)
+		bk := getFinalValue(be)
+
+		id := identifier(d.TagName, bk)
+		if id != nil {
+			c.addB(id, &be)
+		}
+	}
+
+	return d.diffComparative(path, c, exportInterface(a))
+}
+
+// keeps track of elements that have already been matched, to stop duplicate matches from occurring
+type sliceTracker []bool
+
+func (st *sliceTracker) has(s, v reflect.Value, d *Differ) bool {
+	if len(*st) != s.Len() {
+		(*st) = make([]bool, s.Len())
+	}
+
+	for i := 0; i < s.Len(); i++ {
+		// skip already matched elements
+		if (*st)[i] {
+			continue
+		}
+
+		x := s.Index(i)
+
+		var nd Differ
+		nd.Filter = d.Filter
+		nd.customValueDiffers = d.customValueDiffers
+
+		err := nd.diff([]string{}, x, v, nil)
+		if err != nil {
+			continue
+		}
+
+		if len(nd.cl) == 0 {
+			(*st)[i] = true
+			return true
+		}
+	}
+
+	return false
+}
+
+func getFinalValue(t reflect.Value) reflect.Value {
+	switch t.Kind() {
+	case reflect.Interface:
+		return getFinalValue(t.Elem())
+	case reflect.Ptr:
+		return getFinalValue(reflect.Indirect(t))
+	default:
+		return t
+	}
+}
+
+func hasAtSameIndex(s, v reflect.Value, atIndex int) bool {
+	// check the element in the slice at atIndex to see if it matches Value, if it is a valid index into the slice
+	if atIndex < s.Len() {
+		x := s.Index(atIndex)
+		return reflect.DeepEqual(exportInterface(x), exportInterface(v))
+	}
+
+	return false
+}
diff --git a/licenses/github.com/r3labs/diff/v3/diff_string.go b/licenses/github.com/r3labs/diff/v3/diff_string.go
new file mode 100644
index 0000000..74182e2
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/diff_string.go
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff
+
+import "reflect"
+
+func (d *Differ) diffString(path []string, a, b reflect.Value, parent interface{}) error {
+	if a.Kind() == reflect.Invalid {
+		d.cl.Add(CREATE, path, nil, exportInterface(b))
+		return nil
+	}
+
+	if b.Kind() == reflect.Invalid {
+		d.cl.Add(DELETE, path, exportInterface(a), nil)
+		return nil
+	}
+
+	if a.Kind() != b.Kind() {
+		return ErrTypeMismatch
+	}
+
+	if a.String() != b.String() {
+		if a.CanInterface() {
+			// If a and/or b is of a type that is an alias for String, store that type in changelog
+			d.cl.Add(UPDATE, path, exportInterface(a), exportInterface(b), parent)
+		} else {
+			d.cl.Add(UPDATE, path, a.String(), b.String(), parent)
+		}
+	}
+
+	return nil
+}
diff --git a/licenses/github.com/r3labs/diff/v3/diff_struct.go b/licenses/github.com/r3labs/diff/v3/diff_struct.go
new file mode 100644
index 0000000..fb14c57
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/diff_struct.go
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff
+
+import (
+	"reflect"
+	"time"
+)
+
+func (d *Differ) diffStruct(path []string, a, b reflect.Value, parent interface{}) error {
+	if AreType(a, b, reflect.TypeOf(time.Time{})) {
+		return d.diffTime(path, a, b)
+	}
+
+	if a.Kind() == reflect.Invalid {
+		if d.DisableStructValues {
+			d.cl.Add(CREATE, path, nil, exportInterface(b))
+			return nil
+		}
+		return d.structValues(CREATE, path, b)
+	}
+
+	if b.Kind() == reflect.Invalid {
+		if d.DisableStructValues {
+			d.cl.Add(DELETE, path, exportInterface(a), nil)
+			return nil
+		}
+		return d.structValues(DELETE, path, a)
+	}
+
+	for i := 0; i < a.NumField(); i++ {
+		field := a.Type().Field(i)
+		tname := tagName(d.TagName, field)
+
+		if tname == "-" || hasTagOption(d.TagName, field, "immutable") {
+			continue
+		}
+
+		if tname == "" {
+			tname = field.Name
+		}
+
+		af := a.Field(i)
+		bf := b.FieldByName(field.Name)
+
+		fpath := path
+		if !(d.FlattenEmbeddedStructs && field.Anonymous) {
+			fpath = copyAppend(fpath, tname)
+		}
+
+		if d.Filter != nil && !d.Filter(fpath, a.Type(), field) {
+			continue
+		}
+
+		// skip private fields
+		if !a.CanInterface() {
+			continue
+		}
+
+		err := d.diff(fpath, af, bf, exportInterface(a))
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (d *Differ) structValues(t string, path []string, a reflect.Value) error {
+	var nd Differ
+	nd.Filter = d.Filter
+	nd.customValueDiffers = d.customValueDiffers
+
+	if t != CREATE && t != DELETE {
+		return ErrInvalidChangeType
+	}
+
+	if a.Kind() == reflect.Ptr {
+		a = reflect.Indirect(a)
+	}
+
+	if a.Kind() != reflect.Struct {
+		return ErrTypeMismatch
+	}
+
+	x := reflect.New(a.Type()).Elem()
+
+	for i := 0; i < a.NumField(); i++ {
+
+		field := a.Type().Field(i)
+		tname := tagName(d.TagName, field)
+
+		if tname == "-" {
+			continue
+		}
+
+		if tname == "" {
+			tname = field.Name
+		}
+
+		af := a.Field(i)
+		xf := x.FieldByName(field.Name)
+
+		fpath := copyAppend(path, tname)
+
+		if nd.Filter != nil && !nd.Filter(fpath, a.Type(), field) {
+			continue
+		}
+
+		err := nd.diff(fpath, xf, af, exportInterface(a))
+		if err != nil {
+			return err
+		}
+	}
+
+	for i := 0; i < len(nd.cl); i++ {
+		(d.cl) = append(d.cl, swapChange(t, nd.cl[i]))
+	}
+
+	return nil
+}
diff --git a/licenses/github.com/r3labs/diff/v3/diff_test.go b/licenses/github.com/r3labs/diff/v3/diff_test.go
new file mode 100644
index 0000000..4008623
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/diff_test.go
@@ -0,0 +1,1156 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff_test
+
+import (
+	"reflect"
+	"strings"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/r3labs/diff/v3"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+var currentTime = time.Now()
+
+var struct1 = tmstruct{Bar: 1, Foo: "one"}
+var struct2 = tmstruct{Bar: 2, Foo: "two"}
+
+type tistruct struct {
+	Name  string `diff:"name,identifier"`
+	Value int    `diff:"value"`
+}
+
+type tuistruct struct {
+	Value int `diff:"value"`
+}
+
+type tnstruct struct {
+	Slice []tmstruct `diff:"slice"`
+}
+
+type tmstruct struct {
+	Foo string `diff:"foo"`
+	Bar int    `diff:"bar"`
+}
+
+type Embedded struct {
+	Foo string `diff:"foo"`
+	Bar int    `diff:"bar"`
+}
+
+type embedstruct struct {
+	Embedded
+	Baz bool `diff:"baz"`
+}
+
+type customTagStruct struct {
+	Foo string `json:"foo"`
+	Bar int    `json:"bar"`
+}
+
+type privateValueStruct struct {
+	Public  string
+	Private *sync.RWMutex
+}
+
+type privateMapStruct struct {
+	set map[string]interface{}
+}
+
+type CustomStringType string
+type CustomIntType int
+type customTypeStruct struct {
+	Foo CustomStringType `diff:"foo"`
+	Bar CustomIntType    `diff:"bar"`
+}
+
+type tstruct struct {
+	ID              string            `diff:"id,immutable"`
+	Name            string            `diff:"name"`
+	Value           int               `diff:"value"`
+	Bool            bool              `diff:"bool"`
+	Values          []string          `diff:"values"`
+	Map             map[string]string `diff:"map"`
+	Time            time.Time         `diff:"time"`
+	Pointer         *string           `diff:"pointer"`
+	Ignored         bool              `diff:"-"`
+	Identifiables   []tistruct        `diff:"identifiables"`
+	Unidentifiables []tuistruct       `diff:"unidentifiables"`
+	Nested          tnstruct          `diff:"nested"`
+	private         int               `diff:"private"`
+}
+
+func sptr(s string) *string {
+	return &s
+}
+
+func TestDiff(t *testing.T) {
+	cases := []struct {
+		Name      string
+		A, B      interface{}
+		Changelog diff.Changelog
+		Error     error
+	}{
+		{
+			"uint-slice-insert", []uint{1, 2, 3}, []uint{1, 2, 3, 4},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"3"}, To: uint(4)},
+			},
+			nil,
+		},
+		{
+			"uint-array-insert", [3]uint{1, 2, 3}, [4]uint{1, 2, 3, 4},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"3"}, To: uint(4)},
+			},
+			nil,
+		},
+		{
+			"int-slice-insert", []int{1, 2, 3}, []int{1, 2, 3, 4},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"3"}, To: 4},
+			},
+			nil,
+		},
+		{
+			"int-array-insert", [3]int{1, 2, 3}, [4]int{1, 2, 3, 4},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"3"}, To: 4},
+			},
+			nil,
+		},
+		{
+			"uint-slice-delete", []uint{1, 2, 3}, []uint{1, 3},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: uint(2)},
+			},
+			nil,
+		},
+		{
+			"uint-array-delete", [3]uint{1, 2, 3}, [2]uint{1, 3},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: uint(2)},
+			},
+			nil,
+		},
+		{
+			"int-slice-delete", []int{1, 2, 3}, []int{1, 3},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: 2},
+			},
+			nil,
+		},
+		{
+			"uint-slice-insert-delete", []uint{1, 2, 3}, []uint{1, 3, 4},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: uint(2)},
+				diff.Change{Type: diff.CREATE, Path: []string{"2"}, To: uint(4)},
+			},
+			nil,
+		},
+		{
+			"uint-slice-array-delete", [3]uint{1, 2, 3}, [3]uint{1, 3, 4},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: uint(2)},
+				diff.Change{Type: diff.CREATE, Path: []string{"2"}, To: uint(4)},
+			},
+			nil,
+		},
+		{
+			"int-slice-insert-delete", []int{1, 2, 3}, []int{1, 3, 4},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: 2},
+				diff.Change{Type: diff.CREATE, Path: []string{"2"}, To: 4},
+			},
+			nil,
+		},
+		{
+			"string-slice-insert", []string{"1", "2", "3"}, []string{"1", "2", "3", "4"},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"3"}, To: "4"},
+			},
+			nil,
+		},
+		{
+			"string-array-insert", [3]string{"1", "2", "3"}, [4]string{"1", "2", "3", "4"},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"3"}, To: "4"},
+			},
+			nil,
+		},
+		{
+			"string-slice-delete", []string{"1", "2", "3"}, []string{"1", "3"},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: "2"},
+			},
+			nil,
+		},
+		{
+			"string-slice-delete", [3]string{"1", "2", "3"}, [2]string{"1", "3"},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: "2"},
+			},
+			nil,
+		},
+		{
+			"string-slice-insert-delete", []string{"1", "2", "3"}, []string{"1", "3", "4"},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: "2"},
+				diff.Change{Type: diff.CREATE, Path: []string{"2"}, To: "4"},
+			},
+			nil,
+		},
+		{
+			"string-array-insert-delete", [3]string{"1", "2", "3"}, [3]string{"1", "3", "4"},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: "2"},
+				diff.Change{Type: diff.CREATE, Path: []string{"2"}, To: "4"},
+			},
+			nil,
+		},
+		{
+			"comparable-slice-insert", []tistruct{{"one", 1}}, []tistruct{{"one", 1}, {"two", 2}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"two", "name"}, To: "two"},
+				diff.Change{Type: diff.CREATE, Path: []string{"two", "value"}, To: 2},
+			},
+			nil,
+		},
+		{
+			"comparable-array-insert", [1]tistruct{{"one", 1}}, [2]tistruct{{"one", 1}, {"two", 2}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"two", "name"}, To: "two"},
+				diff.Change{Type: diff.CREATE, Path: []string{"two", "value"}, To: 2},
+			},
+			nil,
+		},
+		{
+			"comparable-slice-delete", []tistruct{{"one", 1}, {"two", 2}}, []tistruct{{"one", 1}},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"two", "name"}, From: "two"},
+				diff.Change{Type: diff.DELETE, Path: []string{"two", "value"}, From: 2},
+			},
+			nil,
+		},
+		{
+			"comparable-array-delete", [2]tistruct{{"one", 1}, {"two", 2}}, [1]tistruct{{"one", 1}},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"two", "name"}, From: "two"},
+				diff.Change{Type: diff.DELETE, Path: []string{"two", "value"}, From: 2},
+			},
+			nil,
+		},
+		{
+			"comparable-slice-update", []tistruct{{"one", 1}}, []tistruct{{"one", 50}},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"one", "value"}, From: 1, To: 50},
+			},
+			nil,
+		},
+		{
+			"comparable-array-update", [1]tistruct{{"one", 1}}, [1]tistruct{{"one", 50}},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"one", "value"}, From: 1, To: 50},
+			},
+			nil,
+		},
+		{
+			"map-slice-insert", []map[string]string{{"test": "123"}}, []map[string]string{{"test": "123", "tset": "456"}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"0", "tset"}, To: "456"},
+			},
+			nil,
+		},
+		{
+			"map-array-insert", [1]map[string]string{{"test": "123"}}, [1]map[string]string{{"test": "123", "tset": "456"}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"0", "tset"}, To: "456"},
+			},
+			nil,
+		},
+		{
+			"map-slice-update", []map[string]string{{"test": "123"}}, []map[string]string{{"test": "456"}},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"0", "test"}, From: "123", To: "456"},
+			},
+			nil,
+		},
+		{
+			"map-array-update", [1]map[string]string{{"test": "123"}}, [1]map[string]string{{"test": "456"}},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"0", "test"}, From: "123", To: "456"},
+			},
+			nil,
+		},
+		{
+			"map-slice-delete", []map[string]string{{"test": "123", "tset": "456"}}, []map[string]string{{"test": "123"}},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"0", "tset"}, From: "456"},
+			},
+			nil,
+		},
+		{
+			"map-array-delete", [1]map[string]string{{"test": "123", "tset": "456"}}, [1]map[string]string{{"test": "123"}},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"0", "tset"}, From: "456"},
+			},
+			nil,
+		},
+		{
+			"map-interface-slice-update", []map[string]interface{}{{"test": nil}}, []map[string]interface{}{{"test": "456"}},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"0", "test"}, From: nil, To: "456"},
+			},
+			nil,
+		},
+		{
+			"map-interface-array-update", [1]map[string]interface{}{{"test": nil}}, [1]map[string]interface{}{{"test": "456"}},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"0", "test"}, From: nil, To: "456"},
+			},
+			nil,
+		},
+		{
+			"map-nil", map[string]string{"one": "test"}, nil,
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"\xa3one"}, From: "test", To: nil},
+			},
+			nil,
+		},
+		{
+			"nil-map", nil, map[string]string{"one": "test"},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"\xa3one"}, From: nil, To: "test"},
+			},
+			nil,
+		},
+		{
+			"nested-map-insert", map[string]map[string]string{"a": {"test": "123"}}, map[string]map[string]string{"a": {"test": "123", "tset": "456"}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"a", "tset"}, To: "456"},
+			},
+			nil,
+		},
+		{
+			"nested-map-interface-insert", map[string]map[string]interface{}{"a": {"test": "123"}}, map[string]map[string]interface{}{"a": {"test": "123", "tset": "456"}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"a", "tset"}, To: "456"},
+			},
+			nil,
+		},
+		{
+			"nested-map-update", map[string]map[string]string{"a": {"test": "123"}}, map[string]map[string]string{"a": {"test": "456"}},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"a", "test"}, From: "123", To: "456"},
+			},
+			nil,
+		},
+		{
+			"nested-map-delete", map[string]map[string]string{"a": {"test": "123"}}, map[string]map[string]string{"a": {}},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"a", "test"}, From: "123", To: nil},
+			},
+			nil,
+		},
+		{
+			"nested-slice-insert", map[string][]int{"a": {1, 2, 3}}, map[string][]int{"a": {1, 2, 3, 4}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"a", "3"}, To: 4},
+			},
+			nil,
+		},
+		{
+			"nested-array-insert", map[string][3]int{"a": {1, 2, 3}}, map[string][4]int{"a": {1, 2, 3, 4}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"a", "3"}, To: 4},
+			},
+			nil,
+		},
+		{
+			"nested-slice-update", map[string][]int{"a": {1, 2, 3}}, map[string][]int{"a": {1, 4, 3}},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"a", "1"}, From: 2, To: 4},
+			},
+			nil,
+		},
+		{
+			"nested-array-update", map[string][3]int{"a": {1, 2, 3}}, map[string][3]int{"a": {1, 4, 3}},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"a", "1"}, From: 2, To: 4},
+			},
+			nil,
+		},
+		{
+			"nested-slice-delete", map[string][]int{"a": {1, 2, 3}}, map[string][]int{"a": {1, 3}},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"a", "1"}, From: 2, To: nil},
+			},
+			nil,
+		},
+		{
+			"nested-array-delete", map[string][3]int{"a": {1, 2, 3}}, map[string][2]int{"a": {1, 3}},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"a", "1"}, From: 2, To: nil},
+			},
+			nil,
+		},
+		{
+			"struct-string-update", tstruct{Name: "one"}, tstruct{Name: "two"},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"name"}, From: "one", To: "two"},
+			},
+			nil,
+		},
+		{
+			"struct-int-update", tstruct{Value: 1}, tstruct{Value: 50},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"value"}, From: 1, To: 50},
+			},
+			nil,
+		},
+		{
+			"struct-bool-update", tstruct{Bool: true}, tstruct{Bool: false},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"bool"}, From: true, To: false},
+			},
+			nil,
+		},
+		{
+			"struct-time-update", tstruct{}, tstruct{Time: currentTime},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"time"}, From: time.Time{}, To: currentTime},
+			},
+			nil,
+		},
+		{
+			"struct-map-update", tstruct{Map: map[string]string{"test": "123"}}, tstruct{Map: map[string]string{"test": "456"}},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"map", "test"}, From: "123", To: "456"},
+			},
+			nil,
+		},
+		{
+			"struct-string-pointer-update", tstruct{Pointer: sptr("test")}, tstruct{Pointer: sptr("test2")},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"pointer"}, From: "test", To: "test2"},
+			},
+			nil,
+		},
+		{
+			"struct-nil-string-pointer-update", tstruct{Pointer: nil}, tstruct{Pointer: sptr("test")},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"pointer"}, From: nil, To: sptr("test")},
+			},
+			nil,
+		},
+		{
+			"struct-generic-slice-insert", tstruct{Values: []string{"one"}}, tstruct{Values: []string{"one", "two"}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"values", "1"}, From: nil, To: "two"},
+			},
+			nil,
+		},
+		{
+			"struct-identifiable-slice-insert", tstruct{Identifiables: []tistruct{{"one", 1}}}, tstruct{Identifiables: []tistruct{{"one", 1}, {"two", 2}}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"identifiables", "two", "name"}, From: nil, To: "two"},
+				diff.Change{Type: diff.CREATE, Path: []string{"identifiables", "two", "value"}, From: nil, To: 2},
+			},
+			nil,
+		},
+		{
+			"struct-generic-slice-delete", tstruct{Values: []string{"one", "two"}}, tstruct{Values: []string{"one"}},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"values", "1"}, From: "two", To: nil},
+			},
+			nil,
+		},
+		{
+			"struct-identifiable-slice-delete", tstruct{Identifiables: []tistruct{{"one", 1}, {"two", 2}}}, tstruct{Identifiables: []tistruct{{"one", 1}}},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"identifiables", "two", "name"}, From: "two", To: nil},
+				diff.Change{Type: diff.DELETE, Path: []string{"identifiables", "two", "value"}, From: 2, To: nil},
+			},
+			nil,
+		},
+		{
+			"struct-unidentifiable-slice-insert-delete", tstruct{Unidentifiables: []tuistruct{{1}, {2}, {3}}}, tstruct{Unidentifiables: []tuistruct{{5}, {2}, {3}, {4}}},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"unidentifiables", "0", "value"}, From: 1, To: 5},
+				diff.Change{Type: diff.CREATE, Path: []string{"unidentifiables", "3", "value"}, From: nil, To: 4},
+			},
+			nil,
+		},
+		{
+			"struct-with-private-value", privateValueStruct{Public: "one", Private: new(sync.RWMutex)}, privateValueStruct{Public: "two", Private: new(sync.RWMutex)},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"Public"}, From: "one", To: "two"},
+			},
+			nil,
+		},
+		{
+			"mismatched-values-struct-map", map[string]string{"test": "one"}, &tstruct{Identifiables: []tistruct{{"one", 1}}},
+			diff.Changelog{},
+			diff.ErrTypeMismatch,
+		},
+		{
+			"omittable", tstruct{Ignored: false}, tstruct{Ignored: true},
+			diff.Changelog{},
+			nil,
+		},
+		{
+			"slice", &tstruct{}, &tstruct{Nested: tnstruct{Slice: []tmstruct{{"one", 1}, {"two", 2}}}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"nested", "slice", "0", "foo"}, From: nil, To: "one"},
+				diff.Change{Type: diff.CREATE, Path: []string{"nested", "slice", "0", "bar"}, From: nil, To: 1},
+				diff.Change{Type: diff.CREATE, Path: []string{"nested", "slice", "1", "foo"}, From: nil, To: "two"},
+				diff.Change{Type: diff.CREATE, Path: []string{"nested", "slice", "1", "bar"}, From: nil, To: 2},
+			},
+			nil,
+		},
+		{
+			"slice-duplicate-items", []int{1}, []int{1, 1},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"1"}, From: nil, To: 1},
+			},
+			nil,
+		},
+		{
+			"mixed-slice-map", []map[string]interface{}{{"name": "name1", "type": []string{"null", "string"}}}, []map[string]interface{}{{"name": "name1", "type": []string{"null", "int"}}, {"name": "name2", "type": []string{"null", "string"}}},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"0", "type", "1"}, From: "string", To: "int"},
+				diff.Change{Type: diff.CREATE, Path: []string{"1", "\xa4name"}, From: nil, To: "name2"},
+				diff.Change{Type: diff.CREATE, Path: []string{"1", "\xa4type"}, From: nil, To: []string{"null", "string"}},
+			},
+			nil,
+		},
+		{
+			"map-string-pointer-create",
+			map[string]*tmstruct{"one": &struct1},
+			map[string]*tmstruct{"one": &struct1, "two": &struct2},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"two", "foo"}, From: nil, To: "two"},
+				diff.Change{Type: diff.CREATE, Path: []string{"two", "bar"}, From: nil, To: 2},
+			},
+			nil,
+		},
+		{
+			"map-string-pointer-delete",
+			map[string]*tmstruct{"one": &struct1, "two": &struct2},
+			map[string]*tmstruct{"one": &struct1},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"two", "foo"}, From: "two", To: nil},
+				diff.Change{Type: diff.DELETE, Path: []string{"two", "bar"}, From: 2, To: nil},
+			},
+			nil,
+		},
+		{
+			"private-struct-field",
+			tstruct{private: 1},
+			tstruct{private: 4},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"private"}, From: int64(1), To: int64(4)},
+			},
+			nil,
+		},
+		{
+			"embedded-struct-field",
+			embedstruct{Embedded{Foo: "a", Bar: 2}, true},
+			embedstruct{Embedded{Foo: "b", Bar: 3}, false},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"foo"}, From: "a", To: "b"},
+				diff.Change{Type: diff.UPDATE, Path: []string{"bar"}, From: 2, To: 3},
+				diff.Change{Type: diff.UPDATE, Path: []string{"baz"}, From: true, To: false},
+			},
+			nil,
+		},
+		{
+			"custom-tags",
+			customTagStruct{Foo: "abc", Bar: 3},
+			customTagStruct{Foo: "def", Bar: 4},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"foo"}, From: "abc", To: "def"},
+				diff.Change{Type: diff.UPDATE, Path: []string{"bar"}, From: 3, To: 4},
+			},
+			nil,
+		},
+		{
+			"custom-types",
+			customTypeStruct{Foo: "a", Bar: 1},
+			customTypeStruct{Foo: "b", Bar: 2},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"foo"}, From: CustomStringType("a"), To: CustomStringType("b")},
+				diff.Change{Type: diff.UPDATE, Path: []string{"bar"}, From: CustomIntType(1), To: CustomIntType(2)},
+			},
+			nil,
+		},
+		{
+			"struct-private-map-create", privateMapStruct{set: map[string]interface{}{"1": struct{}{}, "2": struct{}{}}}, privateMapStruct{set: map[string]interface{}{"1": struct{}{}, "2": struct{}{}, "3": struct{}{}}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"set", "3"}, From: nil, To: struct{}{}},
+			},
+			nil,
+		},
+		{
+			"struct-private-map-delete", privateMapStruct{set: map[string]interface{}{"1": struct{}{}, "2": struct{}{}, "3": struct{}{}}}, privateMapStruct{set: map[string]interface{}{"1": struct{}{}, "2": struct{}{}}},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"set", "3"}, From: struct{}{}, To: nil},
+			},
+			nil,
+		},
+		{
+			"struct-private-map-nil-values", privateMapStruct{set: map[string]interface{}{"1": nil, "2": nil}}, privateMapStruct{set: map[string]interface{}{"1": nil, "2": nil, "3": nil}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"set", "3"}, From: nil, To: nil},
+			},
+			nil,
+		},
+		{
+			"slice-of-struct-with-slice",
+			[]tnstruct{{[]tmstruct{struct1, struct2}}, {[]tmstruct{struct2, struct2}}},
+			[]tnstruct{{[]tmstruct{struct2, struct2}}, {[]tmstruct{struct2, struct1}}},
+			diff.Changelog{},
+			nil,
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.Name, func(t *testing.T) {
+
+			var options []func(d *diff.Differ) error
+			switch tc.Name {
+			case "mixed-slice-map", "nil-map", "map-nil":
+				options = append(options, diff.StructMapKeySupport())
+			case "embedded-struct-field":
+				options = append(options, diff.FlattenEmbeddedStructs())
+			case "custom-tags":
+				options = append(options, diff.TagName("json"))
+			}
+			cl, err := diff.Diff(tc.A, tc.B, options...)
+
+			assert.Equal(t, tc.Error, err)
+			require.Equal(t, len(tc.Changelog), len(cl))
+
+			for i, c := range cl {
+				assert.Equal(t, tc.Changelog[i].Type, c.Type)
+				assert.Equal(t, tc.Changelog[i].Path, c.Path)
+				assert.Equal(t, tc.Changelog[i].From, c.From)
+				assert.Equal(t, tc.Changelog[i].To, c.To)
+			}
+		})
+	}
+}
+
+func TestDiffSliceOrdering(t *testing.T) {
+	cases := []struct {
+		Name      string
+		A, B      interface{}
+		Changelog diff.Changelog
+		Error     error
+	}{
+		{
+			"int-slice-insert-in-middle", []int{1, 2, 4}, []int{1, 2, 3, 4},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"2"}, From: 4, To: 3},
+				diff.Change{Type: diff.CREATE, Path: []string{"3"}, To: 4},
+			},
+			nil,
+		},
+		{
+			"int-slice-delete", []int{1, 2, 3}, []int{1, 3},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"1"}, From: 2, To: 3},
+				diff.Change{Type: diff.DELETE, Path: []string{"2"}, From: 3},
+			},
+			nil,
+		},
+		{
+			"int-slice-insert-delete", []int{1, 2, 3}, []int{1, 3, 4},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"1"}, From: 2, To: 3},
+				diff.Change{Type: diff.UPDATE, Path: []string{"2"}, From: 3, To: 4},
+			},
+			nil,
+		},
+		{
+			"int-slice-reorder", []int{1, 2, 3}, []int{1, 3, 2},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"1"}, From: 2, To: 3},
+				diff.Change{Type: diff.UPDATE, Path: []string{"2"}, From: 3, To: 2},
+			},
+			nil,
+		},
+		{
+			"string-slice-delete", []string{"1", "2", "3"}, []string{"1", "3"},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"1"}, From: "2", To: "3"},
+				diff.Change{Type: diff.DELETE, Path: []string{"2"}, From: "3"},
+			},
+			nil,
+		},
+		{
+			"string-slice-insert-delete", []string{"1", "2", "3"}, []string{"1", "3", "4"},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"1"}, From: "2", To: "3"},
+				diff.Change{Type: diff.UPDATE, Path: []string{"2"}, From: "3", To: "4"},
+			},
+			nil,
+		},
+		{
+			"string-slice-reorder", []string{"1", "2", "3"}, []string{"1", "3", "2"},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"1"}, From: "2", To: "3"},
+				diff.Change{Type: diff.UPDATE, Path: []string{"2"}, From: "3", To: "2"},
+			},
+			nil,
+		},
+		{
+			"nested-slice-delete", map[string][]int{"a": {1, 2, 3}}, map[string][]int{"a": {1, 3}},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"a", "1"}, From: 2, To: 3},
+				diff.Change{Type: diff.DELETE, Path: []string{"a", "2"}, From: 3},
+			},
+			nil,
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.Name, func(t *testing.T) {
+			d, err := diff.NewDiffer(diff.SliceOrdering(true))
+			require.Nil(t, err)
+			cl, err := d.Diff(tc.A, tc.B)
+
+			assert.Equal(t, tc.Error, err)
+			require.Equal(t, len(tc.Changelog), len(cl))
+
+			for i, c := range cl {
+				assert.Equal(t, tc.Changelog[i].Type, c.Type)
+				assert.Equal(t, tc.Changelog[i].Path, c.Path)
+				assert.Equal(t, tc.Changelog[i].From, c.From)
+				assert.Equal(t, tc.Changelog[i].To, c.To)
+			}
+		})
+	}
+
+}
+
+func TestFilter(t *testing.T) {
+	cases := []struct {
+		Name     string
+		Filter   []string
+		Expected [][]string
+	}{
+		{"simple", []string{"item-1", "subitem"}, [][]string{{"item-1", "subitem"}}},
+		{"regex", []string{"item-*"}, [][]string{{"item-1", "subitem"}, {"item-2", "subitem"}}},
+	}
+
+	cl := diff.Changelog{
+		{Path: []string{"item-1", "subitem"}},
+		{Path: []string{"item-2", "subitem"}},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.Name, func(t *testing.T) {
+			ncl := cl.Filter(tc.Filter)
+			assert.Len(t, ncl, len(tc.Expected))
+			for i, e := range tc.Expected {
+				assert.Equal(t, e, ncl[i].Path)
+			}
+		})
+	}
+}
+
+func TestFilterOut(t *testing.T) {
+	cases := []struct {
+		Name     string
+		Filter   []string
+		Expected [][]string
+	}{
+		{"simple", []string{"item-1", "subitem"}, [][]string{{"item-2", "subitem"}}},
+		{"regex", []string{"item-*"}, [][]string{}},
+	}
+
+	cl := diff.Changelog{
+		{Path: []string{"item-1", "subitem"}},
+		{Path: []string{"item-2", "subitem"}},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.Name, func(t *testing.T) {
+			ncl := cl.FilterOut(tc.Filter)
+			assert.Len(t, ncl, len(tc.Expected))
+			for i, e := range tc.Expected {
+				assert.Equal(t, e, ncl[i].Path)
+			}
+		})
+	}
+}
+
+func TestStructValues(t *testing.T) {
+	cases := []struct {
+		Name       string
+		ChangeType string
+		X          interface{}
+		Changelog  diff.Changelog
+		Error      error
+	}{
+		{
+			"struct-create", diff.CREATE, tstruct{ID: "xxxxx", Name: "something", Value: 1, Values: []string{"one", "two", "three"}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"id"}, From: nil, To: "xxxxx"},
+				diff.Change{Type: diff.CREATE, Path: []string{"name"}, From: nil, To: "something"},
+				diff.Change{Type: diff.CREATE, Path: []string{"value"}, From: nil, To: 1},
+				diff.Change{Type: diff.CREATE, Path: []string{"values", "0"}, From: nil, To: "one"},
+				diff.Change{Type: diff.CREATE, Path: []string{"values", "1"}, From: nil, To: "two"},
+				diff.Change{Type: diff.CREATE, Path: []string{"values", "2"}, From: nil, To: "three"},
+			},
+			nil,
+		},
+		{
+			"struct-delete", diff.DELETE, tstruct{ID: "xxxxx", Name: "something", Value: 1, Values: []string{"one", "two", "three"}},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"id"}, From: "xxxxx", To: nil},
+				diff.Change{Type: diff.DELETE, Path: []string{"name"}, From: "something", To: nil},
+				diff.Change{Type: diff.DELETE, Path: []string{"value"}, From: 1, To: nil},
+				diff.Change{Type: diff.DELETE, Path: []string{"values", "0"}, From: "one", To: nil},
+				diff.Change{Type: diff.DELETE, Path: []string{"values", "1"}, From: "two", To: nil},
+				diff.Change{Type: diff.DELETE, Path: []string{"values", "2"}, From: "three", To: nil},
+			},
+			nil,
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.Name, func(t *testing.T) {
+			cl, err := diff.StructValues(tc.ChangeType, []string{}, tc.X)
+
+			assert.Equal(t, tc.Error, err)
+			assert.Equal(t, len(tc.Changelog), len(cl))
+
+			for i, c := range cl {
+				assert.Equal(t, tc.Changelog[i].Type, c.Type)
+				assert.Equal(t, tc.Changelog[i].Path, c.Path)
+				assert.Equal(t, tc.Changelog[i].From, c.From)
+				assert.Equal(t, tc.Changelog[i].To, c.To)
+			}
+		})
+	}
+}
+
+func TestDifferReuse(t *testing.T) {
+	d, err := diff.NewDiffer()
+	require.Nil(t, err)
+
+	cl, err := d.Diff([]string{"1", "2", "3"}, []string{"1"})
+	require.Nil(t, err)
+
+	require.Len(t, cl, 2)
+
+	assert.Equal(t, "2", cl[0].From)
+	assert.Equal(t, nil, cl[0].To)
+	assert.Equal(t, "3", cl[1].From)
+	assert.Equal(t, nil, cl[1].To)
+
+	cl, err = d.Diff([]string{"a", "b"}, []string{"a", "c"})
+	require.Nil(t, err)
+
+	require.Len(t, cl, 1)
+
+	assert.Equal(t, "b", cl[0].From)
+	assert.Equal(t, "c", cl[0].To)
+}
+
+func TestDiffingOptions(t *testing.T) {
+	d, err := diff.NewDiffer(diff.SliceOrdering(false))
+	require.Nil(t, err)
+
+	assert.False(t, d.SliceOrdering)
+
+	cl, err := d.Diff([]int{1, 2, 3}, []int{1, 3, 2})
+	require.Nil(t, err)
+
+	assert.Len(t, cl, 0)
+
+	d, err = diff.NewDiffer(diff.SliceOrdering(true))
+	require.Nil(t, err)
+
+	assert.True(t, d.SliceOrdering)
+
+	cl, err = d.Diff([]int{1, 2, 3}, []int{1, 3, 2})
+	require.Nil(t, err)
+
+	assert.Len(t, cl, 2)
+
+	// some other options..
+}
+
+func TestDiffPrivateField(t *testing.T) {
+	cl, err := diff.Diff(tstruct{private: 1}, tstruct{private: 3})
+	require.Nil(t, err)
+	assert.Len(t, cl, 1)
+}
+
+type testType string
+type testTypeDiffer struct {
+	DiffFunc (func(path []string, a, b reflect.Value, p interface{}) error)
+}
+
+func (o *testTypeDiffer) InsertParentDiffer(dfunc func(path []string, a, b reflect.Value, p interface{}) error) {
+	o.DiffFunc = dfunc
+}
+
+func (o *testTypeDiffer) Match(a, b reflect.Value) bool {
+	return diff.AreType(a, b, reflect.TypeOf(testType("")))
+}
+func (o *testTypeDiffer) Diff(dt diff.DiffType, df diff.DiffFunc, cl *diff.Changelog, path []string, a, b reflect.Value, parent interface{}) error {
+	if a.String() != "custom" && b.String() != "match" {
+		cl.Add(diff.UPDATE, path, a.Interface(), b.Interface())
+	}
+	return nil
+}
+
+func TestCustomDiffer(t *testing.T) {
+	type custom struct {
+		T testType
+	}
+
+	d, err := diff.NewDiffer(
+		diff.CustomValueDiffers(
+			&testTypeDiffer{},
+		),
+	)
+	require.Nil(t, err)
+
+	cl, err := d.Diff(custom{"custom"}, custom{"match"})
+	require.Nil(t, err)
+
+	assert.Len(t, cl, 0)
+
+	d, err = diff.NewDiffer(
+		diff.CustomValueDiffers(
+			&testTypeDiffer{},
+		),
+	)
+	require.Nil(t, err)
+
+	cl, err = d.Diff(custom{"same"}, custom{"same"})
+	require.Nil(t, err)
+
+	assert.Len(t, cl, 1)
+}
+
+type testStringInterceptorDiffer struct {
+	DiffFunc (func(path []string, a, b reflect.Value, p interface{}) error)
+}
+
+func (o *testStringInterceptorDiffer) InsertParentDiffer(dfunc func(path []string, a, b reflect.Value, p interface{}) error) {
+	o.DiffFunc = dfunc
+}
+
+func (o *testStringInterceptorDiffer) Match(a, b reflect.Value) bool {
+	return diff.AreType(a, b, reflect.TypeOf(testType("")))
+}
+func (o *testStringInterceptorDiffer) Diff(dt diff.DiffType, df diff.DiffFunc, cl *diff.Changelog, path []string, a, b reflect.Value, parent interface{}) error {
+	if dt.String() == "STRING" {
+		// intercept the data
+		aValue, aOk := a.Interface().(testType)
+		bValue, bOk := b.Interface().(testType)
+
+		if aOk && bOk {
+			if aValue == "avalue" {
+				aValue = testType(strings.ToUpper(string(aValue)))
+				a = reflect.ValueOf(aValue)
+			}
+
+			if bValue == "bvalue" {
+				bValue = testType(strings.ToUpper(string(aValue)))
+				b = reflect.ValueOf(bValue)
+			}
+		}
+	}
+
+	// continue the diff logic passing the updated a/b values
+	return df(path, a, b, parent)
+}
+
+func TestStringInterceptorDiffer(t *testing.T) {
+	d, err := diff.NewDiffer(
+		diff.CustomValueDiffers(
+			&testStringInterceptorDiffer{},
+		),
+	)
+	require.Nil(t, err)
+
+	cl, err := d.Diff(testType("avalue"), testType("bvalue"))
+	require.Nil(t, err)
+
+	assert.Len(t, cl, 0)
+}
+
+type RecursiveTestStruct struct {
+	Id       int
+	Children []RecursiveTestStruct
+}
+
+type recursiveTestStructDiffer struct {
+	DiffFunc (func(path []string, a, b reflect.Value, p interface{}) error)
+}
+
+func (o *recursiveTestStructDiffer) InsertParentDiffer(dfunc func(path []string, a, b reflect.Value, p interface{}) error) {
+	o.DiffFunc = dfunc
+}
+
+func (o *recursiveTestStructDiffer) Match(a, b reflect.Value) bool {
+	return diff.AreType(a, b, reflect.TypeOf(RecursiveTestStruct{}))
+}
+
+func (o *recursiveTestStructDiffer) Diff(dt diff.DiffType, df diff.DiffFunc, cl *diff.Changelog, path []string, a, b reflect.Value, parent interface{}) error {
+	if a.Kind() == reflect.Invalid {
+		cl.Add(diff.CREATE, path, nil, b.Interface())
+		return nil
+	}
+	if b.Kind() == reflect.Invalid {
+		cl.Add(diff.DELETE, path, a.Interface(), nil)
+		return nil
+	}
+	var awt, bwt RecursiveTestStruct
+	awt, _ = a.Interface().(RecursiveTestStruct)
+	bwt, _ = b.Interface().(RecursiveTestStruct)
+	if awt.Id != bwt.Id {
+		cl.Add(diff.UPDATE, path, a.Interface(), b.Interface())
+	}
+	for i := 0; i < a.NumField(); i++ {
+		field := a.Type().Field(i)
+		tname := field.Name
+		if tname != "Children" {
+			continue
+		}
+		af := a.Field(i)
+		bf := b.FieldByName(field.Name)
+		fpath := copyAppend(path, tname)
+		err := o.DiffFunc(fpath, af, bf, nil)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func TestRecursiveCustomDiffer(t *testing.T) {
+	treeA := RecursiveTestStruct{
+		Id:       1,
+		Children: []RecursiveTestStruct{},
+	}
+
+	treeB := RecursiveTestStruct{
+		Id: 1,
+		Children: []RecursiveTestStruct{
+			{
+				Id:       4,
+				Children: []RecursiveTestStruct{},
+			},
+		},
+	}
+	d, err := diff.NewDiffer(
+		diff.CustomValueDiffers(
+			&recursiveTestStructDiffer{},
+		),
+	)
+	require.Nil(t, err)
+	cl, err := d.Diff(treeA, treeB)
+	require.Nil(t, err)
+	assert.Len(t, cl, 1)
+}
+
+func TestHandleDifferentTypes(t *testing.T) {
+	cases := []struct {
+		Name               string
+		A, B               interface{}
+		Changelog          diff.Changelog
+		Error              error
+		HandleTypeMismatch bool
+	}{
+		{
+			"type-change-not-allowed-error",
+			1, "1",
+			nil,
+			diff.ErrTypeMismatch,
+			false,
+		},
+		{
+			"type-change-not-allowed-error-struct",
+			struct {
+				p1 string
+				p2 int
+			}{"1", 1},
+			struct {
+				p1 string
+				p2 string
+			}{"1", "1"},
+			nil,
+			diff.ErrTypeMismatch,
+			false,
+		},
+		{
+			"type-change-allowed",
+			1, "1",
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{}, From: 1, To: "1"},
+			},
+			nil,
+			true,
+		},
+		{
+			"type-change-allowed-struct",
+			struct {
+				P1 string
+				P2 int
+				P3 map[string]string
+			}{"1", 1, map[string]string{"1": "1"}},
+			struct {
+				P1 string
+				P2 string
+				P3 string
+			}{"1", "1", "1"},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"P2"}, From: 1, To: "1"},
+				diff.Change{Type: diff.UPDATE, Path: []string{"P3"}, From: map[string]string{"1": "1"}, To: "1"},
+			},
+			nil,
+			true,
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.Name, func(t *testing.T) {
+			d, err := diff.NewDiffer(diff.AllowTypeMismatch(tc.HandleTypeMismatch))
+			require.Nil(t, err)
+			cl, err := d.Diff(tc.A, tc.B)
+
+			assert.Equal(t, tc.Error, err)
+			require.Equal(t, len(tc.Changelog), len(cl))
+
+			for i, c := range cl {
+				assert.Equal(t, tc.Changelog[i].Type, c.Type)
+				assert.Equal(t, tc.Changelog[i].Path, c.Path)
+				assert.Equal(t, tc.Changelog[i].From, c.From)
+				assert.Equal(t, tc.Changelog[i].To, c.To)
+			}
+		})
+	}
+}
+
+func copyAppend(src []string, elems ...string) []string {
+	dst := make([]string, len(src)+len(elems))
+	copy(dst, src)
+	for i := len(src); i < len(src)+len(elems); i++ {
+		dst[i] = elems[i-len(src)]
+	}
+	return dst
+}
diff --git a/licenses/github.com/r3labs/diff/v3/diff_time.go b/licenses/github.com/r3labs/diff/v3/diff_time.go
new file mode 100644
index 0000000..4275e4a
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/diff_time.go
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff
+
+import (
+	"reflect"
+	"time"
+)
+
+func (d *Differ) diffTime(path []string, a, b reflect.Value) error {
+	if a.Kind() == reflect.Invalid {
+		d.cl.Add(CREATE, path, nil, exportInterface(b))
+		return nil
+	}
+
+	if b.Kind() == reflect.Invalid {
+		d.cl.Add(DELETE, path, exportInterface(a), nil)
+		return nil
+	}
+
+	if a.Kind() != b.Kind() {
+		return ErrTypeMismatch
+	}
+
+	// Marshal and unmarshal time type will lose accuracy. Using unix nano to compare time type.
+	au := exportInterface(a).(time.Time).UnixNano()
+	bu := exportInterface(b).(time.Time).UnixNano()
+
+	if au != bu {
+		d.cl.Add(UPDATE, path, exportInterface(a), exportInterface(b))
+	}
+
+	return nil
+}
diff --git a/licenses/github.com/r3labs/diff/v3/diff_uint.go b/licenses/github.com/r3labs/diff/v3/diff_uint.go
new file mode 100644
index 0000000..fbe133f
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/diff_uint.go
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff
+
+import (
+	"reflect"
+)
+
+func (d *Differ) diffUint(path []string, a, b reflect.Value, parent interface{}) error {
+	if a.Kind() == reflect.Invalid {
+		d.cl.Add(CREATE, path, nil, exportInterface(b))
+		return nil
+	}
+
+	if b.Kind() == reflect.Invalid {
+		d.cl.Add(DELETE, path, exportInterface(a), nil)
+		return nil
+	}
+
+	if a.Kind() != b.Kind() {
+		return ErrTypeMismatch
+	}
+
+	if a.Uint() != b.Uint() {
+		if a.CanInterface() {
+			d.cl.Add(UPDATE, path, exportInterface(a), exportInterface(b), parent)
+		} else {
+			d.cl.Add(UPDATE, path, a.Uint(), b.Uint(), parent)
+		}
+	}
+
+	return nil
+}
diff --git a/licenses/github.com/r3labs/diff/v3/error.go b/licenses/github.com/r3labs/diff/v3/error.go
new file mode 100644
index 0000000..0acc13f
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/error.go
@@ -0,0 +1,74 @@
+package diff
+
+import (
+	"fmt"
+)
+
+var (
+	// ErrTypeMismatch Compared types do not match
+	ErrTypeMismatch = NewError("types do not match")
+	// ErrInvalidChangeType The specified change values are not unsupported
+	ErrInvalidChangeType = NewError("change type must be one of 'create' or 'delete'")
+)
+
+//our own version of an error, which can wrap others
+type DiffError struct {
+	count   int
+	message string
+	next    error
+}
+
+//Unwrap implement 1.13 unwrap feature for compatibility
+func (s *DiffError) Unwrap() error {
+	return s.next
+}
+
+//Error implements the error interface
+func (s DiffError) Error() string {
+	cause := ""
+	if s.next != nil {
+		cause = s.next.Error()
+	}
+	return fmt.Sprintf(" %s (cause count %d)\n%s", s.message, s.count, cause)
+}
+
+//AppendCause appends a new cause error to the chain
+func (s *DiffError) WithCause(err error) *DiffError {
+	if s != nil && err != nil {
+		s.count++
+		if s.next != nil {
+			if v, ok := err.(DiffError); ok {
+				s.next = v.WithCause(s.next)
+			} else if v, ok := err.(*DiffError); ok {
+				s.next = v.WithCause(s.next)
+			} else {
+				v = &DiffError{
+					message: "auto wrapped error",
+					next:    err,
+				}
+				s.next = v.WithCause(s.next)
+			}
+		} else {
+			s.next = err
+		}
+	}
+	return s
+}
+
+//NewErrorf just give me a plain error with formatting
+func NewErrorf(format string, messages ...interface{}) *DiffError {
+	return &DiffError{
+		message: fmt.Sprintf(format, messages...),
+	}
+}
+
+//NewError just give me a plain error
+func NewError(message string, causes ...error) *DiffError {
+	s := &DiffError{
+		message: message,
+	}
+	for _, cause := range causes {
+		s.WithCause(cause) // nolint: errcheck
+	}
+	return s
+}
diff --git a/licenses/github.com/r3labs/diff/v3/filter.go b/licenses/github.com/r3labs/diff/v3/filter.go
new file mode 100644
index 0000000..12e549a
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/filter.go
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package diff
+
+import "regexp"
+
+func pathmatch(filter, path []string) bool {
+	for i, f := range filter {
+		if len(path) < i+1 {
+			return false
+		}
+
+		matched, _ := regexp.MatchString(f, path[i])
+		if !matched {
+			return false
+		}
+	}
+
+	return true
+}
diff --git a/licenses/github.com/r3labs/diff/v3/go.mod b/licenses/github.com/r3labs/diff/v3/go.mod
new file mode 100644
index 0000000..216a3b5
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/go.mod
@@ -0,0 +1,9 @@
+module github.com/r3labs/diff/v3
+
+go 1.13
+
+require (
+	github.com/stretchr/testify v1.5.1
+	github.com/vmihailenco/msgpack v4.0.4+incompatible
+	google.golang.org/appengine v1.6.6 // indirect
+)
diff --git a/licenses/github.com/r3labs/diff/v3/go.sum b/licenses/github.com/r3labs/diff/v3/go.sum
new file mode 100644
index 0000000..9426b74
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/go.sum
@@ -0,0 +1,24 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
+github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/licenses/github.com/r3labs/diff/v3/options.go b/licenses/github.com/r3labs/diff/v3/options.go
new file mode 100644
index 0000000..fadbe86
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/options.go
@@ -0,0 +1,93 @@
+package diff
+
+// ConvertTypes enables values that are convertible to the target type to be converted when patching
+func ConvertCompatibleTypes() func(d *Differ) error {
+	return func(d *Differ) error {
+		d.ConvertCompatibleTypes = true
+		return nil
+	}
+}
+
+// FlattenEmbeddedStructs determines whether fields of embedded structs should behave as if they are directly under the parent
+func FlattenEmbeddedStructs() func(d *Differ) error {
+	return func(d *Differ) error {
+		d.FlattenEmbeddedStructs = true
+		return nil
+	}
+}
+
+// SliceOrdering determines whether the ordering of items in a slice results in a change
+func SliceOrdering(enabled bool) func(d *Differ) error {
+	return func(d *Differ) error {
+		d.SliceOrdering = enabled
+		return nil
+	}
+}
+
+// TagName sets the tag name to use when getting field names and options
+func TagName(tag string) func(d *Differ) error {
+	return func(d *Differ) error {
+		d.TagName = tag
+		return nil
+	}
+}
+
+// DisableStructValues disables populating a separate change for each item in a struct,
+// where the struct is being compared to a nil value
+func DisableStructValues() func(d *Differ) error {
+	return func(d *Differ) error {
+		d.DisableStructValues = true
+		return nil
+	}
+}
+
+// CustomValueDiffers allows you to register custom differs for specific types
+func CustomValueDiffers(vd ...ValueDiffer) func(d *Differ) error {
+	return func(d *Differ) error {
+		d.customValueDiffers = append(d.customValueDiffers, vd...)
+		for k := range d.customValueDiffers {
+			d.customValueDiffers[k].InsertParentDiffer(d.diff)
+		}
+		return nil
+	}
+}
+
+// AllowTypeMismatch changed behaviour to report value as "updated" when its type has changed instead of error
+func AllowTypeMismatch(enabled bool) func(d *Differ) error {
+	return func(d *Differ) error {
+		d.AllowTypeMismatch = enabled
+		return nil
+	}
+}
+
+//StructMapKeySupport - Changelog paths do not provided structured object values for maps that contain complex
+//keys (such as other structs). You must enable this support via an option and it then uses msgpack to encode
+//path elements that are structs. If you don't have this on, and try to patch, your apply will fail for that
+//element.
+func StructMapKeySupport() func(d *Differ) error {
+	return func(d *Differ) error {
+		d.StructMapKeys = true
+		return nil
+	}
+}
+
+//DiscardComplexOrigin - by default, we are now keeping the complex struct associated with a create entry.
+//This allows us to fix the merge to new object issue of not having enough change log details when allocating
+//new objects. This however is a trade off of memory size and complexity vs correctness which is often only
+//necessary when embedding structs in slices and arrays. It memory constrained environments, it may be desirable
+//to turn this feature off however from a computational perspective, keeping the complex origin is actually quite
+//cheap so, make sure you're extremely clear on the pitfalls of turning this off prior to doing so.
+func DiscardComplexOrigin() func(d *Differ) error {
+	return func(d *Differ) error {
+		d.DiscardParent = true
+		return nil
+	}
+}
+
+// Filter allows you to determine which fields the differ descends into
+func Filter(f FilterFunc) func(d *Differ) error {
+	return func(d *Differ) error {
+		d.Filter = f
+		return nil
+	}
+}
diff --git a/licenses/github.com/r3labs/diff/v3/patch.go b/licenses/github.com/r3labs/diff/v3/patch.go
new file mode 100644
index 0000000..5814c53
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/patch.go
@@ -0,0 +1,237 @@
+package diff
+
+import (
+	"reflect"
+)
+
+/**
+	This is a method of applying a changelog to a value or struct. change logs
+    should be generated with Diff and never manually created. This DOES NOT
+    apply fuzzy logic as would be in the case of a text patch. It does however
+    have a few additional features added to our struct tags.
+
+	1) create. This tag on a struct field indicates that the patch should
+       create the value if it's not there. I.e. if it's nil. This works for
+       pointers, maps and slices.
+
+	2) omitunequal. Generally, you don't want to do this, the expectation is
+       that if an item isn't there, you want to add it. For example, if your
+       diff shows an array element at index 6 is a string 'hello' but your target
+       only has 3 elements, none of them matching... you want to add 'hello'
+       regardless of the index. (think in a distributed context, another process
+       may have deleted more than one entry and 'hello' may no longer be in that
+       indexed spot.
+
+       So given this scenario, the default behavior is to scan for the previous
+       value and replace it anyway, or simply append the new value. For maps the
+       default behavior is to simply add the key if it doesn't match.
+
+       However, if you don't like the default behavior, and add the omitunequal
+       tag to your struct, patch will *NOT* update an array or map with the key
+       or array value unless they key or index contains a 'match' to the
+       previous value. In which case it will skip over that change.
+
+    Patch is implemented as a best effort algorithm. That means you can receive
+    multiple nested errors and still successfully have a modified target. This
+    may even be acceptable depending on your use case. So keep in mind, just
+    because err != nil *DOESN'T* mean that the patch didn't accomplish your goal
+    in setting those changes that are actually available. For example, you may
+    diff two structs of the same type, then attempt to apply to an entirely
+    different struct that is similar in constitution (think interface here) and
+    you may in fact get all of the values populated you wished to anyway.
+*/
+
+//Not strictly necessary but might be nice in some cases
+//go:generate stringer -type=PatchFlags
+type PatchFlags uint32
+
+const (
+	OptionCreate PatchFlags = 1 << iota
+	OptionNoCreate
+	OptionOmitUnequal
+	OptionImmutable
+	FlagInvalidTarget
+	FlagApplied
+	FlagFailed
+	FlagCreated
+	FlagIgnored
+	FlagDeleted
+	FlagUpdated
+	FlagParentSetApplied
+	FlagParentSetFailed
+)
+
+//PatchLogEntry defines how a DiffLog entry was applied
+type PatchLogEntry struct {
+	Path   []string    `json:"path"`
+	From   interface{} `json:"from"`
+	To     interface{} `json:"to"`
+	Flags  PatchFlags  `json:"flags"`
+	Errors error       `json:"errors"`
+}
+type PatchLog []PatchLogEntry
+
+//HasFlag - convenience function for users
+func (p PatchLogEntry) HasFlag(flag PatchFlags) bool {
+	return (p.Flags & flag) != 0
+}
+
+//Applied - returns true if all change log entries were actually
+//          applied, regardless of if any errors were encountered
+func (p PatchLog) Applied() bool {
+	if p.HasErrors() {
+		for _, ple := range p {
+			if !ple.HasFlag(FlagApplied) {
+				return false
+			}
+		}
+	}
+	return true
+}
+
+//HasErrors - indicates if a patch log contains any errors
+func (p PatchLog) HasErrors() (ret bool) {
+	for _, ple := range p {
+		if ple.Errors != nil {
+			ret = true
+		}
+	}
+	return
+}
+
+//ErrorCount -- counts the number of errors encountered while patching
+func (p PatchLog) ErrorCount() (ret uint) {
+	for _, ple := range p {
+		if ple.Errors != nil {
+			ret++
+		}
+	}
+	return
+}
+
+func Merge(original interface{}, changed interface{}, target interface{}) (PatchLog, error) {
+	d, _ := NewDiffer()
+	return d.Merge(original, changed, target)
+}
+
+// Merge is a convenience function that diffs, the original and changed items
+// and merges said changes with target all in one call.
+func (d *Differ) Merge(original interface{}, changed interface{}, target interface{}) (PatchLog, error) {
+	StructMapKeySupport()(d) // nolint: errcheck
+	if cl, err := d.Diff(original, changed); err == nil {
+		return Patch(cl, target), nil
+	} else {
+		return nil, err
+	}
+}
+
+func Patch(cl Changelog, target interface{}) (ret PatchLog) {
+	d, _ := NewDiffer()
+	return d.Patch(cl, target)
+}
+
+//Patch... the missing feature.
+func (d *Differ) Patch(cl Changelog, target interface{}) (ret PatchLog) {
+	for _, c := range cl {
+		ret = append(ret, NewPatchLogEntry(NewChangeValue(d, c, target)))
+	}
+	return ret
+}
+
+//NewPatchLogEntry converts our complicated reflection based struct to
+//a simpler format for the consumer
+func NewPatchLogEntry(cv *ChangeValue) PatchLogEntry {
+	return PatchLogEntry{
+		Path:   cv.change.Path,
+		From:   cv.change.From,
+		To:     cv.change.To,
+		Flags:  cv.flags,
+		Errors: cv.err,
+	}
+}
+
+//NewChangeValue idiomatic constructor (also invokes render)
+func NewChangeValue(d *Differ, c Change, target interface{}) (ret *ChangeValue) {
+	val := reflect.ValueOf(target)
+	ret = &ChangeValue{
+		target: &val,
+		change: &c,
+	}
+	d.renderChangeTarget(ret)
+	return
+}
+
+//renderChangeValue applies 'path' in change to target. nil check is foregone
+//                  here as we control usage
+func (d *Differ) renderChangeTarget(c *ChangeValue) {
+	//This particular change element may potentially have the immutable flag
+	if c.HasFlag(OptionImmutable) {
+		c.AddError(NewError("Option immutable set, cannot apply change"))
+		return
+	} //the we always set a failure, and only unset if we successfully render the element
+	c.SetFlag(FlagInvalidTarget)
+
+	//substitute and solve for t (path)
+	switch c.target.Kind() {
+
+	//path element that is a map
+	case reflect.Map:
+		//map elements are 'copies' and immutable so if we set the new value to the
+		//map prior to editing the value, it will fail to stick. To fix this, we
+		//defer the safe until the stack unwinds
+		m, k, v := d.renderMap(c)
+		defer d.updateMapEntry(c, m, k, v)
+
+	//path element that is a slice
+	case reflect.Slice:
+		d.renderSlice(c)
+
+	//walking a path means dealing with real elements
+	case reflect.Interface, reflect.Ptr:
+		if c.target.IsNil() {
+			n := reflect.New(c.target.Type().Elem())
+			c.target.Set(n)
+			c.target = &n
+			d.renderChangeTarget(c)
+			return
+		}
+
+		el := c.target.Elem()
+		c.target = &el
+		c.ClearFlag(FlagInvalidTarget)
+
+	//path element that is a struct
+	case reflect.Struct:
+		d.patchStruct(c)
+	}
+
+	//if for some reason, rendering this element fails, c will no longer be valid
+	//we are best effort though, so we keep on trucking
+	if !c.IsValid() {
+		c.AddError(NewErrorf("Unable to access path position %d. Target field is invalid", c.pos))
+	}
+
+	//we've taken care of this path element, are there any more? if so, process
+	//else, let's take some action
+	if c.pos < len(c.change.Path) && !c.HasFlag(FlagInvalidTarget) {
+		d.renderChangeTarget(c)
+
+	} else { //we're at the end of the line... set the Value
+		switch c.change.Type {
+		case DELETE:
+			switch c.ParentKind() {
+			case reflect.Slice:
+				d.deleteSliceEntry(c)
+			case reflect.Struct:
+				d.deleteStructEntry(c)
+			default:
+				c.SetFlag(FlagIgnored)
+			}
+		case UPDATE, CREATE:
+			// this is generic because... we only deal in primitives here. AND
+			// the diff format To field already contains the correct type.
+			c.Set(reflect.ValueOf(c.change.To), d.ConvertCompatibleTypes)
+			c.SetFlag(FlagUpdated)
+		}
+	}
+}
diff --git a/licenses/github.com/r3labs/diff/v3/patch_map.go b/licenses/github.com/r3labs/diff/v3/patch_map.go
new file mode 100644
index 0000000..8a3906a
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/patch_map.go
@@ -0,0 +1,105 @@
+package diff
+
+import (
+	"errors"
+	"reflect"
+
+	"github.com/vmihailenco/msgpack"
+)
+
+//renderMap - handle map rendering for patch
+func (d *Differ) renderMap(c *ChangeValue) (m, k, v *reflect.Value) {
+	//we must tease out the type of the key, we use the msgpack from diff to recreate the key
+	kt := c.target.Type().Key()
+	field := reflect.New(kt)
+
+	if d.StructMapKeys {
+		if err := msgpack.Unmarshal([]byte(c.change.Path[c.pos]), field.Interface()); err != nil {
+			c.SetFlag(FlagIgnored)
+			c.AddError(NewError("Unable to unmarshal path element to target type for key in map", err))
+			return
+		}
+		c.key = field.Elem()
+	} else {
+		c.key = reflect.ValueOf(c.change.Path[c.pos])
+	}
+
+	if c.target.IsNil() && c.target.IsValid() {
+		c.target.Set(reflect.MakeMap(c.target.Type()))
+	}
+
+	// we need to check that MapIndex does not panic here
+	// when the key type is not a string
+	defer func() {
+		if err := recover(); err != nil {
+			switch x := err.(type) {
+			case error:
+				c.AddError(NewError("Unable to unmarshal path element to target type for key in map", x))
+			case string:
+				c.AddError(NewError("Unable to unmarshal path element to target type for key in map", errors.New(x)))
+			}
+			c.SetFlag(FlagIgnored)
+		}
+	}()
+
+	x := c.target.MapIndex(c.key)
+
+	if !x.IsValid() && c.change.Type != DELETE && !c.HasFlag(OptionNoCreate) {
+		x = c.NewElement()
+	}
+	if x.IsValid() { //Map elements come out as read only so we must convert
+		nv := reflect.New(x.Type()).Elem()
+		nv.Set(x)
+		x = nv
+	}
+
+	if x.IsValid() && !reflect.DeepEqual(c.change.From, x.Interface()) &&
+		c.HasFlag(OptionOmitUnequal) {
+		c.SetFlag(FlagIgnored)
+		c.AddError(NewError("target change doesn't match original"))
+		return
+	}
+	mp := *c.target //these may change out from underneath us as we recurse
+	key := c.key    //so we make copies and pass back pointers to them
+	c.swap(&x)
+
+	return &mp, &key, &x
+
+}
+
+// updateMapEntry - deletes are special, they are handled differently based on options
+//            container type etc. We have to have special handling for each
+//            type. Set values are more generic even if they must be instanced
+func (d *Differ) updateMapEntry(c *ChangeValue, m, k, v *reflect.Value) {
+	if k == nil || m == nil {
+		return
+	}
+
+	switch c.change.Type {
+	case DELETE:
+		if c.HasFlag(FlagDeleted) {
+			return
+		}
+
+		if !m.CanSet() && v.IsValid() && v.Kind() == reflect.Struct {
+			for x := 0; x < v.NumField(); x++ {
+				if !v.Field(x).IsZero() {
+					m.SetMapIndex(*k, *v)
+					return
+				}
+			} //if all the fields are zero, remove from map
+		}
+
+		m.SetMapIndex(*k, reflect.Value{})
+		c.SetFlag(FlagDeleted)
+
+	case CREATE:
+		m.SetMapIndex(*k, *v)
+		c.SetFlag(FlagCreated)
+
+	case UPDATE:
+		m.SetMapIndex(*k, *v)
+		c.SetFlag(FlagUpdated)
+
+	}
+}
diff --git a/licenses/github.com/r3labs/diff/v3/patch_slice.go b/licenses/github.com/r3labs/diff/v3/patch_slice.go
new file mode 100644
index 0000000..9a703e5
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/patch_slice.go
@@ -0,0 +1,78 @@
+package diff
+
+/**
+	Types are being split out to more closely follow the library structure already
+    in place. Keeps the file simpler as well.
+*/
+import (
+	"reflect"
+	"strconv"
+)
+
+//renderSlice - handle slice rendering for patch
+func (d *Differ) renderSlice(c *ChangeValue) {
+
+	var err error
+	field := c.change.Path[c.pos]
+
+	//field better be an index of the slice
+	if c.index, err = strconv.Atoi(field); err != nil {
+		//if struct element is has identifier, use it instead
+		if identifier(d.TagName, reflect.Zero(c.target.Type().Elem())) != nil {
+			for c.index = 0; c.index < c.Len(); c.index++ {
+				if identifier(d.TagName, c.Index(c.index)) == field {
+					break
+				}
+			}
+		} else {
+			c.AddError(NewErrorf("invalid index in path. %s is not a number", field).
+				WithCause(err))
+		}
+	}
+	var x reflect.Value
+	if c.Len() > c.index {
+		x = c.Index(c.index)
+	} else if c.change.Type == CREATE && !c.HasFlag(OptionNoCreate) {
+		x = c.NewArrayElement()
+	}
+	if !x.IsValid() {
+		if !c.HasFlag(OptionOmitUnequal) {
+			c.AddError(NewErrorf("Value index %d is invalid", c.index).
+				WithCause(NewError("scanning for Value index")))
+			for c.index = 0; c.index < c.Len(); c.index++ {
+				y := c.Index(c.index)
+				if reflect.DeepEqual(y, c.change.From) {
+					c.AddError(NewErrorf("Value changed index to %d", c.index))
+					x = y
+					break
+				}
+			}
+		}
+	}
+	if !x.IsValid() && c.change.Type != DELETE && !c.HasFlag(OptionNoCreate) {
+		x = c.NewArrayElement()
+	}
+	if !x.IsValid() && c.change.Type == DELETE {
+		c.index = -1 //no existing element to delete so don't bother
+	}
+	c.swap(&x) //containers must swap out the parent Value
+}
+
+//deleteSliceEntry - deletes are special, they are handled differently based on options
+//              container type etc. We have to have special handling for each
+//              type. Set values are more generic even if they must be instanced
+func (d *Differ) deleteSliceEntry(c *ChangeValue) {
+	//for a slice with only one element
+	if c.ParentLen() == 1 && c.index != -1 {
+		c.ParentSet(reflect.MakeSlice(c.parent.Type(), 0, 0), d.ConvertCompatibleTypes)
+		c.SetFlag(FlagDeleted)
+		//for a slice with multiple elements
+	} else if c.index != -1 { //this is an array delete the element from the parent
+		c.ParentIndex(c.index).Set(c.ParentIndex(c.ParentLen() - 1))
+		c.ParentSet(c.parent.Slice(0, c.ParentLen()-1), d.ConvertCompatibleTypes)
+		c.SetFlag(FlagDeleted)
+		//for other slice elements, we ignore
+	} else {
+		c.SetFlag(FlagIgnored)
+	}
+}
diff --git a/licenses/github.com/r3labs/diff/v3/patch_struct.go b/licenses/github.com/r3labs/diff/v3/patch_struct.go
new file mode 100644
index 0000000..4e2d247
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/patch_struct.go
@@ -0,0 +1,66 @@
+package diff
+
+import "reflect"
+
+/**
+	Types are being split out to more closely follow the library structure already
+    in place. Keeps the file simpler as well.
+*/
+
+type structField struct {
+	f reflect.StructField
+	v reflect.Value
+}
+
+func getNestedFields(v reflect.Value, flattenEmbedded bool) []structField {
+	fields := make([]structField, 0)
+
+	for i := 0; i < v.NumField(); i++ {
+		f := v.Type().Field(i)
+		fv := v.Field(i)
+
+		if fv.Kind() == reflect.Struct && f.Anonymous && flattenEmbedded {
+			fields = append(fields, getNestedFields(fv, flattenEmbedded)...)
+		} else {
+			fields = append(fields, structField{f, fv})
+		}
+	}
+
+	return fields
+}
+
+//patchStruct - handles the rendering of a struct field
+func (d *Differ) patchStruct(c *ChangeValue) {
+
+	field := c.change.Path[c.pos]
+
+	structFields := getNestedFields(*c.target, d.FlattenEmbeddedStructs)
+	for _, structField := range structFields {
+		f := structField.f
+		tname := tagName(d.TagName, f)
+		if tname == "-" {
+			continue
+		}
+		if tname == field || f.Name == field {
+			x := structField.v
+			if hasTagOption(d.TagName, f, "nocreate") {
+				c.SetFlag(OptionNoCreate)
+			}
+			if hasTagOption(d.TagName, f, "omitunequal") {
+				c.SetFlag(OptionOmitUnequal)
+			}
+			if hasTagOption(d.TagName, f, "immutable") {
+				c.SetFlag(OptionImmutable)
+			}
+			c.swap(&x)
+			break
+		}
+	}
+}
+
+//track and zero out struct members
+func (d *Differ) deleteStructEntry(c *ChangeValue) {
+
+	//deleting a struct value set's it to the 'basic' type
+	c.Set(reflect.Zero(c.target.Type()), d.ConvertCompatibleTypes)
+}
diff --git a/licenses/github.com/r3labs/diff/v3/patch_test.go b/licenses/github.com/r3labs/diff/v3/patch_test.go
new file mode 100644
index 0000000..fe7f8cd
--- /dev/null
+++ b/licenses/github.com/r3labs/diff/v3/patch_test.go
@@ -0,0 +1,351 @@
+package diff_test
+
+import (
+	"encoding/json"
+	"testing"
+	"time"
+
+	"github.com/r3labs/diff/v3"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestPatch(t *testing.T) {
+	cases := []struct {
+		Name      string
+		A, B      interface{}
+		Changelog diff.Changelog
+	}{
+		{
+			"uint-slice-insert", &[]uint{1, 2, 3}, &[]uint{1, 2, 3, 4},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"3"}, To: uint(4)},
+			},
+		},
+		{
+			"int-slice-insert", &[]int{1, 2, 3}, &[]int{1, 2, 3, 4},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"3"}, To: 4},
+			},
+		},
+		{
+			"uint-slice-delete", &[]uint{1, 2, 3}, &[]uint{1, 3},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: uint(2)},
+			},
+		},
+		{
+			"int-slice-delete", &[]int{1, 2, 3}, &[]int{1, 3},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: 2},
+			},
+		},
+		{
+			"uint-slice-insert-delete", &[]uint{1, 2, 3}, &[]uint{1, 3, 4},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: uint(2)},
+				diff.Change{Type: diff.CREATE, Path: []string{"2"}, To: uint(4)},
+			},
+		},
+		{
+			"int-slice-insert-delete", &[]int{1, 2, 3}, &[]int{1, 3, 4},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: 2},
+				diff.Change{Type: diff.CREATE, Path: []string{"2"}, To: 4},
+			},
+		},
+		{
+			"string-slice-insert", &[]string{"1", "2", "3"}, &[]string{"1", "2", "3", "4"},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"3"}, To: "4"},
+			},
+		},
+		{
+			"string-slice-delete", &[]string{"1", "2", "3"}, &[]string{"1", "3"},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: "2"},
+			},
+		},
+		{
+			"string-slice-insert-delete", &[]string{"1", "2", "3"}, &[]string{"1", "3", "4"},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: "2"},
+				diff.Change{Type: diff.CREATE, Path: []string{"2"}, To: "4"},
+			},
+		},
+		{
+			"comparable-slice-update", &[]tistruct{{"one", 1}, {"two", 2}}, &[]tistruct{{"one", 1}, {"two", 50}},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"two", "value"}, From: 1, To: 50},
+			},
+		},
+		{
+			"struct-string-update", &tstruct{Name: "one"}, &tstruct{Name: "two"},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"name"}, From: "one", To: "two"},
+			},
+		},
+		{
+			"struct-int-update", &tstruct{Value: 1}, &tstruct{Value: 50},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"value"}, From: 1, To: 50},
+			},
+		},
+		{
+			"struct-bool-update", &tstruct{Bool: true}, &tstruct{Bool: false},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"bool"}, From: true, To: false},
+			},
+		},
+		{
+			"struct-time-update", &tstruct{}, &tstruct{Time: currentTime},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"time"}, From: time.Time{}, To: currentTime},
+			},
+		},
+		{
+			"struct-nil-string-pointer-update", &tstruct{Pointer: nil}, &tstruct{Pointer: sptr("test")},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"pointer"}, From: nil, To: sptr("test")},
+			},
+		},
+		{
+			"struct-string-pointer-update-to-nil", &tstruct{Pointer: sptr("test")}, &tstruct{Pointer: nil},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"pointer"}, From: sptr("test"), To: nil},
+			},
+		}, {
+			"struct-generic-slice-insert", &tstruct{Values: []string{"one"}}, &tstruct{Values: []string{"one", "two"}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"values", "1"}, From: nil, To: "two"},
+			},
+		},
+		{
+			"struct-generic-slice-delete", &tstruct{Values: []string{"one", "two"}}, &tstruct{Values: []string{"one"}},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"values", "1"}, From: "two", To: nil},
+			},
+		},
+		{
+			"struct-unidentifiable-slice-insert-delete", &tstruct{Unidentifiables: []tuistruct{{1}, {2}, {3}}}, &tstruct{Unidentifiables: []tuistruct{{5}, {2}, {3}, {4}}},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"unidentifiables", "0", "value"}, From: 1, To: 5},
+				diff.Change{Type: diff.CREATE, Path: []string{"unidentifiables", "3", "value"}, From: nil, To: 4},
+			},
+		},
+		{
+			"slice", &tstruct{}, &tstruct{Nested: tnstruct{Slice: []tmstruct{{"one", 1}, {"two", 2}}}},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"nested", "slice", "0", "foo"}, From: nil, To: "one"},
+				diff.Change{Type: diff.CREATE, Path: []string{"nested", "slice", "0", "bar"}, From: nil, To: 1},
+				diff.Change{Type: diff.CREATE, Path: []string{"nested", "slice", "1", "foo"}, From: nil, To: "two"},
+				diff.Change{Type: diff.CREATE, Path: []string{"nested", "slice", "1", "bar"}, From: nil, To: 2},
+			},
+		},
+		{
+			"slice-duplicate-items", &[]int{1}, &[]int{1, 1},
+			diff.Changelog{
+				diff.Change{Type: diff.CREATE, Path: []string{"1"}, From: nil, To: 1},
+			},
+		},
+		{
+			"embedded-struct-field",
+			&embedstruct{Embedded{Foo: "a", Bar: 2}, true},
+			&embedstruct{Embedded{Foo: "b", Bar: 3}, false},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"foo"}, From: "a", To: "b"},
+				diff.Change{Type: diff.UPDATE, Path: []string{"bar"}, From: 2, To: 3},
+				diff.Change{Type: diff.UPDATE, Path: []string{"baz"}, From: true, To: false},
+			},
+		},
+		{
+			"custom-tags",
+			&customTagStruct{Foo: "abc", Bar: 3},
+			&customTagStruct{Foo: "def", Bar: 4},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"foo"}, From: "abc", To: "def"},
+				diff.Change{Type: diff.UPDATE, Path: []string{"bar"}, From: 3, To: 4},
+			},
+		},
+		{
+			"custom-types",
+			&customTypeStruct{Foo: "a", Bar: 1},
+			&customTypeStruct{Foo: "b", Bar: 2},
+			diff.Changelog{
+				diff.Change{Type: diff.UPDATE, Path: []string{"foo"}, From: CustomStringType("a"), To: CustomStringType("b")},
+				diff.Change{Type: diff.UPDATE, Path: []string{"bar"}, From: CustomIntType(1), To: CustomIntType(2)},
+			},
+		},
+		{
+			"map",
+			map[string]interface{}{"1": "one", "3": "three"},
+			map[string]interface{}{"2": "two", "3": "tres"},
+			diff.Changelog{
+				diff.Change{Type: diff.DELETE, Path: []string{"1"}, From: "one", To: nil},
+				diff.Change{Type: diff.CREATE, Path: []string{"2"}, From: nil, To: "two"},
+				diff.Change{Type: diff.UPDATE, Path: []string{"3"}, From: "three", To: "tres"},
+			},
+		},
+		{
+			"map-nested-create",
+			map[string]interface{}{"firstName": "John", "lastName": "Michael", "createdBy": "TS", "details": map[string]interface{}{"status": "active", "attributes": map[string]interface{}{"attrA": "A", "attrB": "B"}}},
+			map[string]interface{}{"firstName": "John", "lastName": "Michael", "createdBy": "TS", "details": map[string]interface{}{"status": "active", "attributes": map[string]interface{}{"attrA": "A", "attrB": "B"}, "secondary-attributes": map[string]interface{}{"attrA": "A", "attrB": "B"}}},
+			diff.Changelog{
+				diff.Change{Type: "create", Path: []string{"details", "secondary-attributes"}, From: nil, To: map[string]interface{}{"attrA": "A", "attrB": "B"}},
+			},
+		},
+		{
+			"map-nested-update",
+			map[string]interface{}{"firstName": "John", "lastName": "Michael", "createdBy": "TS", "details": map[string]interface{}{"status": "active", "attributes": map[string]interface{}{"attrA": "A", "attrB": "B"}}},
+			map[string]interface{}{"firstName": "John", "lastName": "Michael", "createdBy": "TS", "details": map[string]interface{}{"status": "active", "attributes": map[string]interface{}{"attrA": "C", "attrD": "X"}}},
+			diff.Changelog{
+				diff.Change{Type: "update", Path: []string{"details", "attributes"}, From: map[string]interface{}{"attrA": "A", "attrB": "B"}, To: map[string]interface{}{"attrA": "C", "attrD": "X"}},
+			},
+		},
+		{
+			"map-nested-delete",
+			map[string]interface{}{"firstName": "John", "lastName": "Michael", "createdBy": "TS", "details": map[string]interface{}{"status": "active", "attributes": map[string]interface{}{"attrA": "A", "attrB": "B"}}},
+			map[string]interface{}{"firstName": "John", "lastName": "Michael", "createdBy": "TS", "details": map[string]interface{}{"status": "active"}},
+			diff.Changelog{
+				diff.Change{Type: "delete", Path: []string{"details", "attributes"}, From: map[string]interface{}{"attrA": "A", "attrB": "B"}, To: nil},
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.Name, func(t *testing.T) {
+
+			var options []func(d *diff.Differ) error
+			switch tc.Name {
+			case "mixed-slice-map", "nil-map", "map-nil":
+				options = append(options, diff.StructMapKeySupport())
+			case "embedded-struct-field":
+				options = append(options, diff.FlattenEmbeddedStructs())
+			case "custom-tags":
+				options = append(options, diff.TagName("json"))
+			}
+			d, err := diff.NewDiffer(options...)
+			if err != nil {
+				panic(err)
+			}
+			pl := d.Patch(tc.Changelog, tc.A)
+
+			assert.Equal(t, tc.B, tc.A)
+			require.Equal(t, len(tc.Changelog), len(pl))
+			assert.False(t, pl.HasErrors())
+		})
+	}
+
+	t.Run("convert-types", func(t *testing.T) {
+		a := &tmstruct{Foo: "a", Bar: 1}
+		b := &customTypeStruct{Foo: "b", Bar: 2}
+		cl := diff.Changelog{
+			diff.Change{Type: diff.UPDATE, Path: []string{"foo"}, From: CustomStringType("a"), To: CustomStringType("b")},
+			diff.Change{Type: diff.UPDATE, Path: []string{"bar"}, From: CustomIntType(1), To: CustomIntType(2)},
+		}
+
+		d, err := diff.NewDiffer()
+		if err != nil {
+			panic(err)
+		}
+		pl := d.Patch(cl, a)
+
+		assert.True(t, pl.HasErrors())
+
+		d, err = diff.NewDiffer(diff.ConvertCompatibleTypes())
+		if err != nil {
+			panic(err)
+		}
+		pl = d.Patch(cl, a)
+
+		assert.False(t, pl.HasErrors())
+		assert.Equal(t, string(b.Foo), a.Foo)
+		assert.Equal(t, int(b.Bar), a.Bar)
+		require.Equal(t, len(cl), len(pl))
+	})
+
+	t.Run("pointer", func(t *testing.T) {
+		type tps struct {
+			S *string
+		}
+
+		str1 := "before"
+		str2 := "after"
+
+		t1 := tps{S: &str1}
+		t2 := tps{S: &str2}
+
+		changelog, err := diff.Diff(t1, t2)
+		assert.NoError(t, err)
+
+		patchLog := diff.Patch(changelog, &t1)
+		assert.False(t, patchLog.HasErrors())
+	})
+
+	t.Run("map-to-pointer", func(t *testing.T) {
+		t1 := make(map[string]*tmstruct)
+		t2 := map[string]*tmstruct{"after": {Foo: "val"}}
+
+		changelog, err := diff.Diff(t1, t2)
+		assert.NoError(t, err)
+
+		patchLog := diff.Patch(changelog, &t1)
+		assert.False(t, patchLog.HasErrors())
+
+		assert.True(t, len(t2) == len(t1))
+		assert.Equal(t, t1["after"], &tmstruct{Foo: "val"})
+	})
+
+	t.Run("map-to-nil-pointer", func(t *testing.T) {
+		t1 := make(map[string]*tmstruct)
+		t2 := map[string]*tmstruct{"after": nil}
+
+		changelog, err := diff.Diff(t1, t2)
+		assert.NoError(t, err)
+
+		patchLog := diff.Patch(changelog, &t1)
+		assert.False(t, patchLog.HasErrors())
+
+		assert.Equal(t, len(t2), len(t1))
+		assert.Nil(t, t1["after"])
+	})
+
+	t.Run("pointer-with-converted-type", func(t *testing.T) {
+		type tps struct {
+			S *int
+		}
+
+		val1 := 1
+		val2 := 2
+
+		t1 := tps{S: &val1}
+		t2 := tps{S: &val2}
+
+		changelog, err := diff.Diff(t1, t2)
+		assert.NoError(t, err)
+
+		js, err := json.Marshal(changelog)
+		assert.NoError(t, err)
+
+		assert.NoError(t, json.Unmarshal(js, &changelog))
+
+		d, err := diff.NewDiffer(diff.ConvertCompatibleTypes())
+		assert.NoError(t, err)
+
+		assert.Equal(t, 1, *t1.S)
+
+		patchLog := d.Patch(changelog, &t1)
+		assert.False(t, patchLog.HasErrors())
+		assert.Equal(t, 2, *t1.S)
+
+		// test nil pointer
+		t1 = tps{S: &val1}
+		t2 = tps{S: nil}
+
+		changelog, err = diff.Diff(t1, t2)
+		assert.NoError(t, err)
+
+		patchLog = d.Patch(changelog, &t1)
+		assert.False(t, patchLog.HasErrors())
+	})
+}
diff --git a/licenses/github.com/vmihailenco/msgpack/LICENSE b/licenses/github.com/vmihailenco/msgpack/LICENSE
new file mode 100644
index 0000000..b749d07
--- /dev/null
+++ b/licenses/github.com/vmihailenco/msgpack/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2013 The github.com/vmihailenco/msgpack Authors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/licenses/golang.org/x/exp/LICENSE b/licenses/golang.org/x/exp/LICENSE
new file mode 100644
index 0000000..6a66aea
--- /dev/null
+++ b/licenses/golang.org/x/exp/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/licenses/gopkg.in/yaml.v3/LICENSE b/licenses/gopkg.in/yaml.v3/LICENSE
new file mode 100644
index 0000000..2683e4b
--- /dev/null
+++ b/licenses/gopkg.in/yaml.v3/LICENSE
@@ -0,0 +1,50 @@
+
+This project is covered by two different licenses: MIT and Apache.
+
+#### MIT License ####
+
+The following files were ported to Go from C files of libyaml, and thus
+are still covered by their original MIT license, with the additional
+copyright staring in 2011 when the project was ported over:
+
+    apic.go emitterc.go parserc.go readerc.go scannerc.go
+    writerc.go yamlh.go yamlprivateh.go
+
+Copyright (c) 2006-2010 Kirill Simonov
+Copyright (c) 2006-2011 Kirill Simonov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+### Apache License ###
+
+All the remaining project files are covered by the Apache license:
+
+Copyright (c) 2011-2019 Canonical Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/licenses/gopkg.in/yaml.v3/NOTICE b/licenses/gopkg.in/yaml.v3/NOTICE
new file mode 100644
index 0000000..866d74a
--- /dev/null
+++ b/licenses/gopkg.in/yaml.v3/NOTICE
@@ -0,0 +1,13 @@
+Copyright 2011-2016 Canonical Ltd.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/properties.go b/properties.go
new file mode 100644
index 0000000..0acc5db
--- /dev/null
+++ b/properties.go
@@ -0,0 +1,35 @@
+package configuration
+
+import (
+	"reflect"
+	"strconv"
+)
+
+func getMapForProperties[C any](c C) (map[string]string, []error) {
+
+	m := make(map[string]string)
+
+	var errors []error
+
+	runOnTags(c, []string{"properties"}, func(tagValue string, field reflect.Value) {
+
+		switch field.Kind() {
+		case reflect.String:
+			m[tagValue] = field.String()
+		case reflect.Int:
+			m[tagValue] = strconv.Itoa(int(field.Int()))
+		case reflect.Bool:
+			if field.Bool() {
+				m[tagValue] = "true"
+			} else {
+				m[tagValue] = "false"
+			}
+
+		default:
+			errors = append(errors, newUnsupportedReflectKindError(field.Type()))
+		}
+		
+	})
+
+	return m, errors
+}
diff --git a/properties_test.go b/properties_test.go
new file mode 100644
index 0000000..499b04f
--- /dev/null
+++ b/properties_test.go
@@ -0,0 +1,29 @@
+package configuration
+
+import (
+	"github.com/magiconair/properties/assert"
+	"testing"
+)
+
+func TestGetMapForProperties(t *testing.T) {
+
+	config := ConfigStruct7{}
+	config.A = "A"
+	config.B = true
+	config.C.CA = "CA"
+	config.C.CB = true
+	config.C.CD.CDA = "CDA"
+
+	s := New(config)
+
+	m, e := getMapForProperties(s.config)
+
+	assert.Equal(t, len(e), 0)
+
+	assert.Equal(t, m["A"], "A")
+	assert.Equal(t, m["B"], "true")
+	assert.Equal(t, m["C.CA"], "CA")
+	assert.Equal(t, m["C.CB"], "true")
+	assert.Equal(t, m["C.CB.CDA"], "CDA")
+
+}
diff --git a/release.json b/release.json
new file mode 100644
index 0000000..b8fbae9
--- /dev/null
+++ b/release.json
@@ -0,0 +1 @@
+{"version":"1.0.0"}
diff --git a/settings.go b/settings.go
new file mode 100644
index 0000000..463d0f9
--- /dev/null
+++ b/settings.go
@@ -0,0 +1,70 @@
+package configuration
+
+import (
+	"github.com/fsnotify/fsnotify"
+	"reflect"
+	"strconv"
+	"sync"
+)
+
+type fileWatch struct {
+	sync.Mutex
+	watcher     *fsnotify.Watcher
+	watchList   map[string]string
+	cancelWatch chan bool
+	onWatch     bool
+}
+
+type setting[C any] struct {
+	files  fileBackend
+	stream streamBackend
+	config C
+	errors []error
+	sync.Mutex
+	mnemonic      string
+	importCounter int
+	hooks         struct {
+		change []EventHook
+	}
+
+	fileWatch fileWatch
+}
+
+func (s *setting[C]) initDefaults() *setting[C] {
+
+	err := runOnTags(&s.config, []string{"default"}, func(v string, field reflect.Value) {
+
+		if field.CanSet() {
+			switch field.Kind() {
+			case reflect.String:
+				field.SetString(v)
+			case reflect.Int:
+				intVar, err := strconv.Atoi(v)
+				if err != nil {
+					s.errors = append(s.errors, err)
+					return
+				}
+				field.SetInt(int64(intVar))
+			case reflect.Bool:
+				field.SetBool(v == "true")
+			default:
+				s.errors = append(s.errors, newUnsupportedTypeError(field.Type()))
+			}
+
+		}
+	})
+
+	if err != nil {
+		s.errors = append(s.errors, err)
+	}
+
+	return s
+}
+
+func validateConfig[C any](config C) error {
+	t := reflect.TypeOf(config)
+	if t.Kind() != reflect.Struct {
+		return newUnsupportedTypeError(t)
+	}
+	return nil
+}
diff --git a/settings_test.go b/settings_test.go
new file mode 100644
index 0000000..b7e1387
--- /dev/null
+++ b/settings_test.go
@@ -0,0 +1,51 @@
+package configuration
+
+type ConfigStruct1 struct {
+	CA struct {
+		CAA int `yaml:"CAA" json:"CAA"`
+	} `yaml:"CA" json:"CA"`
+	CB int `yaml:"CB" json:"CB"`
+}
+
+type ConfigStruct3 interface{}
+
+type ConfigStruct5 struct {
+	HA int    `yaml:"HA" json:"HA" default:"1"`
+	HB string `yaml:"HB" json:"HB" default:"yes"`
+	HC bool   `yaml:"HC" json:"HC" default:"true"`
+	HD bool   `yaml:"HD" json:"HD" default:"false"`
+}
+
+type ConfigStruct6 struct {
+	IA int    `yaml:"IA" json:"IA" default:"1" flag:"value-ia" env:"VALUE_IB"`
+	IB string `yaml:"IB" json:"IB" default:"yes" flag:"value-ib" env:"VALUE_IB"`
+	IC bool   `yaml:"IC" json:"IC" default:"true" flag:"value-ic" env:"VALUE_IC"`
+	ID bool   `yaml:"ID" json:"ID" default:"false" flag:"value-id" env:"VALUE_ID"`
+}
+
+type ConfigStruct2 struct {
+	A string          `yaml:"A" json:"A" toml:"A"`
+	B bool            `yaml:"B" json:"B"`
+	C []ConfigStruct1 `yaml:"C" json:"C"`
+	D ConfigStruct3   `yaml:"D" json:"D"`
+	F int             `yaml:"F" json:"F"`
+	G string          `yaml:"G" json:"G"`
+	H ConfigStruct5   `yaml:"H" json:"H"`
+	I ConfigStruct6   `yaml:"I" json:"I"`
+}
+
+type ConfigStruct4 struct {
+	A string `yaml:"A" json:"A" toml:"A" properties:"A"`
+}
+
+type ConfigStruct7 struct {
+	A string `yaml:"A" json:"A" toml:"A" properties:"A"`
+	B bool   `yaml:"B" json:"B" properties:"B"`
+	C struct {
+		CA string `yaml:"CA" json:"CA" toml:"CA" properties:"C.CA"`
+		CB bool   `yaml:"CB" json:"CB" toml:"CB" properties:"C.CB"`
+		CD struct {
+			CDA string `yaml:"CDA" json:"CDA" toml:"CDA" properties:"C.CB.CDA"`
+		}
+	} `yaml:"C" json:"C" toml:"C" properties:"C"`
+}
diff --git a/stream.go b/stream.go
new file mode 100644
index 0000000..b7e0526
--- /dev/null
+++ b/stream.go
@@ -0,0 +1,41 @@
+package configuration
+
+import "io"
+
+type reader struct {
+	format Format
+	reader io.Reader
+}
+
+//type writer struct {
+//	format Format
+//	reader io.Writer
+//}
+
+type streamBackend struct {
+	readers []reader
+	//writers []writer
+}
+
+//type StreamOption struct {
+//}
+
+//func (s *setting[C]) AddStream(stream io.ReadWriter, format Format) *setting[C] {
+//	return s.
+//		AddReader(stream, format)
+//		//AddWriter(stream, format)
+//}
+
+func (s *setting[C]) AddReader(r io.Reader, format Format) *setting[C] {
+	s.Lock()
+	defer s.Unlock()
+	s.stream.readers = append(s.stream.readers, reader{format, r})
+	return s
+}
+
+//func (s *setting[C]) AddWriter(w io.Writer, format Format) *setting[C] {
+//	s.Lock()
+//	defer s.Unlock()
+//	s.stream.writers = append(s.stream.writers, writer{format, w})
+//	return s
+//}
diff --git a/stream_test.go b/stream_test.go
new file mode 100644
index 0000000..715e710
--- /dev/null
+++ b/stream_test.go
@@ -0,0 +1,95 @@
+package configuration
+
+import (
+	"bytes"
+	"io"
+	"testing"
+)
+
+func TestAddReader(t *testing.T) {
+	var config ConfigStruct2
+	s := New(&config)
+
+	if s == nil {
+		t.Error("Expected not nil")
+	}
+
+	var buf bytes.Buffer
+	buf.WriteString("A: a\n")
+	r := (io.Reader)(&buf)
+
+	s.AddReader(r, Yaml)
+	if len(s.stream.readers) != 1 {
+		t.Error("Expected 1")
+	}
+}
+
+//func TestAddWriter(t *testing.T) {
+//	var config ConfigStruct2
+//	s := New(&config)
+//
+//	if s == nil {
+//		t.Error("Expected not nil")
+//	}
+//
+//	var buf bytes.Buffer
+//	w := (io.Writer)(&buf)
+//
+//	s.AddWriter(w, Yaml)
+//	if len(s.stream.writers) != 1 {
+//		t.Error("Expected 1")
+//	}
+//}
+
+//func TestAddStream(t *testing.T) {
+//	var config ConfigStruct2
+//	s := New(&config)
+//
+//	if s == nil {
+//		t.Error("Expected not nil")
+//	}
+//
+//	var buf bytes.Buffer
+//	sr := (io.ReadWriter)(&buf)
+//
+//	s.AddStream(sr, Yaml)
+//	if len(s.stream.readers) != 1 {
+//		t.Error("Expected 1")
+//	}
+//	if len(s.stream.writers) != 1 {
+//		t.Error("Expected 1")
+//	}
+//}
+
+//func TestAddPropertyStream(t *testing.T) {
+//	var config ConfigStruct4
+//	s := New(config)
+//
+//	if s == nil {
+//		t.Error("Expected not nil")
+//	}
+//
+//	var buf bytes.Buffer
+//	buf.WriteString("A=a\n")
+//	sr := (io.ReadWriter)(&buf)
+//
+//	s.AddStream(sr, Properties)
+//	if len(s.stream.readers) != 1 {
+//		t.Error("Expected 1")
+//	}
+//	if len(s.stream.writers) != 1 {
+//		t.Error("Expected 1")
+//	}
+//
+//	s.Import()
+//
+//	if s.HasError() {
+//		t.Error("Expected no errors but got ", s.Errors())
+//	}
+//
+//	b := s.Config()
+//	if b.A != "a" {
+//		t.Error("Expected a")
+//	}
+//
+//}
diff --git a/tags.go b/tags.go
new file mode 100644
index 0000000..c3b964a
--- /dev/null
+++ b/tags.go
@@ -0,0 +1,51 @@
+package configuration
+
+import (
+	"reflect"
+)
+
+func getTags(data reflect.Value, tags []string, callback handleTagCallback) {
+
+	for i := 0; i < data.NumField(); i++ {
+		field := data.Field(i)
+
+		if field.Kind() == reflect.Struct {
+			getTags(field, tags, callback) // recurse
+			continue
+		}
+
+		for _, tag := range tags {
+			tagValue := data.Type().Field(i).Tag.Get(tag)
+
+			if tagValue != "" {
+				callback(tagValue, field)
+			}
+		}
+	}
+
+}
+
+type handleTagCallback func(s string, field reflect.Value)
+
+func runOnTags(data any, tags []string, callback handleTagCallback) error {
+
+	t := reflect.TypeOf(data)
+
+	if t.Kind() == reflect.Struct {
+		getTags(reflect.ValueOf(data), tags, callback)
+		return nil
+	}
+
+	if t.Kind() == reflect.Ptr {
+		g := reflect.ValueOf(data).Elem()
+		t := g.Type()
+
+		if t.Kind() == reflect.Struct {
+			getTags(g, tags, callback)
+			return nil
+		}
+	}
+
+	return newUnsupportedReflectKindError(t)
+
+}
diff --git a/tags_test.go b/tags_test.go
new file mode 100644
index 0000000..256fd77
--- /dev/null
+++ b/tags_test.go
@@ -0,0 +1,81 @@
+package configuration
+
+import (
+	"reflect"
+	"strconv"
+	"strings"
+	"testing"
+)
+
+type T2 struct {
+	AA string `default:"Hello"`
+}
+
+type T1 struct {
+	A int    `yaml:"A" json:"A" toml:"A" default:"13" properties:"A"`
+	B string `yaml:"B" json:"B" toml:"B" properties:"B"`
+	C string `yaml:"B" default:"Hello" json:"B" toml:"B" properties:"B"`
+	D bool   `yaml:"B" json:"B" toml:"B" properties:"B" default:"true"`
+	E T2     `yaml:"E" json:"E" toml:"E" properties:"E"`
+}
+
+func TestRunOnTags(t *testing.T) {
+	tt := T1{}
+
+	values := []string{}
+
+	err := runOnTags(tt, []string{"default"}, func(s string, field reflect.Value) {
+		values = append(values, s)
+	})
+
+	if strings.Join(values, ",") != "13,Hello,true,Hello" {
+		t.Error("Expected 13,Hello,true,Hello got ", strings.Join(values, ","))
+	}
+
+	if err != nil {
+		t.Error(err)
+	}
+}
+
+func TestRunOnTagsAndSet(t *testing.T) {
+
+	tt := T1{}
+
+	err := runOnTags(&tt, []string{"default"}, func(s string, field reflect.Value) {
+
+		if field.CanSet() {
+			switch field.Kind() {
+			case reflect.String:
+				field.SetString(s)
+			case reflect.Int:
+				intVar, err := strconv.Atoi(s)
+				if err != nil {
+					panic(err)
+				}
+				field.SetInt(int64(intVar))
+			case reflect.Bool:
+				field.SetBool(s == "true")
+			default:
+				t.Errorf("Unsupported type %s", field.Kind())
+			}
+
+		}
+	})
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	if tt.E.AA != "Hello" {
+		t.Error("Expected Hello, got ", tt.E.AA)
+	}
+
+	if tt.A != 13 {
+		t.Error("Expected 13, got ", tt.A)
+	}
+
+	if tt.D != true {
+		t.Error("Expected true, got ", tt.D)
+	}
+
+}
diff --git a/watch.go b/watch.go
new file mode 100644
index 0000000..3556ff5
--- /dev/null
+++ b/watch.go
@@ -0,0 +1,122 @@
+package configuration
+
+import (
+	"github.com/fsnotify/fsnotify"
+)
+
+func (s *setting[C]) initWatch() *setting[C] {
+
+	var err error
+
+	if s.fileWatch.watcher != nil {
+		s.errors = append(s.errors, WatchAlreadyInitializedError)
+		return s
+	}
+
+	s.fileWatch.watcher, err = fsnotify.NewWatcher()
+	if err != nil {
+		s.errors = append(s.errors, err)
+		return s
+	}
+
+	return s
+
+}
+
+func (s *setting[C]) StopWatching() *setting[C] {
+
+	s.fileWatch.Lock()
+	defer s.fileWatch.Unlock()
+
+	if s.fileWatch.watcher == nil {
+		s.errors = append(s.errors, WatchNotInitializedError)
+		return s
+	}
+
+	if !s.fileWatch.onWatch {
+		s.errors = append(s.errors, WatchNotRunningError)
+		return s
+	}
+
+	s.fileWatch.cancelWatch <- true
+
+	return s
+}
+
+// Watch the given file for changes
+func (s *setting[C]) Watch() *setting[C] {
+
+	s.fileWatch.Lock()
+	defer s.fileWatch.Unlock()
+
+	if s.fileWatch.watcher == nil {
+		s.initWatch()
+	}
+
+	if s.fileWatch.watchList == nil {
+		s.errors = append(s.errors, WatchListNotInitializedError)
+		return s
+	}
+
+	if s.fileWatch.onWatch == true {
+		s.errors = append(s.errors, WatchAlreadyRunningError)
+		return s
+	}
+
+	s.fileWatch.onWatch = true
+	s.fileWatch.cancelWatch = make(chan bool)
+
+	// remove all files from the watch list
+	for _, file := range s.fileWatch.watcher.WatchList() {
+		s.fileWatch.watcher.Remove(file)
+	}
+
+	// add all files to the watch list
+	for file := range s.fileWatch.watchList {
+		err := s.fileWatch.watcher.Add(file)
+		if err != nil {
+			s.errors = append(s.errors, err)
+		}
+	}
+
+	if len(s.fileWatch.watcher.WatchList()) == 0 {
+		s.errors = append(s.errors, NoFilesToWatchError)
+		s.Unlock()
+		return s
+	}
+
+	go func() {
+	finished:
+
+		for {
+			select {
+			case event, ok := <-s.fileWatch.watcher.Events:
+				if !ok {
+					return
+				}
+
+				if event.Op&fsnotify.Write == fsnotify.Write {
+					s.Import()
+				} else if event.Op&fsnotify.Remove == fsnotify.Remove {
+					s.Import()
+				} else if event.Op&fsnotify.Rename == fsnotify.Rename {
+					s.Import()
+				}
+
+			case err, ok := <-s.fileWatch.watcher.Errors:
+				if !ok {
+					return
+				}
+				s.errors = append(s.errors, err)
+
+			case <-s.fileWatch.cancelWatch:
+				break finished
+			}
+		}
+
+		s.fileWatch.onWatch = false
+	}()
+
+	return s
+
+}
diff --git a/watch_test.go b/watch_test.go
new file mode 100644
index 0000000..11b0919
--- /dev/null
+++ b/watch_test.go
@@ -0,0 +1,143 @@
+package configuration
+
+import (
+	"github.com/magiconair/properties/assert"
+	"os"
+	"testing"
+	"time"
+)
+
+func TestWatch(t *testing.T) {
+
+	f, err := os.CreateTemp("", "watch_test")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	defer os.Remove(f.Name())
+
+	config := struct {
+		Host string `yaml:"Host"`
+	}{
+		Host: "localhost",
+	}
+
+	c := New(config)
+	c.SetMnemonic("my-app")
+	assert.Equal(t, c.Config().Host, "localhost")
+
+	c.AddFile(f.Name(), Yaml)
+	c.Import()
+
+	signal := make(chan bool)
+
+	c.OnChange(func(event ChangeEvent) {
+		assert.Equal(t, event.Changlog[0].From, "localhost")
+		assert.Equal(t, event.Changlog[0].To, "example.org")
+		signal <- true
+	})
+
+	c.Watch()
+
+	_, err = f.WriteString("Host: example.org")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	select {
+	case <-signal:
+		assert.Equal(t, c.Config().Host, "example.org")
+	case <-time.After(time.Second):
+		t.Fatalf("Timeout")
+
+	}
+
+}
+
+func TestSettingStopWatching(t *testing.T) {
+
+	f, err := os.CreateTemp("", "watch_test")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	defer os.Remove(f.Name())
+
+	config := struct {
+		Host string `yaml:"Host"`
+	}{
+		Host: "localhost",
+	}
+
+	c := New(config)
+	c.SetMnemonic("my-app")
+	assert.Equal(t, c.Config().Host, "localhost")
+
+	c.AddFile(f.Name(), Yaml)
+	c.Import().ResetErrors() // Import error is not relevant here
+
+	c.Watch()
+	c.StopWatching()
+
+	if c.HasError() {
+		t.Error(c.Errors())
+	}
+
+}
+
+func TestSettingStopWatchingNotOnWatch(t *testing.T) {
+
+	config := struct {
+		Host string `yaml:"Host"`
+	}{
+		Host: "localhost",
+	}
+
+	c := New(config)
+	c.StopWatching()
+
+	if !c.HasError() {
+		t.Error("Expected to have an error")
+	}
+
+}
+
+func TestSettingStopWatchingTwice(t *testing.T) {
+
+	f, err := os.CreateTemp("", "watch_test")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	defer os.Remove(f.Name())
+
+	config := struct {
+		Host string `yaml:"Host"`
+	}{
+		Host: "localhost",
+	}
+
+	f.WriteString("Host: example.org")
+
+	c := New(config)
+	c.AddFile(f.Name(), Yaml)
+	c.Import()
+	c.Watch()
+
+	c.StopWatching()
+	c.StopWatching()
+
+	e := c.Errors()
+	if len(e) != 1 {
+		t.Error("Expected to have an error")
+	}
+
+	if e[0] != WatchNotRunningError {
+		t.Error("Expected to have an error")
+	}
+
+}
-- 
GitLab