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

WIP: Changes needed for --cid-base option in go-ipfs. #8

Closed
wants to merge 10 commits into from
2 changes: 1 addition & 1 deletion .gx/lastpubver
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.2: QmbfKu17LbMWyGUxHEUns9Wf5Dkm8PT6be4uPhTkk4YvaV
0.1.4: QmckgkstbdXagMTQ4e1DW2SzxGcjjudbqEvA5H2Rb7uvAT
61 changes: 61 additions & 0 deletions apicid/apicid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package apicid
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

api sounds a bit weird here, I'd probably just move this into cidenc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may sound weird but that is the exact idea I want to capture. This package present a form of the Cid to use in the API struct. The only reason it is here is because this type might need to be used outside of go-ipfs.


import (
cid "github.com/ipfs/go-cid"
"github.com/ipfs/go-cidutil/cidenc"
mbase "github.com/multiformats/go-multibase"
)

// JSONBase is the base to use when Encoding into JSON.
var JSONBase mbase.Encoder = mbase.MustNewEncoder(mbase.Base58BTC)

// apicid.Hash is a type to respesnt a CID in the API which marshals
// as a string
type Hash struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just type Hash string?

str string
}

// FromCid creates an APICid from a Cid
func FromCid(c cid.Cid) Hash {
return Hash{c.Encode(JSONBase)}
}

// Cid converts an APICid to a CID
func (c Hash) Cid() (cid.Cid, error) {
return cid.Decode(c.str)
}

func (c Hash) String() string {
return c.Encode(cidenc.Default)
}

func (c Hash) Encode(enc cidenc.Interface) string {
if c.str == "" {
return ""
}
str, err := enc.Recode(c.str)
if err != nil {
return c.str
}
return str
}

func (c *Hash) UnmarshalText(b []byte) error {
c.str = string(b)
return nil
}

func (c Hash) MarshalText() ([]byte, error) {
return []byte(c.str), nil
}

// Cid is type to represent a normal CID in the API which marshals
// like a normal CID i.e. ({"/": <HASH>}) but may uses cidenc.Default
// for the String() to optionally upgrade a version 0 CID to version 1
type Cid struct {
cid.Cid
}

func (c Cid) String() string {
return cidenc.Default.Encode(c.Cid)
}
48 changes: 48 additions & 0 deletions apicid/apicid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package apicid

import (
"encoding/json"
"testing"

cid "github.com/ipfs/go-cid"
)

func TestJson(t *testing.T) {
cid, _ := cid.Decode("zb2rhak9iRgDiik36KQBRr2qiCJHdyBH7YxFmw7FTdM6zo31m")
hash := FromCid(cid)
data, err := json.Marshal(hash)
if err != nil {
t.Fatal(err)
}
if string(data) != `"zb2rhak9iRgDiik36KQBRr2qiCJHdyBH7YxFmw7FTdM6zo31m"` {
t.Fatalf("json string incorrect: %s\n", data)
}
var hash2 Hash
err = json.Unmarshal(data, &hash2)
if err != nil {
t.Fatal(err)
}
if hash != hash2 {
t.Fatal("round trip failed")
}
}

func TestJsonMap(t *testing.T) {
cid1, _ := cid.Decode("zb2rhak9iRgDiik36KQBRr2qiCJHdyBH7YxFmw7FTdM6zo31m")
cid2, _ := cid.Decode("QmRJggJREPCt7waGQKMXymrXRvrvsSiiPjgFbLK9isuM8K")
hash1 := FromCid(cid1)
hash2 := FromCid(cid2)
m := map[Hash]string{hash1: "a value", hash2: "something else"}
data, err := json.Marshal(m)
if err != nil {
t.Fatal(err)
}
m2 := map[Hash]string{}
err = json.Unmarshal(data, &m2)
if err != nil {
t.Fatal(err)
}
if len(m2) != 2 || m[hash1] != m2[hash1] || m[hash2] != m2[hash2] {
t.Fatal("round trip failed")
}
}
94 changes: 67 additions & 27 deletions cid-fmt/main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package main

import (
"bufio"
"fmt"
"io"
"os"
"strings"

Expand All @@ -12,7 +14,9 @@ import (
)

func usage() {
fmt.Fprintf(os.Stderr, "usage: %s [-b multibase-code] [-v cid-version] <fmt-str> <cid> ...\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "usage: %s [-b multibase-code] [-v cid-version] [--filter] <fmt-str> <cid> ...\n", os.Args[0])
fmt.Fprintf(os.Stderr, "--filter will read from stdin and convert anything that looks like a <cid>\n")
fmt.Fprintf(os.Stderr, " -- including any non-cids that are valid Multihashes).\n")
fmt.Fprintf(os.Stderr, "<fmt-str> is either 'prefix' or a printf style format string:\n%s", cidutil.FormatRef)
os.Exit(2)
}
Expand All @@ -24,8 +28,9 @@ func main() {
newBase := mb.Encoding(-1)
var verConv func(cid c.Cid) (c.Cid, error)
args := os.Args[1:]
filter := false
outer:
for {
for len(args) > 0 {
switch args[0] {
case "-b":
if len(args) < 2 {
Expand All @@ -52,11 +57,14 @@ outer:
os.Exit(2)
}
args = args[2:]
case "--filter":
filter = true
args = args[1:]
default:
break outer
}
}
if len(args) < 2 {
if len(args) < 1 {
usage()
}
fmtStr := args[0]
Expand All @@ -69,41 +77,73 @@ outer:
os.Exit(2)
}
}
for _, cidStr := range args[1:] {
cid, err := c.Decode(cidStr)
if err != nil {
fmt.Fprintf(os.Stdout, "!INVALID_CID!\n")
errorMsg("%s: %v", cidStr, err)
// Don't abort on a bad cid
continue
}
format := func(cid c.Cid, cidStr string) (string, error) {
base := newBase
if newBase == -1 {
if base == -1 {
base, _ = c.ExtractEncoding(cidStr)
}
var err error
if verConv != nil {
cid, err = verConv(cid)
if err != nil {
fmt.Fprintf(os.Stdout, "!ERROR!\n")
errorMsg("%s: %v", cidStr, err)
// Don't abort on a bad conversion
continue
return "", err
}
}
return cidutil.Format(fmtStr, base, cid)
}
if filter {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
buf := scanner.Bytes()
for {
i, j, cid, cidStr := cidutil.ScanForCid(buf)
os.Stdout.Write(buf[0:i])
if i == len(buf) {
os.Stdout.Write([]byte{'\n'})
break
}
str, err := format(cid, cidStr)
switch err.(type) {
case cidutil.FormatStringError:
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(2)
default:
// just use the orignal sting on non-fatal error
str = cidStr
case nil:
}
io.WriteString(os.Stdout, str)
buf = buf[j:]
}
}
str, err := cidutil.Format(fmtStr, base, cid)
switch err.(type) {
case cidutil.FormatStringError:
if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(2)
default:
fmt.Fprintf(os.Stdout, "!ERROR!\n")
errorMsg("%s: %v", cidStr, err)
// Don't abort on cid specific errors
continue
case nil:
// no error
}
fmt.Fprintf(os.Stdout, "%s\n", str)
} else {
for _, cidStr := range args[1:] {
cid, err := c.Decode(cidStr)
if err != nil {
fmt.Fprintf(os.Stdout, "!INVALID_CID!\n")
errorMsg("%s: %v", cidStr, err)
// Don't abort on a bad cid
continue
}
str, err := format(cid, cidStr)
switch err.(type) {
case cidutil.FormatStringError:
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(2)
default:
fmt.Fprintf(os.Stdout, "!ERROR!\n")
errorMsg("%s: %v", cidStr, err)
// Don't abort on cid specific errors
continue
case nil:
// no error
}
fmt.Fprintf(os.Stdout, "%s\n", str)
}
}
os.Exit(exitCode)
}
Expand Down
85 changes: 85 additions & 0 deletions cidenc/encoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package cidenc

import (
"context"

cid "github.com/ipfs/go-cid"
mbase "github.com/multiformats/go-multibase"
)

// Encoder is a basic Encoder that will encode Cid's using a specifed
// base and optionally upgrade a CidV0 to CidV1
type Encoder struct {
Base mbase.Encoder
Upgrade bool
}

// Interface is a generic interface to the Encoder functionally.
type Interface interface {
Encode(c cid.Cid) string
Recode(v string) (string, error)
}

// Default is the default encoder
var Default = Encoder{
Base: mbase.MustNewEncoder(mbase.Base58BTC),
Upgrade: false,
}

func (enc Encoder) Encode(c cid.Cid) string {
if enc.Upgrade && c.Version() == 0 {
c = cid.NewCidV1(c.Type(), c.Hash())
}
return c.Encode(enc.Base)
}

// Recode reencodes the cid string to match the paramaters of the
// encoder
func (enc Encoder) Recode(v string) (string, error) {
skip, err := enc.noopRecode(v)
if skip || err != nil {
return v, err
}

c, err := cid.Decode(v)
if err != nil {
return v, err
}

return enc.Encode(c), nil
}

func (enc Encoder) noopRecode(v string) (bool, error) {
if len(v) < 2 {
return false, cid.ErrCidTooShort
}
ver := cidVer(v)
skip := ver == 0 && !enc.Upgrade || ver == 1 && v[0] == byte(enc.Base.Encoding())
return skip, nil
}

func cidVer(v string) int {
if len(v) == 46 && v[:2] == "Qm" {
return 0
} else {
return 1
}
}

type encoderKey struct{}

// Enable "enables" the encoder in the context using WithValue
func Enable(ctx context.Context, enc Interface) context.Context {
return context.WithValue(ctx, encoderKey{}, enc)
}

// Get gets an encoder from the context if it exists, otherwise the
// default context is called.
func Get(ctx context.Context) Interface {
enc, ok := ctx.Value(encoderKey{}).(Interface)
if !ok {
// FIXME: Warning?
enc = Default
}
return enc
}
18 changes: 18 additions & 0 deletions cidenc/encoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package cidenc

import (
"context"
"testing"

mbase "github.com/multiformats/go-multibase"
)

func TestContext(t *testing.T) {
enc := Encoder{Base: mbase.MustNewEncoder(mbase.Base64)}
ctx := context.Background()
ctx = Enable(ctx, enc)
e, ok := Get(ctx).(Encoder)
if !ok || e.Base.Encoding() != mbase.Base64 {
t.Fatal("Failed to retrive encoder from context")
}
}
Loading