Skip to content

Commit 575054d

Browse files
committed
Make a multicodec.Registry type available.
The hope is that this might be helpful if you want to build a multicodec registry other than the global one, but are still buying into the idea of the registry being keyed by indicator numbers. (I'm... not actually sure how useful this is, because I'd think if you're building something other than the default, there's also a good chance you'd want *more* features than a primitive numerical mapping, which are probably going to be related to whatever your application logic in the area is, and therefore not possible for this library to usefully anticipate. But, I dunno. I'm doing this because people are proposing attaching more features to the global, and I'm not comfortable with it, and I'm hoping this will provide a pressure release valve.) The current interfaces are functionally unchanged. The multicodec.Registry type can now be used when constructing a cidlink.LinkSystem. (This saves you from having to write the glue functions to unbox cidlink and then do the LookupEncoder and LookupDecode calls.) For where this relates to LinkSystem, I considered a mechanism like: `func (r *Registry) InstallOn(lsys *ipld.LinkSystem)` ... and probably would've found that a bit cleaner. However, it doesn't jive with the way we've isolated the CID types into a package with a LinkSystem just for them (sigh; that really is the gift of complexity that just keeps giving); you can see how the EncoderChooser and DecoderChooser funcs need a tiny bit of type assertions in order to figure out how to extract the multicodec indicator from the Link/LinkPrototype types? That bit is a bit that we still want to keep cordoned off the rest of the import tree. DefaultRegistry is also now an exported variable, in addition to the functions which already worked with the global data. I probably would've preferred to keep the DefaultRegistry variable unexported, because I can't imagine any good coming of touching it, but the relationship to LinkSystem detailed in the above paragraph requires some access to it.
1 parent aff91f7 commit 575054d

File tree

4 files changed

+186
-112
lines changed

4 files changed

+186
-112
lines changed

linking/cid/linksystem.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,28 @@ import (
1010
"github.com/ipld/go-ipld-prime/multicodec"
1111
)
1212

13+
// DefaultLinkSystem returns an ipld.LinkSystem which uses cidlink.Link for ipld.Link.
14+
// During selection of encoders, decoders, and hashers, it examines the multicodec indicator numbers and multihash indicator numbers from the CID,
15+
// and uses the default global multicodec registry (see the go-ipld-prime/multicodec package) for resolving codec implementations,
16+
// and the default global multihash registry (see the go-multihash/core package) for resolving multihash implementations.
17+
//
18+
// No storage functions are present in the returned LinkSystem.
19+
// The caller can assign those themselves as desired.
1320
func DefaultLinkSystem() ipld.LinkSystem {
21+
return LinkSystemUsingMulticodecRegistry(multicodec.DefaultRegistry)
22+
}
23+
24+
// LinkSystemUsingMulticodecRegistry is similar to DefaultLinkSystem, but accepts a multicodec.Registry as a parameter.
25+
//
26+
// This can help create a LinkSystem which uses different multicodec implementations than the global registry.
27+
// (Sometimes this can be desired if you want some parts of a program to support a more limited suite of codecs than other parts of the program,
28+
// or needed to use a different multicodec registry than the global one for synchronization purposes, or etc.)
29+
func LinkSystemUsingMulticodecRegistry(mcReg multicodec.Registry) ipld.LinkSystem {
1430
return ipld.LinkSystem{
1531
EncoderChooser: func(lp ipld.LinkPrototype) (ipld.Encoder, error) {
1632
switch lp2 := lp.(type) {
1733
case LinkPrototype:
18-
fn, err := multicodec.LookupEncoder(lp2.GetCodec())
34+
fn, err := mcReg.LookupEncoder(lp2.GetCodec())
1935
if err != nil {
2036
return nil, err
2137
}
@@ -28,7 +44,7 @@ func DefaultLinkSystem() ipld.LinkSystem {
2844
lp := lnk.Prototype()
2945
switch lp2 := lp.(type) {
3046
case LinkPrototype:
31-
fn, err := multicodec.LookupDecoder(lp2.GetCodec())
47+
fn, err := mcReg.LookupDecoder(lp2.GetCodec())
3248
if err != nil {
3349
return nil, err
3450
}

multicodec/defaultRegistry.go

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package multicodec
2+
3+
import (
4+
"github.com/ipld/go-ipld-prime"
5+
)
6+
7+
// DefaultRegistry is a multicodec.Registry instance which is global to the program,
8+
// and is used as a default set of codecs.
9+
//
10+
// Some systems (for example, cidlink.DefaultLinkSystem) will use this default registry,
11+
// which makes it easier to write programs that pass fewer explicit arguments around.
12+
// However, these are *only* for default behaviors;
13+
// variations of functions which allow explicit non-default options should always be available
14+
// (for example, cidlink also has other LinkSystem constructor functions which accept an explicit multicodec.Registry,
15+
// and the LookupEncoder and LookupDecoder functions in any LinkSystem can be replaced).
16+
//
17+
// Since this registry is global, mind that there are also some necessary tradeoffs and limitations:
18+
// It can be difficult to control exactly what's present in this global registry
19+
// (Libraries may register codecs in this registry as a side-effect of importing, so even transitive dependencies can affect its content!).
20+
// Also, this registry is only considered safe to modify at package init time.
21+
// If these are concerns for your program, you can create your own multicodec.Registry values,
22+
// and eschew using the global default.
23+
var DefaultRegistry = Registry{}
24+
25+
// RegisterEncoder updates the global DefaultRegistry to map a multicodec indicator number to the given ipld.Encoder function.
26+
// The encoder functions registered can be subsequently looked up using LookupEncoder.
27+
// It is a shortcut to the RegisterEncoder method on the global DefaultRegistry.
28+
//
29+
// Packages which implement an IPLD codec and have a multicodec number associated with them
30+
// are encouraged to register themselves at package init time using this function.
31+
// (Doing this at package init time ensures the default global registry is populated
32+
// without causing race conditions for application code.)
33+
//
34+
// No effort is made to detect conflicting registrations in this map.
35+
// If your dependency tree is such that this becomes a problem,
36+
// there are two ways to address this:
37+
// If RegisterEncoder is called with the same indicator code more than once, the last call wins.
38+
// In practice, this means that if an application has a strong opinion about what implementation for a certain codec,
39+
// then this can be done by making a Register call with that effect at init time in the application's main package.
40+
// This should have the desired effect because the root of the import tree has its init time effect last.
41+
// Alternatively, one can just avoid use of this registry entirely:
42+
// do this by making a LinkSystem that uses a custom EncoderChooser function.
43+
func RegisterEncoder(indicator uint64, encodeFunc ipld.Encoder) {
44+
DefaultRegistry.RegisterEncoder(indicator, encodeFunc)
45+
}
46+
47+
// LookupEncoder yields an ipld.Encoder function matching a multicodec indicator code number.
48+
// It is a shortcut to the LookupEncoder method on the global DefaultRegistry.
49+
//
50+
// To be available from this lookup function, an encoder must have been registered
51+
// for this indicator number by an earlier call to the RegisterEncoder function.
52+
func LookupEncoder(indicator uint64) (ipld.Encoder, error) {
53+
return DefaultRegistry.LookupEncoder(indicator)
54+
}
55+
56+
// RegisterDecoder updates the global DefaultRegistry a map a multicodec indicator number to the given ipld.Decoder function.
57+
// The decoder functions registered can be subsequently looked up using LookupDecoder.
58+
// It is a shortcut to the RegisterDecoder method on the global DefaultRegistry.
59+
//
60+
// Packages which implement an IPLD codec and have a multicodec number associated with them
61+
// are encouraged to register themselves in this map at package init time.
62+
// (Doing this at package init time ensures the default global registry is populated
63+
// without causing race conditions for application code.)
64+
//
65+
// No effort is made to detect conflicting registrations in this map.
66+
// If your dependency tree is such that this becomes a problem,
67+
// there are two ways to address this:
68+
// If RegisterDecoder is called with the same indicator code more than once, the last call wins.
69+
// In practice, this means that if an application has a strong opinion about what implementation for a certain codec,
70+
// then this can be done by making a Register call with that effect at init time in the application's main package.
71+
// This should have the desired effect because the root of the import tree has its init time effect last.
72+
// Alternatively, one can just avoid use of this registry entirely:
73+
// do this by making a LinkSystem that uses a custom DecoderChooser function.
74+
func RegisterDecoder(indicator uint64, decodeFunc ipld.Decoder) {
75+
DefaultRegistry.RegisterDecoder(indicator, decodeFunc)
76+
}
77+
78+
// LookupDecoder yields an ipld.Decoder function matching a multicodec indicator code number.
79+
// It is a shortcut to the LookupDecoder method on the global DefaultRegistry.
80+
//
81+
// To be available from this lookup function, an decoder must have been registered
82+
// for this indicator number by an earlier call to the RegisterDecoder function.
83+
func LookupDecoder(indicator uint64) (ipld.Decoder, error) {
84+
return DefaultRegistry.LookupDecoder(indicator)
85+
}

multicodec/multicodec.go

-110
This file was deleted.

multicodec/registry.go

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package multicodec
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/ipld/go-ipld-prime"
7+
)
8+
9+
// Registry is a structure for storing mappings of multicodec indicator numbers to ipld.Encoder and ipld.Decoder functions.
10+
//
11+
// The most typical usage of this structure is in combination with an ipld.LinkSystem.
12+
// For example, a linksystem using CIDs and a custom multicodec registry can be constructed
13+
// using cidlink.LinkSystemUsingMulticodecRegistry.
14+
//
15+
// Registry includes no mutexing. If using Registry in a concurrent context, you must handle synchronization yourself.
16+
// (Typically, it is recommended to do initialization earlier in a program, before fanning out goroutines;
17+
// this avoids the need for mutexing overhead.)
18+
//
19+
// go-ipld-prime also has a default registry, which has the same methods as this structure, but are at package scope.
20+
// Some systems, like cidlink.DefaultLinkSystem, will use this default registry.
21+
// However, this default registry is global to the entire program.
22+
// This Registry type is for helping if you wish to make your own registry which does not share that global state.
23+
//
24+
// Multicodec indicator numbers are specified in
25+
// https://github.com/multiformats/multicodec/blob/master/table.csv .
26+
// You should not use indicator numbers which are not specified in that table
27+
// (however, there is nothing in this implementation that will attempt to stop you, either; please behave).
28+
type Registry struct {
29+
encoders map[uint64]ipld.Encoder
30+
decoders map[uint64]ipld.Decoder
31+
}
32+
33+
func (r *Registry) ensureInit() {
34+
if r.encoders != nil {
35+
return
36+
}
37+
r.encoders = make(map[uint64]ipld.Encoder)
38+
r.decoders = make(map[uint64]ipld.Decoder)
39+
}
40+
41+
// RegisterEncoder updates a simple map of multicodec indicator number to ipld.Encoder function.
42+
// The encoder functions registered can be subsequently looked up using LookupEncoder.
43+
func (r *Registry) RegisterEncoder(indicator uint64, encodeFunc ipld.Encoder) {
44+
r.ensureInit()
45+
if encodeFunc == nil {
46+
panic("not sensible to attempt to register a nil function")
47+
}
48+
r.encoders[indicator] = encodeFunc
49+
}
50+
51+
// LookupEncoder yields an ipld.Encoder function matching a multicodec indicator code number.
52+
//
53+
// To be available from this lookup function, an encoder must have been registered
54+
// for this indicator number by an earlier call to the RegisterEncoder function.
55+
func (r *Registry) LookupEncoder(indicator uint64) (ipld.Encoder, error) {
56+
encodeFunc, exists := r.encoders[indicator]
57+
if !exists {
58+
return nil, fmt.Errorf("no encoder registered for multicodec code %d (0x%x)", indicator, indicator)
59+
}
60+
return encodeFunc, nil
61+
}
62+
63+
// RegisterDecoder updates a simple map of multicodec indicator number to ipld.Decoder function.
64+
// The decoder functions registered can be subsequently looked up using LookupDecoder.
65+
func (r *Registry) RegisterDecoder(indicator uint64, decodeFunc ipld.Decoder) {
66+
r.ensureInit()
67+
if decodeFunc == nil {
68+
panic("not sensible to attempt to register a nil function")
69+
}
70+
r.decoders[indicator] = decodeFunc
71+
}
72+
73+
// LookupDecoder yields an ipld.Decoder function matching a multicodec indicator code number.
74+
//
75+
// To be available from this lookup function, an decoder must have been registered
76+
// for this indicator number by an earlier call to the RegisterDecoder function.
77+
func (r *Registry) LookupDecoder(indicator uint64) (ipld.Decoder, error) {
78+
decodeFunc, exists := r.decoders[indicator]
79+
if !exists {
80+
return nil, fmt.Errorf("no decoder registered for multicodec code %d (0x%x)", indicator, indicator)
81+
}
82+
return decodeFunc, nil
83+
}

0 commit comments

Comments
 (0)