From aa38b995b4cf915acc1f19327ff773e7bbc65565 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Thu, 11 Jan 2018 14:25:30 -0800 Subject: [PATCH 1/2] 1st class functions and doc improvements --- pipeline/core.go | 50 ++++++++++++++------------ pipeline/doc.go | 3 +- pipeline/error.go | 12 +++---- pipeline/policies.go | 48 ------------------------- pipeline/policies_test.go | 75 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 78 deletions(-) delete mode 100644 pipeline/policies.go create mode 100644 pipeline/policies_test.go diff --git a/pipeline/core.go b/pipeline/core.go index bef5ea3..72b254f 100644 --- a/pipeline/core.go +++ b/pipeline/core.go @@ -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. @@ -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. @@ -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 @@ -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") } diff --git a/pipeline/doc.go b/pipeline/doc.go index 31be3e7..b5ab05f 100644 --- a/pipeline/doc.go +++ b/pipeline/doc.go @@ -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 diff --git a/pipeline/error.go b/pipeline/error.go index ca2de17..83c192e 100644 --- a/pipeline/error.go +++ b/pipeline/error.go @@ -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. @@ -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. @@ -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) @@ -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), @@ -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) } diff --git a/pipeline/policies.go b/pipeline/policies.go deleted file mode 100644 index 885529d..0000000 --- a/pipeline/policies.go +++ /dev/null @@ -1,48 +0,0 @@ -package pipeline - -import ( - "context" -) - -// 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 */ ) 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 Policy, po *PolicyOptions) Policy { - return &myPolicy{next: next, po: po /* Set desired fields */} -} - -type myPolicy struct { - next Policy - po *PolicyOptions // Optional private field - // Additional desired fields (mutable for use by this specific Policy object) -} - -func (p *myPolicy) Do(ctx context.Context, request Request) (response 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. - - // 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 -} diff --git a/pipeline/policies_test.go b/pipeline/policies_test.go new file mode 100644 index 0000000..ac03304 --- /dev/null +++ b/pipeline/policies_test.go @@ -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 + } + }) +} From cb3adc50eae4ff761ef9db4453c5340898668b12 Mon Sep 17 00:00:00 2001 From: Jeffrey Richter Date: Fri, 26 Jan 2018 12:04:52 -0800 Subject: [PATCH 2/2] Added Close to requestBodyProgress --- pipeline/progress.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pipeline/progress.go b/pipeline/progress.go index fd7b142..efa3c8e 100644 --- a/pipeline/progress.go +++ b/pipeline/progress.go @@ -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