Skip to content

Commit

Permalink
Merge pull request #8 from JeffreyRichter/master
Browse files Browse the repository at this point in the history
1st class function support, doc improvements, & added Close to requestBodyProgress
  • Loading branch information
JeffreyRichter authored Jan 26, 2018
2 parents f4da77e + cb3adc5 commit 9804b77
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 78 deletions.
50 changes: 27 additions & 23 deletions pipeline/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ type Factory interface {
New(next Policy, po *PolicyOptions) Policy
}

// FactoryFunc is an adapter that allows the use of an ordinary function as a Factory interface.
type FactoryFunc func(next Policy, po *PolicyOptions) PolicyFunc

// New calls f(next,po).
func (f FactoryFunc) New(next Policy, po *PolicyOptions) Policy {
return f(next, po)
}

// The Policy interface represents a mutable Policy object created by a Factory. The object can mutate/process
// the HTTP request and then forward it on to the next Policy object in the linked-list. The returned
// Response goes backward through the linked-list for additional processing.
Expand All @@ -26,6 +34,14 @@ type Policy interface {
Do(ctx context.Context, request Request) (Response, error)
}

// PolicyFunc is an adapter that allows the use of an ordinary function as a Policy interface.
type PolicyFunc func(ctx context.Context, request Request) (Response, error)

// Do calls f(ctx, request).
func (f PolicyFunc) Do(ctx context.Context, request Request) (Response, error) {
return f(ctx, request)
}

// Options configures a Pipeline's behavior.
type Options struct {
HTTPSender Factory // If sender is nil, then the pipeline's default client is used to send the HTTP requests.
Expand Down Expand Up @@ -210,30 +226,18 @@ func newDefaultHTTPClient() *http.Client {

// newDefaultHTTPClientFactory creates a DefaultHTTPClientPolicyFactory object that sends HTTP requests to a Go's default http.Client.
func newDefaultHTTPClientFactory() Factory {
return &defaultHTTPClientPolicyFactory{}
}

type defaultHTTPClientPolicyFactory struct {
}

// Create initializes a logging policy object.
func (f *defaultHTTPClientPolicyFactory) New(next Policy, po *PolicyOptions) Policy {
return &defaultHTTPClientPolicy{po: po}
}

type defaultHTTPClientPolicy struct {
po *PolicyOptions
}

func (p *defaultHTTPClientPolicy) Do(ctx context.Context, request Request) (Response, error) {
r, err := pipelineHTTPClient.Do(request.WithContext(ctx))
if err != nil {
err = NewError(err, "HTTP request failed")
}
return NewHTTPResponse(r), err
return FactoryFunc(func(next Policy, po *PolicyOptions) PolicyFunc {
return func(ctx context.Context, request Request) (Response, error) {
r, err := pipelineHTTPClient.Do(request.WithContext(ctx))
if err != nil {
err = NewError(err, "HTTP request failed")
}
return NewHTTPResponse(r), err
}
})
}

var mfm = methodFactoryMarker{}
var mfm = methodFactoryMarker{} // Singleton

// MethodFactoryMarker returns a special marker Factory object. When Pipeline's Do method is called, any
// MethodMarkerFactory object is replaced with the specified methodFactory object. If nil is passed fro Do's
Expand All @@ -245,6 +249,6 @@ func MethodFactoryMarker() Factory {
type methodFactoryMarker struct {
}

func (mpmf methodFactoryMarker) New(next Policy, po *PolicyOptions) Policy {
func (methodFactoryMarker) New(next Policy, po *PolicyOptions) Policy {
panic("methodFactoryMarker policy should have been replaced with a method policy")
}
3 changes: 2 additions & 1 deletion pipeline/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,5 @@ can see the Response but cannot see the additional struct with the deserialized
Policy objects have returned, the pipeline.Response interface is returned by Pipeline's Do method.
The caller of this method can perform a type assertion attempting to get back to the struct type
really returned by the Policy object. If the type assertion is successful, the caller now has
access to both the http.Response and the deserialized struct object.*/package pipeline
access to both the http.Response and the deserialized struct object.*/
package pipeline
12 changes: 6 additions & 6 deletions pipeline/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ type causer interface {
}

// ErrorNode can be an embedded field in a private error object. This field
// adds Program Counter support and a 'cause' (reference to a preceeding error).
// adds Program Counter support and a 'cause' (reference to a preceding error).
// When initializing a error type with this embedded field, initialize the
// ErrorNode field by calling ErrorNode{}.Initialize(cause).
type ErrorNode struct {
pc uintptr // Represents a Program Counter that you can get symbols for.
cause error // Refers to the preceeding error (or nil)
cause error // Refers to the preceding error (or nil)
}

// Error returns a string with the PC's symbols or "" if the PC is invalid.
Expand Down Expand Up @@ -78,7 +78,7 @@ func (e ErrorNode) Timeout() bool {
}

// Initialize is used to initialize an embedded ErrorNode field.
// It captures the caller's program counter and saves the cause (preceeding error).
// It captures the caller's program counter and saves the cause (preceding error).
// To initialize the field, use "ErrorNode{}.Initialize(cause, 3)". A callersToSkip
// value of 3 is very common; but, depending on your code nesting, you may need
// a different value.
Expand All @@ -89,7 +89,7 @@ func (ErrorNode) Initialize(cause error, callersToSkip int) ErrorNode {
return ErrorNode{pc: pc[0], cause: cause}
}

// Cause walks all the preceeding errors and return the originating error.
// Cause walks all the preceding errors and return the originating error.
func Cause(err error) error {
for err != nil {
cause, ok := err.(causer)
Expand All @@ -102,7 +102,7 @@ func Cause(err error) error {
}

// NewError creates a simple string error (like Error.New). But, this
// error also captures the caller's Program Counter and the preceeding error.
// error also captures the caller's Program Counter and the preceding error.
func NewError(cause error, format string, v ...interface{}) error {
return &pcError{
ErrorNode: ErrorNode{}.Initialize(cause, 3),
Expand All @@ -117,5 +117,5 @@ type pcError struct {
}

// Error satisfies the error interface. It shows the error with Program Counter
// symbols and calls Error on the preceeding error so you can see the full error chain.
// symbols and calls Error on the preceding error so you can see the full error chain.
func (e *pcError) Error() string { return e.ErrorNode.Error(e.msg) }
48 changes: 0 additions & 48 deletions pipeline/policies.go

This file was deleted.

75 changes: 75 additions & 0 deletions pipeline/policies_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package pipeline_test

import (
"context"

"github.com/Azure/azure-pipeline-go/pipeline"
)

// Here is the template for defining your own Factory & Policy:

// newMyPolicyFactory creates a 'My' policy factory. Make this function
// public if this should be callable from another package; everything
// else about the factory/policy should remain private to the package.
func newMyPolicyFactory( /* Desired parameters */ ) pipeline.Factory {
return &myPolicyFactory{ /* Set desired fields */ }
}

type myPolicyFactory struct {
// Desired fields (goroutine-safe because the factory is shared by many Policy objects)
}

// New initializes a Xxx policy object.
func (f *myPolicyFactory) New(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.Policy {
return &myPolicy{next: next, po: po /* Set desired fields */}
}

type myPolicy struct {
next pipeline.Policy
po *pipeline.PolicyOptions // Optional private field
// Additional desired fields (mutable for use by this specific Policy object)
}

func (p *myPolicy) Do(ctx context.Context, request pipeline.Request) (response pipeline.Response, err error) {
// TODO: Put your policy behavior code here
// Your code should NOT mutate the ctx or request parameters
// However, you can make a copy of the request and mutate the copy
// You can also pass a different Context on.
// You can optionally use po (PolicyOptions) in this func.

// Forward the request to the next node in the pipeline:
response, err = p.next.Do(ctx, request)

// Process the response here. You can deserialize the body into an object.
// If you do this, also define a struct that wraps an http.Response & your
// deserialized struct. Have your wrapper struct implement the
// pipeline.Response interface and then return your struct (via the interface)
// After the pipeline completes, take response and perform a type assertion
// to get back to the wrapper struct so you can access the deserialized object.

return // Return the response & err
}

func newMyPolicyFactory2( /* Desired parameters */ ) pipeline.Factory {
return pipeline.FactoryFunc(func(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.PolicyFunc {
return func(ctx context.Context, request pipeline.Request) (response pipeline.Response, err error) {
// TODO: Put your policy behavior code here
// Your code should NOT mutate the ctx or request parameters
// However, you can make a copy of the request and mutate the copy
// You can also pass a different Context on.
// You can optionally use po (PolicyOptions) in this func.

// Forward the request to the next node in the pipeline:
response, err = next.Do(ctx, request)

// Process the response here. You can deserialize the body into an object.
// If you do this, also define a struct that wraps an http.Response & your
// deserialized struct. Have your wrapper struct implement the
// pipeline.Response interface and then return your struct (via the interface)
// After the pipeline completes, take response and perform a type assertion
// to get back to the wrapper struct so you can access the deserialized object.

return // Return the response & err
}
})
}
8 changes: 8 additions & 0 deletions pipeline/progress.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ func (rbp *requestBodyProgress) Seek(offset int64, whence int) (offsetFromStart
return rbp.requestBody.Seek(offset, whence)
}

// requestBodyProgress supports Close but the underlying stream may not; if it does, Close will close it.
func (rbp *requestBodyProgress) Close() error {
if c, ok := rbp.requestBody.(io.Closer); ok {
return c.Close()
}
return nil
}

// ********** The following are specific to the response body (a ReadCloser)

// This struct is used when sending a body to the network
Expand Down

0 comments on commit 9804b77

Please sign in to comment.