<commit_before>/*
Copyright 2018 The Kubernetes Authors.

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 azure

import (
	"fmt"
	"testing"

	"github.com/Azure/azure-sdk-for-go/arm/compute"
	"github.com/stretchr/testify/assert"
)

func newTestScaleSet() *scaleSet {
	ss := newScaleSet(getTestCloud())
	return ss.(*scaleSet)
}

func setTestVirtualMachineScaleSets(ss *scaleSet, scaleSetName string, vmList []string) {
	virtualMachineScaleSetsClient := newFakeVirtualMachineScaleSetsClient()
	scaleSets := make(map[string]map[string]compute.VirtualMachineScaleSet)
	scaleSets["rg"] = map[string]compute.VirtualMachineScaleSet{
		scaleSetName: {
			Name: &scaleSetName,
		},
	}
	virtualMachineScaleSetsClient.setFakeStore(scaleSets)

	virtualMachineScaleSetVMsClient := newFakeVirtualMachineScaleSetVMsClient()
	ssVMs := make(map[string]map[string]compute.VirtualMachineScaleSetVM)
	ssVMs["rg"] = make(map[string]compute.VirtualMachineScaleSetVM)
	for i := range vmList {
		ID := fmt.Sprintf("azure:///subscriptions/script/resourceGroups/rg/providers/Microsoft.Compute/virtualMachineScaleSets/%s/virtualMachines/%d", scaleSetName, i)
		nodeName := vmList[i]
		instanceID := fmt.Sprintf("%d", i)
		vmKey := fmt.Sprintf("%s-%s", scaleSetName, nodeName)
		networkInterfaces := []compute.NetworkInterfaceReference{
			{
				ID: &nodeName,
			},
		}
		ssVMs["rg"][vmKey] = compute.VirtualMachineScaleSetVM{
			VirtualMachineScaleSetVMProperties: &compute.VirtualMachineScaleSetVMProperties{
				OsProfile: &compute.OSProfile{
					ComputerName: &nodeName,
				},
				NetworkProfile: &compute.NetworkProfile{
					NetworkInterfaces: &networkInterfaces,
				},
			},
			ID:         &ID,
			InstanceID: &instanceID,
			Location:   &ss.Cloud.Location,
		}
	}
	virtualMachineScaleSetVMsClient.setFakeStore(ssVMs)

	ss.Cloud.VirtualMachineScaleSetsClient = virtualMachineScaleSetsClient
	ss.Cloud.VirtualMachineScaleSetVMsClient = virtualMachineScaleSetVMsClient
}

func TestGetScaleSetVMInstanceID(t *testing.T) {
	tests := []struct {
		msg                string
		machineName        string
		expectError        bool
		expectedInstanceID string
	}{{
		msg:         "invalid vmss instance name",
		machineName: "vmvm",
		expectError: true,
	},
		{
			msg:                "valid vmss instance name",
			machineName:        "vm00000Z",
			expectError:        false,
			expectedInstanceID: "35",
		},
	}

	for i, test := range tests {
		instanceID, err := getScaleSetVMInstanceID(test.machineName)
		if test.expectError {
			assert.Error(t, err, fmt.Sprintf("TestCase[%d]: %s", i, test.msg))
		} else {
			assert.Equal(t, test.expectedInstanceID, instanceID, fmt.Sprintf("TestCase[%d]: %s", i, test.msg))
		}
	}
}

func TestGetInstanceIDByNodeName(t *testing.T) {
	ss := newTestScaleSet()

	testCases := []struct {
		description string
		scaleSet    string
		vmList      []string
		nodeName    string
		expected    string
		expectError bool
	}{
		{
			description: "scaleSet should get instance by node name",
			scaleSet:    "ss",
			vmList:      []string{"vm1", "vm2"},
			nodeName:    "vm1",
			expected:    "azure:///subscriptions/script/resourceGroups/rg/providers/Microsoft.Compute/virtualMachineScaleSets/ss/virtualMachines/0",
		},
		{
			description: "scaleSet should get instance by node name with upper cases hostname",
			scaleSet:    "ss",
			vmList:      []string{"VM1", "vm2"},
			nodeName:    "vm1",
			expected:    "azure:///subscriptions/script/resourceGroups/rg/providers/Microsoft.Compute/virtualMachineScaleSets/ss/virtualMachines/0",
		},
		{
			description: "scaleSet should not get instance for non-exist nodes",
			scaleSet:    "ss",
			vmList:      []string{"VM1", "vm2"},
			nodeName:    "vm3",
			expectError: true,
		},
	}

	for _, test := range testCases {
		setTestVirtualMachineScaleSets(ss, test.scaleSet, test.vmList)
		real, err := ss.GetInstanceIDByNodeName(test.nodeName)
		if test.expectError {
			assert.Error(t, err, test.description)
			continue
		}

		assert.NoError(t, err, test.description)
		assert.Equal(t, test.expected, real, test.description)
	}
}
<commit_msg>fix azure TestGetInstanceIDByNodeName data race<commit_after>/*
Copyright 2018 The Kubernetes Authors.

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 azure

import (
	"fmt"
	"testing"

	"github.com/Azure/azure-sdk-for-go/arm/compute"
	"github.com/stretchr/testify/assert"
)

func newTestScaleSet(scaleSetName string, vmList []string) *scaleSet {
	cloud := getTestCloud()
	setTestVirtualMachineCloud(cloud, scaleSetName, vmList)
	ss := newScaleSet(cloud)

	return ss.(*scaleSet)
}

func setTestVirtualMachineCloud(ss *Cloud, scaleSetName string, vmList []string) {
	virtualMachineScaleSetsClient := newFakeVirtualMachineScaleSetsClient()
	scaleSets := make(map[string]map[string]compute.VirtualMachineScaleSet)
	scaleSets["rg"] = map[string]compute.VirtualMachineScaleSet{
		scaleSetName: {
			Name: &scaleSetName,
		},
	}
	virtualMachineScaleSetsClient.setFakeStore(scaleSets)

	virtualMachineScaleSetVMsClient := newFakeVirtualMachineScaleSetVMsClient()
	ssVMs := make(map[string]map[string]compute.VirtualMachineScaleSetVM)
	ssVMs["rg"] = make(map[string]compute.VirtualMachineScaleSetVM)
	for i := range vmList {
		ID := fmt.Sprintf("azure:///subscriptions/script/resourceGroups/rg/providers/Microsoft.Compute/virtualMachineScaleSets/%s/virtualMachines/%d", scaleSetName, i)
		nodeName := vmList[i]
		instanceID := fmt.Sprintf("%d", i)
		vmKey := fmt.Sprintf("%s-%s", scaleSetName, nodeName)
		networkInterfaces := []compute.NetworkInterfaceReference{
			{
				ID: &nodeName,
			},
		}
		ssVMs["rg"][vmKey] = compute.VirtualMachineScaleSetVM{
			VirtualMachineScaleSetVMProperties: &compute.VirtualMachineScaleSetVMProperties{
				OsProfile: &compute.OSProfile{
					ComputerName: &nodeName,
				},
				NetworkProfile: &compute.NetworkProfile{
					NetworkInterfaces: &networkInterfaces,
				},
			},
			ID:         &ID,
			InstanceID: &instanceID,
			Location:   &ss.Location,
		}
	}
	virtualMachineScaleSetVMsClient.setFakeStore(ssVMs)

	ss.VirtualMachineScaleSetsClient = virtualMachineScaleSetsClient
	ss.VirtualMachineScaleSetVMsClient = virtualMachineScaleSetVMsClient
}

func TestGetScaleSetVMInstanceID(t *testing.T) {
	tests := []struct {
		msg                string
		machineName        string
		expectError        bool
		expectedInstanceID string
	}{{
		msg:         "invalid vmss instance name",
		machineName: "vmvm",
		expectError: true,
	},
		{
			msg:                "valid vmss instance name",
			machineName:        "vm00000Z",
			expectError:        false,
			expectedInstanceID: "35",
		},
	}

	for i, test := range tests {
		instanceID, err := getScaleSetVMInstanceID(test.machineName)
		if test.expectError {
			assert.Error(t, err, fmt.Sprintf("TestCase[%d]: %s", i, test.msg))
		} else {
			assert.Equal(t, test.expectedInstanceID, instanceID, fmt.Sprintf("TestCase[%d]: %s", i, test.msg))
		}
	}
}

func TestGetInstanceIDByNodeName(t *testing.T) {
	testCases := []struct {
		description string
		scaleSet    string
		vmList      []string
		nodeName    string
		expected    string
		expectError bool
	}{
		{
			description: "scaleSet should get instance by node name",
			scaleSet:    "ss",
			vmList:      []string{"vm1", "vm2"},
			nodeName:    "vm1",
			expected:    "azure:///subscriptions/script/resourceGroups/rg/providers/Microsoft.Compute/virtualMachineScaleSets/ss/virtualMachines/0",
		},
		{
			description: "scaleSet should get instance by node name with upper cases hostname",
			scaleSet:    "ss",
			vmList:      []string{"VM1", "vm2"},
			nodeName:    "vm1",
			expected:    "azure:///subscriptions/script/resourceGroups/rg/providers/Microsoft.Compute/virtualMachineScaleSets/ss/virtualMachines/0",
		},
		{
			description: "scaleSet should not get instance for non-exist nodes",
			scaleSet:    "ss",
			vmList:      []string{"VM1", "vm2"},
			nodeName:    "vm3",
			expectError: true,
		},
	}

	for _, test := range testCases {
		ss := newTestScaleSet(test.scaleSet, test.vmList)

		real, err := ss.GetInstanceIDByNodeName(test.nodeName)
		if test.expectError {
			assert.Error(t, err, test.description)
			continue
		}

		assert.NoError(t, err, test.description)
		assert.Equal(t, test.expected, real, test.description)
	}
}
<|endoftext|>


<commit_before>package images

import (
	"fmt"
	"io"
	"net/http"

	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"
)

type Visibility string
type MemberStatus string
type Status string

const (
	Accepted MemberStatus = "accepted"
	Pending               = "pending"
	Rejected              = "rejected"
	All                   = "all"
)

const (
	Public  Visibility = "public"
	Private            = "private"
	Shared             = "shared"
)

const (
	Queued        Status = "queued"
	Saving               = "saving"
	Active               = "active"
	Killed               = "killed"
	Deleted              = "deleted"
	PendingDelete        = "pending_delete"
)

// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
	ToImageListQuery() (string, error)
}

// ListOpts allows the filtering and sorting of paginated collections through
// the API. Filtering is achieved by passing in struct field values that map to
// the server attributes you want to see returned. Marker and Limit are used
// for pagination.
//http://developer.openstack.org/api-ref-image-v2.html
type ListOpts struct {
	// Integer value for the limit of values to return.
	Limit int `q:"limit"`

	// UUID of the server at which you want to set a marker.
	Marker string `q:"marker"`

	Name         string       `q:"name"`
	Visibility   Visibility   `q:"visibility"`
	MemberStatus MemberStatus `q:"member_status"`
	Owner        string       `q:"owner"`
	Status       Status       `q:"status"`
	SizeMin      int64        `q:"size_min"`
	SizeMax      int64        `q:"size_max"`
	SortKey      string       `q:"sort_key"`
	SortDir      string       `q:"sort_dir"`
	Tag          string       `q:"tag"`
}

// ToImageListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToImageListQuery() (string, error) {
	q, err := gophercloud.BuildQueryString(opts)
	if err != nil {
		return "", err
	}
	return q.String(), nil
}

// List implements image list request
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
	url := listURL(c)

	if opts != nil {
		query, err := opts.ToImageListQuery()
		if err != nil {
			return pagination.Pager{Err: err}
		}
		url += query
	}

	createPageFn := func(r pagination.PageResult) pagination.Page {
		return ImagePage{pagination.LinkedPageBase{PageResult: r}}
	}
	return pagination.NewPager(c, url, createPageFn)
}

// Create implements create image request
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
	var res CreateResult
	body, err := opts.ToImageCreateMap()
	if err != nil {
		res.Err = err
		return res
	}
	_, res.Err = client.Post(createURL(client), body, &res.Body, &gophercloud.RequestOpts{OkCodes: []int{201}})
	return res
}

// CreateOptsBuilder describes struct types that can be accepted by the Create call.
// The CreateOpts struct in this package does.
type CreateOptsBuilder interface {
	// Returns value that can be passed to json.Marshal
	ToImageCreateMap() (map[string]interface{}, error)
}

// CreateOpts implements CreateOptsBuilder
type CreateOpts struct {
	// Name [required] is the name of the new image.
	Name string

	// Id [optional] is the the image ID.
	ID string

	// Visibility [optional] defines who can see/use the image.
	Visibility *ImageVisibility

	// Tags [optional] is a set of image tags.
	Tags []string

	// ContainerFormat [optional] is the format of the
	// container. Valid values are ami, ari, aki, bare, and ovf.
	ContainerFormat string

	// DiskFormat [optional] is the format of the disk. If set,
	// valid values are ami, ari, aki, vhd, vmdk, raw, qcow2, vdi,
	// and iso.
	DiskFormat string

	// MinDiskGigabytes [optional] is the amount of disk space in
	// GB that is required to boot the image.
	MinDiskGigabytes int

	// MinRAMMegabytes [optional] is the amount of RAM in MB that
	// is required to boot the image.
	MinRAMMegabytes int

	// protected [optional] is whether the image is not deletable.
	Protected bool

	// properties [optional] is a set of properties, if any, that
	// are associated with the image.
	Properties map[string]string
}

// ToImageCreateMap assembles a request body based on the contents of
// a CreateOpts.
func (opts CreateOpts) ToImageCreateMap() (map[string]interface{}, error) {
	body := map[string]interface{}{}
	if opts.Name == "" {
		return body, fmt.Errorf("'Name' field is requered, but is not set (was: %v)'", opts.Name)
	}

	body["name"] = opts.Name
	if opts.ID != "" {
		body["id"] = opts.ID
	}
	if opts.Visibility != nil {
		body["visibility"] = opts.Visibility
	}
	if opts.Tags != nil {
		body["tags"] = opts.Tags
	}
	if opts.ContainerFormat != "" {
		body["container_format"] = opts.ContainerFormat
	}
	if opts.DiskFormat != "" {
		body["disk_format"] = opts.DiskFormat
	}
	if opts.MinDiskGigabytes != 0 {
		body["min_disk"] = opts.MinDiskGigabytes
	}
	if opts.MinRAMMegabytes != 0 {
		body["min_ram"] = opts.MinRAMMegabytes

	}

	body["protected"] = opts.Protected

	if opts.Properties != nil {
		body["properties"] = opts.Properties
	}
	return body, nil
}

// Delete implements image delete request
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
	var res DeleteResult
	_, res.Err = client.Delete(deleteURL(client, id), &gophercloud.RequestOpts{
		OkCodes: []int{204},
	})
	return res
}

// Get implements image get request
func Get(client *gophercloud.ServiceClient, id string) GetResult {
	var res GetResult
	client.Get(getURL(client, id), &res.Body, nil)
	return res
}

// Update implements image updated request
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
	var res UpdateResult
	reqBody := opts.ToImageUpdateMap()

	_, res.Err = client.Patch(updateURL(client, id), reqBody, &res.Body, &gophercloud.RequestOpts{
		OkCodes:     []int{200},
		MoreHeaders: map[string]string{"Content-Type": "application/openstack-images-v2.1-json-patch"},
	})
	return res
}

// UpdateOptsBuilder implements UpdateOptsBuilder
type UpdateOptsBuilder interface {
	// returns value implementing json.Marshaler which when marshaled matches the patch schema:
	// http://specs.openstack.org/openstack/glance-specs/specs/api/v2/http-patch-image-api-v2.html
	ToImageUpdateMap() []interface{}
}

// UpdateOpts implements UpdateOpts
type UpdateOpts []Patch

// ToImageUpdateMap builder
func (opts UpdateOpts) ToImageUpdateMap() []interface{} {
	m := make([]interface{}, len(opts))
	for i, patch := range opts {
		patchJSON := patch.ToImagePatchMap()
		m[i] = patchJSON
	}
	return m
}

// Patch represents a single update to an existing image. Multiple updates to an image can be
// submitted at the same time.
type Patch interface {
	ToImagePatchMap() map[string]interface{}
}

// UpdateVisibility updated visibility
type UpdateVisibility struct {
	Visibility ImageVisibility
}

// ToImagePatchMap builder
func (u UpdateVisibility) ToImagePatchMap() map[string]interface{} {
	m := map[string]interface{}{}
	m["op"] = "relace"
	m["path"] = "/visibility"
	m["value"] = u.Visibility
	return m
}

// ReplaceImageName implements Patch
type ReplaceImageName struct {
	NewName string
}

// ToImagePatchMap builder
func (r ReplaceImageName) ToImagePatchMap() map[string]interface{} {
	m := map[string]interface{}{}
	m["op"] = "replace"
	m["path"] = "/name"
	m["value"] = r.NewName
	return m
}

// ReplaceImageChecksum implements Patch
type ReplaceImageChecksum struct {
	Checksum string
}

// ReplaceImageChecksum builder
func (rc ReplaceImageChecksum) ReplaceImageChecksum() map[string]interface{} {
	m := map[string]interface{}{}
	m["op"] = "replace"
	m["path"] = "/checksum"
	m["value"] = rc.Checksum
	return m
}

// ReplaceImageTags implements Patch
type ReplaceImageTags struct {
	NewTags []string
}

// ToImagePatchMap builder
func (r ReplaceImageTags) ToImagePatchMap() map[string]interface{} {
	m := map[string]interface{}{}
	m["op"] = "replace"
	m["path"] = "/tags"
	m["value"] = r.NewTags
	return m
}

// PutImageData uploads image file
func PutImageData(client *gophercloud.ServiceClient, id string, data io.ReadSeeker) PutImageDataResult {
	var res PutImageDataResult

	_, res.Err = client.Put(imageDataURL(client, id), data, nil, &gophercloud.RequestOpts{
		MoreHeaders: map[string]string{"Content-Type": "application/octet-stream"},
		OkCodes:     []int{204},
	})

	return res
}

// GetImageData retrieves file
func GetImageData(client *gophercloud.ServiceClient, id string) GetImageDataResult {
	var res GetImageDataResult

	var resp *http.Response
	resp, res.Err = client.Get(imageDataURL(client, id), nil, &gophercloud.RequestOpts{
		OkCodes: []int{200},
	})

	res.Body = resp.Body

	return res
}
<commit_msg>imageservice: Return client.Get() error if any<commit_after>package images

import (
	"fmt"
	"io"
	"net/http"

	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"
)

type Visibility string
type MemberStatus string
type Status string

const (
	Accepted MemberStatus = "accepted"
	Pending               = "pending"
	Rejected              = "rejected"
	All                   = "all"
)

const (
	Public  Visibility = "public"
	Private            = "private"
	Shared             = "shared"
)

const (
	Queued        Status = "queued"
	Saving               = "saving"
	Active               = "active"
	Killed               = "killed"
	Deleted              = "deleted"
	PendingDelete        = "pending_delete"
)

// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
	ToImageListQuery() (string, error)
}

// ListOpts allows the filtering and sorting of paginated collections through
// the API. Filtering is achieved by passing in struct field values that map to
// the server attributes you want to see returned. Marker and Limit are used
// for pagination.
//http://developer.openstack.org/api-ref-image-v2.html
type ListOpts struct {
	// Integer value for the limit of values to return.
	Limit int `q:"limit"`

	// UUID of the server at which you want to set a marker.
	Marker string `q:"marker"`

	Name         string       `q:"name"`
	Visibility   Visibility   `q:"visibility"`
	MemberStatus MemberStatus `q:"member_status"`
	Owner        string       `q:"owner"`
	Status       Status       `q:"status"`
	SizeMin      int64        `q:"size_min"`
	SizeMax      int64        `q:"size_max"`
	SortKey      string       `q:"sort_key"`
	SortDir      string       `q:"sort_dir"`
	Tag          string       `q:"tag"`
}

// ToImageListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToImageListQuery() (string, error) {
	q, err := gophercloud.BuildQueryString(opts)
	if err != nil {
		return "", err
	}
	return q.String(), nil
}

// List implements image list request
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
	url := listURL(c)

	if opts != nil {
		query, err := opts.ToImageListQuery()
		if err != nil {
			return pagination.Pager{Err: err}
		}
		url += query
	}

	createPageFn := func(r pagination.PageResult) pagination.Page {
		return ImagePage{pagination.LinkedPageBase{PageResult: r}}
	}
	return pagination.NewPager(c, url, createPageFn)
}

// Create implements create image request
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
	var res CreateResult
	body, err := opts.ToImageCreateMap()
	if err != nil {
		res.Err = err
		return res
	}
	_, res.Err = client.Post(createURL(client), body, &res.Body, &gophercloud.RequestOpts{OkCodes: []int{201}})
	return res
}

// CreateOptsBuilder describes struct types that can be accepted by the Create call.
// The CreateOpts struct in this package does.
type CreateOptsBuilder interface {
	// Returns value that can be passed to json.Marshal
	ToImageCreateMap() (map[string]interface{}, error)
}

// CreateOpts implements CreateOptsBuilder
type CreateOpts struct {
	// Name [required] is the name of the new image.
	Name string

	// Id [optional] is the the image ID.
	ID string

	// Visibility [optional] defines who can see/use the image.
	Visibility *ImageVisibility

	// Tags [optional] is a set of image tags.
	Tags []string

	// ContainerFormat [optional] is the format of the
	// container. Valid values are ami, ari, aki, bare, and ovf.
	ContainerFormat string

	// DiskFormat [optional] is the format of the disk. If set,
	// valid values are ami, ari, aki, vhd, vmdk, raw, qcow2, vdi,
	// and iso.
	DiskFormat string

	// MinDiskGigabytes [optional] is the amount of disk space in
	// GB that is required to boot the image.
	MinDiskGigabytes int

	// MinRAMMegabytes [optional] is the amount of RAM in MB that
	// is required to boot the image.
	MinRAMMegabytes int

	// protected [optional] is whether the image is not deletable.
	Protected bool

	// properties [optional] is a set of properties, if any, that
	// are associated with the image.
	Properties map[string]string
}

// ToImageCreateMap assembles a request body based on the contents of
// a CreateOpts.
func (opts CreateOpts) ToImageCreateMap() (map[string]interface{}, error) {
	body := map[string]interface{}{}
	if opts.Name == "" {
		return body, fmt.Errorf("'Name' field is requered, but is not set (was: %v)'", opts.Name)
	}

	body["name"] = opts.Name
	if opts.ID != "" {
		body["id"] = opts.ID
	}
	if opts.Visibility != nil {
		body["visibility"] = opts.Visibility
	}
	if opts.Tags != nil {
		body["tags"] = opts.Tags
	}
	if opts.ContainerFormat != "" {
		body["container_format"] = opts.ContainerFormat
	}
	if opts.DiskFormat != "" {
		body["disk_format"] = opts.DiskFormat
	}
	if opts.MinDiskGigabytes != 0 {
		body["min_disk"] = opts.MinDiskGigabytes
	}
	if opts.MinRAMMegabytes != 0 {
		body["min_ram"] = opts.MinRAMMegabytes

	}

	body["protected"] = opts.Protected

	if opts.Properties != nil {
		body["properties"] = opts.Properties
	}
	return body, nil
}

// Delete implements image delete request
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
	var res DeleteResult
	_, res.Err = client.Delete(deleteURL(client, id), &gophercloud.RequestOpts{
		OkCodes: []int{204},
	})
	return res
}

// Get implements image get request
func Get(client *gophercloud.ServiceClient, id string) GetResult {
	var res GetResult
	_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
	return res
}

// Update implements image updated request
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
	var res UpdateResult
	reqBody := opts.ToImageUpdateMap()

	_, res.Err = client.Patch(updateURL(client, id), reqBody, &res.Body, &gophercloud.RequestOpts{
		OkCodes:     []int{200},
		MoreHeaders: map[string]string{"Content-Type": "application/openstack-images-v2.1-json-patch"},
	})
	return res
}

// UpdateOptsBuilder implements UpdateOptsBuilder
type UpdateOptsBuilder interface {
	// returns value implementing json.Marshaler which when marshaled matches the patch schema:
	// http://specs.openstack.org/openstack/glance-specs/specs/api/v2/http-patch-image-api-v2.html
	ToImageUpdateMap() []interface{}
}

// UpdateOpts implements UpdateOpts
type UpdateOpts []Patch

// ToImageUpdateMap builder
func (opts UpdateOpts) ToImageUpdateMap() []interface{} {
	m := make([]interface{}, len(opts))
	for i, patch := range opts {
		patchJSON := patch.ToImagePatchMap()
		m[i] = patchJSON
	}
	return m
}

// Patch represents a single update to an existing image. Multiple updates to an image can be
// submitted at the same time.
type Patch interface {
	ToImagePatchMap() map[string]interface{}
}

// UpdateVisibility updated visibility
type UpdateVisibility struct {
	Visibility ImageVisibility
}

// ToImagePatchMap builder
func (u UpdateVisibility) ToImagePatchMap() map[string]interface{} {
	m := map[string]interface{}{}
	m["op"] = "relace"
	m["path"] = "/visibility"
	m["value"] = u.Visibility
	return m
}

// ReplaceImageName implements Patch
type ReplaceImageName struct {
	NewName string
}

// ToImagePatchMap builder
func (r ReplaceImageName) ToImagePatchMap() map[string]interface{} {
	m := map[string]interface{}{}
	m["op"] = "replace"
	m["path"] = "/name"
	m["value"] = r.NewName
	return m
}

// ReplaceImageChecksum implements Patch
type ReplaceImageChecksum struct {
	Checksum string
}

// ReplaceImageChecksum builder
func (rc ReplaceImageChecksum) ReplaceImageChecksum() map[string]interface{} {
	m := map[string]interface{}{}
	m["op"] = "replace"
	m["path"] = "/checksum"
	m["value"] = rc.Checksum
	return m
}

// ReplaceImageTags implements Patch
type ReplaceImageTags struct {
	NewTags []string
}

// ToImagePatchMap builder
func (r ReplaceImageTags) ToImagePatchMap() map[string]interface{} {
	m := map[string]interface{}{}
	m["op"] = "replace"
	m["path"] = "/tags"
	m["value"] = r.NewTags
	return m
}

// PutImageData uploads image file
func PutImageData(client *gophercloud.ServiceClient, id string, data io.ReadSeeker) PutImageDataResult {
	var res PutImageDataResult

	_, res.Err = client.Put(imageDataURL(client, id), data, nil, &gophercloud.RequestOpts{
		MoreHeaders: map[string]string{"Content-Type": "application/octet-stream"},
		OkCodes:     []int{204},
	})

	return res
}

// GetImageData retrieves file
func GetImageData(client *gophercloud.ServiceClient, id string) GetImageDataResult {
	var res GetImageDataResult

	var resp *http.Response
	resp, res.Err = client.Get(imageDataURL(client, id), nil, &gophercloud.RequestOpts{
		OkCodes: []int{200},
	})

	res.Body = resp.Body

	return res
}
<|endoftext|>


<commit_before>package gpg

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"github.com/hashicorp/vault/logical"
	"github.com/hashicorp/vault/logical/framework"
	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/armor"
	"golang.org/x/crypto/openpgp/packet"
	"io"
	"strings"
)

func pathListKeys(b *backend) *framework.Path {
	return &framework.Path{
		Pattern: "keys/?$",
		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.ListOperation: b.pathKeyList,
		},
		HelpSynopsis:    pathPolicyHelpSyn,
		HelpDescription: pathPolicyHelpDesc,
	}
}

func pathKeys(b *backend) *framework.Path {
	return &framework.Path{
		Pattern: "keys/" + framework.GenericNameRegex("name"),
		Fields: map[string]*framework.FieldSchema{
			"name": {
				Type:        framework.TypeString,
				Description: "Name of the key.",
			},
			"real_name": {
				Type:        framework.TypeString,
				Description: "The real name of the identity associated with the generated GPG key. Must not contain any of \"()<>\x00\". Only used if generate is false.",
			},
			"email": {
				Type:        framework.TypeString,
				Description: "The email of the identity associated with the generated GPG key. Must not contain any of \"()<>\x00\". Only used if generate is false.",
			},
			"comment": {
				Type:        framework.TypeString,
				Description: "The comment of the identity associated with the generated GPG key. Must not contain any of \"()<>\x00\". Only used if generate is false.",
			},
			"key_bits": {
				Type:        framework.TypeInt,
				Default:     2048,
				Description: "The number of bits to use. Only used if generate is false.",
			},
			"key": {
				Type:        framework.TypeString,
				Description: "The ASCII-armored GPG key to use. Only used if generate is false.",
			},
			"exportable": {
				Type:        framework.TypeBool,
				Description: "Enables the key to be exportable.",
			},
			"generate": {
				Type:        framework.TypeBool,
				Default:     true,
				Description: "Determines if a key should be generated by Vault or if a key is being passed from another service.",
			},
		},
		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.ReadOperation:   b.pathKeyRead,
			logical.UpdateOperation: b.pathKeyCreate,
			logical.DeleteOperation: b.pathKeyDelete,
		},
		HelpSynopsis:    pathPolicyHelpSyn,
		HelpDescription: pathPolicyHelpDesc,
	}
}

func (b *backend) key(s logical.Storage, name string) (*keyEntry, error) {
	entry, err := s.Get("key/" + name)
	if err != nil {
		return nil, err
	}
	if entry == nil {
		return nil, nil
	}

	var result keyEntry
	if err := entry.DecodeJSON(&result); err != nil {
		return nil, err
	}

	return &result, nil
}

func (b *backend) entity(entry *keyEntry) (*openpgp.Entity, error) {
	r := bytes.NewReader(entry.SerializedKey)
	el, err := openpgp.ReadKeyRing(r)
	if err != nil {
		return nil, err
	}

	return el[0], nil
}

func serializePrivateWithoutSigning(w io.Writer, e *openpgp.Entity) (err error) {
	foundPrivateKey := false

	if e.PrivateKey != nil {
		foundPrivateKey = true
		err = e.PrivateKey.Serialize(w)
		if err != nil {
			return
		}
	}
	for _, ident := range e.Identities {
		err = ident.UserId.Serialize(w)
		if err != nil {
			return
		}
		err = ident.SelfSignature.Serialize(w)
		if err != nil {
			return
		}
	}
	for _, subkey := range e.Subkeys {
		if subkey.PrivateKey != nil {
			foundPrivateKey = true
			err = subkey.PrivateKey.Serialize(w)
			if err != nil {
				return
			}
		}
		err = subkey.Sig.Serialize(w)
		if err != nil {
			return
		}
	}

	if !foundPrivateKey {
		return fmt.Errorf("No private key has been found")
	}

	return nil
}

func (b *backend) pathKeyRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	entry, err := b.key(req.Storage, data.Get("name").(string))
	if err != nil {
		return nil, err
	}
	if entry == nil {
		return nil, nil
	}
	entity, err := b.entity(entry)
	if err != nil {
		return nil, err
	}

	var buf bytes.Buffer
	w, err := armor.Encode(&buf, openpgp.PublicKeyType, nil)
	err = entity.Serialize(w)
	w.Close()
	if err != nil {
		return nil, err
	}

	return &logical.Response{
		Data: map[string]interface{}{
			"fingerprint": hex.EncodeToString(entity.PrimaryKey.Fingerprint[:]),
			"public_key":  buf.String(),
			"exportable":  entry.Exportable,
		},
	}, nil
}

func (b *backend) pathKeyCreate(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	name := data.Get("name").(string)
	realName := data.Get("real_name").(string)
	email := data.Get("email").(string)
	comment := data.Get("comment").(string)
	keyBits := data.Get("key_bits").(int)
	exportable := data.Get("exportable").(bool)
	generate := data.Get("generate").(bool)
	key := data.Get("key").(string)

	var buf bytes.Buffer
	switch generate {
	case true:
		if keyBits < 2048 {
			return logical.ErrorResponse("Keys < 2048 bits are unsafe and not supported"), nil
		}
		config := packet.Config{
			RSABits: keyBits,
		}
		entity, err := openpgp.NewEntity(realName, comment, email, &config)
		if err != nil {
			return nil, err
		}
		err = entity.SerializePrivate(&buf, nil)
		if err != nil {
			return nil, err
		}
	default:
		if key == "" {
			return logical.ErrorResponse("the key value is required for generated keys"), nil
		}
		el, err := openpgp.ReadArmoredKeyRing(strings.NewReader(key))
		if err != nil {
			return logical.ErrorResponse(err.Error()), nil
		}
		err = serializePrivateWithoutSigning(&buf, el[0])
		if err != nil {
			return logical.ErrorResponse("the key could not be serialized, is a private key present?"), nil
		}
	}

	entry, err := logical.StorageEntryJSON("key/"+name, &keyEntry{
		SerializedKey: buf.Bytes(),
		Exportable:    exportable,
	})
	if err != nil {
		return nil, err
	}
	if err := req.Storage.Put(entry); err != nil {
		return nil, err
	}
	return nil, nil
}

func (b *backend) pathKeyDelete(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	err := req.Storage.Delete("key/" + data.Get("name").(string))
	if err != nil {
		return nil, err
	}
	return nil, nil
}

func (b *backend) pathKeyList(
	req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
	entries, err := req.Storage.List("key/")
	if err != nil {
		return nil, err
	}
	return logical.ListResponse(entries), nil
}

type keyEntry struct {
	SerializedKey []byte
	Exportable    bool
}

const pathPolicyHelpSyn = "Managed named GPG keys"
const pathPolicyHelpDesc = `
This path is used to manage the named GPG keys that are available.
Doing a write with no value against a new named key will create
it using a randomly generated key.
`
<commit_msg>Fix inline documentation about key_bits param usage<commit_after>package gpg

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"github.com/hashicorp/vault/logical"
	"github.com/hashicorp/vault/logical/framework"
	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/armor"
	"golang.org/x/crypto/openpgp/packet"
	"io"
	"strings"
)

func pathListKeys(b *backend) *framework.Path {
	return &framework.Path{
		Pattern: "keys/?$",
		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.ListOperation: b.pathKeyList,
		},
		HelpSynopsis:    pathPolicyHelpSyn,
		HelpDescription: pathPolicyHelpDesc,
	}
}

func pathKeys(b *backend) *framework.Path {
	return &framework.Path{
		Pattern: "keys/" + framework.GenericNameRegex("name"),
		Fields: map[string]*framework.FieldSchema{
			"name": {
				Type:        framework.TypeString,
				Description: "Name of the key.",
			},
			"real_name": {
				Type:        framework.TypeString,
				Description: "The real name of the identity associated with the generated GPG key. Must not contain any of \"()<>\x00\". Only used if generate is false.",
			},
			"email": {
				Type:        framework.TypeString,
				Description: "The email of the identity associated with the generated GPG key. Must not contain any of \"()<>\x00\". Only used if generate is false.",
			},
			"comment": {
				Type:        framework.TypeString,
				Description: "The comment of the identity associated with the generated GPG key. Must not contain any of \"()<>\x00\". Only used if generate is false.",
			},
			"key_bits": {
				Type:        framework.TypeInt,
				Default:     2048,
				Description: "The number of bits to use. Only used if generate is true.",
			},
			"key": {
				Type:        framework.TypeString,
				Description: "The ASCII-armored GPG key to use. Only used if generate is false.",
			},
			"exportable": {
				Type:        framework.TypeBool,
				Description: "Enables the key to be exportable.",
			},
			"generate": {
				Type:        framework.TypeBool,
				Default:     true,
				Description: "Determines if a key should be generated by Vault or if a key is being passed from another service.",
			},
		},
		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.ReadOperation:   b.pathKeyRead,
			logical.UpdateOperation: b.pathKeyCreate,
			logical.DeleteOperation: b.pathKeyDelete,
		},
		HelpSynopsis:    pathPolicyHelpSyn,
		HelpDescription: pathPolicyHelpDesc,
	}
}

func (b *backend) key(s logical.Storage, name string) (*keyEntry, error) {
	entry, err := s.Get("key/" + name)
	if err != nil {
		return nil, err
	}
	if entry == nil {
		return nil, nil
	}

	var result keyEntry
	if err := entry.DecodeJSON(&result); err != nil {
		return nil, err
	}

	return &result, nil
}

func (b *backend) entity(entry *keyEntry) (*openpgp.Entity, error) {
	r := bytes.NewReader(entry.SerializedKey)
	el, err := openpgp.ReadKeyRing(r)
	if err != nil {
		return nil, err
	}

	return el[0], nil
}

func serializePrivateWithoutSigning(w io.Writer, e *openpgp.Entity) (err error) {
	foundPrivateKey := false

	if e.PrivateKey != nil {
		foundPrivateKey = true
		err = e.PrivateKey.Serialize(w)
		if err != nil {
			return
		}
	}
	for _, ident := range e.Identities {
		err = ident.UserId.Serialize(w)
		if err != nil {
			return
		}
		err = ident.SelfSignature.Serialize(w)
		if err != nil {
			return
		}
	}
	for _, subkey := range e.Subkeys {
		if subkey.PrivateKey != nil {
			foundPrivateKey = true
			err = subkey.PrivateKey.Serialize(w)
			if err != nil {
				return
			}
		}
		err = subkey.Sig.Serialize(w)
		if err != nil {
			return
		}
	}

	if !foundPrivateKey {
		return fmt.Errorf("No private key has been found")
	}

	return nil
}

func (b *backend) pathKeyRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	entry, err := b.key(req.Storage, data.Get("name").(string))
	if err != nil {
		return nil, err
	}
	if entry == nil {
		return nil, nil
	}
	entity, err := b.entity(entry)
	if err != nil {
		return nil, err
	}

	var buf bytes.Buffer
	w, err := armor.Encode(&buf, openpgp.PublicKeyType, nil)
	err = entity.Serialize(w)
	w.Close()
	if err != nil {
		return nil, err
	}

	return &logical.Response{
		Data: map[string]interface{}{
			"fingerprint": hex.EncodeToString(entity.PrimaryKey.Fingerprint[:]),
			"public_key":  buf.String(),
			"exportable":  entry.Exportable,
		},
	}, nil
}

func (b *backend) pathKeyCreate(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	name := data.Get("name").(string)
	realName := data.Get("real_name").(string)
	email := data.Get("email").(string)
	comment := data.Get("comment").(string)
	keyBits := data.Get("key_bits").(int)
	exportable := data.Get("exportable").(bool)
	generate := data.Get("generate").(bool)
	key := data.Get("key").(string)

	var buf bytes.Buffer
	switch generate {
	case true:
		if keyBits < 2048 {
			return logical.ErrorResponse("Keys < 2048 bits are unsafe and not supported"), nil
		}
		config := packet.Config{
			RSABits: keyBits,
		}
		entity, err := openpgp.NewEntity(realName, comment, email, &config)
		if err != nil {
			return nil, err
		}
		err = entity.SerializePrivate(&buf, nil)
		if err != nil {
			return nil, err
		}
	default:
		if key == "" {
			return logical.ErrorResponse("the key value is required for generated keys"), nil
		}
		el, err := openpgp.ReadArmoredKeyRing(strings.NewReader(key))
		if err != nil {
			return logical.ErrorResponse(err.Error()), nil
		}
		err = serializePrivateWithoutSigning(&buf, el[0])
		if err != nil {
			return logical.ErrorResponse("the key could not be serialized, is a private key present?"), nil
		}
	}

	entry, err := logical.StorageEntryJSON("key/"+name, &keyEntry{
		SerializedKey: buf.Bytes(),
		Exportable:    exportable,
	})
	if err != nil {
		return nil, err
	}
	if err := req.Storage.Put(entry); err != nil {
		return nil, err
	}
	return nil, nil
}

func (b *backend) pathKeyDelete(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	err := req.Storage.Delete("key/" + data.Get("name").(string))
	if err != nil {
		return nil, err
	}
	return nil, nil
}

func (b *backend) pathKeyList(
	req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
	entries, err := req.Storage.List("key/")
	if err != nil {
		return nil, err
	}
	return logical.ListResponse(entries), nil
}

type keyEntry struct {
	SerializedKey []byte
	Exportable    bool
}

const pathPolicyHelpSyn = "Managed named GPG keys"
const pathPolicyHelpDesc = `
This path is used to manage the named GPG keys that are available.
Doing a write with no value against a new named key will create
it using a randomly generated key.
`
<|endoftext|>


<commit_before>// Package serieslyclient provides access to a seriesly instance.
package serieslyclient

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

// A Seriesly DB.
type Seriesly struct {
	u      *url.URL
	client *http.Client
}

// New creates a new Seriesly instance for the given URL.
func New(u string) (*Seriesly, error) {
	return NewWithClient(u, http.DefaultClient)
}

// NewWithClient creates a new Seriesly instance with the specified
// HTTP client.
func NewWithClient(u string, client *http.Client) (*Seriesly, error) {
	rvu, err := url.Parse(u)
	if err != nil {
		return nil, err
	}
	return &Seriesly{rvu, client}, nil
}

// DBInfo represents database info.
type DBInfo struct {
	DBName       string
	SpaceUsed    json.Number `json:"space_used"`
	LastSeq      json.Number `json:"last_seq"`
	HeaderPos    json.Number `json:"header_pos"`
	DocCount     json.Number `json:"doc_count"`
	DeletedCount json.Number `json:"deleted_count"`
	Error        string
}

// URL returns a copy of the Seriesly client's URL.
func (s *Seriesly) URL() *url.URL {
	rv := *s.u
	return &rv
}

// List all databases.
func (s *Seriesly) List() ([]string, error) {
	u := *s.u
	u.Path = "/_all_dbs"
	res, err := s.client.Get(u.String())
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		return nil, fmt.Errorf("HTTP error: %v", err)
	}

	rv := []string{}
	err = json.NewDecoder(res.Body).Decode(&rv)
	return rv, err
}

// DB returns a SerieslyDB instance for the given DB.
func (s *Seriesly) DB(db string) *SerieslyDB {
	return &SerieslyDB{s, db}
}

// Create creates a new database.
func (s *Seriesly) Create(db string) error {
	u := *s.u
	u.Path = "/" + db
	req, err := http.NewRequest("PUT", u.String(), nil)
	if err != nil {
		return err
	}
	res, err := s.client.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	if res.StatusCode != 201 {
		return fmt.Errorf("HTTP Error creating DB: %v", res.Status)
	}
	return nil
}

// Delete destroys a database.
func (s *Seriesly) Delete(db string) error {
	u := *s.u
	u.Path = "/" + db
	req, err := http.NewRequest("DELETE", u.String(), nil)
	if err != nil {
		return err
	}
	res, err := s.client.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		return fmt.Errorf("HTTP Error deleting DB: %v", res.Status)
	}
	return nil
}
<commit_msg>Use httputil error functions<commit_after>// Package serieslyclient provides access to a seriesly instance.
package serieslyclient

import (
	"encoding/json"
	"net/http"
	"net/url"

	"github.com/dustin/httputil"
)

// A Seriesly DB.
type Seriesly struct {
	u      *url.URL
	client *http.Client
}

// New creates a new Seriesly instance for the given URL.
func New(u string) (*Seriesly, error) {
	return NewWithClient(u, http.DefaultClient)
}

// NewWithClient creates a new Seriesly instance with the specified
// HTTP client.
func NewWithClient(u string, client *http.Client) (*Seriesly, error) {
	rvu, err := url.Parse(u)
	if err != nil {
		return nil, err
	}
	return &Seriesly{rvu, client}, nil
}

// DBInfo represents database info.
type DBInfo struct {
	DBName       string
	SpaceUsed    json.Number `json:"space_used"`
	LastSeq      json.Number `json:"last_seq"`
	HeaderPos    json.Number `json:"header_pos"`
	DocCount     json.Number `json:"doc_count"`
	DeletedCount json.Number `json:"deleted_count"`
	Error        string
}

// URL returns a copy of the Seriesly client's URL.
func (s *Seriesly) URL() *url.URL {
	rv := *s.u
	return &rv
}

// List all databases.
func (s *Seriesly) List() ([]string, error) {
	u := *s.u
	u.Path = "/_all_dbs"
	res, err := s.client.Get(u.String())
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		return nil, httputil.HTTPError(res)
	}

	rv := []string{}
	err = json.NewDecoder(res.Body).Decode(&rv)
	return rv, err
}

// DB returns a SerieslyDB instance for the given DB.
func (s *Seriesly) DB(db string) *SerieslyDB {
	return &SerieslyDB{s, db}
}

// Create creates a new database.
func (s *Seriesly) Create(db string) error {
	u := *s.u
	u.Path = "/" + db
	req, err := http.NewRequest("PUT", u.String(), nil)
	if err != nil {
		return err
	}
	res, err := s.client.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	if res.StatusCode != 201 {
		return httputil.HTTPErrorf(res, "Error creating DB: %S -- %B")
	}
	return nil
}

// Delete destroys a database.
func (s *Seriesly) Delete(db string) error {
	u := *s.u
	u.Path = "/" + db
	req, err := http.NewRequest("DELETE", u.String(), nil)
	if err != nil {
		return err
	}
	res, err := s.client.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		return httputil.HTTPErrorf(res, "Error deleting DB: %S -- %B")
	}
	return nil
}
<|endoftext|>


<commit_before>package members

import (
	"time"

	"github.com/mitchellh/mapstructure"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"
)

// ImageMember model
type ImageMember struct {
	CreatedAt time.Time `mapstructure:"-"`
	ImageID   string    `mapstructure:"image_id"`
	MemberID  string    `mapstructure:"member_id"`
	Schema    string
	// Status could be one of pending, accepted, reject
	Status    string
	UpdatedAt time.Time `mapstructure:"-"`
}

// CreateMemberResult result model
type CreateMemberResult struct {
	gophercloud.Result
}

// Extract ImageMember model from request if possible
func (cm CreateMemberResult) Extract() (*ImageMember, error) {
	if cm.Err != nil {
		return nil, cm.Err
	}
	casted := cm.Body.(map[string]interface{})
	var results ImageMember

	if err := mapstructure.Decode(casted, &results); err != nil {
		return nil, err
	}

	if t, ok := casted["created_at"].(string); ok && t != "" {
		createdAt, err := time.Parse(time.RFC3339, t)
		if err != nil {
			return &results, err
		}
		results.CreatedAt = createdAt
	}

	if t, ok := casted["updated_at"].(string); ok && t != "" {
		updatedAt, err := time.Parse(time.RFC3339, t)
		if err != nil {
			return &results, err
		}
		results.UpdatedAt = updatedAt
	}

	return &results, nil
}

// ListMembersResult model
type ListMembersResult struct {
	gophercloud.Result
}

// Extract returns list of image members
func (lm ListMembersResult) Extract() ([]ImageMember, error) {
	if lm.Err != nil {
		return nil, lm.Err
	}
	casted := lm.Body.(map[string]interface{})

	var results struct {
		ImageMembers []ImageMember `mapstructure:"members"`
	}

	err := mapstructure.Decode(casted, &results)
	return results.ImageMembers, err
}

// MemberPage is a single page of Members results.
type MemberPage struct {
	pagination.SinglePageBase
}

// ExtractMembers returns a slice of Members contained in a single page of results.
func ExtractMembers(page pagination.Page) ([]ImageMember, error) {
	casted := page.(MemberPage).Body
	var response struct {
		ImageMembers []ImageMember `mapstructure:"members"`
	}

	err := mapstructure.Decode(casted, &response)
	return response.ImageMembers, err
}

// IsEmpty determines whether or not a page of Members contains any results.
func (page MemberPage) IsEmpty() (bool, error) {
	tenants, err := ExtractMembers(page)
	if err != nil {
		return false, err
	}
	return len(tenants) == 0, nil
}

// MemberDetailsResult model
type MemberDetailsResult struct {
	gophercloud.Result
}

// Extract returns image member details
func (md MemberDetailsResult) Extract() (*ImageMember, error) {
	if md.Err != nil {
		return nil, md.Err
	}
	casted := md.Body.(map[string]interface{})

	var results ImageMember

	err := mapstructure.Decode(casted, &results)
	return &results, err

}

// MemberDeleteResult model
type MemberDeleteResult struct {
	gophercloud.Result
}

// MemberUpdateResult model
type MemberUpdateResult struct {
	CreateMemberResult
}
<commit_msg>imageservice: MemberDetails has the same semantics as CreateMembers<commit_after>package members

import (
	"time"

	"github.com/mitchellh/mapstructure"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"
)

// ImageMember model
type ImageMember struct {
	CreatedAt time.Time `mapstructure:"-"`
	ImageID   string    `mapstructure:"image_id"`
	MemberID  string    `mapstructure:"member_id"`
	Schema    string
	// Status could be one of pending, accepted, reject
	Status    string
	UpdatedAt time.Time `mapstructure:"-"`
}

// CreateMemberResult result model
type CreateMemberResult struct {
	gophercloud.Result
}

// Extract ImageMember model from request if possible
func (cm CreateMemberResult) Extract() (*ImageMember, error) {
	if cm.Err != nil {
		return nil, cm.Err
	}
	casted := cm.Body.(map[string]interface{})
	var results ImageMember

	if err := mapstructure.Decode(casted, &results); err != nil {
		return nil, err
	}

	if t, ok := casted["created_at"].(string); ok && t != "" {
		createdAt, err := time.Parse(time.RFC3339, t)
		if err != nil {
			return &results, err
		}
		results.CreatedAt = createdAt
	}

	if t, ok := casted["updated_at"].(string); ok && t != "" {
		updatedAt, err := time.Parse(time.RFC3339, t)
		if err != nil {
			return &results, err
		}
		results.UpdatedAt = updatedAt
	}

	return &results, nil
}

// ListMembersResult model
type ListMembersResult struct {
	gophercloud.Result
}

// Extract returns list of image members
func (lm ListMembersResult) Extract() ([]ImageMember, error) {
	if lm.Err != nil {
		return nil, lm.Err
	}
	casted := lm.Body.(map[string]interface{})

	var results struct {
		ImageMembers []ImageMember `mapstructure:"members"`
	}

	err := mapstructure.Decode(casted, &results)
	return results.ImageMembers, err
}

// MemberPage is a single page of Members results.
type MemberPage struct {
	pagination.SinglePageBase
}

// ExtractMembers returns a slice of Members contained in a single page of results.
func ExtractMembers(page pagination.Page) ([]ImageMember, error) {
	casted := page.(MemberPage).Body
	var response struct {
		ImageMembers []ImageMember `mapstructure:"members"`
	}

	err := mapstructure.Decode(casted, &response)
	return response.ImageMembers, err
}

// IsEmpty determines whether or not a page of Members contains any results.
func (page MemberPage) IsEmpty() (bool, error) {
	tenants, err := ExtractMembers(page)
	if err != nil {
		return false, err
	}
	return len(tenants) == 0, nil
}

// MemberDetailsResult model
type MemberDetailsResult struct {
	CreateMemberResult
}

// MemberDeleteResult model
type MemberDeleteResult struct {
	gophercloud.Result
}

// MemberUpdateResult model
type MemberUpdateResult struct {
	CreateMemberResult
}
<|endoftext|>


<commit_before>// Copyright 2017 The go-hep Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package rootio

import (
	"fmt"
	"io"
	"os"

	"github.com/pkg/errors"
)

type Reader interface {
	io.Reader
	io.ReaderAt
	io.Seeker
	io.Closer
}

type Writer interface {
	io.Writer
	io.WriterAt
	io.Seeker
	io.Closer
}

// A ROOT file is a suite of consecutive data records (TKey's) with
// the following format (see also the TKey class). If the key is
// located past the 32 bit file limit (> 2 GB) then some fields will
// be 8 instead of 4 bytes:
//    1->4            Nbytes    = Length of compressed object (in bytes)
//    5->6            Version   = TKey version identifier
//    7->10           ObjLen    = Length of uncompressed object
//    11->14          Datime    = Date and time when object was written to file
//    15->16          KeyLen    = Length of the key structure (in bytes)
//    17->18          Cycle     = Cycle of key
//    19->22 [19->26] SeekKey   = Pointer to record itself (consistency check)
//    23->26 [27->34] SeekPdir  = Pointer to directory header
//    27->27 [35->35] lname     = Number of bytes in the class name
//    28->.. [36->..] ClassName = Object Class Name
//    ..->..          lname     = Number of bytes in the object name
//    ..->..          Name      = lName bytes with the name of the object
//    ..->..          lTitle    = Number of bytes in the object title
//    ..->..          Title     = Title of the object
//    ----->          DATA      = Data bytes associated to the object
//
// The first data record starts at byte fBEGIN (currently set to kBEGIN).
// Bytes 1->kBEGIN contain the file description, when fVersion >= 1000000
// it is a large file (> 2 GB) and the offsets will be 8 bytes long and
// fUnits will be set to 8:
//    1->4            "root"      = Root file identifier
//    5->8            fVersion    = File format version
//    9->12           fBEGIN      = Pointer to first data record
//    13->16 [13->20] fEND        = Pointer to first free word at the EOF
//    17->20 [21->28] fSeekFree   = Pointer to FREE data record
//    21->24 [29->32] fNbytesFree = Number of bytes in FREE data record
//    25->28 [33->36] nfree       = Number of free data records
//    29->32 [37->40] fNbytesName = Number of bytes in TNamed at creation time
//    33->33 [41->41] fUnits      = Number of bytes for file pointers
//    34->37 [42->45] fCompress   = Compression level and algorithm
//    38->41 [46->53] fSeekInfo   = Pointer to TStreamerInfo record
//    42->45 [54->57] fNbytesInfo = Number of bytes in TStreamerInfo record
//    46->63 [58->75] fUUID       = Universal Unique ID
type File struct {
	r      Reader
	w      Writer
	seeker io.Seeker
	closer io.Closer

	id string //non-root, identifies filename, etc.

	version int32
	begin   int64

	// Remainder of record is variable length, 4 or 8 bytes per pointer
	end         int64
	seekfree    int64 // first available record
	nbytesfree  int32 // total bytes available
	nfree       int32 // total free bytes
	nbytesname  int32 // number of bytes in TNamed at creation time
	units       byte
	compression int32
	seekinfo    int64 // pointer to TStreamerInfo
	nbytesinfo  int32 // sizeof(TStreamerInfo)
	uuid        [18]byte

	dir    tdirectory // root directory of this file
	siKey  Key
	sinfos []StreamerInfo
}

// Open opens the named ROOT file for reading. If successful, methods on the
// returned file can be used for reading; the associated file descriptor
// has mode os.O_RDONLY.
func Open(path string) (*File, error) {
	fd, err := openFile(path)
	if err != nil {
		return nil, fmt.Errorf("rootio: unable to open %q (%q)", path, err.Error())
	}

	f := &File{
		r:      fd,
		seeker: fd,
		closer: fd,
		id:     path,
	}
	f.dir = tdirectory{file: f}

	err = f.readHeader()
	if err != nil {
		return nil, fmt.Errorf("rootio: failed to read header %q: %v", path, err)
	}

	return f, nil
}

// NewReader creates a new ROOT file reader.
func NewReader(r Reader, name string) (*File, error) {
	f := &File{
		r:      r,
		seeker: r,
		closer: r,
		id:     name,
	}
	f.dir = tdirectory{file: f}

	err := f.readHeader()
	if err != nil {
		return nil, fmt.Errorf("rootio: failed to read header: %v", err)
	}

	return f, nil
}

// Create creates the named ROOT file for writing.
func Create(name string) (*File, error) {
	fd, err := os.Create(name)
	if err != nil {
		return nil, fmt.Errorf("rootio: unable to create %q (%q)", name, err.Error())
	}

	f := &File{
		w:       fd,
		seeker:  fd,
		closer:  fd,
		id:      name,
		version: rootVersion,
	}
	f.dir = tdirectory{named: tnamed{name: name}, file: f}

	err = f.writeHeader()
	if err != nil {
		_ = fd.Close()
		_ = os.RemoveAll(name)
		return nil, fmt.Errorf("rootio: failed to write header %q: %v", name, err)
	}

	return f, nil
}

// Stat returns the os.FileInfo structure describing this file.
func (f *File) Stat() (os.FileInfo, error) {
	type stater interface {
		Stat() (os.FileInfo, error)
	}
	if f.r != nil {
		if st, ok := f.r.(stater); ok {
			return st.Stat()
		}
	}
	if f.w != nil {
		if st, ok := f.w.(stater); ok {
			return st.Stat()
		}
	}
	return nil, errors.Errorf("rootio: underlying file w/o os.FileInfo")
}

// Read implements io.Reader
func (f *File) Read(p []byte) (int, error) {
	return f.r.Read(p)
}

// ReadAt implements io.ReaderAt
func (f *File) ReadAt(p []byte, off int64) (int, error) {
	return f.r.ReadAt(p, off)
}

// Seek implements io.Seeker
func (f *File) Seek(offset int64, whence int) (int64, error) {
	return f.seeker.Seek(offset, whence)
}

// Version returns the ROOT version this file was created with.
func (f *File) Version() int {
	return int(f.version)
}

func (f *File) readHeader() error {

	buf := make([]byte, 64)
	if _, err := f.ReadAt(buf, 0); err != nil {
		return err
	}

	r := NewRBuffer(buf, nil, 0, nil)

	// Header

	var magic [4]byte
	if _, err := io.ReadFull(r.r, magic[:]); err != nil || string(magic[:]) != "root" {
		if err != nil {
			return fmt.Errorf("rootio: failed to read ROOT file magic header: %v", err)
		}
		return fmt.Errorf("rootio: %q is not a root file", f.id)
	}

	f.version = r.ReadI32()
	f.begin = int64(r.ReadI32())
	if f.version < 1000000 { // small file
		f.end = int64(r.ReadI32())
		f.seekfree = int64(r.ReadI32())
		f.nbytesfree = r.ReadI32()
		f.nfree = r.ReadI32()
		f.nbytesname = r.ReadI32()
		f.units = r.ReadU8()
		f.compression = r.ReadI32()
		f.seekinfo = int64(r.ReadI32())
		f.nbytesinfo = r.ReadI32()
	} else { // large files
		f.end = r.ReadI64()
		f.seekfree = r.ReadI64()
		f.nbytesfree = r.ReadI32()
		f.nfree = r.ReadI32()
		f.nbytesname = r.ReadI32()
		f.units = r.ReadU8()
		f.compression = r.ReadI32()
		f.seekinfo = r.ReadI64()
		f.nbytesinfo = r.ReadI32()
	}
	f.version %= 1000000

	if _, err := io.ReadFull(r.r, f.uuid[:]); err != nil || r.Err() != nil {
		if err != nil {
			return fmt.Errorf("rootio: failed to read ROOT's UUID file: %v", err)
		}
		return r.Err()
	}

	var err error

	err = f.dir.readDirInfo()
	if err != nil {
		return fmt.Errorf("rootio: failed to read ROOT directory infos: %v", err)
	}

	if f.seekinfo > 0 {
		err = f.readStreamerInfo()
		if err != nil {
			return fmt.Errorf("rootio: failed to read ROOT streamer infos: %v", err)
		}
	}

	err = f.dir.readKeys()
	if err != nil {
		return fmt.Errorf("rootio: failed to read ROOT file keys: %v", err)
	}

	return nil
}

func (f *File) writeHeader() error {
	panic("not implemented")
}

func (f *File) Map() {
	for _, k := range f.dir.keys {
		if k.class == "TBasket" {
			//b := k.AsBasket()
			fmt.Printf("%8s %60s %6v %6v %f\n", k.class, k.name, k.bytes-k.keylen, k.objlen, float64(k.objlen)/float64(k.bytes-k.keylen))
		} else {
			//println(k.classname, k.name, k.title)
			fmt.Printf("%8s %60s %6v %6v %f\n", k.class, k.name, k.bytes-k.keylen, k.objlen, float64(k.objlen)/float64(k.bytes-k.keylen))
		}
	}
}

func (f *File) Tell() int64 {
	where, err := f.Seek(0, io.SeekCurrent)
	if err != nil {
		panic(err)
	}
	return where
}

// Close closes the File, rendering it unusable for I/O.
// It returns an error, if any.
func (f *File) Close() error {
	for _, k := range f.dir.keys {
		k.f = nil
	}
	f.dir.keys = nil
	f.dir.file = nil
	return f.closer.Close()
}

// Keys returns the list of keys this File contains
func (f *File) Keys() []Key {
	return f.dir.keys
}

func (f *File) Name() string {
	return f.dir.Name()
}

func (f *File) Title() string {
	return f.dir.Title()
}

func (f *File) Class() string {
	return "TFile"
}

// readStreamerInfo reads the list of StreamerInfo from this file
func (f *File) readStreamerInfo() error {
	if f.seekinfo <= 0 || f.seekinfo >= f.end {
		return fmt.Errorf("rootio: invalid pointer to StreamerInfo (pos=%v end=%v)", f.seekinfo, f.end)

	}
	buf := make([]byte, int(f.nbytesinfo))
	nbytes, err := f.ReadAt(buf, f.seekinfo)
	if err != nil {
		return err
	}
	if nbytes != int(f.nbytesinfo) {
		return fmt.Errorf("rootio: requested [%v] bytes. read [%v] bytes from file", f.nbytesinfo, nbytes)
	}

	err = f.siKey.UnmarshalROOT(NewRBuffer(buf, nil, 0, nil))
	f.siKey.f = f
	if err != nil {
		return err
	}

	objs := f.siKey.Value().(List)
	f.sinfos = make([]StreamerInfo, 0, objs.Len())
	for i := 0; i < objs.Len(); i++ {
		obj, ok := objs.At(i).(StreamerInfo)
		if !ok {
			continue
		}
		f.sinfos = append(f.sinfos, obj)
		streamers.add(obj)
	}
	return nil
}

// StreamerInfos returns the list of StreamerInfos of this file.
func (f *File) StreamerInfos() []StreamerInfo {
	return f.sinfos
}

// StreamerInfo returns the StreamerInfo with name of this file and an error if any.
func (f *File) StreamerInfo(name string) (StreamerInfo, error) {
	if len(f.sinfos) == 0 {
		return nil, fmt.Errorf("rootio: no streamer for %q (no streamerinfo list)", name)
	}

	for _, si := range f.sinfos {
		if si.Name() == name {
			return si, nil
		}
	}

	// no streamer for "name" in that file.
	// try whether "name" isn't actually std::vector<T> and a streamer
	// for T is in that file.
	o := reStdVector.FindStringSubmatch(name)
	if o != nil {
		si := stdvecSIFrom(name, o[1], f)
		if si != nil {
			f.sinfos = append(f.sinfos, si)
			streamers.add(si)
			return si, nil
		}
	}

	return nil, fmt.Errorf("rootio: no streamer for %q", name)
}

// Get returns the object identified by namecycle
//   namecycle has the format name;cycle
//   name  = * is illegal, cycle = * is illegal
//   cycle = "" or cycle = 9999 ==> apply to a memory object
//
//   examples:
//     foo   : get object named foo in memory
//             if object is not in memory, try with highest cycle from file
//     foo;1 : get cycle 1 of foo on file
func (f *File) Get(namecycle string) (Object, error) {
	return f.dir.Get(namecycle)
}

var (
	_ Object    = (*File)(nil)
	_ Named     = (*File)(nil)
	_ Directory = (*File)(nil)
)
<commit_msg>rootio: remove File.Map method<commit_after>// Copyright 2017 The go-hep Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package rootio

import (
	"fmt"
	"io"
	"os"

	"github.com/pkg/errors"
)

type Reader interface {
	io.Reader
	io.ReaderAt
	io.Seeker
	io.Closer
}

type Writer interface {
	io.Writer
	io.WriterAt
	io.Seeker
	io.Closer
}

// A ROOT file is a suite of consecutive data records (TKey's) with
// the following format (see also the TKey class). If the key is
// located past the 32 bit file limit (> 2 GB) then some fields will
// be 8 instead of 4 bytes:
//    1->4            Nbytes    = Length of compressed object (in bytes)
//    5->6            Version   = TKey version identifier
//    7->10           ObjLen    = Length of uncompressed object
//    11->14          Datime    = Date and time when object was written to file
//    15->16          KeyLen    = Length of the key structure (in bytes)
//    17->18          Cycle     = Cycle of key
//    19->22 [19->26] SeekKey   = Pointer to record itself (consistency check)
//    23->26 [27->34] SeekPdir  = Pointer to directory header
//    27->27 [35->35] lname     = Number of bytes in the class name
//    28->.. [36->..] ClassName = Object Class Name
//    ..->..          lname     = Number of bytes in the object name
//    ..->..          Name      = lName bytes with the name of the object
//    ..->..          lTitle    = Number of bytes in the object title
//    ..->..          Title     = Title of the object
//    ----->          DATA      = Data bytes associated to the object
//
// The first data record starts at byte fBEGIN (currently set to kBEGIN).
// Bytes 1->kBEGIN contain the file description, when fVersion >= 1000000
// it is a large file (> 2 GB) and the offsets will be 8 bytes long and
// fUnits will be set to 8:
//    1->4            "root"      = Root file identifier
//    5->8            fVersion    = File format version
//    9->12           fBEGIN      = Pointer to first data record
//    13->16 [13->20] fEND        = Pointer to first free word at the EOF
//    17->20 [21->28] fSeekFree   = Pointer to FREE data record
//    21->24 [29->32] fNbytesFree = Number of bytes in FREE data record
//    25->28 [33->36] nfree       = Number of free data records
//    29->32 [37->40] fNbytesName = Number of bytes in TNamed at creation time
//    33->33 [41->41] fUnits      = Number of bytes for file pointers
//    34->37 [42->45] fCompress   = Compression level and algorithm
//    38->41 [46->53] fSeekInfo   = Pointer to TStreamerInfo record
//    42->45 [54->57] fNbytesInfo = Number of bytes in TStreamerInfo record
//    46->63 [58->75] fUUID       = Universal Unique ID
type File struct {
	r      Reader
	w      Writer
	seeker io.Seeker
	closer io.Closer

	id string //non-root, identifies filename, etc.

	version int32
	begin   int64

	// Remainder of record is variable length, 4 or 8 bytes per pointer
	end         int64
	seekfree    int64 // first available record
	nbytesfree  int32 // total bytes available
	nfree       int32 // total free bytes
	nbytesname  int32 // number of bytes in TNamed at creation time
	units       byte
	compression int32
	seekinfo    int64 // pointer to TStreamerInfo
	nbytesinfo  int32 // sizeof(TStreamerInfo)
	uuid        [18]byte

	dir    tdirectory // root directory of this file
	siKey  Key
	sinfos []StreamerInfo
}

// Open opens the named ROOT file for reading. If successful, methods on the
// returned file can be used for reading; the associated file descriptor
// has mode os.O_RDONLY.
func Open(path string) (*File, error) {
	fd, err := openFile(path)
	if err != nil {
		return nil, fmt.Errorf("rootio: unable to open %q (%q)", path, err.Error())
	}

	f := &File{
		r:      fd,
		seeker: fd,
		closer: fd,
		id:     path,
	}
	f.dir = tdirectory{file: f}

	err = f.readHeader()
	if err != nil {
		return nil, fmt.Errorf("rootio: failed to read header %q: %v", path, err)
	}

	return f, nil
}

// NewReader creates a new ROOT file reader.
func NewReader(r Reader, name string) (*File, error) {
	f := &File{
		r:      r,
		seeker: r,
		closer: r,
		id:     name,
	}
	f.dir = tdirectory{file: f}

	err := f.readHeader()
	if err != nil {
		return nil, fmt.Errorf("rootio: failed to read header: %v", err)
	}

	return f, nil
}

// Create creates the named ROOT file for writing.
func Create(name string) (*File, error) {
	fd, err := os.Create(name)
	if err != nil {
		return nil, fmt.Errorf("rootio: unable to create %q (%q)", name, err.Error())
	}

	f := &File{
		w:       fd,
		seeker:  fd,
		closer:  fd,
		id:      name,
		version: rootVersion,
	}
	f.dir = tdirectory{named: tnamed{name: name}, file: f}

	err = f.writeHeader()
	if err != nil {
		_ = fd.Close()
		_ = os.RemoveAll(name)
		return nil, fmt.Errorf("rootio: failed to write header %q: %v", name, err)
	}

	return f, nil
}

// Stat returns the os.FileInfo structure describing this file.
func (f *File) Stat() (os.FileInfo, error) {
	type stater interface {
		Stat() (os.FileInfo, error)
	}
	if f.r != nil {
		if st, ok := f.r.(stater); ok {
			return st.Stat()
		}
	}
	if f.w != nil {
		if st, ok := f.w.(stater); ok {
			return st.Stat()
		}
	}
	return nil, errors.Errorf("rootio: underlying file w/o os.FileInfo")
}

// Read implements io.Reader
func (f *File) Read(p []byte) (int, error) {
	return f.r.Read(p)
}

// ReadAt implements io.ReaderAt
func (f *File) ReadAt(p []byte, off int64) (int, error) {
	return f.r.ReadAt(p, off)
}

// Seek implements io.Seeker
func (f *File) Seek(offset int64, whence int) (int64, error) {
	return f.seeker.Seek(offset, whence)
}

// Version returns the ROOT version this file was created with.
func (f *File) Version() int {
	return int(f.version)
}

func (f *File) readHeader() error {

	buf := make([]byte, 64)
	if _, err := f.ReadAt(buf, 0); err != nil {
		return err
	}

	r := NewRBuffer(buf, nil, 0, nil)

	// Header

	var magic [4]byte
	if _, err := io.ReadFull(r.r, magic[:]); err != nil || string(magic[:]) != "root" {
		if err != nil {
			return fmt.Errorf("rootio: failed to read ROOT file magic header: %v", err)
		}
		return fmt.Errorf("rootio: %q is not a root file", f.id)
	}

	f.version = r.ReadI32()
	f.begin = int64(r.ReadI32())
	if f.version < 1000000 { // small file
		f.end = int64(r.ReadI32())
		f.seekfree = int64(r.ReadI32())
		f.nbytesfree = r.ReadI32()
		f.nfree = r.ReadI32()
		f.nbytesname = r.ReadI32()
		f.units = r.ReadU8()
		f.compression = r.ReadI32()
		f.seekinfo = int64(r.ReadI32())
		f.nbytesinfo = r.ReadI32()
	} else { // large files
		f.end = r.ReadI64()
		f.seekfree = r.ReadI64()
		f.nbytesfree = r.ReadI32()
		f.nfree = r.ReadI32()
		f.nbytesname = r.ReadI32()
		f.units = r.ReadU8()
		f.compression = r.ReadI32()
		f.seekinfo = r.ReadI64()
		f.nbytesinfo = r.ReadI32()
	}
	f.version %= 1000000

	if _, err := io.ReadFull(r.r, f.uuid[:]); err != nil || r.Err() != nil {
		if err != nil {
			return fmt.Errorf("rootio: failed to read ROOT's UUID file: %v", err)
		}
		return r.Err()
	}

	var err error

	err = f.dir.readDirInfo()
	if err != nil {
		return fmt.Errorf("rootio: failed to read ROOT directory infos: %v", err)
	}

	if f.seekinfo > 0 {
		err = f.readStreamerInfo()
		if err != nil {
			return fmt.Errorf("rootio: failed to read ROOT streamer infos: %v", err)
		}
	}

	err = f.dir.readKeys()
	if err != nil {
		return fmt.Errorf("rootio: failed to read ROOT file keys: %v", err)
	}

	return nil
}

func (f *File) writeHeader() error {
	panic("not implemented")
}

func (f *File) Tell() int64 {
	where, err := f.Seek(0, io.SeekCurrent)
	if err != nil {
		panic(err)
	}
	return where
}

// Close closes the File, rendering it unusable for I/O.
// It returns an error, if any.
func (f *File) Close() error {
	for _, k := range f.dir.keys {
		k.f = nil
	}
	f.dir.keys = nil
	f.dir.file = nil
	return f.closer.Close()
}

// Keys returns the list of keys this File contains
func (f *File) Keys() []Key {
	return f.dir.keys
}

func (f *File) Name() string {
	return f.dir.Name()
}

func (f *File) Title() string {
	return f.dir.Title()
}

func (f *File) Class() string {
	return "TFile"
}

// readStreamerInfo reads the list of StreamerInfo from this file
func (f *File) readStreamerInfo() error {
	if f.seekinfo <= 0 || f.seekinfo >= f.end {
		return fmt.Errorf("rootio: invalid pointer to StreamerInfo (pos=%v end=%v)", f.seekinfo, f.end)

	}
	buf := make([]byte, int(f.nbytesinfo))
	nbytes, err := f.ReadAt(buf, f.seekinfo)
	if err != nil {
		return err
	}
	if nbytes != int(f.nbytesinfo) {
		return fmt.Errorf("rootio: requested [%v] bytes. read [%v] bytes from file", f.nbytesinfo, nbytes)
	}

	err = f.siKey.UnmarshalROOT(NewRBuffer(buf, nil, 0, nil))
	f.siKey.f = f
	if err != nil {
		return err
	}

	objs := f.siKey.Value().(List)
	f.sinfos = make([]StreamerInfo, 0, objs.Len())
	for i := 0; i < objs.Len(); i++ {
		obj, ok := objs.At(i).(StreamerInfo)
		if !ok {
			continue
		}
		f.sinfos = append(f.sinfos, obj)
		streamers.add(obj)
	}
	return nil
}

// StreamerInfos returns the list of StreamerInfos of this file.
func (f *File) StreamerInfos() []StreamerInfo {
	return f.sinfos
}

// StreamerInfo returns the StreamerInfo with name of this file and an error if any.
func (f *File) StreamerInfo(name string) (StreamerInfo, error) {
	if len(f.sinfos) == 0 {
		return nil, fmt.Errorf("rootio: no streamer for %q (no streamerinfo list)", name)
	}

	for _, si := range f.sinfos {
		if si.Name() == name {
			return si, nil
		}
	}

	// no streamer for "name" in that file.
	// try whether "name" isn't actually std::vector<T> and a streamer
	// for T is in that file.
	o := reStdVector.FindStringSubmatch(name)
	if o != nil {
		si := stdvecSIFrom(name, o[1], f)
		if si != nil {
			f.sinfos = append(f.sinfos, si)
			streamers.add(si)
			return si, nil
		}
	}

	return nil, fmt.Errorf("rootio: no streamer for %q", name)
}

// Get returns the object identified by namecycle
//   namecycle has the format name;cycle
//   name  = * is illegal, cycle = * is illegal
//   cycle = "" or cycle = 9999 ==> apply to a memory object
//
//   examples:
//     foo   : get object named foo in memory
//             if object is not in memory, try with highest cycle from file
//     foo;1 : get cycle 1 of foo on file
func (f *File) Get(namecycle string) (Object, error) {
	return f.dir.Get(namecycle)
}

var (
	_ Object    = (*File)(nil)
	_ Named     = (*File)(nil)
	_ Directory = (*File)(nil)
)
<|endoftext|>


<commit_before>// Copyright 2013 Julien Schmidt. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.

package httprouter

import (
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
)

type mockResponseWriter struct{}

func (m *mockResponseWriter) Header() (h http.Header) {
	return http.Header{}
}

func (m *mockResponseWriter) Write(p []byte) (n int, err error) {
	return len(p), nil
}

func (m *mockResponseWriter) WriteString(s string) (n int, err error) {
	return len(s), nil
}

func (m *mockResponseWriter) WriteHeader(int) {}

func TestParams(t *testing.T) {
	ps := Params{
		Param{"param1", "value1"},
		Param{"param2", "value2"},
		Param{"param3", "value3"},
	}
	for i := range ps {
		if val := ps.ByName(ps[i].Key); val != ps[i].Value {
			t.Errorf("Wrong value for %s: Got %s; Want %s", ps[i].Key, val, ps[i].Value)
		}
	}
	if val := ps.ByName("noKey"); val != "" {
		t.Errorf("Expected empty string for not found key; got: %s", val)
	}
}

func TestRouter(t *testing.T) {
	router := New()

	routed := false
	router.Handle("GET", "/user/:name", func(w http.ResponseWriter, r *http.Request, ps Params) {
		routed = true
		want := Params{Param{"name", "gopher"}}
		if !reflect.DeepEqual(ps, want) {
			t.Fatalf("wrong wildcard values: want %v, got %v", want, ps)
		}
	})

	w := new(mockResponseWriter)

	req, _ := http.NewRequest("GET", "/user/gopher", nil)
	router.ServeHTTP(w, req)

	if !routed {
		t.Fatal("routing failed")
	}
}

type handlerStruct struct {
	handeled *bool
}

func (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	*h.handeled = true
}

func TestRouterAPI(t *testing.T) {
	var get, head, options, post, put, patch, delete, handler, handlerFunc bool

	httpHandler := handlerStruct{&handler}

	router := New()
	router.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
		get = true
	})
	router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
		head = true
	})
	router.OPTIONS("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
		options = true
	})
	router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) {
		post = true
	})
	router.PUT("/PUT", func(w http.ResponseWriter, r *http.Request, _ Params) {
		put = true
	})
	router.PATCH("/PATCH", func(w http.ResponseWriter, r *http.Request, _ Params) {
		patch = true
	})
	router.DELETE("/DELETE", func(w http.ResponseWriter, r *http.Request, _ Params) {
		delete = true
	})
	router.Handler("GET", "/Handler", httpHandler)
	router.HandlerFunc("GET", "/HandlerFunc", func(w http.ResponseWriter, r *http.Request) {
		handlerFunc = true
	})

	w := new(mockResponseWriter)

	r, _ := http.NewRequest("GET", "/GET", nil)
	router.ServeHTTP(w, r)
	if !get {
		t.Error("routing GET failed")
	}

	r, _ = http.NewRequest("HEAD", "/GET", nil)
	router.ServeHTTP(w, r)
	if !head {
		t.Error("routing HEAD failed")
	}

	r, _ = http.NewRequest("OPTIONS", "/GET", nil)
	router.ServeHTTP(w, r)
	if !options {
		t.Error("routing OPTIONS failed")
	}

	r, _ = http.NewRequest("POST", "/POST", nil)
	router.ServeHTTP(w, r)
	if !post {
		t.Error("routing POST failed")
	}

	r, _ = http.NewRequest("PUT", "/PUT", nil)
	router.ServeHTTP(w, r)
	if !put {
		t.Error("routing PUT failed")
	}

	r, _ = http.NewRequest("PATCH", "/PATCH", nil)
	router.ServeHTTP(w, r)
	if !patch {
		t.Error("routing PATCH failed")
	}

	r, _ = http.NewRequest("DELETE", "/DELETE", nil)
	router.ServeHTTP(w, r)
	if !delete {
		t.Error("routing DELETE failed")
	}

	r, _ = http.NewRequest("GET", "/Handler", nil)
	router.ServeHTTP(w, r)
	if !handler {
		t.Error("routing Handler failed")
	}

	r, _ = http.NewRequest("GET", "/HandlerFunc", nil)
	router.ServeHTTP(w, r)
	if !handlerFunc {
		t.Error("routing HandlerFunc failed")
	}
}

func TestRouterRoot(t *testing.T) {
	router := New()
	recv := catchPanic(func() {
		router.GET("noSlashRoot", nil)
	})
	if recv == nil {
		t.Fatal("registering path not beginning with '/' did not panic")
	}
}

func TestRouterNotAllowed(t *testing.T) {
	handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}

	router := New()
	router.POST("/path", handlerFunc)

	// Test not allowed
	r, _ := http.NewRequest("GET", "/path", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, r)
	if !(w.Code == http.StatusMethodNotAllowed) {
		t.Errorf("NotAllowed handling failed: Code=%d, Header=%v", w.Code, w.Header())
	}

	w = httptest.NewRecorder()
	responseText := "custom method"
	router.MethodNotAllowed = func(w http.ResponseWriter, req *http.Request) {
		w.WriteHeader(http.StatusTeapot)
		w.Write([]byte(responseText))
	}
	router.ServeHTTP(w, r)
	if got := w.Body.String(); !(got == responseText) {
		t.Errorf("unexpected response got %q want %q", got, responseText)
	}
	if w.Code != http.StatusTeapot {
		t.Errorf("unexpected response code %d want %d", w.Code, http.StatusTeapot)
	}
}

func TestRouterNotFound(t *testing.T) {
	handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}

	router := New()
	router.GET("/path", handlerFunc)
	router.GET("/dir/", handlerFunc)
	router.GET("/", handlerFunc)

	testRoutes := []struct {
		route  string
		code   int
		header string
	}{
		{"/path/", 301, "map[Location:[/path]]"},   // TSR -/
		{"/dir", 301, "map[Location:[/dir/]]"},     // TSR +/
		{"", 301, "map[Location:[/]]"},             // TSR +/
		{"/PATH", 301, "map[Location:[/path]]"},    // Fixed Case
		{"/DIR/", 301, "map[Location:[/dir/]]"},    // Fixed Case
		{"/PATH/", 301, "map[Location:[/path]]"},   // Fixed Case -/
		{"/DIR", 301, "map[Location:[/dir/]]"},     // Fixed Case +/
		{"/../path", 301, "map[Location:[/path]]"}, // CleanPath
		{"/nope", 404, ""},                         // NotFound
	}
	for _, tr := range testRoutes {
		r, _ := http.NewRequest("GET", tr.route, nil)
		w := httptest.NewRecorder()
		router.ServeHTTP(w, r)
		if !(w.Code == tr.code && (w.Code == 404 || fmt.Sprint(w.Header()) == tr.header)) {
			t.Errorf("NotFound handling route %s failed: Code=%d, Header=%v", tr.route, w.Code, w.Header())
		}
	}

	// Test custom not found handler
	var notFound bool
	router.NotFound = func(rw http.ResponseWriter, r *http.Request) {
		rw.WriteHeader(404)
		notFound = true
	}
	r, _ := http.NewRequest("GET", "/nope", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, r)
	if !(w.Code == 404 && notFound == true) {
		t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
	}

	// Test other method than GET (want 307 instead of 301)
	router.PATCH("/path", handlerFunc)
	r, _ = http.NewRequest("PATCH", "/path/", nil)
	w = httptest.NewRecorder()
	router.ServeHTTP(w, r)
	if !(w.Code == 307 && fmt.Sprint(w.Header()) == "map[Location:[/path]]") {
		t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
	}

	// Test special case where no node for the prefix "/" exists
	router = New()
	router.GET("/a", handlerFunc)
	r, _ = http.NewRequest("GET", "/", nil)
	w = httptest.NewRecorder()
	router.ServeHTTP(w, r)
	if !(w.Code == 404) {
		t.Errorf("NotFound handling route / failed: Code=%d", w.Code)
	}
}

func TestRouterPanicHandler(t *testing.T) {
	router := New()
	panicHandled := false

	router.PanicHandler = func(rw http.ResponseWriter, r *http.Request, p interface{}) {
		panicHandled = true
	}

	router.Handle("PUT", "/user/:name", func(_ http.ResponseWriter, _ *http.Request, _ Params) {
		panic("oops!")
	})

	w := new(mockResponseWriter)
	req, _ := http.NewRequest("PUT", "/user/gopher", nil)

	defer func() {
		if rcv := recover(); rcv != nil {
			t.Fatal("handling panic failed")
		}
	}()

	router.ServeHTTP(w, req)

	if !panicHandled {
		t.Fatal("simulating failed")
	}
}

func TestRouterLookup(t *testing.T) {
	routed := false
	wantHandle := func(_ http.ResponseWriter, _ *http.Request, _ Params) {
		routed = true
	}
	wantParams := Params{Param{"name", "gopher"}}

	router := New()

	// try empty router first
	handle, _, tsr := router.Lookup("GET", "/nope")
	if handle != nil {
		t.Fatalf("Got handle for unregistered pattern: %v", handle)
	}
	if tsr {
		t.Error("Got wrong TSR recommendation!")
	}

	// insert route and try again
	router.GET("/user/:name", wantHandle)

	handle, params, tsr := router.Lookup("GET", "/user/gopher")
	if handle == nil {
		t.Fatal("Got no handle!")
	} else {
		handle(nil, nil, nil)
		if !routed {
			t.Fatal("Routing failed!")
		}
	}

	if !reflect.DeepEqual(params, wantParams) {
		t.Fatalf("Wrong parameter values: want %v, got %v", wantParams, params)
	}

	handle, _, tsr = router.Lookup("GET", "/user/gopher/")
	if handle != nil {
		t.Fatalf("Got handle for unregistered pattern: %v", handle)
	}
	if !tsr {
		t.Error("Got no TSR recommendation!")
	}

	handle, _, tsr = router.Lookup("GET", "/nope")
	if handle != nil {
		t.Fatalf("Got handle for unregistered pattern: %v", handle)
	}
	if tsr {
		t.Error("Got wrong TSR recommendation!")
	}
}

type mockFileSystem struct {
	opened bool
}

func (mfs *mockFileSystem) Open(name string) (http.File, error) {
	mfs.opened = true
	return nil, errors.New("this is just a mock")
}

func TestRouterServeFiles(t *testing.T) {
	router := New()
	mfs := &mockFileSystem{}

	recv := catchPanic(func() {
		router.ServeFiles("/noFilepath", mfs)
	})
	if recv == nil {
		t.Fatal("registering path not ending with '*filepath' did not panic")
	}

	router.ServeFiles("/*filepath", mfs)
	w := new(mockResponseWriter)
	r, _ := http.NewRequest("GET", "/favicon.ico", nil)
	router.ServeHTTP(w, r)
	if !mfs.opened {
		t.Error("serving file failed")
	}
}
<commit_msg>added new test cases<commit_after>// Copyright 2013 Julien Schmidt. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.

package httprouter

import (
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
)

type mockResponseWriter struct{}

func (m *mockResponseWriter) Header() (h http.Header) {
	return http.Header{}
}

func (m *mockResponseWriter) Write(p []byte) (n int, err error) {
	return len(p), nil
}

func (m *mockResponseWriter) WriteString(s string) (n int, err error) {
	return len(s), nil
}

func (m *mockResponseWriter) WriteHeader(int) {}

func TestParams(t *testing.T) {
	ps := Params{
		Param{"param1", "value1"},
		Param{"param2", "value2"},
		Param{"param3", "value3"},
	}
	for i := range ps {
		if val := ps.ByName(ps[i].Key); val != ps[i].Value {
			t.Errorf("Wrong value for %s: Got %s; Want %s", ps[i].Key, val, ps[i].Value)
		}
	}
	if val := ps.ByName("noKey"); val != "" {
		t.Errorf("Expected empty string for not found key; got: %s", val)
	}
}

func TestRouter(t *testing.T) {
	router := New()

	routed := false
	router.Handle("GET", "/user/:name", func(w http.ResponseWriter, r *http.Request, ps Params) {
		routed = true
		want := Params{Param{"name", "gopher"}}
		if !reflect.DeepEqual(ps, want) {
			t.Fatalf("wrong wildcard values: want %v, got %v", want, ps)
		}
	})

	w := new(mockResponseWriter)

	req, _ := http.NewRequest("GET", "/user/gopher", nil)
	router.ServeHTTP(w, req)

	if !routed {
		t.Fatal("routing failed")
	}
}

type handlerStruct struct {
	handeled *bool
}

func (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	*h.handeled = true
}

func TestRouterAPI(t *testing.T) {
	var get, head, options, post, put, patch, delete, handler, handlerFunc bool

	httpHandler := handlerStruct{&handler}

	router := New()
	router.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
		get = true
	})
	router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
		head = true
	})
	router.OPTIONS("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
		options = true
	})
	router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) {
		post = true
	})
	router.PUT("/PUT", func(w http.ResponseWriter, r *http.Request, _ Params) {
		put = true
	})
	router.PATCH("/PATCH", func(w http.ResponseWriter, r *http.Request, _ Params) {
		patch = true
	})
	router.DELETE("/DELETE", func(w http.ResponseWriter, r *http.Request, _ Params) {
		delete = true
	})
	router.Handler("GET", "/Handler", httpHandler)
	router.HandlerFunc("GET", "/HandlerFunc", func(w http.ResponseWriter, r *http.Request) {
		handlerFunc = true
	})

	w := new(mockResponseWriter)

	r, _ := http.NewRequest("GET", "/GET", nil)
	router.ServeHTTP(w, r)
	if !get {
		t.Error("routing GET failed")
	}

	r, _ = http.NewRequest("HEAD", "/GET", nil)
	router.ServeHTTP(w, r)
	if !head {
		t.Error("routing HEAD failed")
	}

	r, _ = http.NewRequest("OPTIONS", "/GET", nil)
	router.ServeHTTP(w, r)
	if !options {
		t.Error("routing OPTIONS failed")
	}

	r, _ = http.NewRequest("POST", "/POST", nil)
	router.ServeHTTP(w, r)
	if !post {
		t.Error("routing POST failed")
	}

	r, _ = http.NewRequest("PUT", "/PUT", nil)
	router.ServeHTTP(w, r)
	if !put {
		t.Error("routing PUT failed")
	}

	r, _ = http.NewRequest("PATCH", "/PATCH", nil)
	router.ServeHTTP(w, r)
	if !patch {
		t.Error("routing PATCH failed")
	}

	r, _ = http.NewRequest("DELETE", "/DELETE", nil)
	router.ServeHTTP(w, r)
	if !delete {
		t.Error("routing DELETE failed")
	}

	r, _ = http.NewRequest("GET", "/Handler", nil)
	router.ServeHTTP(w, r)
	if !handler {
		t.Error("routing Handler failed")
	}

	r, _ = http.NewRequest("GET", "/HandlerFunc", nil)
	router.ServeHTTP(w, r)
	if !handlerFunc {
		t.Error("routing HandlerFunc failed")
	}
}

func TestRouterRoot(t *testing.T) {
	router := New()
	recv := catchPanic(func() {
		router.GET("noSlashRoot", nil)
	})
	if recv == nil {
		t.Fatal("registering path not beginning with '/' did not panic")
	}
}

func TestRouterChaining(t *testing.T) {
	router1 := New()
	router2 := New()
	router1.NotFound = router2

	fooHit := false
	router1.POST("/foo", func(w http.ResponseWriter, req *http.Request, _ Params) {
		fooHit = true
		w.WriteHeader(http.StatusOK)
	})

	barHit := false
	router2.POST("/bar", func(w http.ResponseWriter, req *http.Request, _ Params) {
		barHit = true
		w.WriteHeader(http.StatusOK)
	})

	r, _ := http.NewRequest("POST", "/foo", nil)
	w := httptest.NewRecorder()
	router1.ServeHTTP(w, r)
	if !(w.Code == http.StatusOK && fooHit) {
		t.Errorf("Regular routing failed with router chaining.")
		t.FailNow()
	}

	r, _ = http.NewRequest("POST", "/bar", nil)
	w = httptest.NewRecorder()
	router1.ServeHTTP(w, r)
	if !(w.Code == http.StatusOK && barHit) {
		t.Errorf("Chained routing failed with router chaining.")
		t.FailNow()
	}

	r, _ = http.NewRequest("POST", "/qax", nil)
	w = httptest.NewRecorder()
	router1.ServeHTTP(w, r)
	if !(w.Code == http.StatusNotFound) {
		t.Errorf("NotFound behavior failed with router chaining.")
		t.FailNow()
	}
}

func TestRouterNotAllowed(t *testing.T) {
	handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}

	router := New()
	router.POST("/path", handlerFunc)

	// Test not allowed
	r, _ := http.NewRequest("GET", "/path", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, r)
	if !(w.Code == http.StatusMethodNotAllowed) {
		t.Errorf("NotAllowed handling failed: Code=%d, Header=%v", w.Code, w.Header())
	}

	w = httptest.NewRecorder()
	responseText := "custom method"
	router.MethodNotAllowed = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		w.WriteHeader(http.StatusTeapot)
		w.Write([]byte(responseText))
	})
	router.ServeHTTP(w, r)
	if got := w.Body.String(); !(got == responseText) {
		t.Errorf("unexpected response got %q want %q", got, responseText)
	}
	if w.Code != http.StatusTeapot {
		t.Errorf("unexpected response code %d want %d", w.Code, http.StatusTeapot)
	}
}

func TestRouterNotFound(t *testing.T) {
	handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}

	router := New()
	router.GET("/path", handlerFunc)
	router.GET("/dir/", handlerFunc)
	router.GET("/", handlerFunc)

	testRoutes := []struct {
		route  string
		code   int
		header string
	}{
		{"/path/", 301, "map[Location:[/path]]"},   // TSR -/
		{"/dir", 301, "map[Location:[/dir/]]"},     // TSR +/
		{"", 301, "map[Location:[/]]"},             // TSR +/
		{"/PATH", 301, "map[Location:[/path]]"},    // Fixed Case
		{"/DIR/", 301, "map[Location:[/dir/]]"},    // Fixed Case
		{"/PATH/", 301, "map[Location:[/path]]"},   // Fixed Case -/
		{"/DIR", 301, "map[Location:[/dir/]]"},     // Fixed Case +/
		{"/../path", 301, "map[Location:[/path]]"}, // CleanPath
		{"/nope", 404, ""},                         // NotFound
	}
	for _, tr := range testRoutes {
		r, _ := http.NewRequest("GET", tr.route, nil)
		w := httptest.NewRecorder()
		router.ServeHTTP(w, r)
		if !(w.Code == tr.code && (w.Code == 404 || fmt.Sprint(w.Header()) == tr.header)) {
			t.Errorf("NotFound handling route %s failed: Code=%d, Header=%v", tr.route, w.Code, w.Header())
		}
	}

	// Test custom not found handler
	var notFound bool
	router.NotFound = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		rw.WriteHeader(404)
		notFound = true
	})
	r, _ := http.NewRequest("GET", "/nope", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, r)
	if !(w.Code == 404 && notFound == true) {
		t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
	}

	// Test other method than GET (want 307 instead of 301)
	router.PATCH("/path", handlerFunc)
	r, _ = http.NewRequest("PATCH", "/path/", nil)
	w = httptest.NewRecorder()
	router.ServeHTTP(w, r)
	if !(w.Code == 307 && fmt.Sprint(w.Header()) == "map[Location:[/path]]") {
		t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
	}

	// Test special case where no node for the prefix "/" exists
	router = New()
	router.GET("/a", handlerFunc)
	r, _ = http.NewRequest("GET", "/", nil)
	w = httptest.NewRecorder()
	router.ServeHTTP(w, r)
	if !(w.Code == 404) {
		t.Errorf("NotFound handling route / failed: Code=%d", w.Code)
	}
}

func TestRouterPanicHandler(t *testing.T) {
	router := New()
	panicHandled := false

	router.PanicHandler = func(rw http.ResponseWriter, r *http.Request, p interface{}) {
		panicHandled = true
	}

	router.Handle("PUT", "/user/:name", func(_ http.ResponseWriter, _ *http.Request, _ Params) {
		panic("oops!")
	})

	w := new(mockResponseWriter)
	req, _ := http.NewRequest("PUT", "/user/gopher", nil)

	defer func() {
		if rcv := recover(); rcv != nil {
			t.Fatal("handling panic failed")
		}
	}()

	router.ServeHTTP(w, req)

	if !panicHandled {
		t.Fatal("simulating failed")
	}
}

func TestRouterLookup(t *testing.T) {
	routed := false
	wantHandle := func(_ http.ResponseWriter, _ *http.Request, _ Params) {
		routed = true
	}
	wantParams := Params{Param{"name", "gopher"}}

	router := New()

	// try empty router first
	handle, _, tsr := router.Lookup("GET", "/nope")
	if handle != nil {
		t.Fatalf("Got handle for unregistered pattern: %v", handle)
	}
	if tsr {
		t.Error("Got wrong TSR recommendation!")
	}

	// insert route and try again
	router.GET("/user/:name", wantHandle)

	handle, params, tsr := router.Lookup("GET", "/user/gopher")
	if handle == nil {
		t.Fatal("Got no handle!")
	} else {
		handle(nil, nil, nil)
		if !routed {
			t.Fatal("Routing failed!")
		}
	}

	if !reflect.DeepEqual(params, wantParams) {
		t.Fatalf("Wrong parameter values: want %v, got %v", wantParams, params)
	}

	handle, _, tsr = router.Lookup("GET", "/user/gopher/")
	if handle != nil {
		t.Fatalf("Got handle for unregistered pattern: %v", handle)
	}
	if !tsr {
		t.Error("Got no TSR recommendation!")
	}

	handle, _, tsr = router.Lookup("GET", "/nope")
	if handle != nil {
		t.Fatalf("Got handle for unregistered pattern: %v", handle)
	}
	if tsr {
		t.Error("Got wrong TSR recommendation!")
	}
}

type mockFileSystem struct {
	opened bool
}

func (mfs *mockFileSystem) Open(name string) (http.File, error) {
	mfs.opened = true
	return nil, errors.New("this is just a mock")
}

func TestRouterServeFiles(t *testing.T) {
	router := New()
	mfs := &mockFileSystem{}

	recv := catchPanic(func() {
		router.ServeFiles("/noFilepath", mfs)
	})
	if recv == nil {
		t.Fatal("registering path not ending with '*filepath' did not panic")
	}

	router.ServeFiles("/*filepath", mfs)
	w := new(mockResponseWriter)
	r, _ := http.NewRequest("GET", "/favicon.ico", nil)
	router.ServeHTTP(w, r)
	if !mfs.opened {
		t.Error("serving file failed")
	}
}
<|endoftext|>