Skip to content
Snippets Groups Projects
Verified Commit f82e5d14 authored by Volker Schukai's avatar Volker Schukai :alien:
Browse files

fix: running tests failed

parent 244fc7cc
No related branches found
No related tags found
No related merge requests found
Pipeline #29816 passed
Showing
with 0 additions and 2059 deletions
Copyright © 2012 The Go Authors. All rights reserved.
Copyright © fsnotify 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.
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.
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.
The MIT License (MIT)
go-toml v2
Copyright (c) 2021 - 2023 Thomas Pelletier
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.
# 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
# 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).
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.
install:
go install -v ${LDFLAGS}
deps:
go get github.com/stretchr/testify
test:
@go test -v -cover ./...
cover:
@go test -coverprofile cover.out
# 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).
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
}
/* 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
}
/* 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/v5"
)
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
}
/* 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
}
/* 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
}
/* 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
}
/* 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
}
/* 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)
}
/* 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/v5"
)
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
}
/* 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()
}
/* 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
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment