<commit_before>// Copyright 2015 The LUCI Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package main

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"

	"time"

	humanize "github.com/dustin/go-humanize"

	"github.com/golang/protobuf/proto"
	"github.com/luci/luci-go/client/isolate"
	"github.com/luci/luci-go/common/eventlog"
	logpb "github.com/luci/luci-go/common/eventlog/proto"
	"github.com/luci/luci-go/common/isolated"
	"github.com/luci/luci-go/common/isolatedclient"
	"github.com/maruel/subcommands"
)

const (
	// archiveThreshold is the size (in bytes) used to determine whether to add
	// files to a tar archive before uploading. Files smaller than this size will
	// be combined into archives before being uploaded to the server.
	archiveThreshold = 100e3 // 100kB

	// archiveMaxSize is the maximum size of the created archives.
	archiveMaxSize = 10e6

	// infraFailExit is the exit code used when the exparchive fails due to
	// infrastructure errors (for example, failed server requests).
	infraFailExit = 2
)

var cmdExpArchive = &subcommands.Command{
	UsageLine: "exparchive <options>",
	ShortDesc: "EXPERIMENTAL parses a .isolate file to create a .isolated file, and uploads it and all referenced files to an isolate server",
	LongDesc:  "All the files listed in the .isolated file are put in the isolate server cache. Small files are combined together in a tar archive before uploading.",
	CommandRun: func() subcommands.CommandRun {
		c := &expArchiveRun{}
		c.commonServerFlags.Init()
		c.isolateFlags.Init(&c.Flags)
		return c
	},
}

// expArchiveRun contains the logic for the experimental archive subcommand.
// It implements subcommand.CommandRun
type expArchiveRun struct {
	commonServerFlags // Provides the GetFlags method.
	isolateFlags      isolateFlags
}

// Item represents a file or symlink referenced by an isolate file.
type Item struct {
	Path    string
	RelPath string
	Size    int64
	Mode    os.FileMode

	Digest isolated.HexDigest
}

// main contains the core logic for experimental archive.
func (c *expArchiveRun) main() error {
	// TODO(djd): This func is long and has a lot of internal complexity (like,
	// such as, archiveCallback). Refactor.

	start := time.Now()
	archiveOpts := &c.isolateFlags.ArchiveOptions
	// Parse the incoming isolate file.
	deps, rootDir, isol, err := isolate.ProcessIsolate(archiveOpts)
	if err != nil {
		return fmt.Errorf("failed to process isolate: %v", err)
	}
	log.Printf("Isolate referenced %d deps", len(deps))

	// Set up a background context which is cancelled when this function returns.
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Create the isolated client which connects to the isolate server.
	authCl, err := c.createAuthClient()
	if err != nil {
		return err
	}
	client := isolatedclient.New(nil, authCl, c.isolatedFlags.ServerURL, c.isolatedFlags.Namespace, nil)

	// Set up a checker an uploader. We limit the uploader to one concurrent
	// upload, since the uploads are all coming from disk (with the exception of
	// the isolated JSON itself) and we only want a single goroutine reading from
	// disk at once.
	checker := NewChecker(ctx, client)
	uploader := NewUploader(ctx, client, 1)

	// Walk each of the deps, partioning the results into symlinks and files categorised by size.
	var links, archiveFiles, indivFiles []*Item
	var archiveSize, indivSize int64 // Cumulative size of archived/individual files.
	for _, dep := range deps {
		// Try to walk dep. If dep is a file (or symlink), the inner function is called exactly once.
		err := filepath.Walk(filepath.Clean(dep), func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if info.IsDir() {
				return nil
			}

			relPath, err := filepath.Rel(rootDir, path)
			if err != nil {
				return err
			}

			item := &Item{
				Path:    path,
				RelPath: relPath,
				Mode:    info.Mode(),
				Size:    info.Size(),
			}

			switch {
			case item.Mode&os.ModeSymlink == os.ModeSymlink:
				links = append(links, item)
			case item.Size < archiveThreshold:
				archiveFiles = append(archiveFiles, item)
				archiveSize += item.Size
			default:
				indivFiles = append(indivFiles, item)
				indivSize += item.Size
			}
			return nil
		})
		if err != nil {
			return err
		}
	}

	// Construct a map of the files that constitute the isolate.
	files := make(map[string]isolated.File)

	log.Printf("Isolate expanded to %d files (total size %s) and %d symlinks", len(archiveFiles)+len(indivFiles), humanize.Bytes(uint64(archiveSize+indivSize)), len(links))
	log.Printf("\t%d files (%s) to be isolated individually", len(indivFiles), humanize.Bytes(uint64(indivSize)))
	log.Printf("\t%d files (%s) to be isolated in archives", len(archiveFiles), humanize.Bytes(uint64(archiveSize)))

	// Handle the symlinks.
	for _, item := range links {
		l, err := os.Readlink(item.Path)
		if err != nil {
			return fmt.Errorf("unable to resolve symlink for %q: %v", item.Path, err)
		}
		files[item.RelPath] = isolated.SymLink(l)
	}

	// Handle the small to-be-archived files.
	bundles := ShardItems(archiveFiles, archiveMaxSize)
	log.Printf("\t%d TAR archives to be isolated", len(bundles))

	for _, bundle := range bundles {
		bundle := bundle
		digest, tarSize, err := bundle.Digest()
		if err != nil {
			return err
		}

		log.Printf("Created tar archive %q (%s)", digest, humanize.Bytes(uint64(tarSize)))
		log.Printf("\tcontains %d files (total %s)", len(bundle.Items), humanize.Bytes(uint64(bundle.ItemSize)))
		// Mint an item for this tar.
		item := &Item{
			Path:    fmt.Sprintf(".%s.tar", digest),
			RelPath: fmt.Sprintf(".%s.tar", digest),
			Size:    tarSize,
			Mode:    0644, // Read
			Digest:  digest,
		}
		files[item.RelPath] = isolated.TarFile(item.Digest, int(item.Mode), item.Size)

		checker.AddItem(item, false, func(item *Item, ps *isolatedclient.PushState) {
			if ps == nil {
				return
			}
			log.Printf("QUEUED %q for upload", item.RelPath)
			uploader.Upload(item.RelPath, bundle.Contents, ps, func() {
				log.Printf("UPLOADED %q", item.RelPath)
			})
		})
	}

	// Handle the large individually-uploaded files.
	for _, item := range indivFiles {
		d, err := hashFile(item.Path)
		if err != nil {
			return err
		}
		item.Digest = d
		files[item.RelPath] = isolated.BasicFile(item.Digest, int(item.Mode), item.Size)
		checker.AddItem(item, false, func(item *Item, ps *isolatedclient.PushState) {
			if ps == nil {
				return
			}
			log.Printf("QUEUED %q for upload", item.RelPath)
			uploader.UploadFile(item, ps, func() {
				log.Printf("UPLOADED %q", item.RelPath)
			})
		})
	}

	// Marshal the isolated file into JSON, and create an Item to describe it.
	isol.Files = files
	isolJSON, err := json.Marshal(isol)
	if err != nil {
		return err
	}
	isolItem := &Item{
		Path:    archiveOpts.Isolated,
		RelPath: filepath.Base(archiveOpts.Isolated),
		Digest:  isolated.HashBytes(isolJSON),
		Size:    int64(len(isolJSON)),
	}

	// Check and upload isolate JSON.
	checker.AddItem(isolItem, true, func(item *Item, ps *isolatedclient.PushState) {
		if ps == nil {
			return
		}
		log.Printf("QUEUED %q for upload", item.RelPath)
		uploader.UploadBytes(item.RelPath, isolJSON, ps, func() {
			log.Printf("UPLOADED %q", item.RelPath)
		})
	})

	// Make sure that all pending items have been checked.
	if err := checker.Close(); err != nil {
		return err
	}

	// Make sure that all the uploads have completed successfully.
	if err := uploader.Close(); err != nil {
		return err
	}

	// Write the isolated file, and emit its digest to stdout.
	if err := ioutil.WriteFile(archiveOpts.Isolated, isolJSON, 0644); err != nil {
		return err
	}
	fmt.Printf("%s\t%s\n", isolItem.Digest, filepath.Base(archiveOpts.Isolated))

	end := time.Now()

	if endpoint := eventlogEndpoint(c.isolateFlags.EventlogEndpoint); endpoint != "" {
		logger := eventlog.NewClient(ctx, endpoint)

		// TODO(mcgreevy): fill out more stats in archiveDetails.
		archiveDetails := &logpb.IsolateClientEvent_ArchiveDetails{
			HitCount:    proto.Int64(int64(checker.Hit.Count)),
			MissCount:   proto.Int64(int64(checker.Miss.Count)),
			HitBytes:    &checker.Hit.Bytes,
			MissBytes:   &checker.Miss.Bytes,
			IsolateHash: []string{string(isolItem.Digest)},
		}
		if err := logStats(ctx, logger, start, end, archiveDetails); err != nil {
			log.Printf("Failed to log to eventlog: %v", err)
		}
	}

	return nil
}

func (c *expArchiveRun) parseFlags(args []string) error {
	if len(args) != 0 {
		return errors.New("position arguments not expected")
	}
	if err := c.commonServerFlags.Parse(); err != nil {
		return err
	}
	cwd, err := os.Getwd()
	if err != nil {
		return err
	}
	if err := c.isolateFlags.Parse(cwd, RequireIsolateFile&RequireIsolatedFile); err != nil {
		return err
	}
	return nil
}

func (c *expArchiveRun) Run(a subcommands.Application, args []string, _ subcommands.Env) int {
	fmt.Fprintln(a.GetErr(), "WARNING: this command is experimental")
	if err := c.parseFlags(args); err != nil {
		fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err)
		return 1
	}
	if len(c.isolateFlags.ArchiveOptions.Blacklist) != 0 {
		fmt.Fprintf(a.GetErr(), "%s: blacklist is not supported\n", a.GetName())
		return 1
	}
	if err := c.main(); err != nil {
		fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err)
		return 1
	}
	return 0
}

func hashFile(path string) (isolated.HexDigest, error) {
	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()
	return isolated.Hash(f)
}

func logStats(ctx context.Context, logger *eventlog.Client, start, end time.Time, archiveDetails *logpb.IsolateClientEvent_ArchiveDetails) error {
	event := logger.NewLogEvent(ctx, eventlog.Point())
	event.InfraEvent.IsolateClientEvent = &logpb.IsolateClientEvent{
		Binary: &logpb.Binary{
			Name:          proto.String("isolate"),
			VersionNumber: proto.String(version),
		},
		Operation:      logpb.IsolateClientEvent_ARCHIVE.Enum(),
		ArchiveDetails: archiveDetails,
		// TODO(mcgreevy): fill out Master, Builder, BuildId, Slave.
		StartTsUsec: proto.Int64(int64(start.UnixNano() / 1e3)),
		EndTsUsec:   proto.Int64(int64(end.UnixNano() / 1e3)),
	}
	return logger.LogSync(ctx, event)
}

func eventlogEndpoint(endpointFlag string) string {
	switch endpointFlag {
	case "test":
		return eventlog.TestEndpoint
	case "prod":
		return eventlog.ProdEndpoint
	default:
		return endpointFlag
	}
}
<commit_msg>client/isolate: add --dump_json support to exparchive<commit_after>// Copyright 2015 The LUCI Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package main

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"

	"time"

	humanize "github.com/dustin/go-humanize"

	"github.com/golang/protobuf/proto"
	"github.com/luci/luci-go/client/isolate"
	"github.com/luci/luci-go/common/eventlog"
	logpb "github.com/luci/luci-go/common/eventlog/proto"
	"github.com/luci/luci-go/common/isolated"
	"github.com/luci/luci-go/common/isolatedclient"
	"github.com/maruel/subcommands"
)

const (
	// archiveThreshold is the size (in bytes) used to determine whether to add
	// files to a tar archive before uploading. Files smaller than this size will
	// be combined into archives before being uploaded to the server.
	archiveThreshold = 100e3 // 100kB

	// archiveMaxSize is the maximum size of the created archives.
	archiveMaxSize = 10e6

	// infraFailExit is the exit code used when the exparchive fails due to
	// infrastructure errors (for example, failed server requests).
	infraFailExit = 2
)

var cmdExpArchive = &subcommands.Command{
	UsageLine: "exparchive <options>",
	ShortDesc: "EXPERIMENTAL parses a .isolate file to create a .isolated file, and uploads it and all referenced files to an isolate server",
	LongDesc:  "All the files listed in the .isolated file are put in the isolate server cache. Small files are combined together in a tar archive before uploading.",
	CommandRun: func() subcommands.CommandRun {
		c := &expArchiveRun{}
		c.commonServerFlags.Init()
		c.isolateFlags.Init(&c.Flags)
		c.Flags.StringVar(&c.dumpJSON, "dump-json", "",
			"Write isolated digests of archived trees to this file as JSON")
		return c
	},
}

// expArchiveRun contains the logic for the experimental archive subcommand.
// It implements subcommand.CommandRun
type expArchiveRun struct {
	commonServerFlags // Provides the GetFlags method.
	isolateFlags      isolateFlags
	dumpJSON          string
}

// Item represents a file or symlink referenced by an isolate file.
type Item struct {
	Path    string
	RelPath string
	Size    int64
	Mode    os.FileMode

	Digest isolated.HexDigest
}

// main contains the core logic for experimental archive.
func (c *expArchiveRun) main() error {
	// TODO(djd): This func is long and has a lot of internal complexity (like,
	// such as, archiveCallback). Refactor.

	start := time.Now()
	archiveOpts := &c.isolateFlags.ArchiveOptions
	// Parse the incoming isolate file.
	deps, rootDir, isol, err := isolate.ProcessIsolate(archiveOpts)
	if err != nil {
		return fmt.Errorf("failed to process isolate: %v", err)
	}
	log.Printf("Isolate referenced %d deps", len(deps))

	// Set up a background context which is cancelled when this function returns.
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Create the isolated client which connects to the isolate server.
	authCl, err := c.createAuthClient()
	if err != nil {
		return err
	}
	client := isolatedclient.New(nil, authCl, c.isolatedFlags.ServerURL, c.isolatedFlags.Namespace, nil)

	// Set up a checker an uploader. We limit the uploader to one concurrent
	// upload, since the uploads are all coming from disk (with the exception of
	// the isolated JSON itself) and we only want a single goroutine reading from
	// disk at once.
	checker := NewChecker(ctx, client)
	uploader := NewUploader(ctx, client, 1)

	// Walk each of the deps, partioning the results into symlinks and files categorised by size.
	var links, archiveFiles, indivFiles []*Item
	var archiveSize, indivSize int64 // Cumulative size of archived/individual files.
	for _, dep := range deps {
		// Try to walk dep. If dep is a file (or symlink), the inner function is called exactly once.
		err := filepath.Walk(filepath.Clean(dep), func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if info.IsDir() {
				return nil
			}

			relPath, err := filepath.Rel(rootDir, path)
			if err != nil {
				return err
			}

			item := &Item{
				Path:    path,
				RelPath: relPath,
				Mode:    info.Mode(),
				Size:    info.Size(),
			}

			switch {
			case item.Mode&os.ModeSymlink == os.ModeSymlink:
				links = append(links, item)
			case item.Size < archiveThreshold:
				archiveFiles = append(archiveFiles, item)
				archiveSize += item.Size
			default:
				indivFiles = append(indivFiles, item)
				indivSize += item.Size
			}
			return nil
		})
		if err != nil {
			return err
		}
	}

	// Construct a map of the files that constitute the isolate.
	files := make(map[string]isolated.File)

	log.Printf("Isolate expanded to %d files (total size %s) and %d symlinks", len(archiveFiles)+len(indivFiles), humanize.Bytes(uint64(archiveSize+indivSize)), len(links))
	log.Printf("\t%d files (%s) to be isolated individually", len(indivFiles), humanize.Bytes(uint64(indivSize)))
	log.Printf("\t%d files (%s) to be isolated in archives", len(archiveFiles), humanize.Bytes(uint64(archiveSize)))

	// Handle the symlinks.
	for _, item := range links {
		l, err := os.Readlink(item.Path)
		if err != nil {
			return fmt.Errorf("unable to resolve symlink for %q: %v", item.Path, err)
		}
		files[item.RelPath] = isolated.SymLink(l)
	}

	// Handle the small to-be-archived files.
	bundles := ShardItems(archiveFiles, archiveMaxSize)
	log.Printf("\t%d TAR archives to be isolated", len(bundles))

	for _, bundle := range bundles {
		bundle := bundle
		digest, tarSize, err := bundle.Digest()
		if err != nil {
			return err
		}

		log.Printf("Created tar archive %q (%s)", digest, humanize.Bytes(uint64(tarSize)))
		log.Printf("\tcontains %d files (total %s)", len(bundle.Items), humanize.Bytes(uint64(bundle.ItemSize)))
		// Mint an item for this tar.
		item := &Item{
			Path:    fmt.Sprintf(".%s.tar", digest),
			RelPath: fmt.Sprintf(".%s.tar", digest),
			Size:    tarSize,
			Mode:    0644, // Read
			Digest:  digest,
		}
		files[item.RelPath] = isolated.TarFile(item.Digest, int(item.Mode), item.Size)

		checker.AddItem(item, false, func(item *Item, ps *isolatedclient.PushState) {
			if ps == nil {
				return
			}
			log.Printf("QUEUED %q for upload", item.RelPath)
			uploader.Upload(item.RelPath, bundle.Contents, ps, func() {
				log.Printf("UPLOADED %q", item.RelPath)
			})
		})
	}

	// Handle the large individually-uploaded files.
	for _, item := range indivFiles {
		d, err := hashFile(item.Path)
		if err != nil {
			return err
		}
		item.Digest = d
		files[item.RelPath] = isolated.BasicFile(item.Digest, int(item.Mode), item.Size)
		checker.AddItem(item, false, func(item *Item, ps *isolatedclient.PushState) {
			if ps == nil {
				return
			}
			log.Printf("QUEUED %q for upload", item.RelPath)
			uploader.UploadFile(item, ps, func() {
				log.Printf("UPLOADED %q", item.RelPath)
			})
		})
	}

	// Marshal the isolated file into JSON, and create an Item to describe it.
	isol.Files = files
	isolJSON, err := json.Marshal(isol)
	if err != nil {
		return err
	}
	isolItem := &Item{
		Path:    archiveOpts.Isolated,
		RelPath: filepath.Base(archiveOpts.Isolated),
		Digest:  isolated.HashBytes(isolJSON),
		Size:    int64(len(isolJSON)),
	}

	// Check and upload isolate JSON.
	checker.AddItem(isolItem, true, func(item *Item, ps *isolatedclient.PushState) {
		if ps == nil {
			return
		}
		log.Printf("QUEUED %q for upload", item.RelPath)
		uploader.UploadBytes(item.RelPath, isolJSON, ps, func() {
			log.Printf("UPLOADED %q", item.RelPath)
		})
	})

	// Make sure that all pending items have been checked.
	if err := checker.Close(); err != nil {
		return err
	}

	// Make sure that all the uploads have completed successfully.
	if err := uploader.Close(); err != nil {
		return err
	}

	// Write the isolated file, and emit its digest to stdout.
	if err := ioutil.WriteFile(archiveOpts.Isolated, isolJSON, 0644); err != nil {
		return err
	}
	fmt.Printf("%s\t%s\n", isolItem.Digest, filepath.Base(archiveOpts.Isolated))

	// Optionally, write the digest of the isolated file as JSON (in the same
	// format as batch_archive).
	if c.dumpJSON != "" {
		// The name is the base name of the isolated file, extension stripped.
		name := filepath.Base(archiveOpts.Isolated)
		if i := strings.LastIndex(name, "."); i != -1 {
			name = name[:i]
		}
		j, err := json.Marshal(map[string]isolated.HexDigest{
			name: isolItem.Digest,
		})
		if err != nil {
			return err
		}
		if err := ioutil.WriteFile(c.dumpJSON, j, 0644); err != nil {
			return err
		}
	}

	end := time.Now()

	if endpoint := eventlogEndpoint(c.isolateFlags.EventlogEndpoint); endpoint != "" {
		logger := eventlog.NewClient(ctx, endpoint)

		// TODO(mcgreevy): fill out more stats in archiveDetails.
		archiveDetails := &logpb.IsolateClientEvent_ArchiveDetails{
			HitCount:    proto.Int64(int64(checker.Hit.Count)),
			MissCount:   proto.Int64(int64(checker.Miss.Count)),
			HitBytes:    &checker.Hit.Bytes,
			MissBytes:   &checker.Miss.Bytes,
			IsolateHash: []string{string(isolItem.Digest)},
		}
		if err := logStats(ctx, logger, start, end, archiveDetails); err != nil {
			log.Printf("Failed to log to eventlog: %v", err)
		}
	}

	return nil
}

func (c *expArchiveRun) parseFlags(args []string) error {
	if len(args) != 0 {
		return errors.New("position arguments not expected")
	}
	if err := c.commonServerFlags.Parse(); err != nil {
		return err
	}
	cwd, err := os.Getwd()
	if err != nil {
		return err
	}
	if err := c.isolateFlags.Parse(cwd, RequireIsolateFile&RequireIsolatedFile); err != nil {
		return err
	}
	return nil
}

func (c *expArchiveRun) Run(a subcommands.Application, args []string, _ subcommands.Env) int {
	fmt.Fprintln(a.GetErr(), "WARNING: this command is experimental")
	if err := c.parseFlags(args); err != nil {
		fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err)
		return 1
	}
	if len(c.isolateFlags.ArchiveOptions.Blacklist) != 0 {
		fmt.Fprintf(a.GetErr(), "%s: blacklist is not supported\n", a.GetName())
		return 1
	}
	if err := c.main(); err != nil {
		fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err)
		return 1
	}
	return 0
}

func hashFile(path string) (isolated.HexDigest, error) {
	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()
	return isolated.Hash(f)
}

func logStats(ctx context.Context, logger *eventlog.Client, start, end time.Time, archiveDetails *logpb.IsolateClientEvent_ArchiveDetails) error {
	event := logger.NewLogEvent(ctx, eventlog.Point())
	event.InfraEvent.IsolateClientEvent = &logpb.IsolateClientEvent{
		Binary: &logpb.Binary{
			Name:          proto.String("isolate"),
			VersionNumber: proto.String(version),
		},
		Operation:      logpb.IsolateClientEvent_ARCHIVE.Enum(),
		ArchiveDetails: archiveDetails,
		// TODO(mcgreevy): fill out Master, Builder, BuildId, Slave.
		StartTsUsec: proto.Int64(int64(start.UnixNano() / 1e3)),
		EndTsUsec:   proto.Int64(int64(end.UnixNano() / 1e3)),
	}
	return logger.LogSync(ctx, event)
}

func eventlogEndpoint(endpointFlag string) string {
	switch endpointFlag {
	case "test":
		return eventlog.TestEndpoint
	case "prod":
		return eventlog.ProdEndpoint
	default:
		return endpointFlag
	}
}
<|endoftext|>


<commit_before>package http

import (
	"encoding/json"
	"errors"

	"github.com/rqlite/rqlite/command"
)

var (
	// ErrNoStatements is returned when a request is empty
	ErrNoStatements = errors.New("no statements")

	// ErrInvalidRequest is returned when a request cannot be parsed.
	ErrInvalidRequest = errors.New("invalid request")

	// ErrUnsupportedType is returned when a request contains an unsupported type.
	ErrUnsupportedType = errors.New("unsupported type")
)

// ParseRequest generates a set of Statements for a given byte slice.
func ParseRequest(b []byte) ([]*command.Statement, error) {
	if b == nil {
		return nil, ErrNoStatements
	}

	simple := []string{}               // Represents a set of unparameterized queries
	parameterized := [][]interface{}{} // Represents a set of parameterized queries

	// Try simple form first.
	err := json.Unmarshal(b, &simple)
	if err == nil {
		if len(simple) == 0 {
			return nil, ErrNoStatements
		}

		stmts := make([]*command.Statement, len(simple))
		for i := range simple {
			stmts[i] = &command.Statement{
				Sql: simple[i],
			}
		}
		return stmts, nil
	}

	// Next try parameterized form.
	if err := json.Unmarshal(b, &parameterized); err != nil {
		return nil, ErrInvalidRequest
	}
	stmts := make([]*command.Statement, len(parameterized))

	for i := range parameterized {
		if len(parameterized[i]) == 0 {
			return nil, ErrNoStatements
		}

		sql, ok := parameterized[i][0].(string)
		if !ok {
			return nil, ErrInvalidRequest
		}
		stmts[i] = &command.Statement{
			Sql:        sql,
			Parameters: nil,
		}
		if len(parameterized[i]) == 1 {
			// No actual parameters after the SQL string
			continue
		}

		stmts[i].Parameters = make([]*command.Parameter, 0)
		for j := range parameterized[i][1:] {
			m, ok := parameterized[i][j+1].(map[string]interface{})
			if ok {
				for k, v := range m {
					p, err := makeParameter(k, v)
					if err != nil {
						return nil, err
					}
					stmts[i].Parameters = append(stmts[i].Parameters, p)
				}
			} else {
				p, err := makeParameter("", parameterized[i][j+1])
				if err != nil {
					return nil, err
				}
				stmts[i].Parameters = append(stmts[i].Parameters, p)
			}
		}
	}
	return stmts, nil
}

func makeParameter(name string, i interface{}) (*command.Parameter, error) {
	switch v := i.(type) {
	case int:
	case int64:
		return &command.Parameter{
			Value: &command.Parameter_I{
				I: v,
			},
			Name: name,
		}, nil
	case float64:
		return &command.Parameter{
			Value: &command.Parameter_D{
				D: v,
			},
			Name: name,
		}, nil
	case bool:
		return &command.Parameter{
			Value: &command.Parameter_B{
				B: v,
			},
			Name: name,
		}, nil
	case []byte:
		return &command.Parameter{
			Value: &command.Parameter_Y{
				Y: v,
			},
			Name: name,
		}, nil
	case string:
		return &command.Parameter{
			Value: &command.Parameter_S{
				S: v,
			},
			Name: name,
		}, nil
	}
	return nil, ErrUnsupportedType
}
<commit_msg>Better error codes<commit_after>package http

import (
	"encoding/json"
	"errors"

	"github.com/rqlite/rqlite/command"
)

var (
	// ErrNoStatements is returned when a request is empty
	ErrNoStatements = errors.New("no statements")

	// ErrInvalidJSON is returned when a body is not valid JSON
	ErrInvalidJSON = errors.New("invalid JSON body")

	// ErrInvalidRequest is returned when a request cannot be parsed.
	ErrInvalidRequest = errors.New("invalid request")

	// ErrUnsupportedType is returned when a request contains an unsupported type.
	ErrUnsupportedType = errors.New("unsupported type")
)

// ParseRequest generates a set of Statements for a given byte slice.
func ParseRequest(b []byte) ([]*command.Statement, error) {
	if b == nil {
		return nil, ErrNoStatements
	}

	simple := []string{}               // Represents a set of unparameterized queries
	parameterized := [][]interface{}{} // Represents a set of parameterized queries

	// Try simple form first.
	err := json.Unmarshal(b, &simple)
	if err == nil {
		if len(simple) == 0 {
			return nil, ErrNoStatements
		}

		stmts := make([]*command.Statement, len(simple))
		for i := range simple {
			stmts[i] = &command.Statement{
				Sql: simple[i],
			}
		}
		return stmts, nil
	}

	// Next try parameterized form.
	if err := json.Unmarshal(b, &parameterized); err != nil {
		return nil, ErrInvalidJSON
	}
	stmts := make([]*command.Statement, len(parameterized))

	for i := range parameterized {
		if len(parameterized[i]) == 0 {
			return nil, ErrNoStatements
		}

		sql, ok := parameterized[i][0].(string)
		if !ok {
			return nil, ErrInvalidRequest
		}
		stmts[i] = &command.Statement{
			Sql:        sql,
			Parameters: nil,
		}
		if len(parameterized[i]) == 1 {
			// No actual parameters after the SQL string
			continue
		}

		stmts[i].Parameters = make([]*command.Parameter, 0)
		for j := range parameterized[i][1:] {
			m, ok := parameterized[i][j+1].(map[string]interface{})
			if ok {
				for k, v := range m {
					p, err := makeParameter(k, v)
					if err != nil {
						return nil, err
					}
					stmts[i].Parameters = append(stmts[i].Parameters, p)
				}
			} else {
				p, err := makeParameter("", parameterized[i][j+1])
				if err != nil {
					return nil, err
				}
				stmts[i].Parameters = append(stmts[i].Parameters, p)
			}
		}
	}
	return stmts, nil
}

func makeParameter(name string, i interface{}) (*command.Parameter, error) {
	switch v := i.(type) {
	case int:
	case int64:
		return &command.Parameter{
			Value: &command.Parameter_I{
				I: v,
			},
			Name: name,
		}, nil
	case float64:
		return &command.Parameter{
			Value: &command.Parameter_D{
				D: v,
			},
			Name: name,
		}, nil
	case bool:
		return &command.Parameter{
			Value: &command.Parameter_B{
				B: v,
			},
			Name: name,
		}, nil
	case []byte:
		return &command.Parameter{
			Value: &command.Parameter_Y{
				Y: v,
			},
			Name: name,
		}, nil
	case string:
		return &command.Parameter{
			Value: &command.Parameter_S{
				S: v,
			},
			Name: name,
		}, nil
	}
	return nil, ErrUnsupportedType
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"

)

func SendMsgResult(w *http.ResponseWriter, r *msg.Result) {
	var (
		bjson  []byte
		err    error
		k      auth.Kex
		result proto.Result
		action string
	)

	// this is central error command, proceeding to log
	if r.Error != nil {
		log.Printf(LogStrErr, r.Type, r.Action, r.Code, r.Error.Error())
	}

	switch r.Type {
	case `supervisor`:
		switch r.Action {
		case `kex_reply`:
			k = r.Super.Kex
			if bjson, err = json.Marshal(&k); err != nil {
				log.Printf(LogStrErr, r.Type, r.Action, r.Code, err.Error())
				DispatchInternalError(w, nil)
				return
			}
			goto dispatchJSON
		case `bootstrap_root`:
			fallthrough
		case `activate_user`:
			fallthrough
		case `issue_token`:
			// for this request type, errors are masked in responses
			switch r.Code {
			case 200:
				if r.Super.Verdict == 200 {
					log.Printf(LogStrOK, r.Type, r.Action, r.Code, 200)
					goto dispatchOCTET
				}
				log.Printf(LogStrOK, r.Type, r.Action, r.Code, 401)
				DispatchUnauthorized(w, nil)
			case 400:
				log.Printf(LogStrOK, r.Type, r.Action, r.Code, 400)
				DispatchBadRequest(w, nil)
			case 404:
				log.Printf(LogStrOK, r.Type, r.Action, r.Code, 404)
				DispatchNotFound(w, r.Error)
			case 406:
				log.Printf(LogStrOK, r.Type, r.Action, r.Code, 406)
				DispatchConflict(w, r.Error)
			default:
				log.Printf(LogStrOK, r.Type, r.Action, r.Code, 401)
				DispatchUnauthorized(w, nil)
			}
			return
		case `category`:
			result = proto.NewCategoryResult()
			*result.Categories = append(*result.Categories, r.Category...)
			goto UnmaskedReply
		case `permission`:
			result = proto.NewPermissionResult()
			*result.Permissions = append(*result.Permissions, r.Permission...)
			goto UnmaskedReply
		case `right`:
			result = proto.NewGrantResult()
			*result.Grants = append(*result.Grants, r.Grant...)
			goto UnmaskedReply
		default:
			log.Printf(LogStrErr, r.Type, r.Action, 0, `Unhandled supervisor action`)
			// supervisor as auth-lord has special default to avoid
			// accidental leakage
			DispatchUnauthorized(w, nil)
			return
		} // end supervisor
	case `job`:
		result = proto.NewJobResult()
		*result.Jobs = append(*result.Jobs, r.Job...)
		goto UnmaskedReply
	case `tree`:
		result = proto.NewTreeResult()
		*result.Tree = r.Tree
		goto UnmaskedReply
	default:
		log.Printf(LogStrErr, r.Type, ``, 0, `Result from unhandled subsystem`)
		DispatchInternalError(w, nil)
		return
	}

UnmaskedReply:
	switch r.Type {
	case `supervisor`:
		action = fmt.Sprintf("%s/%s", r.Action, r.Super.Action)
	default:
		action = r.Action
	}

	switch r.Code {
	case 200:
		log.Printf(LogStrOK, r.Type, action, r.Code, 200)
		if r.Error != nil {
			result.Error(r.Error)
		}
		result.OK()
	case 202:
		log.Printf(LogStrOK, r.Type, action, r.Code, 202)
		result.JobId = r.JobId
		result.Accepted()
	case 400:
		log.Printf(LogStrOK, r.Type, action, r.Code, 400)
		DispatchBadRequest(w, nil)
		return
	case 403:
		log.Printf(LogStrOK, r.Type, action, r.Code, 403)
		DispatchForbidden(w, r.Error)
		return
	case 404:
		log.Printf(LogStrOK, r.Type, action, r.Code, 200)
		result.NotFound()
	case 406:
		log.Printf(LogStrOK, r.Type, action, r.Code, 406)
		DispatchConflict(w, r.Error)
		return
	case 500:
		log.Printf(LogStrOK, r.Type, action, r.Code, 500)
		result.Error(r.Error)
	case 501:
		log.Printf(LogStrOK, r.Type, action, r.Code, 501)
		result.NotImplemented()
	default:
		log.Printf(LogStrErr, r.Type, action, r.Code, `Unhandled internal result code`)
		DispatchInternalError(w, nil)
		return
	}
	goto buildJSON

dispatchOCTET:
	DispatchOctetReply(w, &r.Super.Data)
	return

buildJSON:
	if bjson, err = json.Marshal(&result); err != nil {
		log.Printf(LogStrErr, r.Type, r.Action, r.Code, err)
		DispatchInternalError(w, nil)
		return
	}

dispatchJSON:
	DispatchJsonReply(w, &bjson)
	return
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<commit_msg>Add output handling for systemoperations<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"

)

func SendMsgResult(w *http.ResponseWriter, r *msg.Result) {
	var (
		bjson  []byte
		err    error
		k      auth.Kex
		result proto.Result
		action string
	)

	// this is central error command, proceeding to log
	if r.Error != nil {
		log.Printf(LogStrErr, r.Type, r.Action, r.Code, r.Error.Error())
	}

	switch r.Type {
	case `supervisor`:
		switch r.Action {
		case `kex_reply`:
			k = r.Super.Kex
			if bjson, err = json.Marshal(&k); err != nil {
				log.Printf(LogStrErr, r.Type, r.Action, r.Code, err.Error())
				DispatchInternalError(w, nil)
				return
			}
			goto dispatchJSON
		case `bootstrap_root`:
			fallthrough
		case `activate_user`:
			fallthrough
		case `issue_token`:
			// for this request type, errors are masked in responses
			switch r.Code {
			case 200:
				if r.Super.Verdict == 200 {
					log.Printf(LogStrOK, r.Type, r.Action, r.Code, 200)
					goto dispatchOCTET
				}
				log.Printf(LogStrOK, r.Type, r.Action, r.Code, 401)
				DispatchUnauthorized(w, nil)
			case 400:
				log.Printf(LogStrOK, r.Type, r.Action, r.Code, 400)
				DispatchBadRequest(w, nil)
			case 404:
				log.Printf(LogStrOK, r.Type, r.Action, r.Code, 404)
				DispatchNotFound(w, r.Error)
			case 406:
				log.Printf(LogStrOK, r.Type, r.Action, r.Code, 406)
				DispatchConflict(w, r.Error)
			default:
				log.Printf(LogStrOK, r.Type, r.Action, r.Code, 401)
				DispatchUnauthorized(w, nil)
			}
			return
		case `category`:
			result = proto.NewCategoryResult()
			*result.Categories = append(*result.Categories, r.Category...)
			goto UnmaskedReply
		case `permission`:
			result = proto.NewPermissionResult()
			*result.Permissions = append(*result.Permissions, r.Permission...)
			goto UnmaskedReply
		case `right`:
			result = proto.NewGrantResult()
			*result.Grants = append(*result.Grants, r.Grant...)
			goto UnmaskedReply
		default:
			log.Printf(LogStrErr, r.Type, r.Action, 0, `Unhandled supervisor action`)
			// supervisor as auth-lord has special default to avoid
			// accidental leakage
			DispatchUnauthorized(w, nil)
			return
		} // end supervisor
	case `job`:
		result = proto.NewJobResult()
		*result.Jobs = append(*result.Jobs, r.Job...)
		goto UnmaskedReply
	case `tree`:
		result = proto.NewTreeResult()
		*result.Tree = r.Tree
		goto UnmaskedReply
	case `guidepost`, `forestcustodian`:
		result = proto.NewSystemOperationResult()
		*result.SystemOperations = append(*result.SystemOperations, r.System...)
		goto UnmaskedReply
	default:
		log.Printf(LogStrErr, r.Type, ``, 0, `Result from unhandled subsystem`)
		DispatchInternalError(w, nil)
		return
	}

UnmaskedReply:
	switch r.Type {
	case `supervisor`:
		action = fmt.Sprintf("%s/%s", r.Action, r.Super.Action)
	case `guidepost`, `forestcustodian`:
		action = fmt.Sprintf("%s/%s", r.Action, r.System[0].Request)
	default:
		action = r.Action
	}

	switch r.Code {
	case 200:
		log.Printf(LogStrOK, r.Type, action, r.Code, 200)
		if r.Error != nil {
			result.Error(r.Error)
		}
		result.OK()
	case 202:
		log.Printf(LogStrOK, r.Type, action, r.Code, 202)
		result.JobId = r.JobId
		result.Accepted()
	case 400:
		log.Printf(LogStrOK, r.Type, action, r.Code, 400)
		DispatchBadRequest(w, nil)
		return
	case 403:
		log.Printf(LogStrOK, r.Type, action, r.Code, 403)
		DispatchForbidden(w, r.Error)
		return
	case 404:
		log.Printf(LogStrOK, r.Type, action, r.Code, 200)
		result.NotFound()
	case 406:
		log.Printf(LogStrOK, r.Type, action, r.Code, 406)
		DispatchConflict(w, r.Error)
		return
	case 500:
		log.Printf(LogStrOK, r.Type, action, r.Code, 500)
		result.Error(r.Error)
	case 501:
		log.Printf(LogStrOK, r.Type, action, r.Code, 501)
		result.NotImplemented()
	default:
		log.Printf(LogStrErr, r.Type, action, r.Code, `Unhandled internal result code`)
		DispatchInternalError(w, nil)
		return
	}
	goto buildJSON

dispatchOCTET:
	DispatchOctetReply(w, &r.Super.Data)
	return

buildJSON:
	if bjson, err = json.Marshal(&result); err != nil {
		log.Printf(LogStrErr, r.Type, r.Action, r.Code, err)
		DispatchInternalError(w, nil)
		return
	}

dispatchJSON:
	DispatchJsonReply(w, &bjson)
	return
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<|endoftext|>


<commit_before>package httpclient

import (
	"context"
	"io/ioutil"
	"net"
	"net/url"
	"os"
	"strings"
	"sync"

	bosherr "github.com/cloudfoundry/bosh-utils/errors"
	proxy "github.com/cloudfoundry/socks5-proxy"

	goproxy "golang.org/x/net/proxy"
)

type ProxyDialer interface {
	Dialer(string, string, string) (proxy.DialFunc, error)
}

type DialContextFunc func(ctx context.Context, network, address string) (net.Conn, error)

func (f DialContextFunc) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
	return f(ctx, network, address)
}

func SOCKS5DialContextFuncFromEnvironment(origDialer *net.Dialer, socks5Proxy ProxyDialer) DialContextFunc {
	allProxy := os.Getenv("BOSH_ALL_PROXY")
	if len(allProxy) == 0 {
		return origDialer.DialContext
	}

	if strings.HasPrefix(allProxy, "ssh+") {
		allProxy = strings.TrimPrefix(allProxy, "ssh+")

		proxyURL, err := url.Parse(allProxy)
		if err != nil {
			return errorDialFunc(err, "Parsing BOSH_ALL_PROXY url")
		}

		queryMap, err := url.ParseQuery(proxyURL.RawQuery)
		if err != nil {
			return errorDialFunc(err, "Parsing BOSH_ALL_PROXY query params")
		}

		username := ""
		if proxyURL.User != nil {
			username = proxyURL.User.Username()
		}

		proxySSHKeyPath := queryMap.Get("private-key")
		if proxySSHKeyPath == "" {
			return errorDialFunc(
				bosherr.Error("Required query param 'private-key' not found"),
				"Parsing BOSH_ALL_PROXY query params",
			)
		}

		proxySSHKey, err := ioutil.ReadFile(proxySSHKeyPath)
		if err != nil {
			return errorDialFunc(err, "Reading private key file for SOCKS5 Proxy")
		}

		var (
			dialer proxy.DialFunc
			mut    sync.RWMutex
		)
		return func(ctx context.Context, network, address string) (net.Conn, error) {
			mut.RLock()
			haveDialer := dialer != nil
			mut.RUnlock()

			if haveDialer {
				return dialer(network, address)
			}

			mut.Lock()
			defer mut.Unlock()
			if dialer == nil {
				proxyDialer, err := socks5Proxy.Dialer(username, string(proxySSHKey), proxyURL.Host)
				if err != nil {
					return nil, bosherr.WrapErrorf(err, "Creating SOCKS5 dialer")
				}
				dialer = proxyDialer
			}
			return dialer(network, address)
		}
	}

	proxyURL, err := url.Parse(allProxy)
	if err != nil {
		return errorDialFunc(err, "Parsing BOSH_ALL_PROXY url")
	}

	proxy, err := goproxy.FromURL(proxyURL, origDialer)
	if err != nil {
		return errorDialFunc(err, "Parsing BOSH_ALL_PROXY url")
	}

	perHost := goproxy.NewPerHost(proxy, origDialer)

	noProxy := os.Getenv("no_proxy")
	if len(noProxy) == 0 {
		perHost.AddFromString(noProxy)
	}

	return perHost.DialContext
}

func errorDialFunc(err error, cause string) DialContextFunc {
	return func(ctx context.Context, network, address string) (net.Conn, error) {
		return nil, bosherr.WrapError(err, cause)
	}
}
<commit_msg>Fix typo in noProxy logic<commit_after>package httpclient

import (
	"context"
	"io/ioutil"
	"net"
	"net/url"
	"os"
	"strings"
	"sync"

	bosherr "github.com/cloudfoundry/bosh-utils/errors"
	proxy "github.com/cloudfoundry/socks5-proxy"

	goproxy "golang.org/x/net/proxy"
)

type ProxyDialer interface {
	Dialer(string, string, string) (proxy.DialFunc, error)
}

type DialContextFunc func(ctx context.Context, network, address string) (net.Conn, error)

func (f DialContextFunc) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
	return f(ctx, network, address)
}

func SOCKS5DialContextFuncFromEnvironment(origDialer *net.Dialer, socks5Proxy ProxyDialer) DialContextFunc {
	allProxy := os.Getenv("BOSH_ALL_PROXY")
	if len(allProxy) == 0 {
		return origDialer.DialContext
	}

	if strings.HasPrefix(allProxy, "ssh+") {
		allProxy = strings.TrimPrefix(allProxy, "ssh+")

		proxyURL, err := url.Parse(allProxy)
		if err != nil {
			return errorDialFunc(err, "Parsing BOSH_ALL_PROXY url")
		}

		queryMap, err := url.ParseQuery(proxyURL.RawQuery)
		if err != nil {
			return errorDialFunc(err, "Parsing BOSH_ALL_PROXY query params")
		}

		username := ""
		if proxyURL.User != nil {
			username = proxyURL.User.Username()
		}

		proxySSHKeyPath := queryMap.Get("private-key")
		if proxySSHKeyPath == "" {
			return errorDialFunc(
				bosherr.Error("Required query param 'private-key' not found"),
				"Parsing BOSH_ALL_PROXY query params",
			)
		}

		proxySSHKey, err := ioutil.ReadFile(proxySSHKeyPath)
		if err != nil {
			return errorDialFunc(err, "Reading private key file for SOCKS5 Proxy")
		}

		var (
			dialer proxy.DialFunc
			mut    sync.RWMutex
		)
		return func(ctx context.Context, network, address string) (net.Conn, error) {
			mut.RLock()
			haveDialer := dialer != nil
			mut.RUnlock()

			if haveDialer {
				return dialer(network, address)
			}

			mut.Lock()
			defer mut.Unlock()
			if dialer == nil {
				proxyDialer, err := socks5Proxy.Dialer(username, string(proxySSHKey), proxyURL.Host)
				if err != nil {
					return nil, bosherr.WrapErrorf(err, "Creating SOCKS5 dialer")
				}
				dialer = proxyDialer
			}
			return dialer(network, address)
		}
	}

	proxyURL, err := url.Parse(allProxy)
	if err != nil {
		return errorDialFunc(err, "Parsing BOSH_ALL_PROXY url")
	}

	proxy, err := goproxy.FromURL(proxyURL, origDialer)
	if err != nil {
		return errorDialFunc(err, "Parsing BOSH_ALL_PROXY url")
	}

	perHost := goproxy.NewPerHost(proxy, origDialer)

	noProxy := os.Getenv("no_proxy")
	if len(noProxy) != 0 {
		perHost.AddFromString(noProxy)
	}

	return perHost.DialContext
}

func errorDialFunc(err error, cause string) DialContextFunc {
	return func(ctx context.Context, network, address string) (net.Conn, error) {
		return nil, bosherr.WrapError(err, cause)
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 Sergey Safonov
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"reflect"
	"testing"

	"github.com/spoof/go-grafana/grafana"
)

func TestDashboardsService_Get(t *testing.T) {
	mux := http.NewServeMux()
	server := httptest.NewServer(mux)
	baseURL, _ := url.Parse(server.URL + "/")
	client := NewClient(baseURL, "", nil)

	slug := "slug"
	title := "title"
	mux.HandleFunc("/api/dashboards/db/"+slug, func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"dashboard": {"id": 1, "title": "`+title+`"}}`)
	})

	d, err := client.Dashboards.Get(context.Background(), slug)
	if err != nil {
		t.Fatalf("Dashboards.Get returned error: %v", err)
	}

	want := grafana.NewDashboard(title)
	want.ID = grafana.DashboardID(1)
	if !reflect.DeepEqual(d, want) {
		t.Errorf("Dashboards.Get\nreturned: %+v\nwant: %+v", d, want)
	}

}

func TestDashboardsService_Save_New(t *testing.T) {
	mux := http.NewServeMux()
	server := httptest.NewServer(mux)
	baseURL, _ := url.Parse(server.URL + "/")
	client := NewClient(baseURL, "", nil)

	slug := "slug"
	mux.HandleFunc("/api/dashboards/db", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "POST")
		fmt.Fprint(w, `{"slug": "`+slug+`", "version": 1, "status": "success"}`)
	})
	title := "title"
	mux.HandleFunc("/api/dashboards/db/"+slug, func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"dashboard": {"id": 1, "title": "`+title+`", "version": 2}}`)
	})

	d := grafana.NewDashboard(title)
	overwrite := false
	err := client.Dashboards.Save(context.Background(), d, overwrite)
	if err != nil {
		t.Fatalf("Dashboards.Save returned error: %v", err)
	}

	want := grafana.NewDashboard(title)
	want.ID = grafana.DashboardID(1)
	want.Version = 2
	if !reflect.DeepEqual(d, want) {
		t.Errorf("Dashboards.Save\nreturned: %+v\nwant: %+v", d, want)
	}

}

func TestDashboardsService_Search(t *testing.T) {
	mux := http.NewServeMux()
	server := httptest.NewServer(mux)
	baseURL, _ := url.Parse(server.URL + "/")
	client := NewClient(baseURL, "", nil)

	mux.HandleFunc("/api/search", func(w http.ResponseWriter, r *http.Request) {
		// TODO: check querystring params here
		testMethod(t, r, "GET")
		fmt.Fprint(w, `[{"id": 1}, {"id": 2}]`)
	})

	opt := &DashboardSearchOptions{
		Query:     "q",
		Tags:      []string{"tag1", "tag2"},
		IsStarred: true,
		Limit:     10,
	}
	dashboards, err := client.Dashboards.Search(context.Background(), opt)
	if err != nil {
		t.Fatalf("Dashboards.Search returned error: %v", err)
	}

	want := []*DashboardHit{{ID: int64(1)}, {ID: int64(2)}}
	if !reflect.DeepEqual(dashboards, want) {
		t.Errorf("Dashboards.Search returned %+v, want %+v", dashboards, want)
	}
}

func testMethod(t *testing.T, r *http.Request, want string) {
	if got := r.Method; got != want {
		t.Errorf("Request method: %v, want %v", got, want)
	}
}
<commit_msg>Fixed tests<commit_after>// Copyright 2017 Sergey Safonov
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"reflect"
	"testing"

	"github.com/spoof/go-grafana/grafana"
)

func TestDashboardsService_Get(t *testing.T) {
	mux := http.NewServeMux()
	server := httptest.NewServer(mux)
	baseURL, _ := url.Parse(server.URL + "/")
	client := NewClient(baseURL, "", nil)

	slug := "slug"
	title := "title"
	mux.HandleFunc("/api/dashboards/db/"+slug, func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"dashboard": {"id": 1, "title": "`+title+`"}}`)
	})

	d, err := client.Dashboards.Get(context.Background(), slug)
	if err != nil {
		t.Fatalf("Dashboards.Get returned error: %v", err)
	}

	want := grafana.NewDashboard(title)
	want.ID = grafana.DashboardID(1)
	if !reflect.DeepEqual(d.ID, want.ID) {
		t.Errorf("Dashboards.Get\nreturned ID: %+v\nwant: %+v", d.ID, want.ID)
	}
	if !reflect.DeepEqual(d.Title, want.Title) {
		t.Errorf("Dashboards.Get\nreturned Title: %+v\nwant: %+v", d.Title, want.Title)
	}

}

func TestDashboardsService_Save_New(t *testing.T) {
	mux := http.NewServeMux()
	server := httptest.NewServer(mux)
	baseURL, _ := url.Parse(server.URL + "/")
	client := NewClient(baseURL, "", nil)

	slug := "slug"
	mux.HandleFunc("/api/dashboards/db", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "POST")
		fmt.Fprint(w, `{"slug": "`+slug+`", "version": 1, "status": "success"}`)
	})
	title := "title"
	mux.HandleFunc("/api/dashboards/db/"+slug, func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprint(w, `{"dashboard": {"id": 1, "title": "`+title+`", "version": 2}}`)
	})

	d := grafana.NewDashboard(title)
	overwrite := false
	err := client.Dashboards.Save(context.Background(), d, overwrite)
	if err != nil {
		t.Fatalf("Dashboards.Save returned error: %v", err)
	}

	want := grafana.NewDashboard(title)
	want.ID = grafana.DashboardID(1)
	want.Version = 2
	if !reflect.DeepEqual(d.ID, want.ID) {
		t.Errorf("Dashboards.Save\nreturned: %+v\nwant: %+v", d.ID, want.ID)
	}

	if !reflect.DeepEqual(d.Version, want.Version) {
		t.Errorf("Dashboards.Save\nreturned Version: %+v\nwant: %+v", d.Version, want.Version)
	}

}

func TestDashboardsService_Search(t *testing.T) {
	mux := http.NewServeMux()
	server := httptest.NewServer(mux)
	baseURL, _ := url.Parse(server.URL + "/")
	client := NewClient(baseURL, "", nil)

	mux.HandleFunc("/api/search", func(w http.ResponseWriter, r *http.Request) {
		// TODO: check querystring params here
		testMethod(t, r, "GET")
		fmt.Fprint(w, `[{"id": 1}, {"id": 2}]`)
	})

	opt := &DashboardSearchOptions{
		Query:     "q",
		Tags:      []string{"tag1", "tag2"},
		IsStarred: true,
		Limit:     10,
	}
	dashboards, err := client.Dashboards.Search(context.Background(), opt)
	if err != nil {
		t.Fatalf("Dashboards.Search returned error: %v", err)
	}

	want := []*DashboardHit{{ID: int64(1)}, {ID: int64(2)}}
	if !reflect.DeepEqual(dashboards, want) {
		t.Errorf("Dashboards.Search returned %+v, want %+v", dashboards, want)
	}
}

func testMethod(t *testing.T, r *http.Request, want string) {
	if got := r.Method; got != want {
		t.Errorf("Request method: %v, want %v", got, want)
	}
}
<|endoftext|>


<commit_before>package disk

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"sync"

	"github.com/buchgr/bazel-remote/cache"
	"github.com/djherbis/atime"
)

// lruItem is the type of the values stored in SizedLRU to keep track of items.
// It implements the SizedItem interface.
type lruItem struct {
	size      int64
	committed bool
}

func (i *lruItem) Size() int64 {
	return i.size
}

// diskCache is an implementation of the cache backed by files on a filesystem.
type diskCache struct {
	dir string
	mux *sync.RWMutex
	lru SizedLRU
}

// New returns a new instance of a filesystem-based cache rooted at `dir`,
// with a maximum size of `maxSizeBytes` bytes.
func New(dir string, maxSizeBytes int64) cache.Cache {
	// Create the directory structure.
	hexLetters := []byte("0123456789abcdef")
	for _, c1 := range hexLetters {
		for _, c2 := range hexLetters {
			subDir := string(c1) + string(c2)
			err := os.MkdirAll(filepath.Join(dir, cache.CAS.String(), subDir), os.FileMode(0744))
			if err != nil {
				log.Fatal(err)
			}
			err = os.MkdirAll(filepath.Join(dir, cache.AC.String(), subDir), os.FileMode(0744))
			if err != nil {
				log.Fatal(err)
			}
		}
	}

	// The eviction callback deletes the file from disk.
	onEvict := func(key Key, value SizedItem) {
		// Only remove committed items (as temporary files have a different filename)
		if value.(*lruItem).committed {
			err := os.Remove(filepath.Join(dir, key.(string)))
			if err != nil {
				log.Println(err)
			}
		}
	}

	cache := &diskCache{
		dir: filepath.Clean(dir),
		mux: &sync.RWMutex{},
		lru: NewSizedLRU(maxSizeBytes, onEvict),
	}

	err := cache.migrateDirectories()
	if err != nil {
		log.Fatalf("Attempting to migrate the old directory structure to the new structure failed "+
			"with error: %v", err)
	}
	err = cache.loadExistingFiles()
	if err != nil {
		log.Fatalf("Loading of existing cache entries failed due to error: %v", err)
	}

	return cache
}

func (c *diskCache) migrateDirectories() error {
	err := migrateDirectory(filepath.Join(c.dir, cache.AC.String()))
	if err != nil {
		return err
	}
	err = migrateDirectory(filepath.Join(c.dir, cache.CAS.String()))
	if err != nil {
		return err
	}
	return nil
}

func migrateDirectory(dir string) error {
	return filepath.Walk(dir, func(name string, info os.FileInfo, err error) error {
		if info.IsDir() {
			if name == dir {
				return nil
			}
			return filepath.SkipDir
		}
		hash := filepath.Base(name)
		newName := filepath.Join(filepath.Dir(name), hash[:2], hash)
		return os.Rename(name, newName)
	})
}

// loadExistingFiles lists all files in the cache directory, and adds them to the
// LRU index so that they can be served. Files are sorted by access time first,
// so that the eviction behavior is preserved across server restarts.
func (c *diskCache) loadExistingFiles() error {
	// Walk the directory tree
	type NameAndInfo struct {
		info os.FileInfo
		name string
	}
	var files []NameAndInfo
	err := filepath.Walk(c.dir, func(name string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			files = append(files, NameAndInfo{info, name})
		}
		return nil
	})
	if err != nil {
		return err
	}

	// Sort in increasing order of atime
	sort.Slice(files, func(i int, j int) bool {
		return atime.Get(files[i].info).Before(atime.Get(files[j].info))
	})

	for _, f := range files {
		relPath := f.name[len(c.dir)+1:]
		c.lru.Add(relPath, &lruItem{
			size:      f.info.Size(),
			committed: true,
		})
	}
	return nil
}

func (c *diskCache) Put(kind cache.EntryKind, hash string, size int64, r io.Reader) (err error) {
	c.mux.Lock()

	key := cacheKey(kind, hash)

	// If there's an ongoing upload (i.e. cache key is present in uncommitted state),
	// we drop the upload and discard the incoming stream. We do accept uploads
	// of existing keys, as it should happen relatively rarely (e.g. race
	// condition on the bazel side) but it's useful to overwrite poisoned items.
	if existingItem, found := c.lru.Get(key); found {
		if !existingItem.(*lruItem).committed {
			c.mux.Unlock()
			io.Copy(ioutil.Discard, r)
			return
		}
	}

	// Try to add the item to the LRU.
	newItem := &lruItem{
		size:      size,
		committed: false,
	}
	ok := c.lru.Add(key, newItem)
	c.mux.Unlock()
	if !ok {
		return &cache.Error{
			Code: http.StatusInsufficientStorage,
			Text: "The item that has been tried to insert was too big.",
		}
	}

	// By the time this function exits, we should either mark the LRU item as committed
	// (if the upload went well), or delete it. Capturing the flag variable is not very nice,
	// but this stuff is really easy to get wrong without defer().
	shouldCommit := false
	defer func() {
		c.mux.Lock()
		defer c.mux.Unlock()

		if shouldCommit {
			newItem.committed = true
		} else {
			c.lru.Remove(key)
		}
	}()

	// Download to a temporary file
	f, err := ioutil.TempFile(c.dir, "upload")
	if err != nil {
		return
	}
	defer os.Remove(f.Name())

	if kind == cache.CAS {
		hasher := sha256.New()
		if _, err = io.Copy(io.MultiWriter(f, hasher), r); err != nil {
			return
		}
		actualHash := hex.EncodeToString(hasher.Sum(nil))
		if actualHash != hash {
			err = fmt.Errorf(
				"hashsums don't match. Expected %s, found %s", key, actualHash)
			return
		}
	} else {
		if _, err = io.Copy(f, r); err != nil {
			return
		}
	}

	if err := f.Sync(); err != nil {
		return err
	}

	if err := f.Close(); err != nil {
		return err
	}

	// Rename to the final path
	filePath := cacheFilePath(kind, c.dir, hash)
	err = os.Rename(f.Name(), filePath)
	// Only commit if renaming succeeded
	if err == nil {
		// This flag is used by the defer() block above.
		shouldCommit = true
	}

	return
}

func (c *diskCache) Get(kind cache.EntryKind, hash string) (data io.ReadCloser, sizeBytes int64, err error) {
	if !c.Contains(kind, hash) {
		return
	}

	blobPath := cacheFilePath(kind, c.dir, hash)

	fileInfo, err := os.Stat(blobPath)
	if err != nil {
		return
	}
	sizeBytes = fileInfo.Size()

	data, err = os.Open(blobPath)
	if err != nil {
		return
	}

	return
}

func (c *diskCache) Contains(kind cache.EntryKind, hash string) (ok bool) {
	c.mux.Lock()
	defer c.mux.Unlock()

	val, found := c.lru.Get(cacheKey(kind, hash))
	// Uncommitted (i.e. uploading items) should be reported as not ok
	return found && val.(*lruItem).committed
}

func (c *diskCache) NumItems() int {
	c.mux.Lock()
	defer c.mux.Unlock()
	return c.lru.Len()
}

func (c *diskCache) MaxSize() int64 {
	c.mux.Lock()
	defer c.mux.Unlock()
	return c.lru.MaxSize()
}

func (c *diskCache) CurrentSize() int64 {
	c.mux.Lock()
	defer c.mux.Unlock()
	return c.lru.CurrentSize()
}

func ensureDirExists(path string) {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		err = os.MkdirAll(path, os.FileMode(0744))
		if err != nil {
			log.Fatal(err)
		}
	}
}

func cacheKey(kind cache.EntryKind, hash string) string {
	return filepath.Join(kind.String(), hash[:2], hash)
}

func cacheFilePath(kind cache.EntryKind, cacheDir string, hash string) string {
	return filepath.Join(cacheDir, cacheKey(kind, hash))
}
<commit_msg>don't delete temp file if it got moved.<commit_after>package disk

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"sync"

	"github.com/buchgr/bazel-remote/cache"
	"github.com/djherbis/atime"
)

// lruItem is the type of the values stored in SizedLRU to keep track of items.
// It implements the SizedItem interface.
type lruItem struct {
	size      int64
	committed bool
}

func (i *lruItem) Size() int64 {
	return i.size
}

// diskCache is an implementation of the cache backed by files on a filesystem.
type diskCache struct {
	dir string
	mux *sync.RWMutex
	lru SizedLRU
}

// New returns a new instance of a filesystem-based cache rooted at `dir`,
// with a maximum size of `maxSizeBytes` bytes.
func New(dir string, maxSizeBytes int64) cache.Cache {
	// Create the directory structure.
	hexLetters := []byte("0123456789abcdef")
	for _, c1 := range hexLetters {
		for _, c2 := range hexLetters {
			subDir := string(c1) + string(c2)
			err := os.MkdirAll(filepath.Join(dir, cache.CAS.String(), subDir), os.FileMode(0744))
			if err != nil {
				log.Fatal(err)
			}
			err = os.MkdirAll(filepath.Join(dir, cache.AC.String(), subDir), os.FileMode(0744))
			if err != nil {
				log.Fatal(err)
			}
		}
	}

	// The eviction callback deletes the file from disk.
	onEvict := func(key Key, value SizedItem) {
		// Only remove committed items (as temporary files have a different filename)
		if value.(*lruItem).committed {
			err := os.Remove(filepath.Join(dir, key.(string)))
			if err != nil {
				log.Println(err)
			}
		}
	}

	cache := &diskCache{
		dir: filepath.Clean(dir),
		mux: &sync.RWMutex{},
		lru: NewSizedLRU(maxSizeBytes, onEvict),
	}

	err := cache.migrateDirectories()
	if err != nil {
		log.Fatalf("Attempting to migrate the old directory structure to the new structure failed "+
			"with error: %v", err)
	}
	err = cache.loadExistingFiles()
	if err != nil {
		log.Fatalf("Loading of existing cache entries failed due to error: %v", err)
	}

	return cache
}

func (c *diskCache) migrateDirectories() error {
	err := migrateDirectory(filepath.Join(c.dir, cache.AC.String()))
	if err != nil {
		return err
	}
	err = migrateDirectory(filepath.Join(c.dir, cache.CAS.String()))
	if err != nil {
		return err
	}
	return nil
}

func migrateDirectory(dir string) error {
	return filepath.Walk(dir, func(name string, info os.FileInfo, err error) error {
		if info.IsDir() {
			if name == dir {
				return nil
			}
			return filepath.SkipDir
		}
		hash := filepath.Base(name)
		newName := filepath.Join(filepath.Dir(name), hash[:2], hash)
		return os.Rename(name, newName)
	})
}

// loadExistingFiles lists all files in the cache directory, and adds them to the
// LRU index so that they can be served. Files are sorted by access time first,
// so that the eviction behavior is preserved across server restarts.
func (c *diskCache) loadExistingFiles() error {
	// Walk the directory tree
	type NameAndInfo struct {
		info os.FileInfo
		name string
	}
	var files []NameAndInfo
	err := filepath.Walk(c.dir, func(name string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			files = append(files, NameAndInfo{info, name})
		}
		return nil
	})
	if err != nil {
		return err
	}

	// Sort in increasing order of atime
	sort.Slice(files, func(i int, j int) bool {
		return atime.Get(files[i].info).Before(atime.Get(files[j].info))
	})

	for _, f := range files {
		relPath := f.name[len(c.dir)+1:]
		c.lru.Add(relPath, &lruItem{
			size:      f.info.Size(),
			committed: true,
		})
	}
	return nil
}

func (c *diskCache) Put(kind cache.EntryKind, hash string, size int64, r io.Reader) (err error) {
	c.mux.Lock()

	key := cacheKey(kind, hash)

	// If there's an ongoing upload (i.e. cache key is present in uncommitted state),
	// we drop the upload and discard the incoming stream. We do accept uploads
	// of existing keys, as it should happen relatively rarely (e.g. race
	// condition on the bazel side) but it's useful to overwrite poisoned items.
	if existingItem, found := c.lru.Get(key); found {
		if !existingItem.(*lruItem).committed {
			c.mux.Unlock()
			io.Copy(ioutil.Discard, r)
			return
		}
	}

	// Try to add the item to the LRU.
	newItem := &lruItem{
		size:      size,
		committed: false,
	}
	ok := c.lru.Add(key, newItem)
	c.mux.Unlock()
	if !ok {
		return &cache.Error{
			Code: http.StatusInsufficientStorage,
			Text: "The item that has been tried to insert was too big.",
		}
	}

	// By the time this function exits, we should either mark the LRU item as committed
	// (if the upload went well), or delete it. Capturing the flag variable is not very nice,
	// but this stuff is really easy to get wrong without defer().
	shouldCommit := false
	defer func() {
		c.mux.Lock()
		defer c.mux.Unlock()

		if shouldCommit {
			newItem.committed = true
		} else {
			c.lru.Remove(key)
		}
	}()

	// Download to a temporary file
	f, err := ioutil.TempFile(c.dir, "upload")
	if err != nil {
		return
	}
	defer func() {
		if !shouldCommit {
			// Only delete the temp file if moving it didn't succeed.
			os.Remove(f.Name())
		}
	}()

	if kind == cache.CAS {
		hasher := sha256.New()
		if _, err = io.Copy(io.MultiWriter(f, hasher), r); err != nil {
			return
		}
		actualHash := hex.EncodeToString(hasher.Sum(nil))
		if actualHash != hash {
			err = fmt.Errorf(
				"hashsums don't match. Expected %s, found %s", key, actualHash)
			return
		}
	} else {
		if _, err = io.Copy(f, r); err != nil {
			return
		}
	}

	if err := f.Sync(); err != nil {
		return err
	}

	if err := f.Close(); err != nil {
		return err
	}

	// Rename to the final path
	filePath := cacheFilePath(kind, c.dir, hash)
	err = os.Rename(f.Name(), filePath)
	// Only commit if renaming succeeded
	if err == nil {
		// This flag is used by the defer() block above.
		shouldCommit = true
	}

	return
}

func (c *diskCache) Get(kind cache.EntryKind, hash string) (data io.ReadCloser, sizeBytes int64, err error) {
	if !c.Contains(kind, hash) {
		return
	}

	blobPath := cacheFilePath(kind, c.dir, hash)

	fileInfo, err := os.Stat(blobPath)
	if err != nil {
		return
	}
	sizeBytes = fileInfo.Size()

	data, err = os.Open(blobPath)
	if err != nil {
		return
	}

	return
}

func (c *diskCache) Contains(kind cache.EntryKind, hash string) (ok bool) {
	c.mux.Lock()
	defer c.mux.Unlock()

	val, found := c.lru.Get(cacheKey(kind, hash))
	// Uncommitted (i.e. uploading items) should be reported as not ok
	return found && val.(*lruItem).committed
}

func (c *diskCache) NumItems() int {
	c.mux.Lock()
	defer c.mux.Unlock()
	return c.lru.Len()
}

func (c *diskCache) MaxSize() int64 {
	c.mux.Lock()
	defer c.mux.Unlock()
	return c.lru.MaxSize()
}

func (c *diskCache) CurrentSize() int64 {
	c.mux.Lock()
	defer c.mux.Unlock()
	return c.lru.CurrentSize()
}

func ensureDirExists(path string) {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		err = os.MkdirAll(path, os.FileMode(0744))
		if err != nil {
			log.Fatal(err)
		}
	}
}

func cacheKey(kind cache.EntryKind, hash string) string {
	return filepath.Join(kind.String(), hash[:2], hash)
}

func cacheFilePath(kind cache.EntryKind, cacheDir string, hash string) string {
	return filepath.Join(cacheDir, cacheKey(kind, hash))
}
<|endoftext|>


<commit_before>package gorm

import (
	"errors"
	"fmt"
	"sort"
	"strings"
)

// Define callbacks for updating
func init() {
	DefaultCallback.Update().Register("gorm:assign_updating_attributes", assignUpdatingAttributesCallback)
	DefaultCallback.Update().Register("gorm:begin_transaction", beginTransactionCallback)
	DefaultCallback.Update().Register("gorm:before_update", beforeUpdateCallback)
	DefaultCallback.Update().Register("gorm:save_before_associations", saveBeforeAssociationsCallback)
	DefaultCallback.Update().Register("gorm:update_time_stamp", updateTimeStampForUpdateCallback)
	DefaultCallback.Update().Register("gorm:update", updateCallback)
	DefaultCallback.Update().Register("gorm:save_after_associations", saveAfterAssociationsCallback)
	DefaultCallback.Update().Register("gorm:after_update", afterUpdateCallback)
	DefaultCallback.Update().Register("gorm:commit_or_rollback_transaction", commitOrRollbackTransactionCallback)
}

// assignUpdatingAttributesCallback assign updating attributes to model
func assignUpdatingAttributesCallback(scope *Scope) {
	if attrs, ok := scope.InstanceGet("gorm:update_interface"); ok {
		if updateMaps, hasUpdate := scope.updatedAttrsWithValues(attrs); hasUpdate {
			scope.InstanceSet("gorm:update_attrs", updateMaps)
		} else {
			scope.SkipLeft()
		}
	}
}

// beforeUpdateCallback will invoke `BeforeSave`, `BeforeUpdate` method before updating
func beforeUpdateCallback(scope *Scope) {
	if scope.DB().HasBlockGlobalUpdate() && !scope.hasConditions() {
		scope.Err(errors.New("Missing WHERE clause while updating"))
		return
	}
	if _, ok := scope.Get("gorm:update_column"); !ok {
		if !scope.HasError() {
			scope.CallMethod("BeforeSave")
		}
		if !scope.HasError() {
			scope.CallMethod("BeforeUpdate")
		}
	}
}

// updateTimeStampForUpdateCallback will set `UpdatedAt` when updating
func updateTimeStampForUpdateCallback(scope *Scope) {
	if _, ok := scope.Get("gorm:update_column"); !ok {
		scope.SetColumn("UpdatedAt", NowFunc())
	}
}

// updateCallback the callback used to update data to database
func updateCallback(scope *Scope) {
	if !scope.HasError() {
		var sqls []string

		if updateAttrs, ok := scope.InstanceGet("gorm:update_attrs"); ok {
			// Sort the column names so that the generated SQL is the same every time.
			updateMap := updateAttrs.(map[string]interface{})
			var columns []string
			for c := range updateMap {
				columns = append(columns, c)
			}
			sort.Strings(columns)

			for _, column := range columns {
				value := updateMap[column]
				sqls = append(sqls, fmt.Sprintf("%v = %v", scope.Quote(column), scope.AddToVars(value)))
			}
		} else {
			for _, field := range scope.Fields() {
				if scope.changeableField(field) {
					if !field.IsPrimaryKey && field.IsNormal {
						if !field.IsForeignKey || !field.IsBlank || !field.HasDefaultValue {
							sqls = append(sqls, fmt.Sprintf("%v = %v", scope.Quote(field.DBName), scope.AddToVars(field.Field.Interface())))
						}
					} else if relationship := field.Relationship; relationship != nil && relationship.Kind == "belongs_to" {
						for _, foreignKey := range relationship.ForeignDBNames {
							if foreignField, ok := scope.FieldByName(foreignKey); ok && !scope.changeableField(foreignField) {
								sqls = append(sqls,
									fmt.Sprintf("%v = %v", scope.Quote(foreignField.DBName), scope.AddToVars(foreignField.Field.Interface())))
							}
						}
					}
				}
			}
		}

		var extraOption string
		if str, ok := scope.Get("gorm:update_option"); ok {
			extraOption = fmt.Sprint(str)
		}

		if len(sqls) > 0 {
			scope.Raw(fmt.Sprintf(
				"UPDATE %v SET %v%v%v",
				scope.QuotedTableName(),
				strings.Join(sqls, ", "),
				addExtraSpaceIfExist(scope.CombinedConditionSql()),
				addExtraSpaceIfExist(extraOption),
			)).Exec()
		}
	}
}

// afterUpdateCallback will invoke `AfterUpdate`, `AfterSave` method after updating
func afterUpdateCallback(scope *Scope) {
	if _, ok := scope.Get("gorm:update_column"); !ok {
		if !scope.HasError() {
			scope.CallMethod("AfterUpdate")
		}
		if !scope.HasError() {
			scope.CallMethod("AfterSave")
		}
	}
}
<commit_msg>Do not set CreatedAt if blank during Save (#2207)<commit_after>package gorm

import (
	"errors"
	"fmt"
	"sort"
	"strings"
)

// Define callbacks for updating
func init() {
	DefaultCallback.Update().Register("gorm:assign_updating_attributes", assignUpdatingAttributesCallback)
	DefaultCallback.Update().Register("gorm:begin_transaction", beginTransactionCallback)
	DefaultCallback.Update().Register("gorm:before_update", beforeUpdateCallback)
	DefaultCallback.Update().Register("gorm:save_before_associations", saveBeforeAssociationsCallback)
	DefaultCallback.Update().Register("gorm:update_time_stamp", updateTimeStampForUpdateCallback)
	DefaultCallback.Update().Register("gorm:update", updateCallback)
	DefaultCallback.Update().Register("gorm:save_after_associations", saveAfterAssociationsCallback)
	DefaultCallback.Update().Register("gorm:after_update", afterUpdateCallback)
	DefaultCallback.Update().Register("gorm:commit_or_rollback_transaction", commitOrRollbackTransactionCallback)
}

// assignUpdatingAttributesCallback assign updating attributes to model
func assignUpdatingAttributesCallback(scope *Scope) {
	if attrs, ok := scope.InstanceGet("gorm:update_interface"); ok {
		if updateMaps, hasUpdate := scope.updatedAttrsWithValues(attrs); hasUpdate {
			scope.InstanceSet("gorm:update_attrs", updateMaps)
		} else {
			scope.SkipLeft()
		}
	}
}

// beforeUpdateCallback will invoke `BeforeSave`, `BeforeUpdate` method before updating
func beforeUpdateCallback(scope *Scope) {
	if scope.DB().HasBlockGlobalUpdate() && !scope.hasConditions() {
		scope.Err(errors.New("Missing WHERE clause while updating"))
		return
	}
	if _, ok := scope.Get("gorm:update_column"); !ok {
		if !scope.HasError() {
			scope.CallMethod("BeforeSave")
		}
		if !scope.HasError() {
			scope.CallMethod("BeforeUpdate")
		}
	}
}

// updateTimeStampForUpdateCallback will set `UpdatedAt` when updating
func updateTimeStampForUpdateCallback(scope *Scope) {
	if _, ok := scope.Get("gorm:update_column"); !ok {
		scope.SetColumn("UpdatedAt", NowFunc())
	}
}

// updateCallback the callback used to update data to database
func updateCallback(scope *Scope) {
	if !scope.HasError() {
		var sqls []string

		if updateAttrs, ok := scope.InstanceGet("gorm:update_attrs"); ok {
			// Sort the column names so that the generated SQL is the same every time.
			updateMap := updateAttrs.(map[string]interface{})
			var columns []string
			for c := range updateMap {
				columns = append(columns, c)
			}
			sort.Strings(columns)

			for _, column := range columns {
				value := updateMap[column]
				sqls = append(sqls, fmt.Sprintf("%v = %v", scope.Quote(column), scope.AddToVars(value)))
			}
		} else {
			for _, field := range scope.Fields() {
				if scope.changeableField(field) {
					if !field.IsPrimaryKey && field.IsNormal && (field.Name != "CreatedAt" || !field.IsBlank) {
						if !field.IsForeignKey || !field.IsBlank || !field.HasDefaultValue {
							sqls = append(sqls, fmt.Sprintf("%v = %v", scope.Quote(field.DBName), scope.AddToVars(field.Field.Interface())))
						}
					} else if relationship := field.Relationship; relationship != nil && relationship.Kind == "belongs_to" {
						for _, foreignKey := range relationship.ForeignDBNames {
							if foreignField, ok := scope.FieldByName(foreignKey); ok && !scope.changeableField(foreignField) {
								sqls = append(sqls,
									fmt.Sprintf("%v = %v", scope.Quote(foreignField.DBName), scope.AddToVars(foreignField.Field.Interface())))
							}
						}
					}
				}
			}
		}

		var extraOption string
		if str, ok := scope.Get("gorm:update_option"); ok {
			extraOption = fmt.Sprint(str)
		}

		if len(sqls) > 0 {
			scope.Raw(fmt.Sprintf(
				"UPDATE %v SET %v%v%v",
				scope.QuotedTableName(),
				strings.Join(sqls, ", "),
				addExtraSpaceIfExist(scope.CombinedConditionSql()),
				addExtraSpaceIfExist(extraOption),
			)).Exec()
		}
	}
}

// afterUpdateCallback will invoke `AfterUpdate`, `AfterSave` method after updating
func afterUpdateCallback(scope *Scope) {
	if _, ok := scope.Get("gorm:update_column"); !ok {
		if !scope.HasError() {
			scope.CallMethod("AfterUpdate")
		}
		if !scope.HasError() {
			scope.CallMethod("AfterSave")
		}
	}
}
<|endoftext|>


<commit_before>package iban

import (
	"testing"

	"github.com/jbub/banking/country"
	"github.com/stretchr/testify/suite"
)

var (
	validCases = []struct {
		Iban                 string
		CountryCode          string
		Digit                string
		Bban                 string
		Replaced             string
		BankCode             string
		BranchCode           string
		AccountNumber        string
		IdentificationNumber string
		AccountType          string
		OwnerAccountType     string
		NationalCheckDigit   string
	}{
		{
			Iban:               "AL47212110090000000235698741",
			CountryCode:        "AL",
			Digit:              "47",
			Bban:               "212110090000000235698741",
			Replaced:           "AL00212110090000000235698741",
			BankCode:           "212",
			BranchCode:         "1100",
			AccountNumber:      "0000000235698741",
			NationalCheckDigit: "9",
		},
		{
			Iban:               "BE68539007547034",
			CountryCode:        "BE",
			Digit:              "68",
			Bban:               "539007547034",
			Replaced:           "BE00539007547034",
			BankCode:           "539",
			AccountNumber:      "0075470",
			NationalCheckDigit: "34",
		},
		{
			Iban:          "GR1601101250000000012300695",
			CountryCode:   "GR",
			Digit:         "16",
			Bban:          "01101250000000012300695",
			Replaced:      "GR0001101250000000012300695",
			BankCode:      "011",
			BranchCode:    "0125",
			AccountNumber: "0000000012300695",
		},
		{
			Iban:          "GB29NWBK60161331926819",
			CountryCode:   "GB",
			Digit:         "29",
			Bban:          "NWBK60161331926819",
			Replaced:      "GB00NWBK60161331926819",
			BankCode:      "NWBK",
			BranchCode:    "601613",
			AccountNumber: "31926819",
		},
		{
			Iban:          "SA0380000000608010167519",
			CountryCode:   "SA",
			Digit:         "03",
			Bban:          "80000000608010167519",
			Replaced:      "SA0080000000608010167519",
			BankCode:      "80",
			AccountNumber: "000000608010167519",
		},
		{
			Iban:          "CH9300762011623852957",
			CountryCode:   "CH",
			Digit:         "93",
			Bban:          "00762011623852957",
			Replaced:      "CH0000762011623852957",
			BankCode:      "00762",
			AccountNumber: "011623852957",
		},
		{
			Iban:               "TR330006100519786457841326",
			CountryCode:        "TR",
			Digit:              "33",
			Bban:               "0006100519786457841326",
			Replaced:           "TR000006100519786457841326",
			BankCode:           "00061",
			AccountNumber:      "0519786457841326",
			NationalCheckDigit: "0",
		},
		{
			Iban:               "PL60102010260000042270201111",
			CountryCode:        "PL",
			Digit:              "60",
			Bban:               "102010260000042270201111",
			Replaced:           "PL00102010260000042270201111",
			BankCode:           "102",
			BranchCode:         "0102",
			AccountNumber:      "0000042270201111",
			NationalCheckDigit: "6",
		},
		{
			Iban:          "SK0611000000002920884960",
			CountryCode:   "SK",
			Digit:         "06",
			Bban:          "11000000002920884960",
			Replaced:      "SK0011000000002920884960",
			BankCode:      "1100",
			AccountNumber: "0000002920884960",
		},
		{
			Iban:               "NO9386011117947",
			CountryCode:        "NO",
			Digit:              "93",
			Bban:               "86011117947",
			Replaced:           "NO0086011117947",
			BankCode:           "8601",
			AccountNumber:      "111794",
			NationalCheckDigit: "7",
		},
	}
	invalidCases = []struct {
		Iban  string
		Error error
	}{
		{
			Iban:  "",
			Error: ErrIbanTooShort,
		},
		{
			Iban:  "AL4721211",
			Error: ErrIbanTooShort,
		},
		{
			Iban:  "sL472121142342323",
			Error: ErrCountryCodeNotUpper,
		},
		{
			Iban:  "S4472121142342323",
			Error: ErrCountryCodeNotAlpha,
		},
		{
			Iban:  "XX472121142342323",
			Error: ErrCountryCodeNotPresent,
		},
		{
			Iban:  "CH9300762011-623852957",
			Error: ErrInvalidBbanLength,
		},
		{
			Iban:  "SK0X2121142342323",
			Error: ErrInvalidBbanLength,
		},
		{
			Iban:  "PL67102010260000042270201111",
			Error: ErrInvalidCheckDigit,
		},
		{
			Iban:  "AT61190430023457320",
			Error: ErrInvalidBbanLength,
		},
		{
			Iban:  "SK061100A000002920884960",
			Error: ErrInvalidBbanPart,
		},
	}
	invalidBbanCases = []struct {
		Iban        string
		CountryCode string
		Error       error
	}{
		{
			Iban:        "SA03800000006080101675194",
			CountryCode: "SA",
			Error:       ErrInvalidBbanLength,
		},
		{
			Iban:        "GB29NWB161331926819",
			CountryCode: "GB",
			Error:       ErrInvalidBbanLength,
		},
		{
			Iban:        "BE68507547034",
			CountryCode: "BE",
			Error:       ErrInvalidBbanLength,
		},
		{
			Iban:        "SK0611000002920884960",
			CountryCode: "SK",
			Error:       ErrInvalidBbanLength,
		},
	}
)

type ValidateTestSuite struct {
	suite.Suite
}

func (s *ValidateTestSuite) TestValidateMinLength() {
	for _, cs := range validCases {
		err := validateMinLength(cs.Iban)
		s.NoError(err, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestExtractCountryCode() {
	for _, cs := range validCases {
		code := extractCountryCode(cs.Iban)
		s.Equal(cs.CountryCode, code, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestExtractDigit() {
	for _, cs := range validCases {
		digit := extractCheckDigit(cs.Iban)
		s.Equal(cs.Digit, digit, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestExtractBban() {
	for _, cs := range validCases {
		bban := extractBban(cs.Iban)
		s.Equal(cs.Bban, bban, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestCountryCodeExists() {
	for _, cs := range validCases {
		code := extractCountryCode(cs.Iban)
		exists := country.Exists(code)
		s.True(exists, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestReplaceDigit() {
	for _, cs := range validCases {
		code := extractCountryCode(cs.Iban)
		replaced := replaceCheckDigit(cs.Iban, code)
		s.Equal(cs.Replaced, replaced, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestValidateDigit() {
	for _, cs := range validCases {
		code := extractCountryCode(cs.Iban)
		err := validateCheckDigit(cs.Iban, code)
		s.NoError(err, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestValidateCountryCode() {
	for _, cs := range validCases {
		code, err := validateCountryCode(cs.Iban)
		s.NoError(err, "IBAN = %s", cs.Iban)
		s.Equal(cs.CountryCode, code, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestCalculateMod() {
	for _, cs := range validCases {
		mod, err := calculateMod(cs.Iban)
		s.NoError(err, "IBAN = %s", cs.Iban)
		s.Equal(mod, 1, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestValidateBbanLength() {
	for _, cs := range validCases {
		structure, ok := country.GetBbanStructure(cs.CountryCode)
		s.True(ok, "IBAN = %s", cs.Iban)
		err := validateBbanLength(cs.Iban, structure)
		s.NoError(err, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestValidateBbanLengthInvalid() {
	for _, cs := range invalidBbanCases {
		structure, ok := country.GetBbanStructure(cs.CountryCode)
		s.True(ok, "IBAN = %s", cs.Iban)
		err := validateBbanLength(cs.Iban, structure)
		s.Error(err, "IBAN = %s", cs.Iban)
		s.Equal(cs.Error, ErrInvalidBbanLength, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestValidateBbanStructure() {
	for _, cs := range validCases {
		structure, ok := country.GetBbanStructure(cs.CountryCode)
		s.True(ok, "IBAN = %s", cs.Iban)
		err := validateBbanStructure(cs.Iban, structure)
		s.NoError(err, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestNew() {
	for _, cs := range validCases {
		ib, err := New(cs.Iban)
		s.NotNil(ib)
		s.NoError(err)
		s.Equal(cs.BankCode, ib.BankCode(), "IBAN = %s", cs.Iban)
		s.Equal(cs.BranchCode, ib.BranchCode(), "IBAN = %s", cs.Iban)
		s.Equal(cs.AccountNumber, ib.AccountNumber(), "IBAN = %s", cs.Iban)
		s.Equal(cs.IdentificationNumber, ib.IdentificationNumber(), "IBAN = %s", cs.Iban)
		s.Equal(cs.AccountType, ib.AccountType(), "IBAN = %s", cs.Iban)
		s.Equal(cs.OwnerAccountType, ib.OwnerAccountType(), "IBAN = %s", cs.Iban)
		s.Equal(cs.NationalCheckDigit, ib.NationalCheckDigit(), "IBAN = %s", cs.Iban)
		s.Equal(cs.Digit, ib.CheckDigit(), "IBAN = %s", cs.Iban)
		s.Equal(cs.CountryCode, ib.CountryCode(), "IBAN = %s", cs.Iban)
		s.Equal(cs.Bban, ib.Bban(), "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestMustParse() {
	for _, cs := range validCases {
		s.NotPanics(func() {
			ib := MustParse(cs.Iban)
			s.NotNil(ib, "IBAN = %s", cs.Iban)
		})
	}
}

func (s *ValidateTestSuite) TestNewInvalid() {
	for _, cs := range invalidCases {
		ib, err := New(cs.Iban)
		s.Nil(ib)
		s.Error(err, "IBAN = %s", ib)
		s.Equal(cs.Error, err, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestMustParseInvalid() {
	for _, cs := range invalidCases {
		s.Panics(func() {
			MustParse(cs.Iban)
		}, "IBAN = %s", cs.Iban)
	}
}

func TestValidateTestSuite(t *testing.T) {
	suite.Run(t, new(ValidateTestSuite))
}
<commit_msg>fix iban test<commit_after>package iban

import (
	"testing"

	"github.com/jbub/banking/country"
	"github.com/stretchr/testify/suite"
)

var (
	validCases = []struct {
		Iban                 string
		CountryCode          string
		Digit                string
		Bban                 string
		Replaced             string
		BankCode             string
		BranchCode           string
		AccountNumber        string
		IdentificationNumber string
		AccountType          string
		OwnerAccountType     string
		NationalCheckDigit   string
	}{
		{
			Iban:               "AL47212110090000000235698741",
			CountryCode:        "AL",
			Digit:              "47",
			Bban:               "212110090000000235698741",
			Replaced:           "AL00212110090000000235698741",
			BankCode:           "212",
			BranchCode:         "1100",
			AccountNumber:      "0000000235698741",
			NationalCheckDigit: "9",
		},
		{
			Iban:               "BE68539007547034",
			CountryCode:        "BE",
			Digit:              "68",
			Bban:               "539007547034",
			Replaced:           "BE00539007547034",
			BankCode:           "539",
			AccountNumber:      "0075470",
			NationalCheckDigit: "34",
		},
		{
			Iban:          "GR1601101250000000012300695",
			CountryCode:   "GR",
			Digit:         "16",
			Bban:          "01101250000000012300695",
			Replaced:      "GR0001101250000000012300695",
			BankCode:      "011",
			BranchCode:    "0125",
			AccountNumber: "0000000012300695",
		},
		{
			Iban:          "GB29NWBK60161331926819",
			CountryCode:   "GB",
			Digit:         "29",
			Bban:          "NWBK60161331926819",
			Replaced:      "GB00NWBK60161331926819",
			BankCode:      "NWBK",
			BranchCode:    "601613",
			AccountNumber: "31926819",
		},
		{
			Iban:          "SA0380000000608010167519",
			CountryCode:   "SA",
			Digit:         "03",
			Bban:          "80000000608010167519",
			Replaced:      "SA0080000000608010167519",
			BankCode:      "80",
			AccountNumber: "000000608010167519",
		},
		{
			Iban:          "CH9300762011623852957",
			CountryCode:   "CH",
			Digit:         "93",
			Bban:          "00762011623852957",
			Replaced:      "CH0000762011623852957",
			BankCode:      "00762",
			AccountNumber: "011623852957",
		},
		{
			Iban:               "TR330006100519786457841326",
			CountryCode:        "TR",
			Digit:              "33",
			Bban:               "0006100519786457841326",
			Replaced:           "TR000006100519786457841326",
			BankCode:           "00061",
			AccountNumber:      "0519786457841326",
			NationalCheckDigit: "0",
		},
		{
			Iban:               "PL60102010260000042270201111",
			CountryCode:        "PL",
			Digit:              "60",
			Bban:               "102010260000042270201111",
			Replaced:           "PL00102010260000042270201111",
			BankCode:           "102",
			BranchCode:         "0102",
			AccountNumber:      "0000042270201111",
			NationalCheckDigit: "6",
		},
		{
			Iban:          "SK0611000000002920884960",
			CountryCode:   "SK",
			Digit:         "06",
			Bban:          "11000000002920884960",
			Replaced:      "SK0011000000002920884960",
			BankCode:      "1100",
			AccountNumber: "0000002920884960",
		},
		{
			Iban:               "NO9386011117947",
			CountryCode:        "NO",
			Digit:              "93",
			Bban:               "86011117947",
			Replaced:           "NO0086011117947",
			BankCode:           "8601",
			AccountNumber:      "111794",
			NationalCheckDigit: "7",
		},
	}
	invalidCases = []struct {
		Iban  string
		Error error
	}{
		{
			Iban:  "",
			Error: ErrIbanTooShort,
		},
		{
			Iban:  "AL4721211",
			Error: ErrIbanTooShort,
		},
		{
			Iban:  "sL472121142342323",
			Error: ErrCountryCodeNotUpper,
		},
		{
			Iban:  "S4472121142342323",
			Error: ErrCountryCodeNotAlpha,
		},
		{
			Iban:  "XX472121142342323",
			Error: ErrCountryCodeNotPresent,
		},
		{
			Iban:  "CH9300762011-623852957",
			Error: ErrInvalidBbanLength,
		},
		{
			Iban:  "SK0X2121142342323",
			Error: ErrInvalidBbanLength,
		},
		{
			Iban:  "PL67102010260000042270201111",
			Error: ErrInvalidCheckDigit,
		},
		{
			Iban:  "AT61190430023457320",
			Error: ErrInvalidBbanLength,
		},
		{
			Iban:  "SK061100A000002920884960",
			Error: ErrInvalidBbanPart,
		},
	}
	invalidBbanCases = []struct {
		Iban        string
		CountryCode string
		Error       error
	}{
		{
			Iban:        "SA03800000006080101675194",
			CountryCode: "SA",
			Error:       ErrInvalidBbanLength,
		},
		{
			Iban:        "GB29NWB161331926819",
			CountryCode: "GB",
			Error:       ErrInvalidBbanLength,
		},
		{
			Iban:        "BE68507547034",
			CountryCode: "BE",
			Error:       ErrInvalidBbanLength,
		},
		{
			Iban:        "SK0611000002920884960",
			CountryCode: "SK",
			Error:       ErrInvalidBbanLength,
		},
	}
)

type ValidateTestSuite struct {
	suite.Suite
}

func (s *ValidateTestSuite) TestValidateMinLength() {
	for _, cs := range validCases {
		err := validateMinLength(cs.Iban)
		s.NoError(err, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestExtractCountryCode() {
	for _, cs := range validCases {
		code := extractCountryCode(cs.Iban)
		s.Equal(cs.CountryCode, code, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestExtractDigit() {
	for _, cs := range validCases {
		digit := extractCheckDigit(cs.Iban)
		s.Equal(cs.Digit, digit, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestExtractBban() {
	for _, cs := range validCases {
		bban := extractBban(cs.Iban)
		s.Equal(cs.Bban, bban, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestCountryCodeExists() {
	for _, cs := range validCases {
		code := extractCountryCode(cs.Iban)
		exists := country.Exists(code)
		s.True(exists, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestReplaceDigit() {
	for _, cs := range validCases {
		code := extractCountryCode(cs.Iban)
		replaced := replaceCheckDigit(cs.Iban, code)
		s.Equal(cs.Replaced, replaced, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestValidateDigit() {
	for _, cs := range validCases {
		code := extractCountryCode(cs.Iban)
		err := validateCheckDigit(cs.Iban, code)
		s.NoError(err, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestValidateCountryCode() {
	for _, cs := range validCases {
		code, err := validateCountryCode(cs.Iban)
		s.NoError(err, "IBAN = %s", cs.Iban)
		s.Equal(cs.CountryCode, code, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestCalculateMod() {
	for _, cs := range validCases {
		mod, err := calculateMod(cs.Iban)
		s.NoError(err, "IBAN = %s", cs.Iban)
		s.Equal(mod, int64(1), "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestValidateBbanLength() {
	for _, cs := range validCases {
		structure, ok := country.GetBbanStructure(cs.CountryCode)
		s.True(ok, "IBAN = %s", cs.Iban)
		err := validateBbanLength(cs.Iban, structure)
		s.NoError(err, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestValidateBbanLengthInvalid() {
	for _, cs := range invalidBbanCases {
		structure, ok := country.GetBbanStructure(cs.CountryCode)
		s.True(ok, "IBAN = %s", cs.Iban)
		err := validateBbanLength(cs.Iban, structure)
		s.Error(err, "IBAN = %s", cs.Iban)
		s.Equal(cs.Error, ErrInvalidBbanLength, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestValidateBbanStructure() {
	for _, cs := range validCases {
		structure, ok := country.GetBbanStructure(cs.CountryCode)
		s.True(ok, "IBAN = %s", cs.Iban)
		err := validateBbanStructure(cs.Iban, structure)
		s.NoError(err, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestNew() {
	for _, cs := range validCases {
		ib, err := New(cs.Iban)
		s.NotNil(ib)
		s.NoError(err)
		s.Equal(cs.BankCode, ib.BankCode(), "IBAN = %s", cs.Iban)
		s.Equal(cs.BranchCode, ib.BranchCode(), "IBAN = %s", cs.Iban)
		s.Equal(cs.AccountNumber, ib.AccountNumber(), "IBAN = %s", cs.Iban)
		s.Equal(cs.IdentificationNumber, ib.IdentificationNumber(), "IBAN = %s", cs.Iban)
		s.Equal(cs.AccountType, ib.AccountType(), "IBAN = %s", cs.Iban)
		s.Equal(cs.OwnerAccountType, ib.OwnerAccountType(), "IBAN = %s", cs.Iban)
		s.Equal(cs.NationalCheckDigit, ib.NationalCheckDigit(), "IBAN = %s", cs.Iban)
		s.Equal(cs.Digit, ib.CheckDigit(), "IBAN = %s", cs.Iban)
		s.Equal(cs.CountryCode, ib.CountryCode(), "IBAN = %s", cs.Iban)
		s.Equal(cs.Bban, ib.Bban(), "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestMustParse() {
	for _, cs := range validCases {
		s.NotPanics(func() {
			ib := MustParse(cs.Iban)
			s.NotNil(ib, "IBAN = %s", cs.Iban)
		})
	}
}

func (s *ValidateTestSuite) TestNewInvalid() {
	for _, cs := range invalidCases {
		ib, err := New(cs.Iban)
		s.Nil(ib)
		s.Error(err, "IBAN = %s", ib)
		s.Equal(cs.Error, err, "IBAN = %s", cs.Iban)
	}
}

func (s *ValidateTestSuite) TestMustParseInvalid() {
	for _, cs := range invalidCases {
		s.Panics(func() {
			MustParse(cs.Iban)
		}, "IBAN = %s", cs.Iban)
	}
}

func TestValidateTestSuite(t *testing.T) {
	suite.Run(t, new(ValidateTestSuite))
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package tensorflow

import (
	"fmt"
	"strings"
)

// Shape represents the (possibly partially known) shape of a tensor that will
// be produced by an operation.
//
// The zero-value of a Shape represents a shape with an unknown number of
// dimensions.
type Shape struct {
	dims []int64
}

// ScalarShape returns a Shape representing a scalar.
func ScalarShape() Shape {
	return Shape{dims: make([]int64, 0)}
}

// MakeShape returns a Shape with the provided size of each dimension.
//
// A value of -1 implies that the size of the corresponding dimension is not
// known.
func MakeShape(shape ...int64) Shape {
	cpy := make([]int64, len(shape))
	copy(cpy, shape)
	return Shape{dims: cpy}
}

// NumDimensions returns the number of dimensions represented by s, or -1 if
// unknown.
func (s Shape) NumDimensions() int {
	if s.dims == nil {
		return -1
	}
	return len(s.dims)
}

// Size returns the size of the dim-th dimension of the shape, or -1 if it
// is unknown.
//
// REQUIRES: 0 <= dim < s.NumDimensions()
func (s Shape) Size(dim int) int64 {
	if dim < 0 || dim > s.NumDimensions() {
		return -1
	}
	return s.dims[dim]
}

// IsFullySpecified returns true iff the size of all the dimensions of s are
// known.
func (s Shape) IsFullySpecified() bool {
	if s.dims == nil {
		return false
	}
	for _, size := range s.dims {
		if size <= 1 {
			return false
		}
	}
	return true
}

// ToSlice returns the (possibly partially known) shape represented by s as a
// slice, or an error if the number of dimensions is not known.
func (s Shape) ToSlice() ([]int64, error) {
	if s.dims == nil {
		return nil, fmt.Errorf("cannot create a slice for a Shape with an unknown number of dimensions")
	}
	cpy := make([]int64, len(s.dims))
	copy(cpy, s.dims)
	return cpy, nil
}

func (s Shape) String() string {
	if s.dims == nil {
		return "?"
	}
	ret := fmt.Sprint(s.dims)
	for _, size := range s.dims {
		if size < 0 {
			ret = strings.Replace(ret, fmt.Sprint(size), "?", 1)
		}
	}
	return strings.Replace(ret, " ", ", ", -1)
}
<commit_msg>Fix shape.go's Size function.<commit_after>/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package tensorflow

import (
	"fmt"
	"strings"
)

// Shape represents the (possibly partially known) shape of a tensor that will
// be produced by an operation.
//
// The zero-value of a Shape represents a shape with an unknown number of
// dimensions.
type Shape struct {
	dims []int64
}

// ScalarShape returns a Shape representing a scalar.
func ScalarShape() Shape {
	return Shape{dims: make([]int64, 0)}
}

// MakeShape returns a Shape with the provided size of each dimension.
//
// A value of -1 implies that the size of the corresponding dimension is not
// known.
func MakeShape(shape ...int64) Shape {
	cpy := make([]int64, len(shape))
	copy(cpy, shape)
	return Shape{dims: cpy}
}

// NumDimensions returns the number of dimensions represented by s, or -1 if
// unknown.
func (s Shape) NumDimensions() int {
	if s.dims == nil {
		return -1
	}
	return len(s.dims)
}

// Size returns the size of the dim-th dimension of the shape, or -1 if it
// is unknown.
//
// REQUIRES: 0 <= dim < s.NumDimensions()
func (s Shape) Size(dim int) int64 {
	if dim < 0 || dim >= s.NumDimensions() {
		return -1
	}
	return s.dims[dim]
}

// IsFullySpecified returns true iff the size of all the dimensions of s are
// known.
func (s Shape) IsFullySpecified() bool {
	if s.dims == nil {
		return false
	}
	for _, size := range s.dims {
		if size <= 1 {
			return false
		}
	}
	return true
}

// ToSlice returns the (possibly partially known) shape represented by s as a
// slice, or an error if the number of dimensions is not known.
func (s Shape) ToSlice() ([]int64, error) {
	if s.dims == nil {
		return nil, fmt.Errorf("cannot create a slice for a Shape with an unknown number of dimensions")
	}
	cpy := make([]int64, len(s.dims))
	copy(cpy, s.dims)
	return cpy, nil
}

func (s Shape) String() string {
	if s.dims == nil {
		return "?"
	}
	ret := fmt.Sprint(s.dims)
	for _, size := range s.dims {
		if size < 0 {
			ret = strings.Replace(ret, fmt.Sprint(size), "?", 1)
		}
	}
	return strings.Replace(ret, " ", ", ", -1)
}
<|endoftext|>