Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Patch spec #187

Merged
merged 8 commits into from
Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
325 changes: 325 additions & 0 deletions specs/patch/fixtures/fixtures-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
Patch Fixtures 1
================

This file contains a bunch of fixtures for the Patch system.
These are suitable both as mechanical test fixtures, and as examples for humans.

### What's covered?

All the fixtures in this file are operating on single blocks --
the data can still be a tree (maps, lists, etc), but contains no links.
(Other fixtures cover data that spans multiple blocks by use of links.)

### What's the fixture format?

This is the [testmark format](https://github.com/warpfork/go-testmark);
it can be consumed programmatically.
(Note that you probably _can't_ see these labels as this page is rendered on the website;
you'll have to find the markdown [source](/specs/about/#source).)

### This doesn't have to be JSON!

We've used JSON (representing the DAG-JSON format) here for the initial data, as well as the patch instructions and the results.
However, this is a totally stylistic choice. We're just using JSON here because it's convenient and it's human-readable.
You can equally well express all of these objects in DAG-CBOR, or other [codecs](/docs/codecs/); it makes no difference to IPLD.
The patch instruction and the subject data don't even have to be in the same codec, either.


Fixtures
--------

### Adding an entry to a map

Given an initial document:

[testmark]:# (adding-a-map-entry/initial)
```json
{"foo": "bar"}
```

and a Patch OperationSequence:

[testmark]:# (adding-a-map-entry/patch)
```json
[
{ "op": "add", "path": "/baz", "value": "qux" }
]
```

The following document should result:

[testmark]:# (adding-a-map-entry/result)
```json
{
"foo": "bar",
"baz": "qux"
}
```

Note that if order is sensitive, the additions are considered to go at the end of map.


### Inserting into a list

Given an initial document:

[testmark]:# (inserting-into-a-list/initial)
```json
["bar", "baz"]
```

and a Patch OperationSequence:

[testmark]:# (inserting-into-a-list/patch)
```json
[
{ "op": "add", "path": "/1", "value": "qux" }
]
```

The following document should result:

[testmark]:# (inserting-into-a-list/result)
```json
[
"bar",
"qux",
"baz"
]
```


### Removing an entry from a map

Given an initial document:

[testmark]:# (removing-map-entry/initial)
```json
{
"baz": "qux",
"foo": "bar"
}
```

and a Patch OperationSequence:

[testmark]:# (removing-map-entry/patch)
```json
[
{ "op": "remove", "path": "/baz" }
]
```

The following document should result:

[testmark]:# (removing-map-entry/result)
```json
{
"foo": "bar"
}
```


### Replacing an entry from a map

Given an initial document:

[testmark]:# (replacing-map-entry/initial)
```json
{
"baz": "qux",
"foo": "bar"
}
```

and a Patch OperationSequence:

[testmark]:# (replacing-map-entry/patch)
```json
[
{ "op": "replace", "path": "/baz", "value": "boo" }
]
```

The following document should result:

[testmark]:# (replacing-map-entry/result)
```json
{
"baz": "boo",
"foo": "bar"
}
```


### Copying a value

Given an initial document:

[testmark]:# (copy/initial)
```json
{
"foo": {
"bar": "baz",
"waldo": "fred"
},
"qux": {
"corge": "grault"
}
}
```

and a Patch OperationSequence:

[testmark]:# (copy/patch)
```json
[
{ "op": "copy", "from": "/foo/waldo", "path": "/qux/thud" }
]
```

The following document should result:

[testmark]:# (copy/result)
```json
{
"foo": {
"bar": "baz",
"waldo": "fred"
},
"qux": {
"corge": "grault",
"thud": "fred"
}
}
```


### Moving a value

Given an initial document:

[testmark]:# (move/initial)
```json
{
"foo": {
"bar": "baz",
"waldo": "fred"
},
"qux": {
"corge": "grault"
}
}
```

and a Patch OperationSequence:

[testmark]:# (move/patch)
```json
[
{ "op": "move", "from": "/foo/waldo", "path": "/qux/thud" }
]
```

The following document should result:

[testmark]:# (move/result)
```json
{
"foo": {
"bar": "baz"
},
"qux": {
"corge": "grault",
"thud": "fred"
}
}
```


### Testing and conditional modification

Given an initial document:

[testmark]:# (test-and-conditional-modify/initial)
```json
{
"baz": "qux",
"foo": [
"a",
2,
"c"
]
}
```

and a Patch OperationSequence:

[testmark]:# (test-and-conditional-modify/patch)
```json
[
{ "op": "test", "path": "/baz", "value": "qux" },
{ "op": "test", "path": "/foo/1", "value": 2 },
{ "op": "add", "path": "/bar", "value": "zar" }
]
```

(Note that this contains both test operations as well as a subsequent add operation!
The add operation should only apply if the test operations yield true.)

The following document should result:

[testmark]:# (test-and-conditional-modify/result)
```json
{
"baz": "qux",
"foo": [
"a",
2,
"c"
],
"bar": "zar"
}
```


### Testing and conditional modification - Failing case

Given an initial document:

[testmark]:# (test-and-conditional-fail/initial)
```json
{
"baz": "qux",
"foo": [
"a",
2,
"c"
]
}
```

and a Patch OperationSequence:

[testmark]:# (test-and-conditional-fail/patch)
```json
[
{ "op": "test", "path": "/baz", "value": "qux" },
{ "op": "test", "path": "/foo/1", "value": 3 },
{ "op": "add", "path": "/bar", "value": "zar" }
]
```

The operation should abort and the document should remain as:

[testmark]:# (test-and-conditional-fail/result)
```json
{
"baz": "qux",
"foo": [
"a",
2,
"c"
]
}
```
11 changes: 11 additions & 0 deletions specs/patch/fixtures/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
title: "IPLD Patch Test Fixtures"
eleventyNavigation:
synopsys: "Fixtures to be used to help determine specification compliance for implementations"
---

IPLD Patch Test Fixtures
==========

{% import "listing.njk" as listing %}
{{ listing.childrenTableWithSynopsys(collections.all, page.url) }}
32 changes: 32 additions & 0 deletions specs/patch/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
title: "IPLD Patch Specs"
navTitle: "Patch"
eleventyNavigation:
order: 70
synopsys: "A declarative Patch system (based on JSON Patch) exists for working with IPLD."
---

IPLD Patch
==========

IPLD Patch is a system for declaratively specifying patches to a document, which can then be applied to produce a new, modified document.

IPLD Patch is roughly based on the [JSON Patch (RFC 6902)](https://datatracker.ietf.org/doc/html/rfc6902/) specification -- it should feel very familiar.

For the full details, see the [Fixtures](./fixtures/) -- the test fixture material is both for implementers, but also for end users, and provides rich examples and explanatory notes.

In brief highlights:

- There are six operations -- add, replace, remove, copy, move, test.
- (This is the same as RFC 6902.)
- Operations are supplied in a list, and applied linearly. Any operation erroring results in the entire patch not being applied.
- (This is the same as RFC 6902.)
- Operations do not create parent paths automatically.
- (This is the same as RFC 6902.)
- Because this is IPLD, both the subject data, as well as the patch instructions themselves, can be communicated in any codec.
(e.g. you can easily serialize a patch in [CBOR](/docs/codecs/known/dag-cbor), and apply it to a document encoded in [dag-pb](/docs/codecs/known/dag-pb), and so on).
- Furthermore: because the Patch system is specified over the IPLD [Data Model](/docs/data-model/), you can expect it to work over other layers as well --
for example, Patches can be applied over data processed with [Schemas](/docs/schemas/) or [ADLs](/docs/advanced-data-layouts/).
- The Patch system in IPLD is expected to maintain order whenever possible. Wherever this is ambiguous (e.g. operations adding data to a map under a key that didn't previously exist), operations are defined as appending to the end of the relevant node.

See the [Fixtures](./fixtures/) for more details.