path.go 4.94 KB
Newer Older
1 2 3
package iface

import (
4 5 6
	"strings"

	cid "github.com/ipfs/go-cid"
Łukasz Magiera's avatar
Łukasz Magiera committed
7
	ipfspath "github.com/ipfs/go-path"
8 9
)

10 11
//TODO: merge with ipfspath so we don't depend on it

12 13
// Path is a generic wrapper for paths used in the API. A path can be resolved
// to a CID using one of Resolve functions in the API.
Łukasz Magiera's avatar
Łukasz Magiera committed
14 15 16 17 18 19 20
//
// Paths must be prefixed with a valid prefix:
//
// * /ipfs - Immutable unixfs path (files)
// * /ipld - Immutable ipld path (data)
// * /ipns - Mutable names. Usually resolves to one of the immutable paths
//TODO: /local (MFS)
21 22 23
type Path interface {
	// String returns the path as a string.
	String() string
24

25 26 27
	// Namespace returns the first component of the path.
	//
	// For example path "/ipfs/QmHash", calling Namespace() will return "ipfs"
28 29 30
	//
	// Calling this method on invalid paths (IsValid() != nil) will result in
	// empty string
31
	Namespace() string
Łukasz Magiera's avatar
Łukasz Magiera committed
32 33 34 35 36 37

	// Mutable returns false if the data pointed to by this path in guaranteed
	// to not change.
	//
	// Note that resolved mutable path can be immutable.
	Mutable() bool
38 39 40 41

	// IsValid checks if this path is a valid ipfs Path, returning nil iff it is
	// valid
	IsValid() error
42 43
}

44 45
// ResolvedPath is a path which was resolved to the last resolvable node.
// ResolvedPaths are guaranteed to return nil from `IsValid`
46
type ResolvedPath interface {
47 48
	// Cid returns the CID of the node referenced by the path. Remainder of the
	// path is guaranteed to be within the node.
49
	//
50 51 52 53 54 55 56 57
	// Examples:
	// If you have 3 linked objects: QmRoot -> A -> B:
	//
	// cidB := {"foo": {"bar": 42 }}
	// cidA := {"B": {"/": cidB }}
	// cidRoot := {"A": {"/": cidA }}
	//
	// And resolve paths:
58
	//
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
	// * "/ipfs/${cidRoot}"
	//   * Calling Cid() will return `cidRoot`
	//   * Calling Root() will return `cidRoot`
	//   * Calling Remainder() will return ``
	//
	// * "/ipfs/${cidRoot}/A"
	//   * Calling Cid() will return `cidA`
	//   * Calling Root() will return `cidRoot`
	//   * Calling Remainder() will return ``
	//
	// * "/ipfs/${cidRoot}/A/B/foo"
	//   * Calling Cid() will return `cidB`
	//   * Calling Root() will return `cidRoot`
	//   * Calling Remainder() will return `foo`
	//
	// * "/ipfs/${cidRoot}/A/B/foo/bar"
	//   * Calling Cid() will return `cidB`
	//   * Calling Root() will return `cidRoot`
	//   * Calling Remainder() will return `foo/bar`
78
	Cid() cid.Cid
79

80 81 82 83 84
	// Root returns the CID of the root object of the path
	//
	// Example:
	// If you have 3 linked objects: QmRoot -> A -> B, and resolve path
	// "/ipfs/QmRoot/A/B", the Root method will return the CID of object QmRoot
85 86
	//
	// For more examples see the documentation of Cid() method
87
	Root() cid.Cid
88

Łukasz Magiera's avatar
Łukasz Magiera committed
89
	// Remainder returns unresolved part of the path
90 91 92 93 94
	//
	// Example:
	// If you have 2 linked objects: QmRoot -> A, where A is a CBOR node
	// containing the following data:
	//
95
	// {"foo": {"bar": 42 }}
96 97
	//
	// When resolving "/ipld/QmRoot/A/foo/bar", Remainder will return "foo/bar"
98 99
	//
	// For more examples see the documentation of Cid() method
Łukasz Magiera's avatar
Łukasz Magiera committed
100
	Remainder() string
101 102

	Path
103
}
104 105 106

// path implements coreiface.Path
type path struct {
107
	path string
108 109 110 111 112
}

// resolvedPath implements coreiface.resolvedPath
type resolvedPath struct {
	path
113 114
	cid       cid.Cid
	root      cid.Cid
115 116 117
	remainder string
}

118 119
// Join appends provided segments to the base path
func Join(base Path, a ...string) Path {
120 121
	s := strings.Join(append([]string{base.String()}, a...), "/")
	return &path{path: s}
122 123
}

124
// IpfsPath creates new /ipfs path from the provided CID
125
func IpfsPath(c cid.Cid) ResolvedPath {
126
	return &resolvedPath{
127
		path:      path{"/ipfs/" + c.String()},
128 129 130 131 132 133 134
		cid:       c,
		root:      c,
		remainder: "",
	}
}

// IpldPath creates new /ipld path from the provided CID
135
func IpldPath(c cid.Cid) ResolvedPath {
136
	return &resolvedPath{
137
		path:      path{"/ipld/" + c.String()},
138 139 140 141 142 143 144
		cid:       c,
		root:      c,
		remainder: "",
	}
}

// ParsePath parses string path to a Path
145 146 147
func ParsePath(p string) Path {
	if pp, err := ipfspath.ParsePath(p); err == nil {
		p = pp.String()
148 149
	}

150
	return &path{path: p}
151 152 153 154 155
}

// NewResolvedPath creates new ResolvedPath. This function performs no checks
// and is intended to be used by resolver implementations. Incorrect inputs may
// cause panics. Handle with care.
156
func NewResolvedPath(ipath ipfspath.Path, c cid.Cid, root cid.Cid, remainder string) ResolvedPath {
157
	return &resolvedPath{
158
		path:      path{ipath.String()},
159 160 161 162 163 164 165
		cid:       c,
		root:      root,
		remainder: remainder,
	}
}

func (p *path) String() string {
166
	return p.path
167 168 169
}

func (p *path) Namespace() string {
170 171 172 173 174 175
	ip, err := ipfspath.ParsePath(p.path)
	if err != nil {
		return ""
	}

	if len(ip.Segments()) < 1 {
176 177
		panic("path without namespace") //this shouldn't happen under any scenario
	}
178
	return ip.Segments()[0]
179 180 181 182 183 184 185
}

func (p *path) Mutable() bool {
	//TODO: MFS: check for /local
	return p.Namespace() == "ipns"
}

186 187 188 189 190
func (p *path) IsValid() error {
	_, err := ipfspath.ParsePath(p.path)
	return err
}

191
func (p *resolvedPath) Cid() cid.Cid {
192 193 194
	return p.cid
}

195
func (p *resolvedPath) Root() cid.Cid {
196 197 198 199 200 201
	return p.root
}

func (p *resolvedPath) Remainder() string {
	return p.remainder
}