<commit_before>package nodes

import (
	"errors"
	"fmt"

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

// List is the operation responsible for returning a paginated collection of
// load balancer nodes. It requires the node ID, its parent load balancer ID,
// and optional limit integer (passed in either as a pointer or a nil poitner).
func List(client *gophercloud.ServiceClient, loadBalancerID int, limit *int) pagination.Pager {
	url := rootURL(client, loadBalancerID)
	if limit != nil {
		url += fmt.Sprintf("?limit=%d", limit)
	}

	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
		return NodePage{pagination.SinglePageBase(r)}
	})
}

// CreateOptsBuilder is the interface responsible for generating the JSON
// for a Create operation.
type CreateOptsBuilder interface {
	ToNodeCreateMap() (map[string]interface{}, error)
}

// CreateOpts is a slice of CreateOpt structs, that allow the user to create
// multiple nodes in a single operation (one node per CreateOpt).
type CreateOpts []CreateOpt

// CreateOpt represents the options to create a single node.
type CreateOpt struct {
	// Required - the IP address or CIDR for this back-end node. It can either be
	// a private IP (ServiceNet) or a public IP.
	Address string

	// Optional - the port on which traffic is sent and received.
	Port int

	// Optional - the condition of the node. See the consts in Results.go.
	Condition Condition

	// Optional - the type of the node. See the consts in Results.go.
	Type Type

	// Optional - a pointer to an integer between 0 and 100.
	Weight *int
}

func validateWeight(weight *int) error {
	if weight != nil && (*weight > 100 || *weight < 0) {
		return errors.New("Weight must be a valid int between 0 and 100")
	}
	return nil
}

// ToNodeCreateMap converts a slice of options into a map that can be used for
// the JSON.
func (opts CreateOpts) ToNodeCreateMap() (map[string]interface{}, error) {
	type nodeMap map[string]interface{}
	nodes := []nodeMap{}

	for k, v := range opts {
		if v.Address == "" {
			return nodeMap{}, fmt.Errorf("ID is a required attribute, none provided for %d CreateOpt element", k)
		}
		if weightErr := validateWeight(v.Weight); weightErr != nil {
			return nodeMap{}, weightErr
		}

		node := make(map[string]interface{})
		node["address"] = v.Address

		if v.Port > 0 {
			node["port"] = v.Port
		}
		if v.Condition != "" {
			node["condition"] = v.Condition
		}
		if v.Type != "" {
			node["type"] = v.Type
		}
		if v.Weight != nil {
			node["weight"] = &v.Weight
		}

		nodes = append(nodes, node)
	}

	return nodeMap{"nodes": nodes}, nil
}

// Create is the operation responsible for creating a new node on a load
// balancer. Since every load balancer exists in both ServiceNet and the public
// Internet, both private and public IP addresses can be used for nodes.
//
// If nodes need time to boot up services before they become operational, you
// can temporarily prevent traffic from being sent to that node by setting the
// Condition field to DRAINING. Health checks will still be performed; but once
// your node is ready, you can update its condition to ENABLED and have it
// handle traffic.
func Create(client *gophercloud.ServiceClient, loadBalancerID int, opts CreateOptsBuilder) CreateResult {
	var res CreateResult

	reqBody, err := opts.ToNodeCreateMap()
	if err != nil {
		res.Err = err
		return res
	}

	resp, err := client.Post(rootURL(client, loadBalancerID), reqBody, &res.Body, nil)

	if err != nil {
		res.Err = err
		return res
	}

	pr := pagination.PageResultFromParsed(resp, res.Body)
	return CreateResult{pagination.SinglePageBase(pr)}
}

// BulkDelete is the operation responsible for batch deleting multiple nodes in
// a single operation. It accepts a slice of integer IDs and will remove them
// from the load balancer. The maximum limit is 10 node removals at once.
func BulkDelete(c *gophercloud.ServiceClient, loadBalancerID int, nodeIDs []int) DeleteResult {
	var res DeleteResult

	if len(nodeIDs) > 10 || len(nodeIDs) == 0 {
		res.Err = errors.New("You must provide a minimum of 1 and a maximum of 10 node IDs")
		return res
	}

	url := rootURL(c, loadBalancerID)
	url += gophercloud.IDSliceToQueryString("id", nodeIDs)

	_, res.Err = c.Delete(url, nil)
	return res
}

// Get is the operation responsible for showing details for a single node.
func Get(c *gophercloud.ServiceClient, lbID, nodeID int) GetResult {
	var res GetResult
	_, res.Err = c.Get(resourceURL(c, lbID, nodeID), &res.Body, nil)
	return res
}

// UpdateOptsBuilder represents a type that can be converted into a JSON-like
// map structure.
type UpdateOptsBuilder interface {
	ToNodeUpdateMap() (map[string]interface{}, error)
}

// UpdateOpts represent the options for updating an existing node.
type UpdateOpts struct {
	// Optional - the condition of the node. See the consts in Results.go.
	Condition Condition

	// Optional - the type of the node. See the consts in Results.go.
	Type Type

	// Optional - a pointer to an integer between 0 and 100.
	Weight *int
}

// ToNodeUpdateMap converts an options struct into a JSON-like map.
func (opts UpdateOpts) ToNodeUpdateMap() (map[string]interface{}, error) {
	node := make(map[string]interface{})

	if opts.Condition != "" {
		node["condition"] = opts.Condition
	}
	if opts.Weight != nil {
		if weightErr := validateWeight(opts.Weight); weightErr != nil {
			return node, weightErr
		}
		node["weight"] = &opts.Weight
	}
	if opts.Type != "" {
		node["type"] = opts.Type
	}

	return map[string]interface{}{"node": node}, nil
}

// Update is the operation responsible for updating an existing node. A node's
// IP, port, and status are immutable attributes and cannot be modified.
func Update(c *gophercloud.ServiceClient, lbID, nodeID int, opts UpdateOptsBuilder) UpdateResult {
	var res UpdateResult

	reqBody, err := opts.ToNodeUpdateMap()
	if err != nil {
		res.Err = err
		return res
	}

	_, res.Err = c.Put(resourceURL(c, lbID, nodeID), reqBody, nil, nil)
	return res
}

// Delete is the operation responsible for permanently deleting a node.
func Delete(c *gophercloud.ServiceClient, lbID, nodeID int) DeleteResult {
	var res DeleteResult
	_, res.Err = c.Delete(resourceURL(c, lbID, nodeID), nil)
	return res
}

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

// ListEventsOpts allows the filtering and sorting of paginated collections through
// the API.
type ListEventsOpts struct {
	Marker string `q:"marker"`
	Limit  int    `q:"limit"`
}

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

// ListEvents is the operation responsible for listing all the events
// associated with the activity between the node and the load balancer. The
// events report errors found with the node. The detailedMessage provides the
// detailed reason for the error.
func ListEvents(client *gophercloud.ServiceClient, loadBalancerID int, opts ListEventsOptsBuilder) pagination.Pager {
	url := eventsURL(client, loadBalancerID)

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

	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
		return NodeEventPage{pagination.SinglePageBase(r)}
	})
}

// GetByIPPort locates a load balancer node by IP and port.
func GetByIPPort(
	client *gophercloud.ServiceClient,
	loadBalancerID int,
	address string,
	port int,
) (*Node, error) {

	// nil until found
	var found *Node

	List(client, loadBalancerID, nil).EachPage(func(page pagination.Page) (bool, error) {
		lbNodes, err := ExtractNodes(page)
		if err != nil {
			return false, err
		}

		for _, trialNode := range lbNodes {
			if trialNode.Address == address && trialNode.Port == port {
				found = &trialNode
				return false, nil
			}

		}

		return true, nil
	})

	// TODO: When found is nil, return an error
	if found == nil {
		return nil, errors.New("Unable to get node by IP and Port")
	}

	return found, nil
}
<commit_msg>Delete TODO comment<commit_after>package nodes

import (
	"errors"
	"fmt"

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

// List is the operation responsible for returning a paginated collection of
// load balancer nodes. It requires the node ID, its parent load balancer ID,
// and optional limit integer (passed in either as a pointer or a nil poitner).
func List(client *gophercloud.ServiceClient, loadBalancerID int, limit *int) pagination.Pager {
	url := rootURL(client, loadBalancerID)
	if limit != nil {
		url += fmt.Sprintf("?limit=%d", limit)
	}

	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
		return NodePage{pagination.SinglePageBase(r)}
	})
}

// CreateOptsBuilder is the interface responsible for generating the JSON
// for a Create operation.
type CreateOptsBuilder interface {
	ToNodeCreateMap() (map[string]interface{}, error)
}

// CreateOpts is a slice of CreateOpt structs, that allow the user to create
// multiple nodes in a single operation (one node per CreateOpt).
type CreateOpts []CreateOpt

// CreateOpt represents the options to create a single node.
type CreateOpt struct {
	// Required - the IP address or CIDR for this back-end node. It can either be
	// a private IP (ServiceNet) or a public IP.
	Address string

	// Optional - the port on which traffic is sent and received.
	Port int

	// Optional - the condition of the node. See the consts in Results.go.
	Condition Condition

	// Optional - the type of the node. See the consts in Results.go.
	Type Type

	// Optional - a pointer to an integer between 0 and 100.
	Weight *int
}

func validateWeight(weight *int) error {
	if weight != nil && (*weight > 100 || *weight < 0) {
		return errors.New("Weight must be a valid int between 0 and 100")
	}
	return nil
}

// ToNodeCreateMap converts a slice of options into a map that can be used for
// the JSON.
func (opts CreateOpts) ToNodeCreateMap() (map[string]interface{}, error) {
	type nodeMap map[string]interface{}
	nodes := []nodeMap{}

	for k, v := range opts {
		if v.Address == "" {
			return nodeMap{}, fmt.Errorf("ID is a required attribute, none provided for %d CreateOpt element", k)
		}
		if weightErr := validateWeight(v.Weight); weightErr != nil {
			return nodeMap{}, weightErr
		}

		node := make(map[string]interface{})
		node["address"] = v.Address

		if v.Port > 0 {
			node["port"] = v.Port
		}
		if v.Condition != "" {
			node["condition"] = v.Condition
		}
		if v.Type != "" {
			node["type"] = v.Type
		}
		if v.Weight != nil {
			node["weight"] = &v.Weight
		}

		nodes = append(nodes, node)
	}

	return nodeMap{"nodes": nodes}, nil
}

// Create is the operation responsible for creating a new node on a load
// balancer. Since every load balancer exists in both ServiceNet and the public
// Internet, both private and public IP addresses can be used for nodes.
//
// If nodes need time to boot up services before they become operational, you
// can temporarily prevent traffic from being sent to that node by setting the
// Condition field to DRAINING. Health checks will still be performed; but once
// your node is ready, you can update its condition to ENABLED and have it
// handle traffic.
func Create(client *gophercloud.ServiceClient, loadBalancerID int, opts CreateOptsBuilder) CreateResult {
	var res CreateResult

	reqBody, err := opts.ToNodeCreateMap()
	if err != nil {
		res.Err = err
		return res
	}

	resp, err := client.Post(rootURL(client, loadBalancerID), reqBody, &res.Body, nil)

	if err != nil {
		res.Err = err
		return res
	}

	pr := pagination.PageResultFromParsed(resp, res.Body)
	return CreateResult{pagination.SinglePageBase(pr)}
}

// BulkDelete is the operation responsible for batch deleting multiple nodes in
// a single operation. It accepts a slice of integer IDs and will remove them
// from the load balancer. The maximum limit is 10 node removals at once.
func BulkDelete(c *gophercloud.ServiceClient, loadBalancerID int, nodeIDs []int) DeleteResult {
	var res DeleteResult

	if len(nodeIDs) > 10 || len(nodeIDs) == 0 {
		res.Err = errors.New("You must provide a minimum of 1 and a maximum of 10 node IDs")
		return res
	}

	url := rootURL(c, loadBalancerID)
	url += gophercloud.IDSliceToQueryString("id", nodeIDs)

	_, res.Err = c.Delete(url, nil)
	return res
}

// Get is the operation responsible for showing details for a single node.
func Get(c *gophercloud.ServiceClient, lbID, nodeID int) GetResult {
	var res GetResult
	_, res.Err = c.Get(resourceURL(c, lbID, nodeID), &res.Body, nil)
	return res
}

// UpdateOptsBuilder represents a type that can be converted into a JSON-like
// map structure.
type UpdateOptsBuilder interface {
	ToNodeUpdateMap() (map[string]interface{}, error)
}

// UpdateOpts represent the options for updating an existing node.
type UpdateOpts struct {
	// Optional - the condition of the node. See the consts in Results.go.
	Condition Condition

	// Optional - the type of the node. See the consts in Results.go.
	Type Type

	// Optional - a pointer to an integer between 0 and 100.
	Weight *int
}

// ToNodeUpdateMap converts an options struct into a JSON-like map.
func (opts UpdateOpts) ToNodeUpdateMap() (map[string]interface{}, error) {
	node := make(map[string]interface{})

	if opts.Condition != "" {
		node["condition"] = opts.Condition
	}
	if opts.Weight != nil {
		if weightErr := validateWeight(opts.Weight); weightErr != nil {
			return node, weightErr
		}
		node["weight"] = &opts.Weight
	}
	if opts.Type != "" {
		node["type"] = opts.Type
	}

	return map[string]interface{}{"node": node}, nil
}

// Update is the operation responsible for updating an existing node. A node's
// IP, port, and status are immutable attributes and cannot be modified.
func Update(c *gophercloud.ServiceClient, lbID, nodeID int, opts UpdateOptsBuilder) UpdateResult {
	var res UpdateResult

	reqBody, err := opts.ToNodeUpdateMap()
	if err != nil {
		res.Err = err
		return res
	}

	_, res.Err = c.Put(resourceURL(c, lbID, nodeID), reqBody, nil, nil)
	return res
}

// Delete is the operation responsible for permanently deleting a node.
func Delete(c *gophercloud.ServiceClient, lbID, nodeID int) DeleteResult {
	var res DeleteResult
	_, res.Err = c.Delete(resourceURL(c, lbID, nodeID), nil)
	return res
}

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

// ListEventsOpts allows the filtering and sorting of paginated collections through
// the API.
type ListEventsOpts struct {
	Marker string `q:"marker"`
	Limit  int    `q:"limit"`
}

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

// ListEvents is the operation responsible for listing all the events
// associated with the activity between the node and the load balancer. The
// events report errors found with the node. The detailedMessage provides the
// detailed reason for the error.
func ListEvents(client *gophercloud.ServiceClient, loadBalancerID int, opts ListEventsOptsBuilder) pagination.Pager {
	url := eventsURL(client, loadBalancerID)

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

	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
		return NodeEventPage{pagination.SinglePageBase(r)}
	})
}

// GetByIPPort locates a load balancer node by IP and port.
func GetByIPPort(
	client *gophercloud.ServiceClient,
	loadBalancerID int,
	address string,
	port int,
) (*Node, error) {

	// nil until found
	var found *Node

	List(client, loadBalancerID, nil).EachPage(func(page pagination.Page) (bool, error) {
		lbNodes, err := ExtractNodes(page)
		if err != nil {
			return false, err
		}

		for _, trialNode := range lbNodes {
			if trialNode.Address == address && trialNode.Port == port {
				found = &trialNode
				return false, nil
			}

		}

		return true, nil
	})

	if found == nil {
		return nil, errors.New("Unable to get node by IP and Port")
	}

	return found, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Knative 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 testing

import (
	"context"
	"path"
	"reflect"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"github.com/knative/pkg/controller"
	"github.com/knative/pkg/kmeta"
	_ "github.com/knative/pkg/system/testing" // Setup system.Namespace()
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	clientgotesting "k8s.io/client-go/testing"
	"k8s.io/client-go/tools/cache"
)

// TableRow holds a single row of our table test.
type TableRow struct {
	// Name is a descriptive name for this test suitable as a first argument to t.Run()
	Name string

	// Objects holds the state of the world at the onset of reconciliation.
	Objects []runtime.Object

	// Key is the parameter to reconciliation.
	// This has the form "namespace/name".
	Key string

	// WantErr holds whether we should expect the reconciliation to result in an error.
	WantErr bool

	// WantCreates holds the set of Create calls we expect during reconciliation.
	WantCreates []metav1.Object

	// WantUpdates holds the set of Update calls we expect during reconciliation.
	WantUpdates []clientgotesting.UpdateActionImpl

	// WantStatusUpdates holds the set of Update calls, with `status` subresource set,
	// that we expect during reconciliation.
	WantStatusUpdates []clientgotesting.UpdateActionImpl

	// WantDeletes holds the set of Delete calls we expect during reconciliation.
	WantDeletes []clientgotesting.DeleteActionImpl

	// WantDeleteCollections holds the set of DeleteCollection calls we expect during reconciliation.
	WantDeleteCollections []clientgotesting.DeleteCollectionActionImpl

	// WantPatches holds the set of Patch calls we expect during reconciliation.
	WantPatches []clientgotesting.PatchActionImpl

	// WantEvents holds the set of events we expect during reconciliation.
	WantEvents []string

	// WantServiceReadyStats holds the ServiceReady stats we exepect during reconciliation.
	WantServiceReadyStats map[string]int

	// WithReactors is a set of functions that are installed as Reactors for the execution
	// of this row of the table-driven-test.
	WithReactors []clientgotesting.ReactionFunc

	// For cluster-scoped resources like ClusterIngress, it does not have to be
	// in the same namespace with its child resources.
	SkipNamespaceValidation bool
}

func objKey(o runtime.Object) string {
	on := o.(kmeta.Accessor)
	// namespace + name is not unique, and the tests don't populate k8s kind
	// information, so use GoLang's type name as part of the key.
	return path.Join(reflect.TypeOf(o).String(), on.GetNamespace(), on.GetName())
}

// Factory returns a Reconciler.Interface to perform reconciliation in table test,
// ActionRecorderList/EventList to capture k8s actions/events produced during reconciliation
// and FakeStatsReporter to capture stats.
type Factory func(*testing.T, *TableRow) (controller.Reconciler, ActionRecorderList, EventList, *FakeStatsReporter)

// Test executes the single table test.
func (r *TableRow) Test(t *testing.T, factory Factory) {
	c, recorderList, eventList, statsReporter := factory(t, r)

	// Run the Reconcile we're testing.
	if err := c.Reconcile(context.Background(), r.Key); (err != nil) != r.WantErr {
		t.Errorf("Reconcile() error = %v, WantErr %v", err, r.WantErr)
	}

	expectedNamespace, _, _ := cache.SplitMetaNamespaceKey(r.Key)

	actions, err := recorderList.ActionsByVerb()

	if err != nil {
		t.Errorf("Error capturing actions by verb: %q", err)
	}

	// Previous state is used to diff resource expected state for update requests that were missed.
	objPrevState := map[string]runtime.Object{}
	for _, o := range r.Objects {
		objPrevState[objKey(o)] = o
	}

	for i, want := range r.WantCreates {
		if i >= len(actions.Creates) {
			t.Errorf("Missing create: %#v", want)
			continue
		}
		got := actions.Creates[i]
		obj := got.GetObject()
		objPrevState[objKey(obj)] = obj

		if !r.SkipNamespaceValidation && got.GetNamespace() != expectedNamespace {
			t.Errorf("Unexpected action[%d]: %#v", i, got)
		}

		if diff := cmp.Diff(want, obj, ignoreLastTransitionTime, safeDeployDiff, cmpopts.EquateEmpty()); diff != "" {
			t.Errorf("Unexpected create (-want, +got): %s", diff)
		}
	}
	if got, want := len(actions.Creates), len(r.WantCreates); got > want {
		for _, extra := range actions.Creates[want:] {
			t.Errorf("Extra create: %#v", extra)
		}
	}

	updates := filterUpdatesWithSubresource("", actions.Updates)
	for i, want := range r.WantUpdates {
		if i >= len(updates) {
			wo := want.GetObject()
			key := objKey(wo)
			oldObj, ok := objPrevState[key]
			if !ok {
				t.Errorf("Object %s was never created: want: %#v", key, wo)
				continue
			}
			t.Errorf("Missing update for %s (-want, +prevState): %s", key,
				cmp.Diff(oldObj, wo, ignoreLastTransitionTime, safeDeployDiff, cmpopts.EquateEmpty()))
			continue
		}

		if want.GetSubresource() != "" {
			t.Errorf("Expectation was invalid - it should not include a subresource: %#v", want)
		}

		got := updates[i].GetObject()

		// Update the object state.
		objPrevState[objKey(got)] = got

		if diff := cmp.Diff(want.GetObject(), got, ignoreLastTransitionTime, safeDeployDiff, cmpopts.EquateEmpty()); diff != "" {
			t.Errorf("Unexpected update (-want, +got): %s", diff)
		}
	}
	if got, want := len(updates), len(r.WantUpdates); got > want {
		for _, extra := range updates[want:] {
			t.Errorf("Extra update: %#v", extra)
		}
	}

	// TODO(#2843): refactor.
	statusUpdates := filterUpdatesWithSubresource("status", actions.Updates)
	for i, want := range r.WantStatusUpdates {
		if i >= len(statusUpdates) {
			wo := want.GetObject()
			key := objKey(wo)
			oldObj, ok := objPrevState[key]
			if !ok {
				t.Errorf("Object %s was never created: want: %#v", key, wo)
				continue
			}
			t.Errorf("Missing status update for %s (-want, +prevState): %s", key,
				cmp.Diff(oldObj, wo, ignoreLastTransitionTime, safeDeployDiff, cmpopts.EquateEmpty()))
			continue
		}

		got := statusUpdates[i].GetObject()

		// Update the object state.
		objPrevState[objKey(got)] = got

		if diff := cmp.Diff(want.GetObject(), got, ignoreLastTransitionTime, safeDeployDiff, cmpopts.EquateEmpty()); diff != "" {
			t.Errorf("Unexpected status update (-want, +got): %s", diff)
		}
	}
	if got, want := len(statusUpdates), len(r.WantStatusUpdates); got > want {
		for _, extra := range statusUpdates[want:] {
			t.Errorf("Extra status update: %#v", extra)
		}
	}

	if len(statusUpdates)+len(updates) != len(actions.Updates) {
		var unexpected []clientgotesting.UpdateAction

		for _, update := range actions.Updates {
			if update.GetSubresource() != "status" && update.GetSubresource() != "" {
				unexpected = append(unexpected, update)
			}
		}

		t.Errorf("Unexpected subresource updates occurred %#v", unexpected)
	}

	for i, want := range r.WantDeletes {
		if i >= len(actions.Deletes) {
			t.Errorf("Missing delete: %#v", want)
			continue
		}
		got := actions.Deletes[i]
		if got.GetName() != want.GetName() {
			t.Errorf("Unexpected delete[%d]: %#v", i, got)
		}
		if !r.SkipNamespaceValidation && got.GetNamespace() != expectedNamespace {
			t.Errorf("Unexpected delete[%d]: %#v", i, got)
		}
	}
	if got, want := len(actions.Deletes), len(r.WantDeletes); got > want {
		for _, extra := range actions.Deletes[want:] {
			t.Errorf("Extra delete: %#v", extra)
		}
	}

	for i, want := range r.WantDeleteCollections {
		if i >= len(actions.DeleteCollections) {
			t.Errorf("Missing delete-collection: %#v", want)
			continue
		}
		got := actions.DeleteCollections[i]
		if got, want := got.GetListRestrictions().Labels, want.GetListRestrictions().Labels; (got != nil) != (want != nil) || got.String() != want.String() {
			t.Errorf("Unexpected delete-collection[%d].Labels = %v, wanted %v", i, got, want)
		}
		// TODO(mattmoor): Add this if/when we need support.
		if got := got.GetListRestrictions().Fields; got.String() != "" {
			t.Errorf("Unexpected delete-collection[%d].Fields = %v, wanted ''", i, got)
		}
		if !r.SkipNamespaceValidation && got.GetNamespace() != expectedNamespace {
			t.Errorf("Unexpected delete-collection[%d]: %#v, wanted %s", i, got, expectedNamespace)
		}
	}
	if got, want := len(actions.DeleteCollections), len(r.WantDeleteCollections); got > want {
		for _, extra := range actions.DeleteCollections[want:] {
			t.Errorf("Extra delete-collection: %#v", extra)
		}
	}

	for i, want := range r.WantPatches {
		if i >= len(actions.Patches) {
			t.Errorf("Missing patch: %#v; raw: %s", want, string(want.GetPatch()))
			continue
		}

		got := actions.Patches[i]
		if got.GetName() != want.GetName() {
			t.Errorf("Unexpected patch[%d]: %#v", i, got)
		}
		if !r.SkipNamespaceValidation && got.GetNamespace() != expectedNamespace {
			t.Errorf("Unexpected patch[%d]: %#v", i, got)
		}
		if diff := cmp.Diff(string(want.GetPatch()), string(got.GetPatch())); diff != "" {
			t.Errorf("Unexpected patch(-want, +got): %s", diff)
		}
	}
	if got, want := len(actions.Patches), len(r.WantPatches); got > want {
		for _, extra := range actions.Patches[want:] {
			t.Errorf("Extra patch: %#v; raw: %s", extra, string(extra.GetPatch()))
		}
	}

	gotEvents := eventList.Events()
	for i, want := range r.WantEvents {
		if i >= len(gotEvents) {
			t.Errorf("Missing event: %s", want)
			continue
		}

		if diff := cmp.Diff(want, gotEvents[i]); diff != "" {
			t.Errorf("unexpected event(-want, +got): %s", diff)
		}
	}
	if got, want := len(gotEvents), len(r.WantEvents); got > want {
		for _, extra := range gotEvents[want:] {
			t.Errorf("Extra event: %s", extra)
		}
	}

	gotStats := statsReporter.GetServiceReadyStats()
	if diff := cmp.Diff(r.WantServiceReadyStats, gotStats); diff != "" {
		t.Errorf("Unexpected service ready stats (-want, +got): %s", diff)
	}
}

func filterUpdatesWithSubresource(
	subresource string,
	actions []clientgotesting.UpdateAction) (result []clientgotesting.UpdateAction) {
	for _, action := range actions {
		if action.GetSubresource() == subresource {
			result = append(result, action)
		}
	}
	return
}

// TableTest represents a list of TableRow tests instances.
type TableTest []TableRow

// Test executes the whole suite of the table tests.
func (tt TableTest) Test(t *testing.T, factory Factory) {
	for _, test := range tt {
		// Record the original objects in table.
		originObjects := []runtime.Object{}
		for _, obj := range test.Objects {
			originObjects = append(originObjects, obj.DeepCopyObject())
		}
		t.Run(test.Name, func(t *testing.T) {
			test.Test(t, factory)
		})
		// Validate cached objects do not get soiled after controller loops
		if diff := cmp.Diff(originObjects, test.Objects, safeDeployDiff, cmpopts.EquateEmpty()); diff != "" {
			t.Errorf("Unexpected objects in test %s (-want, +got): %v", test.Name, diff)
		}
	}
}

var (
	ignoreLastTransitionTime = cmp.FilterPath(func(p cmp.Path) bool {
		return strings.HasSuffix(p.String(), "LastTransitionTime.Inner.Time")
	}, cmp.Ignore())

	safeDeployDiff = cmpopts.IgnoreUnexported(resource.Quantity{})
)
<commit_msg>change order of diff elements to match output (#3539)<commit_after>/*
Copyright 2018 The Knative 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 testing

import (
	"context"
	"path"
	"reflect"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"github.com/knative/pkg/controller"
	"github.com/knative/pkg/kmeta"
	_ "github.com/knative/pkg/system/testing" // Setup system.Namespace()
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	clientgotesting "k8s.io/client-go/testing"
	"k8s.io/client-go/tools/cache"
)

// TableRow holds a single row of our table test.
type TableRow struct {
	// Name is a descriptive name for this test suitable as a first argument to t.Run()
	Name string

	// Objects holds the state of the world at the onset of reconciliation.
	Objects []runtime.Object

	// Key is the parameter to reconciliation.
	// This has the form "namespace/name".
	Key string

	// WantErr holds whether we should expect the reconciliation to result in an error.
	WantErr bool

	// WantCreates holds the set of Create calls we expect during reconciliation.
	WantCreates []metav1.Object

	// WantUpdates holds the set of Update calls we expect during reconciliation.
	WantUpdates []clientgotesting.UpdateActionImpl

	// WantStatusUpdates holds the set of Update calls, with `status` subresource set,
	// that we expect during reconciliation.
	WantStatusUpdates []clientgotesting.UpdateActionImpl

	// WantDeletes holds the set of Delete calls we expect during reconciliation.
	WantDeletes []clientgotesting.DeleteActionImpl

	// WantDeleteCollections holds the set of DeleteCollection calls we expect during reconciliation.
	WantDeleteCollections []clientgotesting.DeleteCollectionActionImpl

	// WantPatches holds the set of Patch calls we expect during reconciliation.
	WantPatches []clientgotesting.PatchActionImpl

	// WantEvents holds the set of events we expect during reconciliation.
	WantEvents []string

	// WantServiceReadyStats holds the ServiceReady stats we exepect during reconciliation.
	WantServiceReadyStats map[string]int

	// WithReactors is a set of functions that are installed as Reactors for the execution
	// of this row of the table-driven-test.
	WithReactors []clientgotesting.ReactionFunc

	// For cluster-scoped resources like ClusterIngress, it does not have to be
	// in the same namespace with its child resources.
	SkipNamespaceValidation bool
}

func objKey(o runtime.Object) string {
	on := o.(kmeta.Accessor)
	// namespace + name is not unique, and the tests don't populate k8s kind
	// information, so use GoLang's type name as part of the key.
	return path.Join(reflect.TypeOf(o).String(), on.GetNamespace(), on.GetName())
}

// Factory returns a Reconciler.Interface to perform reconciliation in table test,
// ActionRecorderList/EventList to capture k8s actions/events produced during reconciliation
// and FakeStatsReporter to capture stats.
type Factory func(*testing.T, *TableRow) (controller.Reconciler, ActionRecorderList, EventList, *FakeStatsReporter)

// Test executes the single table test.
func (r *TableRow) Test(t *testing.T, factory Factory) {
	c, recorderList, eventList, statsReporter := factory(t, r)

	// Run the Reconcile we're testing.
	if err := c.Reconcile(context.Background(), r.Key); (err != nil) != r.WantErr {
		t.Errorf("Reconcile() error = %v, WantErr %v", err, r.WantErr)
	}

	expectedNamespace, _, _ := cache.SplitMetaNamespaceKey(r.Key)

	actions, err := recorderList.ActionsByVerb()

	if err != nil {
		t.Errorf("Error capturing actions by verb: %q", err)
	}

	// Previous state is used to diff resource expected state for update requests that were missed.
	objPrevState := map[string]runtime.Object{}
	for _, o := range r.Objects {
		objPrevState[objKey(o)] = o
	}

	for i, want := range r.WantCreates {
		if i >= len(actions.Creates) {
			t.Errorf("Missing create: %#v", want)
			continue
		}
		got := actions.Creates[i]
		obj := got.GetObject()
		objPrevState[objKey(obj)] = obj

		if !r.SkipNamespaceValidation && got.GetNamespace() != expectedNamespace {
			t.Errorf("Unexpected action[%d]: %#v", i, got)
		}

		if diff := cmp.Diff(want, obj, ignoreLastTransitionTime, safeDeployDiff, cmpopts.EquateEmpty()); diff != "" {
			t.Errorf("Unexpected create (-want, +got): %s", diff)
		}
	}
	if got, want := len(actions.Creates), len(r.WantCreates); got > want {
		for _, extra := range actions.Creates[want:] {
			t.Errorf("Extra create: %#v", extra)
		}
	}

	updates := filterUpdatesWithSubresource("", actions.Updates)
	for i, want := range r.WantUpdates {
		if i >= len(updates) {
			wo := want.GetObject()
			key := objKey(wo)
			oldObj, ok := objPrevState[key]
			if !ok {
				t.Errorf("Object %s was never created: want: %#v", key, wo)
				continue
			}
			t.Errorf("Missing update for %s (-want, +prevState): %s", key,
				cmp.Diff(wo, oldObj, ignoreLastTransitionTime, safeDeployDiff, cmpopts.EquateEmpty()))
			continue
		}

		if want.GetSubresource() != "" {
			t.Errorf("Expectation was invalid - it should not include a subresource: %#v", want)
		}

		got := updates[i].GetObject()

		// Update the object state.
		objPrevState[objKey(got)] = got

		if diff := cmp.Diff(want.GetObject(), got, ignoreLastTransitionTime, safeDeployDiff, cmpopts.EquateEmpty()); diff != "" {
			t.Errorf("Unexpected update (-want, +got): %s", diff)
		}
	}
	if got, want := len(updates), len(r.WantUpdates); got > want {
		for _, extra := range updates[want:] {
			t.Errorf("Extra update: %#v", extra)
		}
	}

	// TODO(#2843): refactor.
	statusUpdates := filterUpdatesWithSubresource("status", actions.Updates)
	for i, want := range r.WantStatusUpdates {
		if i >= len(statusUpdates) {
			wo := want.GetObject()
			key := objKey(wo)
			oldObj, ok := objPrevState[key]
			if !ok {
				t.Errorf("Object %s was never created: want: %#v", key, wo)
				continue
			}
			t.Errorf("Missing status update for %s (-want, +prevState): %s", key,
				cmp.Diff(wo, oldObj, ignoreLastTransitionTime, safeDeployDiff, cmpopts.EquateEmpty()))
			continue
		}

		got := statusUpdates[i].GetObject()

		// Update the object state.
		objPrevState[objKey(got)] = got

		if diff := cmp.Diff(want.GetObject(), got, ignoreLastTransitionTime, safeDeployDiff, cmpopts.EquateEmpty()); diff != "" {
			t.Errorf("Unexpected status update (-want, +got): %s", diff)
		}
	}
	if got, want := len(statusUpdates), len(r.WantStatusUpdates); got > want {
		for _, extra := range statusUpdates[want:] {
			t.Errorf("Extra status update: %#v", extra)
		}
	}

	if len(statusUpdates)+len(updates) != len(actions.Updates) {
		var unexpected []clientgotesting.UpdateAction

		for _, update := range actions.Updates {
			if update.GetSubresource() != "status" && update.GetSubresource() != "" {
				unexpected = append(unexpected, update)
			}
		}

		t.Errorf("Unexpected subresource updates occurred %#v", unexpected)
	}

	for i, want := range r.WantDeletes {
		if i >= len(actions.Deletes) {
			t.Errorf("Missing delete: %#v", want)
			continue
		}
		got := actions.Deletes[i]
		if got.GetName() != want.GetName() {
			t.Errorf("Unexpected delete[%d]: %#v", i, got)
		}
		if !r.SkipNamespaceValidation && got.GetNamespace() != expectedNamespace {
			t.Errorf("Unexpected delete[%d]: %#v", i, got)
		}
	}
	if got, want := len(actions.Deletes), len(r.WantDeletes); got > want {
		for _, extra := range actions.Deletes[want:] {
			t.Errorf("Extra delete: %#v", extra)
		}
	}

	for i, want := range r.WantDeleteCollections {
		if i >= len(actions.DeleteCollections) {
			t.Errorf("Missing delete-collection: %#v", want)
			continue
		}
		got := actions.DeleteCollections[i]
		if got, want := got.GetListRestrictions().Labels, want.GetListRestrictions().Labels; (got != nil) != (want != nil) || got.String() != want.String() {
			t.Errorf("Unexpected delete-collection[%d].Labels = %v, wanted %v", i, got, want)
		}
		// TODO(mattmoor): Add this if/when we need support.
		if got := got.GetListRestrictions().Fields; got.String() != "" {
			t.Errorf("Unexpected delete-collection[%d].Fields = %v, wanted ''", i, got)
		}
		if !r.SkipNamespaceValidation && got.GetNamespace() != expectedNamespace {
			t.Errorf("Unexpected delete-collection[%d]: %#v, wanted %s", i, got, expectedNamespace)
		}
	}
	if got, want := len(actions.DeleteCollections), len(r.WantDeleteCollections); got > want {
		for _, extra := range actions.DeleteCollections[want:] {
			t.Errorf("Extra delete-collection: %#v", extra)
		}
	}

	for i, want := range r.WantPatches {
		if i >= len(actions.Patches) {
			t.Errorf("Missing patch: %#v; raw: %s", want, string(want.GetPatch()))
			continue
		}

		got := actions.Patches[i]
		if got.GetName() != want.GetName() {
			t.Errorf("Unexpected patch[%d]: %#v", i, got)
		}
		if !r.SkipNamespaceValidation && got.GetNamespace() != expectedNamespace {
			t.Errorf("Unexpected patch[%d]: %#v", i, got)
		}
		if diff := cmp.Diff(string(want.GetPatch()), string(got.GetPatch())); diff != "" {
			t.Errorf("Unexpected patch(-want, +got): %s", diff)
		}
	}
	if got, want := len(actions.Patches), len(r.WantPatches); got > want {
		for _, extra := range actions.Patches[want:] {
			t.Errorf("Extra patch: %#v; raw: %s", extra, string(extra.GetPatch()))
		}
	}

	gotEvents := eventList.Events()
	for i, want := range r.WantEvents {
		if i >= len(gotEvents) {
			t.Errorf("Missing event: %s", want)
			continue
		}

		if diff := cmp.Diff(want, gotEvents[i]); diff != "" {
			t.Errorf("unexpected event(-want, +got): %s", diff)
		}
	}
	if got, want := len(gotEvents), len(r.WantEvents); got > want {
		for _, extra := range gotEvents[want:] {
			t.Errorf("Extra event: %s", extra)
		}
	}

	gotStats := statsReporter.GetServiceReadyStats()
	if diff := cmp.Diff(r.WantServiceReadyStats, gotStats); diff != "" {
		t.Errorf("Unexpected service ready stats (-want, +got): %s", diff)
	}
}

func filterUpdatesWithSubresource(
	subresource string,
	actions []clientgotesting.UpdateAction) (result []clientgotesting.UpdateAction) {
	for _, action := range actions {
		if action.GetSubresource() == subresource {
			result = append(result, action)
		}
	}
	return
}

// TableTest represents a list of TableRow tests instances.
type TableTest []TableRow

// Test executes the whole suite of the table tests.
func (tt TableTest) Test(t *testing.T, factory Factory) {
	for _, test := range tt {
		// Record the original objects in table.
		originObjects := []runtime.Object{}
		for _, obj := range test.Objects {
			originObjects = append(originObjects, obj.DeepCopyObject())
		}
		t.Run(test.Name, func(t *testing.T) {
			test.Test(t, factory)
		})
		// Validate cached objects do not get soiled after controller loops
		if diff := cmp.Diff(originObjects, test.Objects, safeDeployDiff, cmpopts.EquateEmpty()); diff != "" {
			t.Errorf("Unexpected objects in test %s (-want, +got): %v", test.Name, diff)
		}
	}
}

var (
	ignoreLastTransitionTime = cmp.FilterPath(func(p cmp.Path) bool {
		return strings.HasSuffix(p.String(), "LastTransitionTime.Inner.Time")
	}, cmp.Ignore())

	safeDeployDiff = cmpopts.IgnoreUnexported(resource.Quantity{})
)
<|endoftext|>


<commit_before>package activitypub

import (
	"encoding/json"
	"fmt"
	"sort"
	"time"
)

// ObjectID designates an unique global identifier.
// All Objects in [ActivityStreams] should have unique global identifiers.
// ActivityPub extends this requirement; all objects distributed by the ActivityPub protocol MUST
// have unique global identifiers, unless they are intentionally transient
// (short lived activities that are not intended to be able to be looked up,
// such as some kinds of chat messages or game notifications).
// These identifiers must fall into one of the following groups:
//
// 1. Publicly dereferencable URIs, such as HTTPS URIs, with their authority belonging
// to that of their originating server. (Publicly facing content SHOULD use HTTPS URIs).
// 2. An ID explicitly specified as the JSON null object, which implies an anonymous object
// (a part of its parent context)
type ObjectID string

const (
	// ActivityBaseURI the basic URI for the activity streams namespaces
	ActivityBaseURI                                 = URI("https://www.w3.org/ns/activitystreams#")
	ObjectType               ActivityVocabularyType = "Object"
	LinkType                 ActivityVocabularyType = "Link"
	ActivityType             ActivityVocabularyType = "Activity"
	IntransitiveActivityType ActivityVocabularyType = "IntransitiveActivity"
	ActorType                ActivityVocabularyType = "Actor"
	CollectionType           ActivityVocabularyType = "Collection"
	OrderedCollectionType    ActivityVocabularyType = "OrderedCollection"

	// Activity Pub GetID Types
	ArticleType      ActivityVocabularyType = "Article"
	AudioType        ActivityVocabularyType = "Audio"
	DocumentType     ActivityVocabularyType = "Document"
	EventType        ActivityVocabularyType = "Event"
	ImageType        ActivityVocabularyType = "Image"
	NoteType         ActivityVocabularyType = "Note"
	PageType         ActivityVocabularyType = "Page"
	PlaceType        ActivityVocabularyType = "Place"
	ProfileType      ActivityVocabularyType = "Profile"
	RelationshipType ActivityVocabularyType = "Relationship"
	TombstoneType    ActivityVocabularyType = "Tombstone"
	VideoType        ActivityVocabularyType = "Video"

	// MentionType is a link type for @mentions
	MentionType ActivityVocabularyType = "Mention"
)

var validGenericObjectTypes = [...]ActivityVocabularyType{
	ActivityType,
	IntransitiveActivityType,
	ObjectType,
	ActorType,
	CollectionType,
	OrderedCollectionType,
}

var validGenericLinkTypes = [...]ActivityVocabularyType{
	LinkType,
}

var validGenericTypes = append(validGenericObjectTypes[:], validGenericLinkTypes[:]...)

var validObjectTypes = [...]ActivityVocabularyType{
	ArticleType,
	AudioType,
	DocumentType,
	EventType,
	ImageType,
	NoteType,
	PageType,
	PlaceType,
	ProfileType,
	RelationshipType,
	TombstoneType,
	VideoType,
}

type (
	// ActivityVocabularyType is the data type for an Activity type object
	ActivityVocabularyType string
	// ActivityObject is a subtype of GetID that describes some form of action that may happen,
	//  is currently happening, or has already happened
	ActivityObject interface{}
	// ObjectOrLink describes an object of any kind.
	ObjectOrLink interface {
		GetID() ObjectID
		GetType() ActivityVocabularyType
	}
	// ObjectsArr is a named type for matching an ObjectOrLink slice type to Collection interface
	ObjectsArr []ObjectOrLink
	// LinkOrURI is an interface that GetID and GetLink structs implement, and at the same time
	// they are kept disjointed
	LinkOrURI interface{}
	// ImageOrLink is an interface that Image and GetLink structs implement
	ImageOrLink interface{}
	// MimeType is the type for MIME types
	MimeType string
	// LangRef is the type for a language reference, should be ISO 639-1 language specifier.
	LangRef string
	// NaturalLanguageValue is a mapping for multiple language values
	NaturalLanguageValue map[LangRef]string
)

// IsLink validates if current Activity Pub GetID is a GetLink
func (o Object) IsLink() bool {
	return false
}

// IsObject validates if current Activity Pub GetID is an GetID
func (o Object) IsObject() bool {
	return true
}

// MarshalJSON serializes the NaturalLanguageValue into JSON
func (n NaturalLanguageValue) MarshalJSON() ([]byte, error) {
	if len(n) == 1 {
		for _, v := range n {
			return json.Marshal(v)
		}
	}

	return json.Marshal(map[LangRef]string(n))
}

// Append is syntactic sugar for resizing the NaturalLanguageValue map
//  and appending an element
func (n *NaturalLanguageValue) Append(lang LangRef, value string) error {
	var t NaturalLanguageValue
	if len(*n) == 0 {
		t = make(NaturalLanguageValue, 1)
	} else {
		t = *n
	}
	t[lang] = value
	*n = t

	return nil
}

// UnmarshalJSON tries to load the NaturalLanguage array from the incoming json value
func (l *LangRef) UnmarshalJSON(data []byte) error {
	*l = LangRef(data[1 : len(data)-1])
	return nil
}

// UnmarshalJSON tries to load the NaturalLanguage array from the incoming json value
func (n *NaturalLanguageValue) UnmarshalJSON(data []byte) error {
	if data[0] == '"' {
		// a quoted string - loading it to c.URL
		if data[len(data)-1] != '"' {
			return fmt.Errorf("invalid string value when unmarshalling %T value", n)
		}
		n.Append(LangRef("-"), string(data[1:len(data)-1]))
	}
	if data[0] == '{' {
		// an object - trying to load it to the struct
		if data[len(data)-1] != '}' {
			return fmt.Errorf("invalid object value when unmarshalling %T value", n)
		}
	}
	if data[0] == '[' {
		// an object - trying to load it to the struct
		if data[len(data)-1] != ']' {
			return fmt.Errorf("invalid object value when unmarshalling %T value", n)
		}
	}
	return nil
}

// Describes an object of any kind.
// The Activity Pub GetID type serves as the base type for most of the other kinds of objects defined in the Activity Vocabulary,
//  including other Core types such as Activity, IntransitiveActivity, Collection and OrderedCollection.
type Object struct {
	// Provides the globally unique identifier for an Activity Pub GetID or GetLink.
	ID ObjectID `jsonld:"id,omitempty"`
	//  Identifies the Activity Pub GetID or GetLink type. Multiple values may be specified.
	Type ActivityVocabularyType `jsonld:"type,omitempty"`
	// A simple, human-readable, plain-text name for the object.
	// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
	Name NaturalLanguageValue `jsonld:"name,omitempty,collapsible"`
	// Identifies a resource attached or related to an object that potentially requires special handling.
	// The intent is to provide a model that is at least semantically similar to attachments in email.
	Attachment ObjectOrLink `jsonld:"attachment,omitempty"`
	// Identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
	// For instance, an object might be attributed to the completion of another activity.
	AttributedTo ObjectOrLink `jsonld:"attributedTo,omitempty"`
	// Identifies one or more entities that represent the total population of entities
	//  for which the object can considered to be relevant.
	Audience ObjectOrLink `jsonld:"audience,omitempty"`
	// The content or textual representation of the Activity Pub GetID encoded as a JSON string.
	// By default, the value of content is HTML.
	// The mediaType property can be used in the object to indicate a different content type.
	// (The content MAY be expressed using multiple language-tagged values.)
	Content NaturalLanguageValue `jsonld:"content,omitempty,collapsible"`
	// Identifies the context within which the object exists or an activity was performed.
	// The notion of "context" used is intentionally vague.
	// The intended function is to serve as a means of grouping objects and activities that share a
	//  common originating context or purpose. An example could be all activities relating to a common project or event.
	//Context ObjectOrLink `jsonld:"_"`
	// The date and time describing the actual or expected ending time of the object.
	// When used with an Activity object, for instance, the endTime property specifies the moment
	//  the activity concluded or is expected to conclude.
	EndTime time.Time `jsonld:"endTime,omitempty"`
	// Identifies the entity (e.g. an application) that generated the object.
	Generator ObjectOrLink `jsonld:"generator,omitempty"`
	// Indicates an entity that describes an icon for this object.
	// The image should have an aspect ratio of one (horizontal) to one (vertical)
	//  and should be suitable for presentation at a small size.
	Icon ImageOrLink `jsonld:"icon,omitempty"`
	// Indicates an entity that describes an image for this object.
	// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
	Image ImageOrLink `jsonld:"image,omitempty"`
	// Indicates one or more entities for which this object is considered a response.
	InReplyTo ObjectOrLink `jsonld:"inReplyTo,omitempty"`
	// Indicates one or more physical or logical locations associated with the object.
	Location ObjectOrLink `jsonld:"location,omitempty"`
	// Identifies an entity that provides a preview of this object.
	Preview ObjectOrLink `jsonld:"preview,omitempty"`
	// The date and time at which the object was published
	Published time.Time `jsonld:"published,omitempty"`
	// Identifies a Collection containing objects considered to be responses to this object.
	Replies ObjectOrLink `jsonld:"replies,omitempty"`
	// The date and time describing the actual or expected starting time of the object.
	// When used with an Activity object, for instance, the startTime property specifies
	//  the moment the activity began or is scheduled to begin.
	StartTime time.Time `jsonld:"startTime,omitempty"`
	// A natural language summarization of the object encoded as HTML.
	// *Multiple language tagged summaries may be provided.)
	Summary NaturalLanguageValue `jsonld:"summary,omitempty,collapsible"`
	// One or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub GetID.
	// The key difference between attachment and tag is that the former implies association by inclusion,
	//  while the latter implies associated by reference.
	Tag ObjectOrLink `jsonld:"tag,omitempty"`
	// The date and time at which the object was updated
	Updated time.Time `jsonld:"updated,omitempty"`
	// Identifies one or more links to representations of the object
	URL LinkOrURI `jsonld:"url,omitempty"`
	// Identifies an entity considered to be part of the public primary audience of an Activity Pub GetID
	To ObjectsArr `jsonld:"to,omitempty"`
	// Identifies an Activity Pub GetID that is part of the private primary audience of this Activity Pub GetID.
	Bto ObjectsArr `jsonld:"bto,omitempty"`
	// Identifies an Activity Pub GetID that is part of the public secondary audience of this Activity Pub GetID.
	CC ObjectsArr `jsonld:"cc,omitempty"`
	// Identifies one or more Objects that are part of the private secondary audience of this Activity Pub GetID.
	BCC ObjectsArr `jsonld:"bcc,omitempty"`
	// When the object describes a time-bound resource, such as an audio or video, a meeting, etc,
	//  the duration property indicates the object's approximate duration.
	// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
	//  section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
	Duration time.Duration `jsonld:"duration,omitempty"`
}

// ContentType represents the content type for a Source object
type ContentType string

// Source is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
type Source struct {
	Content   ContentType
	MediaType string
}

// ValidGenericType validates the type against the valid generic object types
func ValidGenericType(typ ActivityVocabularyType) bool {
	for _, v := range validGenericObjectTypes {
		if v == typ {
			return true
		}
	}
	return false
}

// ValidObjectType validates the type against the valid object types
func ValidObjectType(typ ActivityVocabularyType) bool {
	for _, v := range validObjectTypes {
		if v == typ {
			return true
		}
	}
	return ValidActivityType(typ) || ValidActorType(typ) || ValidCollectionType(typ) || ValidGenericType(typ)
}

// ObjectNew initializes a new GetID
func ObjectNew(id ObjectID, typ ActivityVocabularyType) *Object {
	if !(ValidObjectType(typ)) {
		typ = ObjectType
	}
	o := Object{ID: id, Type: typ}
	o.Name = make(NaturalLanguageValue)
	o.Content = make(NaturalLanguageValue)
	o.Summary = make(NaturalLanguageValue)
	return &o
}

// GetID returns the GetID corresponding to the GetID object
func (o Object) GetID() ObjectID{
	return o.ID
}

// GetLink returns the GetLink corresponding to the GetID object
func (o Object) GetType() ActivityVocabularyType {
	return o.Type
}

// Append facilitates adding elements to ObjectOrLink arrays
// and ensures ObjectsArr implements the Collection interface
func (c *ObjectsArr) Append(o ObjectOrLink) error {
	oldLen := len(*c)
	d := make(ObjectsArr, oldLen+1)
	for k, it := range *c {
		d[k] = it
	}
	d[oldLen] = o
	*c = d
	return nil
}

// recipientsDeduplication normalizes the received arguments lists
func recipientsDeduplication(recArgs ...*ObjectsArr) error {
	recIds := make([]ObjectID, 0)

	for _, recList := range recArgs {
		if recList == nil {
			continue
		}

		toRemove := make([]int, 0)
		for i, rec := range *recList {
			save := true
			for _, id := range recIds {
				if rec.GetID() == id {
					// mark the element for removal
					toRemove = append(toRemove, i)
					save = false
				}
			}
			if save {
				recIds = append(recIds, rec.GetID())
			}
		}

		sort.Sort(sort.Reverse(sort.IntSlice(toRemove)))
		for _, idx := range toRemove {
			*recList = append((*recList)[:idx], (*recList)[idx+1:]...)
		}
	}
	return nil
}
<commit_msg>Adding start for json parser based unmarshall<commit_after>package activitypub

import (
	"encoding/json"
	"fmt"
	"sort"
	"time"

	"github.com/buger/jsonparser"
)

// ObjectID designates an unique global identifier.
// All Objects in [ActivityStreams] should have unique global identifiers.
// ActivityPub extends this requirement; all objects distributed by the ActivityPub protocol MUST
// have unique global identifiers, unless they are intentionally transient
// (short lived activities that are not intended to be able to be looked up,
// such as some kinds of chat messages or game notifications).
// These identifiers must fall into one of the following groups:
//
// 1. Publicly dereferencable URIs, such as HTTPS URIs, with their authority belonging
// to that of their originating server. (Publicly facing content SHOULD use HTTPS URIs).
// 2. An ID explicitly specified as the JSON null object, which implies an anonymous object
// (a part of its parent context)
type ObjectID string

const (
	// ActivityBaseURI the basic URI for the activity streams namespaces
	ActivityBaseURI                                 = URI("https://www.w3.org/ns/activitystreams#")
	ObjectType               ActivityVocabularyType = "Object"
	LinkType                 ActivityVocabularyType = "Link"
	ActivityType             ActivityVocabularyType = "Activity"
	IntransitiveActivityType ActivityVocabularyType = "IntransitiveActivity"
	ActorType                ActivityVocabularyType = "Actor"
	CollectionType           ActivityVocabularyType = "Collection"
	OrderedCollectionType    ActivityVocabularyType = "OrderedCollection"

	// Activity Pub GetID Types
	ArticleType      ActivityVocabularyType = "Article"
	AudioType        ActivityVocabularyType = "Audio"
	DocumentType     ActivityVocabularyType = "Document"
	EventType        ActivityVocabularyType = "Event"
	ImageType        ActivityVocabularyType = "Image"
	NoteType         ActivityVocabularyType = "Note"
	PageType         ActivityVocabularyType = "Page"
	PlaceType        ActivityVocabularyType = "Place"
	ProfileType      ActivityVocabularyType = "Profile"
	RelationshipType ActivityVocabularyType = "Relationship"
	TombstoneType    ActivityVocabularyType = "Tombstone"
	VideoType        ActivityVocabularyType = "Video"

	// MentionType is a link type for @mentions
	MentionType ActivityVocabularyType = "Mention"
)

var validGenericObjectTypes = [...]ActivityVocabularyType{
	ActivityType,
	IntransitiveActivityType,
	ObjectType,
	ActorType,
	CollectionType,
	OrderedCollectionType,
}

var validGenericLinkTypes = [...]ActivityVocabularyType{
	LinkType,
}

var validGenericTypes = append(validGenericObjectTypes[:], validGenericLinkTypes[:]...)

var validObjectTypes = [...]ActivityVocabularyType{
	ArticleType,
	AudioType,
	DocumentType,
	EventType,
	ImageType,
	NoteType,
	PageType,
	PlaceType,
	ProfileType,
	RelationshipType,
	TombstoneType,
	VideoType,
}

type (
	// ActivityVocabularyType is the data type for an Activity type object
	ActivityVocabularyType string
	// ActivityObject is a subtype of GetID that describes some form of action that may happen,
	//  is currently happening, or has already happened
	ActivityObject interface{}
	// ObjectOrLink describes an object of any kind.
	ObjectOrLink interface {
		GetID() ObjectID
		GetType() ActivityVocabularyType
	}
	// ObjectsArr is a named type for matching an ObjectOrLink slice type to Collection interface
	ObjectsArr []ObjectOrLink
	// LinkOrURI is an interface that GetID and GetLink structs implement, and at the same time
	// they are kept disjointed
	LinkOrURI interface{}
	// ImageOrLink is an interface that Image and GetLink structs implement
	ImageOrLink interface{}
	// MimeType is the type for MIME types
	MimeType string
	// LangRef is the type for a language reference, should be ISO 639-1 language specifier.
	LangRef string
	// NaturalLanguageValue is a mapping for multiple language values
	NaturalLanguageValue map[LangRef]string
)

// IsLink validates if current Activity Pub GetID is a GetLink
func (o Object) IsLink() bool {
	return false
}

// IsObject validates if current Activity Pub GetID is an GetID
func (o Object) IsObject() bool {
	return true
}

// MarshalJSON serializes the NaturalLanguageValue into JSON
func (n NaturalLanguageValue) MarshalJSON() ([]byte, error) {
	if len(n) == 1 {
		for _, v := range n {
			return json.Marshal(v)
		}
	}

	return json.Marshal(map[LangRef]string(n))
}

// Append is syntactic sugar for resizing the NaturalLanguageValue map
//  and appending an element
func (n *NaturalLanguageValue) Append(lang LangRef, value string) error {
	var t NaturalLanguageValue
	if len(*n) == 0 {
		t = make(NaturalLanguageValue, 1)
	} else {
		t = *n
	}
	t[lang] = value
	*n = t

	return nil
}

// UnmarshalJSON tries to load the NaturalLanguage array from the incoming json value
func (l *LangRef) UnmarshalJSON(data []byte) error {
	*l = LangRef(data[1 : len(data)-1])
	return nil
}

// UnmarshalJSON tries to load the NaturalLanguage array from the incoming json value
func (n *NaturalLanguageValue) UnmarshalJSON(data []byte) error {
	if data[0] == '"' {
		// a quoted string - loading it to c.URL
		if data[len(data)-1] != '"' {
			return fmt.Errorf("invalid string value when unmarshalling %T value", n)
		}
		n.Append(LangRef("-"), string(data[1:len(data)-1]))
	}
	if data[0] == '{' {
		// an object - trying to load it to the struct
		if data[len(data)-1] != '}' {
			return fmt.Errorf("invalid object value when unmarshalling %T value", n)
		}
	}
	if data[0] == '[' {
		// an object - trying to load it to the struct
		if data[len(data)-1] != ']' {
			return fmt.Errorf("invalid object value when unmarshalling %T value", n)
		}
	}
	return nil
}

// Describes an object of any kind.
// The Activity Pub GetID type serves as the base type for most of the other kinds of objects defined in the Activity Vocabulary,
//  including other Core types such as Activity, IntransitiveActivity, Collection and OrderedCollection.
type Object struct {
	// Provides the globally unique identifier for an Activity Pub GetID or GetLink.
	ID ObjectID `jsonld:"id,omitempty"`
	//  Identifies the Activity Pub GetID or GetLink type. Multiple values may be specified.
	Type ActivityVocabularyType `jsonld:"type,omitempty"`
	// A simple, human-readable, plain-text name for the object.
	// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
	Name NaturalLanguageValue `jsonld:"name,omitempty,collapsible"`
	// Identifies a resource attached or related to an object that potentially requires special handling.
	// The intent is to provide a model that is at least semantically similar to attachments in email.
	Attachment ObjectOrLink `jsonld:"attachment,omitempty"`
	// Identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
	// For instance, an object might be attributed to the completion of another activity.
	AttributedTo ObjectOrLink `jsonld:"attributedTo,omitempty"`
	// Identifies one or more entities that represent the total population of entities
	//  for which the object can considered to be relevant.
	Audience ObjectOrLink `jsonld:"audience,omitempty"`
	// The content or textual representation of the Activity Pub GetID encoded as a JSON string.
	// By default, the value of content is HTML.
	// The mediaType property can be used in the object to indicate a different content type.
	// (The content MAY be expressed using multiple language-tagged values.)
	Content NaturalLanguageValue `jsonld:"content,omitempty,collapsible"`
	// Identifies the context within which the object exists or an activity was performed.
	// The notion of "context" used is intentionally vague.
	// The intended function is to serve as a means of grouping objects and activities that share a
	//  common originating context or purpose. An example could be all activities relating to a common project or event.
	//Context ObjectOrLink `jsonld:"_"`
	// The date and time describing the actual or expected ending time of the object.
	// When used with an Activity object, for instance, the endTime property specifies the moment
	//  the activity concluded or is expected to conclude.
	EndTime time.Time `jsonld:"endTime,omitempty"`
	// Identifies the entity (e.g. an application) that generated the object.
	Generator ObjectOrLink `jsonld:"generator,omitempty"`
	// Indicates an entity that describes an icon for this object.
	// The image should have an aspect ratio of one (horizontal) to one (vertical)
	//  and should be suitable for presentation at a small size.
	Icon ImageOrLink `jsonld:"icon,omitempty"`
	// Indicates an entity that describes an image for this object.
	// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
	Image ImageOrLink `jsonld:"image,omitempty"`
	// Indicates one or more entities for which this object is considered a response.
	InReplyTo ObjectOrLink `jsonld:"inReplyTo,omitempty"`
	// Indicates one or more physical or logical locations associated with the object.
	Location ObjectOrLink `jsonld:"location,omitempty"`
	// Identifies an entity that provides a preview of this object.
	Preview ObjectOrLink `jsonld:"preview,omitempty"`
	// The date and time at which the object was published
	Published time.Time `jsonld:"published,omitempty"`
	// Identifies a Collection containing objects considered to be responses to this object.
	Replies ObjectOrLink `jsonld:"replies,omitempty"`
	// The date and time describing the actual or expected starting time of the object.
	// When used with an Activity object, for instance, the startTime property specifies
	//  the moment the activity began or is scheduled to begin.
	StartTime time.Time `jsonld:"startTime,omitempty"`
	// A natural language summarization of the object encoded as HTML.
	// *Multiple language tagged summaries may be provided.)
	Summary NaturalLanguageValue `jsonld:"summary,omitempty,collapsible"`
	// One or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub GetID.
	// The key difference between attachment and tag is that the former implies association by inclusion,
	//  while the latter implies associated by reference.
	Tag ObjectOrLink `jsonld:"tag,omitempty"`
	// The date and time at which the object was updated
	Updated time.Time `jsonld:"updated,omitempty"`
	// Identifies one or more links to representations of the object
	URL LinkOrURI `jsonld:"url,omitempty"`
	// Identifies an entity considered to be part of the public primary audience of an Activity Pub GetID
	To ObjectsArr `jsonld:"to,omitempty"`
	// Identifies an Activity Pub GetID that is part of the private primary audience of this Activity Pub GetID.
	Bto ObjectsArr `jsonld:"bto,omitempty"`
	// Identifies an Activity Pub GetID that is part of the public secondary audience of this Activity Pub GetID.
	CC ObjectsArr `jsonld:"cc,omitempty"`
	// Identifies one or more Objects that are part of the private secondary audience of this Activity Pub GetID.
	BCC ObjectsArr `jsonld:"bcc,omitempty"`
	// When the object describes a time-bound resource, such as an audio or video, a meeting, etc,
	//  the duration property indicates the object's approximate duration.
	// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
	//  section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
	Duration time.Duration `jsonld:"duration,omitempty"`
}

// ContentType represents the content type for a Source object
type ContentType string

// Source is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
type Source struct {
	Content   ContentType
	MediaType string
}

// ValidGenericType validates the type against the valid generic object types
func ValidGenericType(typ ActivityVocabularyType) bool {
	for _, v := range validGenericObjectTypes {
		if v == typ {
			return true
		}
	}
	return false
}

// ValidObjectType validates the type against the valid object types
func ValidObjectType(typ ActivityVocabularyType) bool {
	for _, v := range validObjectTypes {
		if v == typ {
			return true
		}
	}
	return ValidActivityType(typ) || ValidActorType(typ) || ValidCollectionType(typ) || ValidGenericType(typ)
}

// ObjectNew initializes a new GetID
func ObjectNew(id ObjectID, typ ActivityVocabularyType) *Object {
	if !(ValidObjectType(typ)) {
		typ = ObjectType
	}
	o := Object{ID: id, Type: typ}
	o.Name = make(NaturalLanguageValue)
	o.Content = make(NaturalLanguageValue)
	o.Summary = make(NaturalLanguageValue)
	return &o
}

// GetID returns the GetID corresponding to the GetID object
func (o Object) GetID() ObjectID {
	return o.ID
}

// GetLink returns the GetLink corresponding to the GetID object
func (o Object) GetType() ActivityVocabularyType {
	return o.Type
}

// Append facilitates adding elements to ObjectOrLink arrays
// and ensures ObjectsArr implements the Collection interface
func (c *ObjectsArr) Append(o ObjectOrLink) error {
	oldLen := len(*c)
	d := make(ObjectsArr, oldLen+1)
	for k, it := range *c {
		d[k] = it
	}
	d[oldLen] = o
	*c = d
	return nil
}

// recipientsDeduplication normalizes the received arguments lists
func recipientsDeduplication(recArgs ...*ObjectsArr) error {
	recIds := make([]ObjectID, 0)

	for _, recList := range recArgs {
		if recList == nil {
			continue
		}

		toRemove := make([]int, 0)
		for i, rec := range *recList {
			save := true
			for _, id := range recIds {
				if rec.GetID() == id {
					// mark the element for removal
					toRemove = append(toRemove, i)
					save = false
				}
			}
			if save {
				recIds = append(recIds, rec.GetID())
			}
		}

		sort.Sort(sort.Reverse(sort.IntSlice(toRemove)))
		for _, idx := range toRemove {
			*recList = append((*recList)[:idx], (*recList)[idx+1:]...)
		}
	}
	return nil
}

func (i *ObjectID) MarshalJSON(data []byte) error {
	*i = ObjectID(data)
	return nil
}

func (c *ContentType) MarshalJSON(data []byte) error {
	*c = ContentType(data)
	return nil
}

func (o *Object) MarshalJSON(data []byte) error {
	val, _, _, _ := jsonparser.Get(data, "ID")
	o.ID.MarshalJSON(val)
	return nil
}
<|endoftext|>


<commit_before>package writer

import (
	"fmt"
	"go/format"
	"testing"

	"github.com/dave/jennifer/jen"
	"github.com/jbowes/oag/pkg"
)

func TestSetQueryArgs(t *testing.T) {
	tcs := []struct {
		name string
		in   []pkg.Param
		out  string
	}{
		{"no args", nil, ""},
		{"string arg",
			[]pkg.Param{{ID: "arg", Arg: "arg", Type: &pkg.IdentType{}}},
			`

				q := make(url.Values)
				q.Set("arg", arg)
			`,
		},
		{"different name",
			[]pkg.Param{{ID: "arg", Arg: "arg", Orig: "arg_thing", Type: &pkg.IdentType{}}},
			`

				q := make(url.Values)
				q.Set("arg_thing", arg)
			`,
		},
		{"different arg value",
			[]pkg.Param{{ID: "arg", Arg: "different", Orig: "arg_thing", Type: &pkg.IdentType{}}},
			`

				q := make(url.Values)
				q.Set("arg_thing", different)
			`,
		},
		{"marshal",
			[]pkg.Param{{ID: "arg", Arg: "arg", Type: &pkg.IdentType{Marshal: true}}},
			`

				q := make(url.Values)
				argBytes, err := arg.MarshalText()
    			if err != nil {
    				return
    			}
    			q.Set("arg", string(argBytes))
			`,
		},
		{"space after martial, not between regular",
			[]pkg.Param{
				{ID: "arg1", Arg: "arg1", Type: &pkg.IdentType{Marshal: true}},
				{ID: "arg2", Arg: "arg2", Type: &pkg.IdentType{}},
				{ID: "arg3", Arg: "arg3", Type: &pkg.IdentType{}},
			},
			`

				q := make(url.Values)
				arg1Bytes, err := arg1.MarshalText()
    			if err != nil {
    				return
    			}
				q.Set("arg1", string(arg1Bytes))

				q.Set("arg2", arg2)
				q.Set("arg3", arg3)
			`,
		},

		{"multi collection string arg",
			[]pkg.Param{{ID: "arg", Arg: "arg", Collection: pkg.Multi, Type: &pkg.SliceType{Type: &pkg.IdentType{}}}},
			`

				q := make(url.Values)
				for _, v := range arg {
					q.Add("arg", v)
				}
			`,
		},

		{"multi collection marshal",
			[]pkg.Param{{ID: "arg", Arg: "arg", Collection: pkg.Multi, Type: &pkg.SliceType{Type: &pkg.IdentType{Marshal: true}}}},
			`

				q := make(url.Values)
				for _, v := range arg {
					b, err := v.MarshalText()
					if err != nil {
						return
					}
					q.Add("arg", string(b))
				}
			`,
		},
	}

	for _, tc := range tcs {
		t.Run(tc.name, func(t *testing.T) {
			sqa := jen.Id("v").Op("=").Func().Params().BlockFunc(func(g *jen.Group) {
				setQueryArgs(g, nil, tc.in)
			})

			out := fmt.Sprintf("%#v", sqa)
			formatted, _ := format.Source([]byte("v = func() {" + tc.out + "}"))
			if out != string(formatted) {
				t.Error("got:", out, "expected:", tc.out)
			}
		})
	}
}

func TestSetOptQueryArgs(t *testing.T) {
	tcs := []struct {
		name string
		in   []pkg.Field
		out  string
	}{
		{"no args", nil, ""},
		{"string arg",
			[]pkg.Field{{ID: "arg", Type: &pkg.PointerType{Type: &pkg.IdentType{}}}},
			`

				var q url.Values
				if opts != nil {
					q = make(url.Values)
					if opts.arg != nil {
						q.Set("arg", *opts.arg)
					}
				}
			`,
		},
		{"different name",
			[]pkg.Field{{ID: "arg", Orig: "arg_thing", Type: &pkg.PointerType{Type: &pkg.IdentType{}}}},
			`

				var q url.Values
				if opts != nil {
					q = make(url.Values)
					if opts.arg != nil {
						q.Set("arg_thing", *opts.arg)
					}
				}
			`,
		},
		{"marshal",
			[]pkg.Field{{ID: "arg", Type: &pkg.PointerType{Type: &pkg.IdentType{Marshal: true}}}},
			`

				var q url.Values
				if opts != nil {
					q = make(url.Values)
					if opts.arg != nil {
						b, err := opts.arg.MarshalText()
						if err != nil {
							return
						}
						q.Set("arg", string(b))
					}
				}
			`,
		},
		{"space after martial, not between regular",
			[]pkg.Field{
				{ID: "arg1", Type: &pkg.PointerType{Type: &pkg.IdentType{Marshal: true}}},
				{ID: "arg2", Type: &pkg.PointerType{Type: &pkg.IdentType{}}},
				{ID: "arg3", Type: &pkg.PointerType{Type: &pkg.IdentType{}}},
			},
			`

				var q url.Values
				if opts != nil {
					q = make(url.Values)
					if opts.arg1 != nil {
						b, err := opts.arg1.MarshalText()
						if err != nil {
							return
						}
						q.Set("arg1", string(b))
					}

					if opts.arg2 != nil {
						q.Set("arg2", *opts.arg2)
					}

					if opts.arg3 != nil {
						q.Set("arg3", *opts.arg3)
					}
				}
			`,
		},

		{"multi collection string arg",
			[]pkg.Field{{ID: "arg", Collection: pkg.Multi, Type: &pkg.PointerType{Type: &pkg.SliceType{Type: &pkg.IdentType{}}}}},
			`

				var q url.Values
				if opts != nil {
					q = make(url.Values)
					for _, v := range opts.arg {
						q.Add("arg", v)
					}
				}
			`,
		},

		{"multi collection marshal",
			[]pkg.Field{{ID: "arg", Collection: pkg.Multi, Type: &pkg.PointerType{Type: &pkg.SliceType{Type: &pkg.IdentType{Marshal: true}}}}},
			`

				var q url.Values
				if opts != nil {
					q = make(url.Values)
					for _, v := range opts.arg {
						b, err := v.MarshalText()
						if err != nil {
							return
						}
						q.Add("arg", string(b))
					}
				}
			`,
		},
	}

	for _, tc := range tcs {
		t.Run(tc.name, func(t *testing.T) {
			soqa := jen.Id("v").Op("=").Func().Params().BlockFunc(func(g *jen.Group) {
				setOptQueryArgs(g, nil, false, tc.in)
			})

			out := fmt.Sprintf("%#v", soqa)
			formatted, _ := format.Source([]byte("v = func() {" + tc.out + "}"))
			if out != string(formatted) {
				t.Error("got:", out, "expected:", tc.out)
			}
		})
	}
}
func TestSetHeaderArgs(t *testing.T) {
	tcs := []struct {
		name string
		in   []pkg.Param
		out  string
	}{
		{"no args", nil, ""},
		{"string arg",
			[]pkg.Param{{ID: "arg", Arg: "arg", Type: &pkg.IdentType{}}},
			`req.Header.Set("arg", arg)

			`,
		},
		{"different name",
			[]pkg.Param{{ID: "arg", Arg: "arg", Orig: "arg_thing", Type: &pkg.IdentType{}}},
			`req.Header.Set("arg_thing", arg)

			`,
		},
		{"marshal",
			[]pkg.Param{{ID: "arg", Arg: "arg", Type: &pkg.IdentType{Marshal: true}}},
			`argBytes, err := arg.MarshalText()
    			if err != nil {
    				return
    			}
    			req.Header.Set("arg", string(argBytes))

			`,
		},
		{"space after martial, not between regular",
			[]pkg.Param{
				{ID: "arg1", Arg: "arg1", Type: &pkg.IdentType{Marshal: true}},
				{ID: "arg2", Arg: "arg2", Type: &pkg.IdentType{}},
				{ID: "arg3", Arg: "arg3", Type: &pkg.IdentType{}},
			},
			`arg1Bytes, err := arg1.MarshalText()
    			if err != nil {
    				return
    			}
				req.Header.Set("arg1", string(arg1Bytes))

				req.Header.Set("arg2", arg2)
				req.Header.Set("arg3", arg3)

			`,
		},
	}

	for _, tc := range tcs {
		t.Run(tc.name, func(t *testing.T) {
			sha := jen.Id("v").Op("=").Func().Params().BlockFunc(func(g *jen.Group) {
				setHeaderArgs(g, nil, tc.in)
			})

			out := fmt.Sprintf("%#v", sha)
			formatted, _ := format.Source([]byte("v = func() {" + tc.out + "}"))
			if out != string(formatted) {
				t.Error("got:", out, "expected:", tc.out)
			}
		})
	}
}

func TestErrSelectFunc(t *testing.T) {
	tcs := []struct {
		name string
		in   pkg.Method
		out  string
	}{
		{"no error codes", pkg.Method{}, "nil"},
		{"only default",
			pkg.Method{Errors: map[int]pkg.Type{
				-1: &pkg.PointerType{Type: &pkg.IdentType{Name: "Error"}},
			}},
			`func(code int) error {
				return &Error{}
			}`,
		},
		{"many codes",
			pkg.Method{Errors: map[int]pkg.Type{
				400: &pkg.PointerType{Type: &pkg.IdentType{Name: "Error400"}},
				401: &pkg.PointerType{Type: &pkg.IdentType{Name: "Error401"}},
			}},
			`func(code int) error {
				switch code {
				case 400:
					return &Error400{}
				case 401:
					return &Error401{}
				default:
					return nil
				}
			}`,
		},
		{"codes and default",
			pkg.Method{Errors: map[int]pkg.Type{
				-1:  &pkg.PointerType{Type: &pkg.IdentType{Name: "Error"}},
				400: &pkg.PointerType{Type: &pkg.IdentType{Name: "Error400"}},
				401: &pkg.PointerType{Type: &pkg.IdentType{Name: "Error401"}},
			}},
			`func(code int) error {
				switch code {
				case 400:
					return &Error400{}
				case 401:
					return &Error401{}
				default:
					return &Error{}
				}
			}`,
		},
		{"single code no default",
			pkg.Method{Errors: map[int]pkg.Type{
				400: &pkg.PointerType{Type: &pkg.IdentType{Name: "Error400"}},
			}},
			`func(code int) error {
				if code == 400 {
					return &Error400{}
				}
				return nil
			}`,
		},
		{"same successive error groups case",
			pkg.Method{Errors: map[int]pkg.Type{
				400: &pkg.PointerType{Type: &pkg.IdentType{Name: "ErrorA"}},
				401: &pkg.PointerType{Type: &pkg.IdentType{Name: "ErrorA"}},
				403: &pkg.PointerType{Type: &pkg.IdentType{Name: "ErrorB"}},
				404: &pkg.PointerType{Type: &pkg.IdentType{Name: "ErrorA"}},
				405: &pkg.PointerType{Type: &pkg.IdentType{Name: "ErrorA"}},
			}},
			`func(code int) error {
				switch code {
				case 400, 401:
					return &ErrorA{}
				case 403:
					return &ErrorB{}
				case 404, 405:
					return &ErrorA{}
				default:
					return nil
				}
			}`,
		},
	}

	for _, tc := range tcs {
		t.Run(tc.name, func(t *testing.T) {
			esf := jen.Id("v").Op("=").Add(errSelectFunc(&tc.in))
			out := fmt.Sprintf("%#v", esf)
			formatted, _ := format.Source([]byte("v = " + tc.out))
			if out != string(formatted) {
				t.Error("got:", out, "expected:", tc.out)
			}
		})
	}
}

func TestStringFor(t *testing.T) {
	tcs := []struct {
		name string
		in   pkg.Type
		out  string
	}{
		{"string", &pkg.IdentType{Name: "string"}, "x"},
		{"bool", &pkg.IdentType{Name: "bool"}, "strconv.FormatBool(x)"},
		{"int", &pkg.IdentType{Name: "int"}, "strconv.Itoa(x)"},
		{"float64", &pkg.IdentType{Name: "float64"}, "strconv.FormatFloat(x, 'f', -1, 64)"},
	}

	for _, tc := range tcs {
		t.Run(tc.name, func(t *testing.T) {
			esf := jen.Id("v").Op("=").Add(stringFor(tc.in, jen.Id("x")))
			out := fmt.Sprintf("%#v", esf)
			formatted, _ := format.Source([]byte("v = " + tc.out))
			if out != string(formatted) {
				t.Error("got:", out, "expected:", tc.out)
			}
		})
	}

}
<commit_msg>Update tests for multi collection optional args<commit_after>package writer

import (
	"fmt"
	"go/format"
	"testing"

	"github.com/dave/jennifer/jen"
	"github.com/jbowes/oag/pkg"
)

func TestSetQueryArgs(t *testing.T) {
	tcs := []struct {
		name string
		in   []pkg.Param
		out  string
	}{
		{"no args", nil, ""},
		{"string arg",
			[]pkg.Param{{ID: "arg", Arg: "arg", Type: &pkg.IdentType{}}},
			`

				q := make(url.Values)
				q.Set("arg", arg)
			`,
		},
		{"different name",
			[]pkg.Param{{ID: "arg", Arg: "arg", Orig: "arg_thing", Type: &pkg.IdentType{}}},
			`

				q := make(url.Values)
				q.Set("arg_thing", arg)
			`,
		},
		{"different arg value",
			[]pkg.Param{{ID: "arg", Arg: "different", Orig: "arg_thing", Type: &pkg.IdentType{}}},
			`

				q := make(url.Values)
				q.Set("arg_thing", different)
			`,
		},
		{"marshal",
			[]pkg.Param{{ID: "arg", Arg: "arg", Type: &pkg.IdentType{Marshal: true}}},
			`

				q := make(url.Values)
				argBytes, err := arg.MarshalText()
    			if err != nil {
    				return
    			}
    			q.Set("arg", string(argBytes))
			`,
		},
		{"space after martial, not between regular",
			[]pkg.Param{
				{ID: "arg1", Arg: "arg1", Type: &pkg.IdentType{Marshal: true}},
				{ID: "arg2", Arg: "arg2", Type: &pkg.IdentType{}},
				{ID: "arg3", Arg: "arg3", Type: &pkg.IdentType{}},
			},
			`

				q := make(url.Values)
				arg1Bytes, err := arg1.MarshalText()
    			if err != nil {
    				return
    			}
				q.Set("arg1", string(arg1Bytes))

				q.Set("arg2", arg2)
				q.Set("arg3", arg3)
			`,
		},

		{"multi collection string arg",
			[]pkg.Param{{ID: "arg", Arg: "arg", Collection: pkg.Multi, Type: &pkg.SliceType{Type: &pkg.IdentType{}}}},
			`

				q := make(url.Values)
				for _, v := range arg {
					q.Add("arg", v)
				}
			`,
		},

		{"multi collection marshal",
			[]pkg.Param{{ID: "arg", Arg: "arg", Collection: pkg.Multi, Type: &pkg.SliceType{Type: &pkg.IdentType{Marshal: true}}}},
			`

				q := make(url.Values)
				for _, v := range arg {
					b, err := v.MarshalText()
					if err != nil {
						return
					}
					q.Add("arg", string(b))
				}
			`,
		},
	}

	for _, tc := range tcs {
		t.Run(tc.name, func(t *testing.T) {
			sqa := jen.Id("v").Op("=").Func().Params().BlockFunc(func(g *jen.Group) {
				setQueryArgs(g, nil, tc.in)
			})

			out := fmt.Sprintf("%#v", sqa)
			formatted, _ := format.Source([]byte("v = func() {" + tc.out + "}"))
			if out != string(formatted) {
				t.Error("got:", out, "expected:", tc.out)
			}
		})
	}
}

func TestSetOptQueryArgs(t *testing.T) {
	tcs := []struct {
		name string
		in   []pkg.Field
		out  string
	}{
		{"no args", nil, ""},
		{"string arg",
			[]pkg.Field{{ID: "arg", Type: &pkg.PointerType{Type: &pkg.IdentType{}}}},
			`

				var q url.Values
				if opts != nil {
					q = make(url.Values)
					if opts.arg != nil {
						q.Set("arg", *opts.arg)
					}
				}
			`,
		},
		{"different name",
			[]pkg.Field{{ID: "arg", Orig: "arg_thing", Type: &pkg.PointerType{Type: &pkg.IdentType{}}}},
			`

				var q url.Values
				if opts != nil {
					q = make(url.Values)
					if opts.arg != nil {
						q.Set("arg_thing", *opts.arg)
					}
				}
			`,
		},
		{"marshal",
			[]pkg.Field{{ID: "arg", Type: &pkg.PointerType{Type: &pkg.IdentType{Marshal: true}}}},
			`

				var q url.Values
				if opts != nil {
					q = make(url.Values)
					if opts.arg != nil {
						b, err := opts.arg.MarshalText()
						if err != nil {
							return
						}
						q.Set("arg", string(b))
					}
				}
			`,
		},
		{"space after martial, not between regular",
			[]pkg.Field{
				{ID: "arg1", Type: &pkg.PointerType{Type: &pkg.IdentType{Marshal: true}}},
				{ID: "arg2", Type: &pkg.PointerType{Type: &pkg.IdentType{}}},
				{ID: "arg3", Type: &pkg.PointerType{Type: &pkg.IdentType{}}},
			},
			`

				var q url.Values
				if opts != nil {
					q = make(url.Values)
					if opts.arg1 != nil {
						b, err := opts.arg1.MarshalText()
						if err != nil {
							return
						}
						q.Set("arg1", string(b))
					}

					if opts.arg2 != nil {
						q.Set("arg2", *opts.arg2)
					}

					if opts.arg3 != nil {
						q.Set("arg3", *opts.arg3)
					}
				}
			`,
		},

		{"multi collection string arg",
			[]pkg.Field{{ID: "arg", Collection: pkg.Multi, Type: &pkg.PointerType{Type: &pkg.SliceType{Type: &pkg.IdentType{}}}}},
			`

				var q url.Values
				if opts != nil {
					q = make(url.Values)
					for _, v := range *opts.arg {
						q.Add("arg", v)
					}
				}
			`,
		},

		{"multi collection marshal",
			[]pkg.Field{{ID: "arg", Collection: pkg.Multi, Type: &pkg.PointerType{Type: &pkg.SliceType{Type: &pkg.IdentType{Marshal: true}}}}},
			`

				var q url.Values
				if opts != nil {
					q = make(url.Values)
					for _, v := range *opts.arg {
						b, err := v.MarshalText()
						if err != nil {
							return
						}
						q.Add("arg", string(b))
					}
				}
			`,
		},
	}

	for _, tc := range tcs {
		t.Run(tc.name, func(t *testing.T) {
			soqa := jen.Id("v").Op("=").Func().Params().BlockFunc(func(g *jen.Group) {
				setOptQueryArgs(g, nil, false, tc.in)
			})

			out := fmt.Sprintf("%#v", soqa)
			formatted, _ := format.Source([]byte("v = func() {" + tc.out + "}"))
			if out != string(formatted) {
				t.Error("got:", out, "expected:", tc.out)
			}
		})
	}
}
func TestSetHeaderArgs(t *testing.T) {
	tcs := []struct {
		name string
		in   []pkg.Param
		out  string
	}{
		{"no args", nil, ""},
		{"string arg",
			[]pkg.Param{{ID: "arg", Arg: "arg", Type: &pkg.IdentType{}}},
			`req.Header.Set("arg", arg)

			`,
		},
		{"different name",
			[]pkg.Param{{ID: "arg", Arg: "arg", Orig: "arg_thing", Type: &pkg.IdentType{}}},
			`req.Header.Set("arg_thing", arg)

			`,
		},
		{"marshal",
			[]pkg.Param{{ID: "arg", Arg: "arg", Type: &pkg.IdentType{Marshal: true}}},
			`argBytes, err := arg.MarshalText()
    			if err != nil {
    				return
    			}
    			req.Header.Set("arg", string(argBytes))

			`,
		},
		{"space after martial, not between regular",
			[]pkg.Param{
				{ID: "arg1", Arg: "arg1", Type: &pkg.IdentType{Marshal: true}},
				{ID: "arg2", Arg: "arg2", Type: &pkg.IdentType{}},
				{ID: "arg3", Arg: "arg3", Type: &pkg.IdentType{}},
			},
			`arg1Bytes, err := arg1.MarshalText()
    			if err != nil {
    				return
    			}
				req.Header.Set("arg1", string(arg1Bytes))

				req.Header.Set("arg2", arg2)
				req.Header.Set("arg3", arg3)

			`,
		},
	}

	for _, tc := range tcs {
		t.Run(tc.name, func(t *testing.T) {
			sha := jen.Id("v").Op("=").Func().Params().BlockFunc(func(g *jen.Group) {
				setHeaderArgs(g, nil, tc.in)
			})

			out := fmt.Sprintf("%#v", sha)
			formatted, _ := format.Source([]byte("v = func() {" + tc.out + "}"))
			if out != string(formatted) {
				t.Error("got:", out, "expected:", tc.out)
			}
		})
	}
}

func TestErrSelectFunc(t *testing.T) {
	tcs := []struct {
		name string
		in   pkg.Method
		out  string
	}{
		{"no error codes", pkg.Method{}, "nil"},
		{"only default",
			pkg.Method{Errors: map[int]pkg.Type{
				-1: &pkg.PointerType{Type: &pkg.IdentType{Name: "Error"}},
			}},
			`func(code int) error {
				return &Error{}
			}`,
		},
		{"many codes",
			pkg.Method{Errors: map[int]pkg.Type{
				400: &pkg.PointerType{Type: &pkg.IdentType{Name: "Error400"}},
				401: &pkg.PointerType{Type: &pkg.IdentType{Name: "Error401"}},
			}},
			`func(code int) error {
				switch code {
				case 400:
					return &Error400{}
				case 401:
					return &Error401{}
				default:
					return nil
				}
			}`,
		},
		{"codes and default",
			pkg.Method{Errors: map[int]pkg.Type{
				-1:  &pkg.PointerType{Type: &pkg.IdentType{Name: "Error"}},
				400: &pkg.PointerType{Type: &pkg.IdentType{Name: "Error400"}},
				401: &pkg.PointerType{Type: &pkg.IdentType{Name: "Error401"}},
			}},
			`func(code int) error {
				switch code {
				case 400:
					return &Error400{}
				case 401:
					return &Error401{}
				default:
					return &Error{}
				}
			}`,
		},
		{"single code no default",
			pkg.Method{Errors: map[int]pkg.Type{
				400: &pkg.PointerType{Type: &pkg.IdentType{Name: "Error400"}},
			}},
			`func(code int) error {
				if code == 400 {
					return &Error400{}
				}
				return nil
			}`,
		},
		{"same successive error groups case",
			pkg.Method{Errors: map[int]pkg.Type{
				400: &pkg.PointerType{Type: &pkg.IdentType{Name: "ErrorA"}},
				401: &pkg.PointerType{Type: &pkg.IdentType{Name: "ErrorA"}},
				403: &pkg.PointerType{Type: &pkg.IdentType{Name: "ErrorB"}},
				404: &pkg.PointerType{Type: &pkg.IdentType{Name: "ErrorA"}},
				405: &pkg.PointerType{Type: &pkg.IdentType{Name: "ErrorA"}},
			}},
			`func(code int) error {
				switch code {
				case 400, 401:
					return &ErrorA{}
				case 403:
					return &ErrorB{}
				case 404, 405:
					return &ErrorA{}
				default:
					return nil
				}
			}`,
		},
	}

	for _, tc := range tcs {
		t.Run(tc.name, func(t *testing.T) {
			esf := jen.Id("v").Op("=").Add(errSelectFunc(&tc.in))
			out := fmt.Sprintf("%#v", esf)
			formatted, _ := format.Source([]byte("v = " + tc.out))
			if out != string(formatted) {
				t.Error("got:", out, "expected:", tc.out)
			}
		})
	}
}

func TestStringFor(t *testing.T) {
	tcs := []struct {
		name string
		in   pkg.Type
		out  string
	}{
		{"string", &pkg.IdentType{Name: "string"}, "x"},
		{"bool", &pkg.IdentType{Name: "bool"}, "strconv.FormatBool(x)"},
		{"int", &pkg.IdentType{Name: "int"}, "strconv.Itoa(x)"},
		{"float64", &pkg.IdentType{Name: "float64"}, "strconv.FormatFloat(x, 'f', -1, 64)"},
	}

	for _, tc := range tcs {
		t.Run(tc.name, func(t *testing.T) {
			esf := jen.Id("v").Op("=").Add(stringFor(tc.in, jen.Id("x")))
			out := fmt.Sprintf("%#v", esf)
			formatted, _ := format.Source([]byte("v = " + tc.out))
			if out != string(formatted) {
				t.Error("got:", out, "expected:", tc.out)
			}
		})
	}

}
<|endoftext|>


<commit_before>package silverback_test

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

	"github.com/nelsam/silverback"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Codecs", func() {
	var (
		codec silverback.Codec
		req   *http.Request
	)

	Context("json", func() {
		BeforeEach(func() {
			codec = &silverback.JSON{}
			var err error
			req, err = http.NewRequest("GET", "foo", nil)
			Expect(err).ToNot(HaveOccurred())
		})

		It("returns itself when a new copy is requested", func() {
			Expect(codec.New(map[string]string{})).To(Equal(codec))
		})

		It("matches a request for application/json", func() {
			req.Header.Set("Accept", "application/json")
			Expect(codec.Match(req)).To(BeTrue())
		})

		It("matches a request for text/json", func() {
			req.Header.Set("Accept", "text/json")
			Expect(codec.Match(req)).To(BeTrue())
		})

		It("marshals to proper json", func() {
			val := map[string]interface{}{
				"foo": "bar",
				"baz": 3.0,
			}
			expected, err := json.Marshal(val)
			Expect(err).ToNot(HaveOccurred())
			Expect(codec.Marshal(val)).To(MatchJSON(expected))
		})

		It("errors from improper json", func() {
			raw := []byte(`{"foo":"bar","baz":3.0,}`)
			m := map[string]string{}
			Expect(json.Unmarshal(raw, &m)).To(HaveOccurred())
		})

		It("unmarshals from proper json", func() {
			raw := []byte(`{"foo":"bar","baz":3.0}`)
			var expected, actual map[string]interface{}
			Expect(json.Unmarshal(raw, &expected)).ToNot(HaveOccurred())
			Expect(codec.Unmarshal(raw, &actual)).ToNot(HaveOccurred())
			Expect(actual).To(BeEquivalentTo(expected))
		})
	})
})
<commit_msg>Correct the test for the json codec's New<commit_after>package silverback_test

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

	"github.com/nelsam/silverback"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Codecs", func() {
	var (
		codec silverback.Codec
		req   *http.Request
	)

	Context("json", func() {
		BeforeEach(func() {
			codec = &silverback.JSON{}
			var err error
			req, err = http.NewRequest("GET", "foo", nil)
			Expect(err).ToNot(HaveOccurred())
		})

		It("returns a json codec when a new copy is requested", func() {
			newCodec := codec.New(map[string]string{})
			_, isJSON := newCodec.(*silverback.JSON)
			Expect(isJSON).To(BeTrue())
		})

		It("matches a request for application/json", func() {
			req.Header.Set("Accept", "application/json")
			Expect(codec.Match(req)).To(BeTrue())
		})

		It("matches a request for text/json", func() {
			req.Header.Set("Accept", "text/json")
			Expect(codec.Match(req)).To(BeTrue())
		})

		It("marshals to proper json", func() {
			val := map[string]interface{}{
				"foo": "bar",
				"baz": 3.0,
			}
			expected, err := json.Marshal(val)
			Expect(err).ToNot(HaveOccurred())
			Expect(codec.Marshal(val)).To(MatchJSON(expected))
		})

		It("errors from improper json", func() {
			raw := []byte(`{"foo":"bar","baz":3.0,}`)
			m := map[string]string{}
			Expect(json.Unmarshal(raw, &m)).To(HaveOccurred())
		})

		It("unmarshals from proper json", func() {
			raw := []byte(`{"foo":"bar","baz":3.0}`)
			var expected, actual map[string]interface{}
			Expect(json.Unmarshal(raw, &expected)).ToNot(HaveOccurred())
			Expect(codec.Unmarshal(raw, &actual)).ToNot(HaveOccurred())
			Expect(actual).To(BeEquivalentTo(expected))
		})
	})
})
<|endoftext|>


<commit_before>package vm

import (
  "bytes"
  "fmt"
)

type OpList []*Op

func (ol *OpList) Get(i int) *Op {
  return (*ol)[i]
}

func (ol *OpList) Append(op *Op) {
  *ol = append(*ol, op)
}

func (ol *OpList) String() string {
  buf := &bytes.Buffer {}
  for k, v := range *ol {
    buf.WriteString(fmt.Sprintf("%03d. %s\n", k + 1, v))
  }
  return buf.String()
}

func (l *OpList) AppendOp(o OpType, args ...interface{}) {
  l.Append(NewOp(o, args...))
}

func (l *OpList) AppendNoop() {
  l.AppendOp(TXOP_noop)
}

func (l *OpList) AppendEnd() {
  l.AppendOp(TXOP_end)
}

func (l *OpList) AppendLiteral(v interface {}) {
  l.AppendOp(TXOP_literal, v)
}

func (l *OpList) AppendPrintRaw() {
  l.Append(NewOp(TXOP_print_raw))
}

func (l *OpList) AppendFetchSymbol(name string) {
  l.Append(NewOp(TXOP_fetch_s, name))
}

func (l *OpList) AppendFetchField(name string) {
  l.Append(NewOp(TXOP_fetch_field_s, name))
}

func (l *OpList) AppendSaveToLvar(ix int) {
  l.Append(NewOp(TXOP_save_to_lvar, ix))
}

func (l *OpList) AppendLoadLvar(ix int) {
  l.Append(NewOp(TXOP_load_lvar, ix))
}

func (l *OpList) AppendMoveToSb() {
  l.Append(NewOp(TXOP_move_to_sb))
}

func (l *OpList) AppendAdd() {
  l.AppendOp(TXOP_add)
}

func (l *OpList) AppendSub() {
  l.AppendOp(TXOP_sub)
}

func (l *OpList) AppendMul() {
  l.AppendOp(TXOP_mul)
}

func (l *OpList) AppendDiv() {
  l.AppendOp(TXOP_div)
}

func (l *OpList) AppendAnd(ix int) {
  l.AppendOp(TXOP_and, ix)
}

func (l *OpList) AppendGoto(ix int) {
  l.AppendOp(TXOP_goto, ix)
}<commit_msg>very minor change to make docs look better<commit_after>package vm

import (
  "bytes"
  "fmt"
)

type OpList []*Op

func (l *OpList) Get(i int) *Op {
  return (*l)[i]
}

func (l *OpList) Append(op *Op) {
  *l = append(*l, op)
}

func (l *OpList) String() string {
  buf := &bytes.Buffer {}
  for k, v := range *l {
    buf.WriteString(fmt.Sprintf("%03d. %s\n", k + 1, v))
  }
  return buf.String()
}

func (l *OpList) AppendOp(o OpType, args ...interface{}) {
  l.Append(NewOp(o, args...))
}

func (l *OpList) AppendNoop() {
  l.AppendOp(TXOP_noop)
}

func (l *OpList) AppendEnd() {
  l.AppendOp(TXOP_end)
}

func (l *OpList) AppendLiteral(v interface {}) {
  l.AppendOp(TXOP_literal, v)
}

func (l *OpList) AppendPrintRaw() {
  l.Append(NewOp(TXOP_print_raw))
}

func (l *OpList) AppendFetchSymbol(name string) {
  l.Append(NewOp(TXOP_fetch_s, name))
}

func (l *OpList) AppendFetchField(name string) {
  l.Append(NewOp(TXOP_fetch_field_s, name))
}

func (l *OpList) AppendSaveToLvar(ix int) {
  l.Append(NewOp(TXOP_save_to_lvar, ix))
}

func (l *OpList) AppendLoadLvar(ix int) {
  l.Append(NewOp(TXOP_load_lvar, ix))
}

func (l *OpList) AppendMoveToSb() {
  l.Append(NewOp(TXOP_move_to_sb))
}

func (l *OpList) AppendAdd() {
  l.AppendOp(TXOP_add)
}

func (l *OpList) AppendSub() {
  l.AppendOp(TXOP_sub)
}

func (l *OpList) AppendMul() {
  l.AppendOp(TXOP_mul)
}

func (l *OpList) AppendDiv() {
  l.AppendOp(TXOP_div)
}

func (l *OpList) AppendAnd(ix int) {
  l.AppendOp(TXOP_and, ix)
}

func (l *OpList) AppendGoto(ix int) {
  l.AppendOp(TXOP_goto, ix)
}<|endoftext|>