From 12baa5ca94797750f4938ede8890b5af721cadd6 Mon Sep 17 00:00:00 2001
From: Steven Allen <steven@stebalien.com>
Date: Wed, 27 Jun 2018 20:35:59 -0700
Subject: [PATCH] add some helpers for creating new CIDs

Unlike Prefix, we can extend continue to extend this helper as the options grow.
---
 cid.go    |   4 ++
 format.go | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 148 insertions(+)
 create mode 100644 format.go

diff --git a/cid.go b/cid.go
index 99a26a6..a4cf84c 100644
--- a/cid.go
+++ b/cid.go
@@ -145,6 +145,8 @@ func NewCidV1(codecType uint64, mhash mh.Multihash) *Cid {
 }
 
 // NewPrefixV0 returns a CIDv0 prefix with the specified multihash type.
+//
+// Deprecated: Please use the Format constructors (e.g., NewFormat).
 func NewPrefixV0(mhType uint64) Prefix {
 	return Prefix{
 		MhType:   mhType,
@@ -156,6 +158,8 @@ func NewPrefixV0(mhType uint64) Prefix {
 
 // NewPrefixV1 returns a CIDv1 prefix with the specified codec and multihash
 // type.
+//
+// Deprecated: Please use the Format constructors (e.g., NewFormat).
 func NewPrefixV1(codecType uint64, mhType uint64) Prefix {
 	return Prefix{
 		MhType:   mhType,
diff --git a/format.go b/format.go
new file mode 100644
index 0000000..2cf9947
--- /dev/null
+++ b/format.go
@@ -0,0 +1,144 @@
+package cid
+
+import (
+	"fmt"
+
+	mh "github.com/multiformats/go-multihash"
+)
+
+// TODO: Move these to the correct go-ipld-* crates?
+
+// DagProtobufV0Format is the default CID construction for DagProtobuf (CID V0)
+// nodes.
+var DagProtobufV0Format = Format{
+	version:   0,
+	codec:     DagProtobuf,
+	mhLength:  -1,
+	mhType:    mh.SHA2_256,
+	inlineMax: -1,
+}
+
+// DagProtobufV1Format is the default CID construction for DagProtobuf (CID V1)
+// nodes.
+var DagProtobufV1Format = Format{
+	version:   1,
+	codec:     DagProtobuf,
+	mhLength:  -1,
+	mhType:    mh.SHA2_256,
+	inlineMax: -1,
+}
+
+// RawFormat is the default CID construction for Raw IPLD nodes.
+var RawFormat = Format{
+	version:   1,
+	codec:     Raw,
+	mhType:    mh.SHA2_256,
+	mhLength:  -1,
+	inlineMax: -1,
+}
+
+// DagCBORFormat is the default CID construction for CBOR IPLD nodes.
+var DagCBORFormat = Format{
+	version:   1,
+	codec:     DagCBOR,
+	mhType:    mh.SHA2_256,
+	mhLength:  -1,
+	inlineMax: -1,
+}
+
+// Format represents a CID format.
+type Format struct {
+	// CID spec
+	codec   uint64
+	version uint64
+
+	// multhash spec
+	mhType   uint64
+	mhLength int
+
+	// inline when the data is at most inlineMax in length
+	inlineMax int
+}
+
+// TODO: detect bad formats? Probably not worth it. We catch them on Sum anyways.
+
+// With extends the format with the given options, returning a new format.
+func (f Format) With(opts ...FormatOption) Format {
+	for _, opt := range opts {
+		opt(&f)
+	}
+	return f
+}
+
+// Sum constructs a CID for the given data. It *does not* check any properties
+// of the data.
+func (f *Format) Sum(data []byte) (*Cid, error) {
+	mhType := f.mhType
+	mhLen := f.mhLength
+	if len(data) <= f.inlineMax {
+		mhType = mh.ID
+		mhLen = -1
+	}
+
+	hash, err := mh.Sum(data, mhType, mhLen)
+	if err != nil {
+		return nil, err
+	}
+
+	switch f.version {
+	case 0:
+		if f.inlineMax != 0 {
+			return nil, fmt.Errorf("cannot inline with V0 CIDs")
+		}
+		if f.mhType != mh.SHA2_256 || (f.mhLength != -1 && f.mhLength != 256) {
+			return nil, fmt.Errorf("CIDv0 only supports 256bit SHA2_256 hashes")
+		}
+		return NewCidV0(hash), nil
+	case 1:
+		return NewCidV1(f.codec, hash), nil
+	default:
+		return nil, fmt.Errorf("invalid cid version")
+	}
+}
+
+// FormatOption is a format option.
+type FormatOption func(f *Format)
+
+// OptCodec configures the format to use the specified IPLD codec.
+func OptCodec(codec uint64) FormatOption {
+	return func(f *Format) {
+		f.codec = codec
+	}
+}
+
+// OptInline configures the format to auto-inline (using the identity hash
+// function) objects at most maxSize in length.
+//
+// Specify a value < 0 to disable auto-inlining.
+func OptInline(maxSize int) FormatOption {
+	return func(f *Format) {
+		f.inlineMax = maxSize
+	}
+}
+
+// TODO: Split length and hash?
+
+// OptHash sets the hash function to use in the format. Pass -1 as the length to
+// use the default.
+func OptHash(hash uint64, length int) FormatOption {
+	return func(f *Format) {
+		f.mhType = hash
+		f.mhLength = length
+	}
+}
+
+// TODO: Do we actually want this or should users always extend Format
+// That is, DagCBORFormat.With(OptHash(...))
+
+// NewFormat constructs a new CID format. It defaults to:
+//
+// * The DagCBOR format.
+// * The SHA256 hash function.
+func NewFormat(opts ...FormatOption) Format {
+	return DagCBORFormat.With(opts...)
+}