mirror of https://github.com/knative/docs.git
fix build test (#5251)
This commit is contained in:
parent
892540960d
commit
26a720ed86
|
@ -1,201 +0,0 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
|
@ -1,169 +0,0 @@
|
|||
// Package v2 reexports a subset of the SDK v2 API.
|
||||
package v2
|
||||
|
||||
// Package cloudevents alias' common functions and types to improve discoverability and reduce
|
||||
// the number of imports for simple HTTP clients.
|
||||
|
||||
import (
|
||||
"github.com/cloudevents/sdk-go/v2/binding"
|
||||
"github.com/cloudevents/sdk-go/v2/client"
|
||||
"github.com/cloudevents/sdk-go/v2/context"
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
"github.com/cloudevents/sdk-go/v2/protocol"
|
||||
"github.com/cloudevents/sdk-go/v2/protocol/http"
|
||||
"github.com/cloudevents/sdk-go/v2/types"
|
||||
)
|
||||
|
||||
// Client
|
||||
|
||||
type ClientOption client.Option
|
||||
type Client = client.Client
|
||||
|
||||
// Event
|
||||
|
||||
type Event = event.Event
|
||||
type Result = protocol.Result
|
||||
|
||||
// Context
|
||||
|
||||
type EventContext = event.EventContext
|
||||
type EventContextV1 = event.EventContextV1
|
||||
type EventContextV03 = event.EventContextV03
|
||||
|
||||
// Custom Types
|
||||
|
||||
type Timestamp = types.Timestamp
|
||||
type URIRef = types.URIRef
|
||||
|
||||
// HTTP Protocol
|
||||
|
||||
type HTTPOption http.Option
|
||||
|
||||
type HTTPProtocol = http.Protocol
|
||||
|
||||
// Encoding
|
||||
|
||||
type Encoding = binding.Encoding
|
||||
|
||||
// Message
|
||||
|
||||
type Message = binding.Message
|
||||
|
||||
const (
|
||||
// ReadEncoding
|
||||
|
||||
ApplicationXML = event.ApplicationXML
|
||||
ApplicationJSON = event.ApplicationJSON
|
||||
TextPlain = event.TextPlain
|
||||
ApplicationCloudEventsJSON = event.ApplicationCloudEventsJSON
|
||||
ApplicationCloudEventsBatchJSON = event.ApplicationCloudEventsBatchJSON
|
||||
Base64 = event.Base64
|
||||
|
||||
// Event Versions
|
||||
|
||||
VersionV1 = event.CloudEventsVersionV1
|
||||
VersionV03 = event.CloudEventsVersionV03
|
||||
|
||||
// Encoding
|
||||
|
||||
EncodingBinary = binding.EncodingBinary
|
||||
EncodingStructured = binding.EncodingStructured
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
// ContentType Helpers
|
||||
|
||||
StringOfApplicationJSON = event.StringOfApplicationJSON
|
||||
StringOfApplicationXML = event.StringOfApplicationXML
|
||||
StringOfTextPlain = event.StringOfTextPlain
|
||||
StringOfApplicationCloudEventsJSON = event.StringOfApplicationCloudEventsJSON
|
||||
StringOfApplicationCloudEventsBatchJSON = event.StringOfApplicationCloudEventsBatchJSON
|
||||
StringOfBase64 = event.StringOfBase64
|
||||
|
||||
// Client Creation
|
||||
|
||||
NewClient = client.New
|
||||
NewClientObserved = client.NewObserved
|
||||
NewDefaultClient = client.NewDefault
|
||||
NewHTTPReceiveHandler = client.NewHTTPReceiveHandler
|
||||
|
||||
// Client Options
|
||||
|
||||
WithEventDefaulter = client.WithEventDefaulter
|
||||
WithUUIDs = client.WithUUIDs
|
||||
WithTimeNow = client.WithTimeNow
|
||||
WithTracePropagation = client.WithTracePropagation()
|
||||
|
||||
// Event Creation
|
||||
|
||||
NewEvent = event.New
|
||||
|
||||
// Results
|
||||
|
||||
NewResult = protocol.NewResult
|
||||
ResultIs = protocol.ResultIs
|
||||
ResultAs = protocol.ResultAs
|
||||
|
||||
// Receipt helpers
|
||||
|
||||
NewReceipt = protocol.NewReceipt
|
||||
|
||||
ResultACK = protocol.ResultACK
|
||||
ResultNACK = protocol.ResultNACK
|
||||
|
||||
IsACK = protocol.IsACK
|
||||
IsNACK = protocol.IsNACK
|
||||
|
||||
// HTTP Results
|
||||
|
||||
NewHTTPResult = http.NewResult
|
||||
NewHTTPRetriesResult = http.NewRetriesResult
|
||||
|
||||
// Message Creation
|
||||
|
||||
ToMessage = binding.ToMessage
|
||||
|
||||
// HTTP Messages
|
||||
|
||||
WriteHTTPRequest = http.WriteRequest
|
||||
|
||||
// Tracing
|
||||
|
||||
EnableTracing = observability.EnableTracing
|
||||
|
||||
// Context
|
||||
|
||||
ContextWithTarget = context.WithTarget
|
||||
TargetFromContext = context.TargetFrom
|
||||
ContextWithRetriesConstantBackoff = context.WithRetriesConstantBackoff
|
||||
ContextWithRetriesLinearBackoff = context.WithRetriesLinearBackoff
|
||||
ContextWithRetriesExponentialBackoff = context.WithRetriesExponentialBackoff
|
||||
|
||||
WithEncodingBinary = binding.WithForceBinary
|
||||
WithEncodingStructured = binding.WithForceStructured
|
||||
|
||||
// Custom Types
|
||||
|
||||
ParseTimestamp = types.ParseTimestamp
|
||||
ParseURIRef = types.ParseURIRef
|
||||
ParseURI = types.ParseURI
|
||||
|
||||
// HTTP Protocol
|
||||
|
||||
NewHTTP = http.New
|
||||
|
||||
// HTTP Protocol Options
|
||||
|
||||
WithTarget = http.WithTarget
|
||||
WithHeader = http.WithHeader
|
||||
WithShutdownTimeout = http.WithShutdownTimeout
|
||||
//WithEncoding = http.WithEncoding
|
||||
//WithStructuredEncoding = http.WithStructuredEncoding // TODO: expose new way
|
||||
WithPort = http.WithPort
|
||||
WithPath = http.WithPath
|
||||
WithMiddleware = http.WithMiddleware
|
||||
WithListener = http.WithListener
|
||||
WithRoundTripper = http.WithRoundTripper
|
||||
)
|
|
@ -1,47 +0,0 @@
|
|||
package binding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding/spec"
|
||||
)
|
||||
|
||||
// MessageMetadataWriter is used to set metadata when a binary Message is visited.
|
||||
type MessageMetadataWriter interface {
|
||||
// Set a standard attribute.
|
||||
//
|
||||
// The value can either be the correct golang type for the attribute, or a canonical
|
||||
// string encoding, or nil. If value is nil, then the attribute should be deleted.
|
||||
// See package types to perform the needed conversions.
|
||||
SetAttribute(attribute spec.Attribute, value interface{}) error
|
||||
|
||||
// Set an extension attribute.
|
||||
//
|
||||
// The value can either be the correct golang type for the attribute, or a canonical
|
||||
// string encoding, or nil. If value is nil, then the extension should be deleted.
|
||||
// See package types to perform the needed conversions.
|
||||
SetExtension(name string, value interface{}) error
|
||||
}
|
||||
|
||||
// BinaryWriter is used to visit a binary Message and generate a new representation.
|
||||
//
|
||||
// Protocols that supports binary encoding should implement this interface to implement direct
|
||||
// binary to binary encoding and event to binary encoding.
|
||||
//
|
||||
// Start() and End() methods must be invoked by the caller of Message.ReadBinary() every time
|
||||
// the BinaryWriter implementation is used to visit a Message.
|
||||
type BinaryWriter interface {
|
||||
MessageMetadataWriter
|
||||
|
||||
// Method invoked at the beginning of the visit. Useful to perform initial memory allocations
|
||||
Start(ctx context.Context) error
|
||||
|
||||
// SetData receives an io.Reader for the data attribute.
|
||||
// io.Reader is not invoked when the data attribute is empty
|
||||
SetData(data io.Reader) error
|
||||
|
||||
// End method is invoked only after the whole encoding process ends successfully.
|
||||
// If it fails, it's never invoked. It can be used to finalize the message.
|
||||
End(ctx context.Context) error
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
|
||||
Package binding defines interfaces for protocol bindings.
|
||||
|
||||
NOTE: Most applications that emit or consume events should use the ../client
|
||||
package, which provides a simpler API to the underlying binding.
|
||||
|
||||
The interfaces in this package provide extra encoding and protocol information
|
||||
to allow efficient forwarding and end-to-end reliable delivery between a
|
||||
Receiver and a Sender belonging to different bindings. This is useful for
|
||||
intermediary applications that route or forward events, but not necessary for
|
||||
most "endpoint" applications that emit or consume events.
|
||||
|
||||
Protocol Bindings
|
||||
|
||||
A protocol binding usually implements a Message, a Sender and Receiver, a StructuredWriter and a BinaryWriter (depending on the supported encodings of the protocol) and an Write[ProtocolMessage] method.
|
||||
|
||||
Read and write events
|
||||
|
||||
The core of this package is the binding.Message interface.
|
||||
Through binding.MessageReader It defines how to read a protocol specific message for an
|
||||
encoded event in structured mode or binary mode.
|
||||
The entity who receives a protocol specific data structure representing a message
|
||||
(e.g. an HttpRequest) encapsulates it in a binding.Message implementation using a NewMessage method (e.g. http.NewMessage).
|
||||
Then the entity that wants to send the binding.Message back on the wire,
|
||||
translates it back to the protocol specific data structure (e.g. a Kafka ConsumerMessage), using
|
||||
the writers BinaryWriter and StructuredWriter specific to that protocol.
|
||||
Binding implementations exposes their writers
|
||||
through a specific Write[ProtocolMessage] function (e.g. kafka.EncodeProducerMessage),
|
||||
in order to simplify the encoding process.
|
||||
|
||||
The encoding process can be customized in order to mutate the final result with binding.TransformerFactory.
|
||||
A bunch of these are provided directly by the binding/transformer module.
|
||||
|
||||
Usually binding.Message implementations can be encoded only one time, because the encoding process drain the message itself.
|
||||
In order to consume a message several times, the binding/buffering package provides several APIs to buffer the Message.
|
||||
|
||||
A message can be converted to an event.Event using binding.ToEvent() method.
|
||||
An event.Event can be used as Message casting it to binding.EventMessage.
|
||||
|
||||
In order to simplify the encoding process for each protocol, this package provide several utility methods like binding.Write and binding.DirectWrite.
|
||||
The binding.Write method tries to preserve the structured/binary encoding, in order to be as much efficient as possible.
|
||||
|
||||
Messages can be eventually wrapped to change their behaviours and binding their lifecycle, like the binding.FinishMessage.
|
||||
Every Message wrapper implements the MessageWrapper interface
|
||||
|
||||
Sender and Receiver
|
||||
|
||||
A Receiver receives protocol specific messages and wraps them to into binding.Message implementations.
|
||||
|
||||
A Sender converts arbitrary Message implementations to a protocol-specific form using the protocol specific Write method
|
||||
and sends them.
|
||||
|
||||
Message and ExactlyOnceMessage provide methods to allow acknowledgments to
|
||||
propagate when a reliable messages is forwarded from a Receiver to a Sender.
|
||||
QoS 0 (unreliable), 1 (at-least-once) and 2 (exactly-once) are supported.
|
||||
|
||||
Transport
|
||||
|
||||
A binding implementation providing Sender and Receiver implementations can be used as a Transport through the BindingTransport adapter.
|
||||
|
||||
*/
|
||||
package binding
|
|
@ -1,26 +0,0 @@
|
|||
package binding
|
||||
|
||||
import "errors"
|
||||
|
||||
// Encoding enum specifies the type of encodings supported by binding interfaces
|
||||
type Encoding int
|
||||
|
||||
const (
|
||||
// Binary encoding as specified in https://github.com/cloudevents/spec/blob/master/spec.md#message
|
||||
EncodingBinary Encoding = iota
|
||||
// Structured encoding as specified in https://github.com/cloudevents/spec/blob/master/spec.md#message
|
||||
EncodingStructured
|
||||
// Message is an instance of EventMessage or it contains EventMessage nested (through MessageWrapper)
|
||||
EncodingEvent
|
||||
// When the encoding is unknown (which means that the message is a non-event)
|
||||
EncodingUnknown
|
||||
)
|
||||
|
||||
// ErrUnknownEncoding specifies that the Message is not an event or it is encoded with an unknown encoding
|
||||
var ErrUnknownEncoding = errors.New("unknown Message encoding")
|
||||
|
||||
// ErrNotStructured returned by Message.Structured for non-structured messages.
|
||||
var ErrNotStructured = errors.New("message is not in structured mode")
|
||||
|
||||
// ErrNotBinary returned by Message.Binary for non-binary messages.
|
||||
var ErrNotBinary = errors.New("message is not in binary mode")
|
|
@ -1,103 +0,0 @@
|
|||
package binding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding/format"
|
||||
"github.com/cloudevents/sdk-go/v2/binding/spec"
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
)
|
||||
|
||||
type eventFormatKey int
|
||||
|
||||
const (
|
||||
formatEventStructured eventFormatKey = iota
|
||||
)
|
||||
|
||||
// EventMessage type-converts a event.Event object to implement Message.
|
||||
// This allows local event.Event objects to be sent directly via Sender.Send()
|
||||
// s.Send(ctx, binding.EventMessage(e))
|
||||
// When an event is wrapped into a EventMessage, the original event could be
|
||||
// potentially mutated. If you need to use the Event again, after wrapping it into
|
||||
// an Event message, you should copy it before
|
||||
type EventMessage event.Event
|
||||
|
||||
func ToMessage(e *event.Event) Message {
|
||||
return (*EventMessage)(e)
|
||||
}
|
||||
|
||||
func (m *EventMessage) ReadEncoding() Encoding {
|
||||
return EncodingEvent
|
||||
}
|
||||
|
||||
func (m *EventMessage) ReadStructured(ctx context.Context, builder StructuredWriter) error {
|
||||
f := GetOrDefaultFromCtx(ctx, formatEventStructured, format.JSON).(format.Format)
|
||||
b, err := f.Marshal((*event.Event)(m))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return builder.SetStructuredEvent(ctx, f, bytes.NewReader(b))
|
||||
}
|
||||
|
||||
func (m *EventMessage) ReadBinary(ctx context.Context, b BinaryWriter) (err error) {
|
||||
err = eventContextToBinaryWriter(m.Context, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Pass the body
|
||||
body := (*event.Event)(m).Data()
|
||||
if len(body) > 0 {
|
||||
err = b.SetData(bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EventMessage) GetAttribute(k spec.Kind) (spec.Attribute, interface{}) {
|
||||
sv := spec.VS.Version(m.Context.GetSpecVersion())
|
||||
a := sv.AttributeFromKind(k)
|
||||
if a != nil {
|
||||
return a, a.Get(m.Context)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *EventMessage) GetExtension(name string) interface{} {
|
||||
ext, _ := m.Context.GetExtension(name)
|
||||
return ext
|
||||
}
|
||||
|
||||
func eventContextToBinaryWriter(c event.EventContext, b BinaryWriter) (err error) {
|
||||
// Pass all attributes
|
||||
sv := spec.VS.Version(c.GetSpecVersion())
|
||||
for _, a := range sv.Attributes() {
|
||||
value := a.Get(c)
|
||||
if value != nil {
|
||||
err = b.SetAttribute(a, value)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Pass all extensions
|
||||
for k, v := range c.GetExtensions() {
|
||||
err = b.SetExtension(k, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*EventMessage) Finish(error) error { return nil }
|
||||
|
||||
var _ Message = (*EventMessage)(nil) // Test it conforms to the interface
|
||||
var _ MessageMetadataReader = (*EventMessage)(nil) // Test it conforms to the interface
|
||||
|
||||
// UseFormatForEvent configures which format to use when marshalling the event to structured mode
|
||||
func UseFormatForEvent(ctx context.Context, f format.Format) context.Context {
|
||||
return context.WithValue(ctx, formatEventStructured, f)
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package binding
|
||||
|
||||
import "github.com/cloudevents/sdk-go/v2/binding/spec"
|
||||
|
||||
type finishMessage struct {
|
||||
Message
|
||||
finish func(error)
|
||||
}
|
||||
|
||||
func (m *finishMessage) GetAttribute(k spec.Kind) (spec.Attribute, interface{}) {
|
||||
return m.Message.(MessageMetadataReader).GetAttribute(k)
|
||||
}
|
||||
|
||||
func (m *finishMessage) GetExtension(s string) interface{} {
|
||||
return m.Message.(MessageMetadataReader).GetExtension(s)
|
||||
}
|
||||
|
||||
func (m *finishMessage) GetWrappedMessage() Message {
|
||||
return m.Message
|
||||
}
|
||||
|
||||
func (m *finishMessage) Finish(err error) error {
|
||||
err2 := m.Message.Finish(err) // Finish original message first
|
||||
if m.finish != nil {
|
||||
m.finish(err) // Notify callback
|
||||
}
|
||||
return err2
|
||||
}
|
||||
|
||||
var _ MessageWrapper = (*finishMessage)(nil)
|
||||
|
||||
// WithFinish returns a wrapper for m that calls finish() and
|
||||
// m.Finish() in its Finish().
|
||||
// Allows code to be notified when a message is Finished.
|
||||
func WithFinish(m Message, finish func(error)) Message {
|
||||
return &finishMessage{Message: m, finish: finish}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
/*
|
||||
Package format formats structured events.
|
||||
|
||||
The "application/cloudevents+json" format is built-in and always
|
||||
available. Other formats may be added.
|
||||
*/
|
||||
package format
|
|
@ -1,75 +0,0 @@
|
|||
package format
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mime"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
)
|
||||
|
||||
// Format marshals and unmarshals structured events to bytes.
|
||||
type Format interface {
|
||||
// MediaType identifies the format
|
||||
MediaType() string
|
||||
// Marshal event to bytes
|
||||
Marshal(*event.Event) ([]byte, error)
|
||||
// Unmarshal bytes to event
|
||||
Unmarshal([]byte, *event.Event) error
|
||||
}
|
||||
|
||||
// Prefix for event-format media types.
|
||||
const Prefix = "application/cloudevents"
|
||||
|
||||
// IsFormat returns true if mediaType begins with "application/cloudevents"
|
||||
func IsFormat(mediaType string) bool { return strings.HasPrefix(mediaType, Prefix) }
|
||||
|
||||
// JSON is the built-in "application/cloudevents+json" format.
|
||||
var JSON = jsonFmt{}
|
||||
|
||||
type jsonFmt struct{}
|
||||
|
||||
func (jsonFmt) MediaType() string { return event.ApplicationCloudEventsJSON }
|
||||
|
||||
func (jsonFmt) Marshal(e *event.Event) ([]byte, error) { return json.Marshal(e) }
|
||||
func (jsonFmt) Unmarshal(b []byte, e *event.Event) error {
|
||||
return json.Unmarshal(b, e)
|
||||
}
|
||||
|
||||
// built-in formats
|
||||
var formats map[string]Format
|
||||
|
||||
func init() {
|
||||
formats = map[string]Format{}
|
||||
Add(JSON)
|
||||
}
|
||||
|
||||
// Lookup returns the format for contentType, or nil if not found.
|
||||
func Lookup(contentType string) Format {
|
||||
mediaType, _, _ := mime.ParseMediaType(contentType)
|
||||
return formats[mediaType]
|
||||
}
|
||||
|
||||
func unknown(mediaType string) error {
|
||||
return fmt.Errorf("unknown event format media-type %#v", mediaType)
|
||||
}
|
||||
|
||||
// Add a new Format. It can be retrieved by Lookup(f.MediaType())
|
||||
func Add(f Format) { formats[f.MediaType()] = f }
|
||||
|
||||
// Marshal an event to bytes using the mediaType event format.
|
||||
func Marshal(mediaType string, e *event.Event) ([]byte, error) {
|
||||
if f := formats[mediaType]; f != nil {
|
||||
return f.Marshal(e)
|
||||
}
|
||||
return nil, unknown(mediaType)
|
||||
}
|
||||
|
||||
// Unmarshal bytes to an event using the mediaType event format.
|
||||
func Unmarshal(mediaType string, b []byte, e *event.Event) error {
|
||||
if f := formats[mediaType]; f != nil {
|
||||
return f.Unmarshal(b, e)
|
||||
}
|
||||
return unknown(mediaType)
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
package binding
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding/spec"
|
||||
)
|
||||
|
||||
// MessageReader defines the read-related portion of the Message interface.
|
||||
//
|
||||
// The ReadStructured and ReadBinary methods allows to perform an optimized encoding of a Message to a specific data structure.
|
||||
//
|
||||
// If MessageReader.ReadEncoding() can be equal to EncodingBinary, then the implementation of MessageReader
|
||||
// MUST also implement MessageMetadataReader.
|
||||
//
|
||||
// A Sender should try each method of interest and fall back to binding.ToEvent() if none are supported.
|
||||
// An out of the box algorithm is provided for writing a message: binding.Write().
|
||||
type MessageReader interface {
|
||||
// Return the type of the message Encoding.
|
||||
// The encoding should be preferably computed when the message is constructed.
|
||||
ReadEncoding() Encoding
|
||||
|
||||
// ReadStructured transfers a structured-mode event to a StructuredWriter.
|
||||
// It must return ErrNotStructured if message is not in structured mode.
|
||||
//
|
||||
// Returns a different err if something wrong happened while trying to read the structured event.
|
||||
// In this case, the caller must Finish the message with appropriate error.
|
||||
//
|
||||
// This allows Senders to avoid re-encoding messages that are
|
||||
// already in suitable structured form.
|
||||
ReadStructured(context.Context, StructuredWriter) error
|
||||
|
||||
// ReadBinary transfers a binary-mode event to an BinaryWriter.
|
||||
// It must return ErrNotBinary if message is not in binary mode.
|
||||
//
|
||||
// The implementation of ReadBinary must not control the lifecycle with BinaryWriter.Start() and BinaryWriter.End(),
|
||||
// because the caller must control the lifecycle.
|
||||
//
|
||||
// Returns a different err if something wrong happened while trying to read the binary event
|
||||
// In this case, the caller must Finish the message with appropriate error
|
||||
//
|
||||
// This allows Senders to avoid re-encoding messages that are
|
||||
// already in suitable binary form.
|
||||
ReadBinary(context.Context, BinaryWriter) error
|
||||
}
|
||||
|
||||
// MessageMetadataReader defines how to read metadata from a binary/event message
|
||||
//
|
||||
// If a message implementing MessageReader is encoded as binary (MessageReader.ReadEncoding() == EncodingBinary)
|
||||
// or it's an EventMessage, then it's safe to assume that it also implements this interface
|
||||
type MessageMetadataReader interface {
|
||||
// GetAttribute returns:
|
||||
//
|
||||
// * attribute, value: if the message contains an attribute of that attribute kind
|
||||
// * attribute, nil: if the message spec version supports the attribute kind, but doesn't have any value
|
||||
// * nil, nil: if the message spec version doesn't support the attribute kind
|
||||
GetAttribute(attributeKind spec.Kind) (spec.Attribute, interface{})
|
||||
// GetExtension returns the value of that extension, if any.
|
||||
GetExtension(name string) interface{}
|
||||
}
|
||||
|
||||
// Message is the interface to a binding-specific message containing an event.
|
||||
//
|
||||
// Reliable Delivery
|
||||
//
|
||||
// There are 3 reliable qualities of service for messages:
|
||||
//
|
||||
// 0/at-most-once/unreliable: messages can be dropped silently.
|
||||
//
|
||||
// 1/at-least-once: messages are not dropped without signaling an error
|
||||
// to the sender, but they may be duplicated in the event of a re-send.
|
||||
//
|
||||
// 2/exactly-once: messages are never dropped (without error) or
|
||||
// duplicated, as long as both sending and receiving ends maintain
|
||||
// some binding-specific delivery state. Whether this is persisted
|
||||
// depends on the configuration of the binding implementations.
|
||||
//
|
||||
// The Message interface supports QoS 0 and 1, the ExactlyOnceMessage interface
|
||||
// supports QoS 2
|
||||
//
|
||||
// Message includes the MessageReader interface to read messages. Every binding.Message implementation *must* specify if the message can be accessed one or more times.
|
||||
//
|
||||
// When a Message can be forgotten by the entity who produced the message, Message.Finish() *must* be invoked.
|
||||
type Message interface {
|
||||
MessageReader
|
||||
|
||||
// Finish *must* be called when message from a Receiver can be forgotten by
|
||||
// the receiver. A QoS 1 sender should not call Finish() until it gets an acknowledgment of
|
||||
// receipt on the underlying transport. For QoS 2 see ExactlyOnceMessage.
|
||||
//
|
||||
// Note that, depending on the Message implementation, forgetting to Finish the message
|
||||
// could produce memory/resources leaks!
|
||||
//
|
||||
// Passing a non-nil err indicates sending or processing failed.
|
||||
// A non-nil return indicates that the message was not accepted
|
||||
// by the receivers peer.
|
||||
Finish(error) error
|
||||
}
|
||||
|
||||
// ExactlyOnceMessage is implemented by received Messages
|
||||
// that support QoS 2. Only transports that support QoS 2 need to
|
||||
// implement or use this interface.
|
||||
type ExactlyOnceMessage interface {
|
||||
Message
|
||||
|
||||
// Received is called by a forwarding QoS2 Sender when it gets
|
||||
// acknowledgment of receipt (e.g. AMQP 'accept' or MQTT PUBREC)
|
||||
//
|
||||
// The receiver must call settle(nil) when it get's the ack-of-ack
|
||||
// (e.g. AMQP 'settle' or MQTT PUBCOMP) or settle(err) if the
|
||||
// transfer fails.
|
||||
//
|
||||
// Finally the Sender calls Finish() to indicate the message can be
|
||||
// discarded.
|
||||
//
|
||||
// If sending fails, or if the sender does not support QoS 2, then
|
||||
// Finish() may be called without any call to Received()
|
||||
Received(settle func(error))
|
||||
}
|
||||
|
||||
// MessageWrapper interface is used to walk through a decorated Message and unwrap it.
|
||||
type MessageWrapper interface {
|
||||
Message
|
||||
MessageMetadataReader
|
||||
|
||||
// Method to get the wrapped message
|
||||
GetWrappedMessage() Message
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
package spec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/types"
|
||||
)
|
||||
|
||||
// Kind is a version-independent identifier for a CloudEvent context attribute.
|
||||
type Kind uint8
|
||||
|
||||
const (
|
||||
// Required cloudevents attributes
|
||||
ID Kind = iota
|
||||
Source
|
||||
SpecVersion
|
||||
Type
|
||||
// Optional cloudevents attributes
|
||||
DataContentType
|
||||
DataSchema
|
||||
Subject
|
||||
Time
|
||||
)
|
||||
const nAttrs = int(Time) + 1
|
||||
|
||||
var kindNames = [nAttrs]string{
|
||||
"id",
|
||||
"source",
|
||||
"specversion",
|
||||
"type",
|
||||
"datacontenttype",
|
||||
"dataschema",
|
||||
"subject",
|
||||
"time",
|
||||
}
|
||||
|
||||
// String is a human-readable string, for a valid attribute name use Attribute.Name
|
||||
func (k Kind) String() string { return kindNames[k] }
|
||||
|
||||
// IsRequired returns true for attributes defined as "required" by the CE spec.
|
||||
func (k Kind) IsRequired() bool { return k < DataContentType }
|
||||
|
||||
// Attribute is a named attribute accessor.
|
||||
// The attribute name is specific to a Version.
|
||||
type Attribute interface {
|
||||
Kind() Kind
|
||||
// Name of the attribute with respect to the current spec Version() with prefix
|
||||
PrefixedName() string
|
||||
// Name of the attribute with respect to the current spec Version()
|
||||
Name() string
|
||||
// Version of the spec that this attribute belongs to
|
||||
Version() Version
|
||||
// Get the value of this attribute from an event context
|
||||
Get(event.EventContextReader) interface{}
|
||||
// Set the value of this attribute on an event context
|
||||
Set(event.EventContextWriter, interface{}) error
|
||||
// Delete this attribute from and event context, when possible
|
||||
Delete(event.EventContextWriter) error
|
||||
}
|
||||
|
||||
// accessor provides Kind, Get, Set.
|
||||
type accessor interface {
|
||||
Kind() Kind
|
||||
Get(event.EventContextReader) interface{}
|
||||
Set(event.EventContextWriter, interface{}) error
|
||||
Delete(event.EventContextWriter) error
|
||||
}
|
||||
|
||||
var acc = [nAttrs]accessor{
|
||||
&aStr{aKind(ID), event.EventContextReader.GetID, event.EventContextWriter.SetID},
|
||||
&aStr{aKind(Source), event.EventContextReader.GetSource, event.EventContextWriter.SetSource},
|
||||
&aStr{aKind(SpecVersion), event.EventContextReader.GetSpecVersion, func(writer event.EventContextWriter, s string) error { return nil }},
|
||||
&aStr{aKind(Type), event.EventContextReader.GetType, event.EventContextWriter.SetType},
|
||||
&aStr{aKind(DataContentType), event.EventContextReader.GetDataContentType, event.EventContextWriter.SetDataContentType},
|
||||
&aStr{aKind(DataSchema), event.EventContextReader.GetDataSchema, event.EventContextWriter.SetDataSchema},
|
||||
&aStr{aKind(Subject), event.EventContextReader.GetSubject, event.EventContextWriter.SetSubject},
|
||||
&aTime{aKind(Time), event.EventContextReader.GetTime, event.EventContextWriter.SetTime},
|
||||
}
|
||||
|
||||
// aKind implements Kind()
|
||||
type aKind Kind
|
||||
|
||||
func (kind aKind) Kind() Kind { return Kind(kind) }
|
||||
|
||||
type aStr struct {
|
||||
aKind
|
||||
get func(event.EventContextReader) string
|
||||
set func(event.EventContextWriter, string) error
|
||||
}
|
||||
|
||||
func (a *aStr) Get(c event.EventContextReader) interface{} {
|
||||
if s := a.get(c); s != "" {
|
||||
return s
|
||||
}
|
||||
return nil // Treat blank as missing
|
||||
}
|
||||
|
||||
func (a *aStr) Set(c event.EventContextWriter, v interface{}) error {
|
||||
s, err := types.ToString(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for %s: %#v", a.Kind(), v)
|
||||
}
|
||||
return a.set(c, s)
|
||||
}
|
||||
|
||||
func (a *aStr) Delete(c event.EventContextWriter) error {
|
||||
return a.set(c, "")
|
||||
}
|
||||
|
||||
type aTime struct {
|
||||
aKind
|
||||
get func(event.EventContextReader) time.Time
|
||||
set func(event.EventContextWriter, time.Time) error
|
||||
}
|
||||
|
||||
func (a *aTime) Get(c event.EventContextReader) interface{} {
|
||||
if v := a.get(c); !v.IsZero() {
|
||||
return v
|
||||
}
|
||||
return nil // Treat zero time as missing.
|
||||
}
|
||||
|
||||
func (a *aTime) Set(c event.EventContextWriter, v interface{}) error {
|
||||
t, err := types.ToTime(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for %s: %#v", a.Kind(), v)
|
||||
}
|
||||
return a.set(c, t)
|
||||
}
|
||||
|
||||
func (a *aTime) Delete(c event.EventContextWriter) error {
|
||||
return a.set(c, time.Time{})
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
/*
|
||||
Package spec provides spec-version metadata.
|
||||
|
||||
For use by code that maps events using (prefixed) attribute name strings.
|
||||
Supports handling multiple spec versions uniformly.
|
||||
|
||||
*/
|
||||
package spec
|
|
@ -1,76 +0,0 @@
|
|||
package spec
|
||||
|
||||
import (
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
)
|
||||
|
||||
type matchExactVersion struct {
|
||||
version
|
||||
}
|
||||
|
||||
func (v *matchExactVersion) Attribute(name string) Attribute { return v.attrMap[name] }
|
||||
|
||||
var _ Version = (*matchExactVersion)(nil)
|
||||
|
||||
func newMatchExactVersionVersion(
|
||||
prefix string,
|
||||
attributeNameMatchMapper func(string) string,
|
||||
context event.EventContext,
|
||||
convert func(event.EventContextConverter) event.EventContext,
|
||||
attrs ...*attribute,
|
||||
) *matchExactVersion {
|
||||
v := &matchExactVersion{
|
||||
version: version{
|
||||
prefix: prefix,
|
||||
context: context,
|
||||
convert: convert,
|
||||
attrMap: map[string]Attribute{},
|
||||
attrs: make([]Attribute, len(attrs)),
|
||||
},
|
||||
}
|
||||
for i, a := range attrs {
|
||||
a.version = v
|
||||
v.attrs[i] = a
|
||||
v.attrMap[attributeNameMatchMapper(a.name)] = a
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// WithPrefixMatchExact returns a set of versions with prefix added to all attribute names.
|
||||
func WithPrefixMatchExact(attributeNameMatchMapper func(string) string, prefix string) *Versions {
|
||||
attr := func(name string, kind Kind) *attribute {
|
||||
return &attribute{accessor: acc[kind], name: name}
|
||||
}
|
||||
vs := &Versions{
|
||||
m: map[string]Version{},
|
||||
prefix: prefix,
|
||||
all: []Version{
|
||||
newMatchExactVersionVersion(prefix, attributeNameMatchMapper, event.EventContextV1{}.AsV1(),
|
||||
func(c event.EventContextConverter) event.EventContext { return c.AsV1() },
|
||||
attr("id", ID),
|
||||
attr("source", Source),
|
||||
attr("specversion", SpecVersion),
|
||||
attr("type", Type),
|
||||
attr("datacontenttype", DataContentType),
|
||||
attr("dataschema", DataSchema),
|
||||
attr("subject", Subject),
|
||||
attr("time", Time),
|
||||
),
|
||||
newMatchExactVersionVersion(prefix, attributeNameMatchMapper, event.EventContextV03{}.AsV03(),
|
||||
func(c event.EventContextConverter) event.EventContext { return c.AsV03() },
|
||||
attr("specversion", SpecVersion),
|
||||
attr("type", Type),
|
||||
attr("source", Source),
|
||||
attr("schemaurl", DataSchema),
|
||||
attr("subject", Subject),
|
||||
attr("id", ID),
|
||||
attr("time", Time),
|
||||
attr("datacontenttype", DataContentType),
|
||||
),
|
||||
},
|
||||
}
|
||||
for _, v := range vs.all {
|
||||
vs.m[v.String()] = v
|
||||
}
|
||||
return vs
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
package spec
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
)
|
||||
|
||||
// Version provides meta-data for a single spec-version.
|
||||
type Version interface {
|
||||
// String name of the version, e.g. "1.0"
|
||||
String() string
|
||||
// Prefix for attribute names.
|
||||
Prefix() string
|
||||
// Attribute looks up a prefixed attribute name (case insensitive).
|
||||
// Returns nil if not found.
|
||||
Attribute(prefixedName string) Attribute
|
||||
// Attribute looks up the attribute from kind.
|
||||
// Returns nil if not found.
|
||||
AttributeFromKind(kind Kind) Attribute
|
||||
// Attributes returns all the context attributes for this version.
|
||||
Attributes() []Attribute
|
||||
// Convert translates a context to this version.
|
||||
Convert(event.EventContextConverter) event.EventContext
|
||||
// NewContext returns a new context for this version.
|
||||
NewContext() event.EventContext
|
||||
// SetAttribute sets named attribute to value.
|
||||
//
|
||||
// Name is case insensitive.
|
||||
// Does nothing if name does not start with prefix.
|
||||
SetAttribute(context event.EventContextWriter, name string, value interface{}) error
|
||||
}
|
||||
|
||||
// Versions contains all known versions with the same attribute prefix.
|
||||
type Versions struct {
|
||||
prefix string
|
||||
all []Version
|
||||
m map[string]Version
|
||||
}
|
||||
|
||||
// Versions returns the list of all known versions, most recent first.
|
||||
func (vs *Versions) Versions() []Version { return vs.all }
|
||||
|
||||
// Version returns the named version.
|
||||
func (vs *Versions) Version(name string) Version {
|
||||
return vs.m[name]
|
||||
}
|
||||
|
||||
// Latest returns the latest Version
|
||||
func (vs *Versions) Latest() Version { return vs.all[0] }
|
||||
|
||||
// PrefixedSpecVersionName returns the specversion attribute PrefixedName
|
||||
func (vs *Versions) PrefixedSpecVersionName() string { return vs.prefix + "specversion" }
|
||||
|
||||
// Prefix is the lowercase attribute name prefix.
|
||||
func (vs *Versions) Prefix() string { return vs.prefix }
|
||||
|
||||
type attribute struct {
|
||||
accessor
|
||||
name string
|
||||
version Version
|
||||
}
|
||||
|
||||
func (a *attribute) PrefixedName() string { return a.version.Prefix() + a.name }
|
||||
func (a *attribute) Name() string { return a.name }
|
||||
func (a *attribute) Version() Version { return a.version }
|
||||
|
||||
type version struct {
|
||||
prefix string
|
||||
context event.EventContext
|
||||
convert func(event.EventContextConverter) event.EventContext
|
||||
attrMap map[string]Attribute
|
||||
attrs []Attribute
|
||||
}
|
||||
|
||||
func (v *version) Attribute(name string) Attribute { return v.attrMap[strings.ToLower(name)] }
|
||||
func (v *version) Attributes() []Attribute { return v.attrs }
|
||||
func (v *version) String() string { return v.context.GetSpecVersion() }
|
||||
func (v *version) Prefix() string { return v.prefix }
|
||||
func (v *version) NewContext() event.EventContext { return v.context.Clone() }
|
||||
|
||||
// HasPrefix is a case-insensitive prefix check.
|
||||
func (v *version) HasPrefix(name string) bool {
|
||||
return strings.HasPrefix(strings.ToLower(name), v.prefix)
|
||||
}
|
||||
|
||||
func (v *version) Convert(c event.EventContextConverter) event.EventContext { return v.convert(c) }
|
||||
|
||||
func (v *version) SetAttribute(c event.EventContextWriter, name string, value interface{}) error {
|
||||
if a := v.Attribute(name); a != nil { // Standard attribute
|
||||
return a.Set(c, value)
|
||||
}
|
||||
name = strings.ToLower(name)
|
||||
var err error
|
||||
if v.HasPrefix(name) { // Extension attribute
|
||||
return c.SetExtension(strings.TrimPrefix(name, v.prefix), value)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (v *version) AttributeFromKind(kind Kind) Attribute {
|
||||
for _, a := range v.Attributes() {
|
||||
if a.Kind() == kind {
|
||||
return a
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newVersion(
|
||||
prefix string,
|
||||
context event.EventContext,
|
||||
convert func(event.EventContextConverter) event.EventContext,
|
||||
attrs ...*attribute,
|
||||
) *version {
|
||||
v := &version{
|
||||
prefix: strings.ToLower(prefix),
|
||||
context: context,
|
||||
convert: convert,
|
||||
attrMap: map[string]Attribute{},
|
||||
attrs: make([]Attribute, len(attrs)),
|
||||
}
|
||||
for i, a := range attrs {
|
||||
a.version = v
|
||||
v.attrs[i] = a
|
||||
v.attrMap[strings.ToLower(a.PrefixedName())] = a
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// WithPrefix returns a set of versions with prefix added to all attribute names.
|
||||
func WithPrefix(prefix string) *Versions {
|
||||
attr := func(name string, kind Kind) *attribute {
|
||||
return &attribute{accessor: acc[kind], name: name}
|
||||
}
|
||||
vs := &Versions{
|
||||
m: map[string]Version{},
|
||||
prefix: prefix,
|
||||
all: []Version{
|
||||
newVersion(prefix, event.EventContextV1{}.AsV1(),
|
||||
func(c event.EventContextConverter) event.EventContext { return c.AsV1() },
|
||||
attr("id", ID),
|
||||
attr("source", Source),
|
||||
attr("specversion", SpecVersion),
|
||||
attr("type", Type),
|
||||
attr("datacontenttype", DataContentType),
|
||||
attr("dataschema", DataSchema),
|
||||
attr("subject", Subject),
|
||||
attr("time", Time),
|
||||
),
|
||||
newVersion(prefix, event.EventContextV03{}.AsV03(),
|
||||
func(c event.EventContextConverter) event.EventContext { return c.AsV03() },
|
||||
attr("specversion", SpecVersion),
|
||||
attr("type", Type),
|
||||
attr("source", Source),
|
||||
attr("schemaurl", DataSchema),
|
||||
attr("subject", Subject),
|
||||
attr("id", ID),
|
||||
attr("time", Time),
|
||||
attr("datacontenttype", DataContentType),
|
||||
),
|
||||
},
|
||||
}
|
||||
for _, v := range vs.all {
|
||||
vs.m[v.String()] = v
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
// New returns a set of versions
|
||||
func New() *Versions { return WithPrefix("") }
|
||||
|
||||
// Built-in un-prefixed versions.
|
||||
var (
|
||||
VS *Versions
|
||||
V03 Version
|
||||
V1 Version
|
||||
)
|
||||
|
||||
func init() {
|
||||
VS = New()
|
||||
V03 = VS.Version("0.3")
|
||||
V1 = VS.Version("1.0")
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package binding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding/format"
|
||||
)
|
||||
|
||||
// StructuredWriter is used to visit a structured Message and generate a new representation.
|
||||
//
|
||||
// Protocols that supports structured encoding should implement this interface to implement direct
|
||||
// structured to structured encoding and event to structured encoding.
|
||||
type StructuredWriter interface {
|
||||
// Event receives an io.Reader for the whole event.
|
||||
SetStructuredEvent(ctx context.Context, format format.Format, event io.Reader) error
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
package binding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding/format"
|
||||
"github.com/cloudevents/sdk-go/v2/binding/spec"
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
"github.com/cloudevents/sdk-go/v2/types"
|
||||
)
|
||||
|
||||
// ErrCannotConvertToEvent is a generic error when a conversion of a Message to an Event fails
|
||||
var ErrCannotConvertToEvent = errors.New("cannot convert message to event")
|
||||
|
||||
// ToEvent translates a Message with a valid Structured or Binary representation to an Event.
|
||||
// This function returns the Event generated from the Message and the original encoding of the message or
|
||||
// an error that points the conversion error.
|
||||
// transformers can be nil and this function guarantees that they are invoked only once during the encoding process.
|
||||
func ToEvent(ctx context.Context, message MessageReader, transformers ...Transformer) (*event.Event, error) {
|
||||
if message == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
messageEncoding := message.ReadEncoding()
|
||||
if messageEncoding == EncodingEvent {
|
||||
m := message
|
||||
for m != nil {
|
||||
switch mt := m.(type) {
|
||||
case *EventMessage:
|
||||
e := (*event.Event)(mt)
|
||||
return e, Transformers(transformers).Transform(mt, (*messageToEventBuilder)(e))
|
||||
case MessageWrapper:
|
||||
m = mt.GetWrappedMessage()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil, ErrCannotConvertToEvent
|
||||
}
|
||||
|
||||
e := event.New()
|
||||
encoder := (*messageToEventBuilder)(&e)
|
||||
_, err := DirectWrite(
|
||||
context.Background(),
|
||||
message,
|
||||
encoder,
|
||||
encoder,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, Transformers(transformers).Transform((*EventMessage)(&e), encoder)
|
||||
}
|
||||
|
||||
type messageToEventBuilder event.Event
|
||||
|
||||
var _ StructuredWriter = (*messageToEventBuilder)(nil)
|
||||
var _ BinaryWriter = (*messageToEventBuilder)(nil)
|
||||
|
||||
func (b *messageToEventBuilder) SetStructuredEvent(ctx context.Context, format format.Format, ev io.Reader) error {
|
||||
var buf bytes.Buffer
|
||||
_, err := io.Copy(&buf, ev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return format.Unmarshal(buf.Bytes(), (*event.Event)(b))
|
||||
}
|
||||
|
||||
func (b *messageToEventBuilder) Start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *messageToEventBuilder) End(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *messageToEventBuilder) SetData(data io.Reader) error {
|
||||
var buf bytes.Buffer
|
||||
w, err := io.Copy(&buf, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if w != 0 {
|
||||
b.DataEncoded = buf.Bytes()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *messageToEventBuilder) SetAttribute(attribute spec.Attribute, value interface{}) error {
|
||||
if value == nil {
|
||||
_ = attribute.Delete(b.Context)
|
||||
return nil
|
||||
}
|
||||
// If spec version we need to change to right context struct
|
||||
if attribute.Kind() == spec.SpecVersion {
|
||||
str, err := types.ToString(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch str {
|
||||
case event.CloudEventsVersionV03:
|
||||
b.Context = b.Context.AsV03()
|
||||
case event.CloudEventsVersionV1:
|
||||
b.Context = b.Context.AsV1()
|
||||
default:
|
||||
return fmt.Errorf("unrecognized event version %s", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return attribute.Set(b.Context, value)
|
||||
}
|
||||
|
||||
func (b *messageToEventBuilder) SetExtension(name string, value interface{}) error {
|
||||
if value == nil {
|
||||
return b.Context.SetExtension(name, nil)
|
||||
}
|
||||
value, err := types.Validate(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Context.SetExtension(name, value)
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package binding
|
||||
|
||||
// Transformer is an interface that implements a transformation
|
||||
// process while transferring the event from the Message
|
||||
// implementation to the provided encoder
|
||||
//
|
||||
// When a write function (binding.Write, binding.ToEvent, buffering.CopyMessage, etc.)
|
||||
// takes Transformer(s) as parameter, it eventually converts the message to a form
|
||||
// which correctly implements MessageMetadataReader, in order to guarantee that transformation
|
||||
// is applied
|
||||
type Transformer interface {
|
||||
Transform(MessageMetadataReader, MessageMetadataWriter) error
|
||||
}
|
||||
|
||||
// TransformerFunc is a type alias to implement a Transformer through a function pointer
|
||||
type TransformerFunc func(MessageMetadataReader, MessageMetadataWriter) error
|
||||
|
||||
func (t TransformerFunc) Transform(r MessageMetadataReader, w MessageMetadataWriter) error {
|
||||
return t(r, w)
|
||||
}
|
||||
|
||||
var _ Transformer = (TransformerFunc)(nil)
|
||||
|
||||
// Transformers is a utility alias to run several Transformer
|
||||
type Transformers []Transformer
|
||||
|
||||
func (t Transformers) Transform(r MessageMetadataReader, w MessageMetadataWriter) error {
|
||||
for _, transformer := range t {
|
||||
err := transformer.Transform(r, w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ Transformer = (Transformers)(nil)
|
|
@ -1,174 +0,0 @@
|
|||
package binding
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
)
|
||||
|
||||
type eventEncodingKey int
|
||||
|
||||
const (
|
||||
skipDirectStructuredEncoding eventEncodingKey = iota
|
||||
skipDirectBinaryEncoding
|
||||
preferredEventEncoding
|
||||
)
|
||||
|
||||
// DirectWrite invokes the encoders. structuredWriter and binaryWriter could be nil if the protocol doesn't support it.
|
||||
// transformers can be nil and this function guarantees that they are invoked only once during the encoding process.
|
||||
// This function MUST be invoked only if message.ReadEncoding() == EncodingBinary or message.ReadEncoding() == EncodingStructured
|
||||
//
|
||||
// Returns:
|
||||
// * EncodingStructured, nil if message is correctly encoded in structured encoding
|
||||
// * EncodingBinary, nil if message is correctly encoded in binary encoding
|
||||
// * EncodingStructured, err if message was structured but error happened during the encoding
|
||||
// * EncodingBinary, err if message was binary but error happened during the encoding
|
||||
// * EncodingUnknown, ErrUnknownEncoding if message is not a structured or a binary Message
|
||||
func DirectWrite(
|
||||
ctx context.Context,
|
||||
message MessageReader,
|
||||
structuredWriter StructuredWriter,
|
||||
binaryWriter BinaryWriter,
|
||||
transformers ...Transformer,
|
||||
) (Encoding, error) {
|
||||
if structuredWriter != nil && len(transformers) == 0 && !GetOrDefaultFromCtx(ctx, skipDirectStructuredEncoding, false).(bool) {
|
||||
if err := message.ReadStructured(ctx, structuredWriter); err == nil {
|
||||
return EncodingStructured, nil
|
||||
} else if err != ErrNotStructured {
|
||||
return EncodingStructured, err
|
||||
}
|
||||
}
|
||||
|
||||
if binaryWriter != nil && !GetOrDefaultFromCtx(ctx, skipDirectBinaryEncoding, false).(bool) && message.ReadEncoding() == EncodingBinary {
|
||||
return EncodingBinary, writeBinaryWithTransformer(ctx, message, binaryWriter, transformers)
|
||||
}
|
||||
|
||||
return EncodingUnknown, ErrUnknownEncoding
|
||||
}
|
||||
|
||||
// Write executes the full algorithm to encode a Message using transformers:
|
||||
// 1. It first tries direct encoding using DirectWrite
|
||||
// 2. If no direct encoding is possible, it uses ToEvent to generate an Event representation
|
||||
// 3. From the Event, the message is encoded back to the provided structured or binary encoders
|
||||
// You can tweak the encoding process using the context decorators WithForceStructured, WithForceStructured, etc.
|
||||
// transformers can be nil and this function guarantees that they are invoked only once during the encoding process.
|
||||
// Returns:
|
||||
// * EncodingStructured, nil if message is correctly encoded in structured encoding
|
||||
// * EncodingBinary, nil if message is correctly encoded in binary encoding
|
||||
// * EncodingUnknown, ErrUnknownEncoding if message.ReadEncoding() == EncodingUnknown
|
||||
// * _, err if error happened during the encoding
|
||||
func Write(
|
||||
ctx context.Context,
|
||||
message MessageReader,
|
||||
structuredWriter StructuredWriter,
|
||||
binaryWriter BinaryWriter,
|
||||
transformers ...Transformer,
|
||||
) (Encoding, error) {
|
||||
enc := message.ReadEncoding()
|
||||
var err error
|
||||
// Skip direct encoding if the event is an event message
|
||||
if enc != EncodingEvent {
|
||||
enc, err = DirectWrite(ctx, message, structuredWriter, binaryWriter, transformers...)
|
||||
if enc != EncodingUnknown {
|
||||
// Message directly encoded, nothing else to do here
|
||||
return enc, err
|
||||
}
|
||||
}
|
||||
|
||||
var e *event.Event
|
||||
e, err = ToEvent(ctx, message, transformers...)
|
||||
if err != nil {
|
||||
return enc, err
|
||||
}
|
||||
|
||||
message = (*EventMessage)(e)
|
||||
|
||||
if GetOrDefaultFromCtx(ctx, preferredEventEncoding, EncodingBinary).(Encoding) == EncodingStructured {
|
||||
if structuredWriter != nil {
|
||||
return EncodingStructured, message.ReadStructured(ctx, structuredWriter)
|
||||
}
|
||||
if binaryWriter != nil {
|
||||
return EncodingBinary, writeBinary(ctx, message, binaryWriter)
|
||||
}
|
||||
} else {
|
||||
if binaryWriter != nil {
|
||||
return EncodingBinary, writeBinary(ctx, message, binaryWriter)
|
||||
}
|
||||
if structuredWriter != nil {
|
||||
return EncodingStructured, message.ReadStructured(ctx, structuredWriter)
|
||||
}
|
||||
}
|
||||
|
||||
return EncodingUnknown, ErrUnknownEncoding
|
||||
}
|
||||
|
||||
// WithSkipDirectStructuredEncoding skips direct structured to structured encoding during the encoding process
|
||||
func WithSkipDirectStructuredEncoding(ctx context.Context, skip bool) context.Context {
|
||||
return context.WithValue(ctx, skipDirectStructuredEncoding, skip)
|
||||
}
|
||||
|
||||
// WithSkipDirectBinaryEncoding skips direct binary to binary encoding during the encoding process
|
||||
func WithSkipDirectBinaryEncoding(ctx context.Context, skip bool) context.Context {
|
||||
return context.WithValue(ctx, skipDirectBinaryEncoding, skip)
|
||||
}
|
||||
|
||||
// WithPreferredEventEncoding defines the preferred encoding from event to message during the encoding process
|
||||
func WithPreferredEventEncoding(ctx context.Context, enc Encoding) context.Context {
|
||||
return context.WithValue(ctx, preferredEventEncoding, enc)
|
||||
}
|
||||
|
||||
// WithForceStructured forces structured encoding during the encoding process
|
||||
func WithForceStructured(ctx context.Context) context.Context {
|
||||
return context.WithValue(context.WithValue(ctx, preferredEventEncoding, EncodingStructured), skipDirectBinaryEncoding, true)
|
||||
}
|
||||
|
||||
// WithForceBinary forces binary encoding during the encoding process
|
||||
func WithForceBinary(ctx context.Context) context.Context {
|
||||
return context.WithValue(context.WithValue(ctx, preferredEventEncoding, EncodingBinary), skipDirectStructuredEncoding, true)
|
||||
}
|
||||
|
||||
// GetOrDefaultFromCtx gets a configuration value from the provided context
|
||||
func GetOrDefaultFromCtx(ctx context.Context, key interface{}, def interface{}) interface{} {
|
||||
if val := ctx.Value(key); val != nil {
|
||||
return val
|
||||
} else {
|
||||
return def
|
||||
}
|
||||
}
|
||||
|
||||
func writeBinaryWithTransformer(
|
||||
ctx context.Context,
|
||||
message MessageReader,
|
||||
binaryWriter BinaryWriter,
|
||||
transformers Transformers,
|
||||
) error {
|
||||
err := binaryWriter.Start(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = message.ReadBinary(ctx, binaryWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = transformers.Transform(message.(MessageMetadataReader), binaryWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return binaryWriter.End(ctx)
|
||||
}
|
||||
|
||||
func writeBinary(
|
||||
ctx context.Context,
|
||||
message MessageReader,
|
||||
binaryWriter BinaryWriter,
|
||||
) error {
|
||||
err := binaryWriter.Start(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = message.ReadBinary(ctx, binaryWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return binaryWriter.End(ctx)
|
||||
}
|
|
@ -1,227 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding"
|
||||
cecontext "github.com/cloudevents/sdk-go/v2/context"
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
"github.com/cloudevents/sdk-go/v2/protocol"
|
||||
)
|
||||
|
||||
// Client interface defines the runtime contract the CloudEvents client supports.
|
||||
type Client interface {
|
||||
// Send will transmit the given event over the client's configured transport.
|
||||
Send(ctx context.Context, event event.Event) protocol.Result
|
||||
|
||||
// Request will transmit the given event over the client's configured
|
||||
// transport and return any response event.
|
||||
Request(ctx context.Context, event event.Event) (*event.Event, protocol.Result)
|
||||
|
||||
// StartReceiver will register the provided function for callback on receipt
|
||||
// of a cloudevent. It will also start the underlying protocol as it has
|
||||
// been configured.
|
||||
// This call is blocking.
|
||||
// Valid fn signatures are:
|
||||
// * func()
|
||||
// * func() error
|
||||
// * func(context.Context)
|
||||
// * func(context.Context) protocol.Result
|
||||
// * func(event.Event)
|
||||
// * func(event.Event) protocol.Result
|
||||
// * func(context.Context, event.Event)
|
||||
// * func(context.Context, event.Event) protocol.Result
|
||||
// * func(event.Event) *event.Event
|
||||
// * func(event.Event) (*event.Event, protocol.Result)
|
||||
// * func(context.Context, event.Event) *event.Event
|
||||
// * func(context.Context, event.Event) (*event.Event, protocol.Result)
|
||||
StartReceiver(ctx context.Context, fn interface{}) error
|
||||
}
|
||||
|
||||
// New produces a new client with the provided transport object and applied
|
||||
// client options.
|
||||
func New(obj interface{}, opts ...Option) (Client, error) {
|
||||
c := &ceClient{}
|
||||
|
||||
if p, ok := obj.(protocol.Sender); ok {
|
||||
c.sender = p
|
||||
}
|
||||
if p, ok := obj.(protocol.Requester); ok {
|
||||
c.requester = p
|
||||
}
|
||||
if p, ok := obj.(protocol.Responder); ok {
|
||||
c.responder = p
|
||||
}
|
||||
if p, ok := obj.(protocol.Receiver); ok {
|
||||
c.receiver = p
|
||||
}
|
||||
if p, ok := obj.(protocol.Opener); ok {
|
||||
c.opener = p
|
||||
}
|
||||
|
||||
if err := c.applyOptions(opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type ceClient struct {
|
||||
sender protocol.Sender
|
||||
requester protocol.Requester
|
||||
receiver protocol.Receiver
|
||||
responder protocol.Responder
|
||||
// Optional.
|
||||
opener protocol.Opener
|
||||
|
||||
outboundContextDecorators []func(context.Context) context.Context
|
||||
invoker Invoker
|
||||
receiverMu sync.Mutex
|
||||
eventDefaulterFns []EventDefaulter
|
||||
}
|
||||
|
||||
func (c *ceClient) applyOptions(opts ...Option) error {
|
||||
for _, fn := range opts {
|
||||
if err := fn(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ceClient) Send(ctx context.Context, e event.Event) protocol.Result {
|
||||
if c.sender == nil {
|
||||
return errors.New("sender not set")
|
||||
}
|
||||
|
||||
for _, f := range c.outboundContextDecorators {
|
||||
ctx = f(ctx)
|
||||
}
|
||||
|
||||
if len(c.eventDefaulterFns) > 0 {
|
||||
for _, fn := range c.eventDefaulterFns {
|
||||
e = fn(ctx, e)
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.sender.Send(ctx, (*binding.EventMessage)(&e))
|
||||
}
|
||||
|
||||
func (c *ceClient) Request(ctx context.Context, e event.Event) (*event.Event, protocol.Result) {
|
||||
if c.requester == nil {
|
||||
return nil, errors.New("requester not set")
|
||||
}
|
||||
for _, f := range c.outboundContextDecorators {
|
||||
ctx = f(ctx)
|
||||
}
|
||||
|
||||
if len(c.eventDefaulterFns) > 0 {
|
||||
for _, fn := range c.eventDefaulterFns {
|
||||
e = fn(ctx, e)
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If provided a requester, use it to do request/response.
|
||||
var resp *event.Event
|
||||
msg, err := c.requester.Request(ctx, (*binding.EventMessage)(&e))
|
||||
if msg != nil {
|
||||
defer func() {
|
||||
if err := msg.Finish(err); err != nil {
|
||||
cecontext.LoggerFrom(ctx).Warnw("failed calling message.Finish", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// try to turn msg into an event, it might not work and that is ok.
|
||||
if rs, rserr := binding.ToEvent(ctx, msg); rserr != nil {
|
||||
cecontext.LoggerFrom(ctx).Debugw("response: failed calling ToEvent", zap.Error(rserr), zap.Any("resp", msg))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%w; failed to convert response into event: %s", err, rserr)
|
||||
} else {
|
||||
// If the protocol returns no error, it is an ACK on the request, but we had
|
||||
// issues turning the response into an event, so make an ACK Result and pass
|
||||
// down the ToEvent error as well.
|
||||
err = fmt.Errorf("%w; failed to convert response into event: %s", protocol.ResultACK, rserr)
|
||||
}
|
||||
} else {
|
||||
resp = rs
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// StartReceiver sets up the given fn to handle Receive.
|
||||
// See Client.StartReceiver for details. This is a blocking call.
|
||||
func (c *ceClient) StartReceiver(ctx context.Context, fn interface{}) error {
|
||||
c.receiverMu.Lock()
|
||||
defer c.receiverMu.Unlock()
|
||||
|
||||
if c.invoker != nil {
|
||||
return fmt.Errorf("client already has a receiver")
|
||||
}
|
||||
|
||||
invoker, err := newReceiveInvoker(fn, c.eventDefaulterFns...) // TODO: this will have to pick between a observed invoker or not.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if invoker.IsReceiver() && c.receiver == nil {
|
||||
return fmt.Errorf("mismatched receiver callback without protocol.Receiver supported by protocol")
|
||||
}
|
||||
if invoker.IsResponder() && c.responder == nil {
|
||||
return fmt.Errorf("mismatched receiver callback without protocol.Responder supported by protocol")
|
||||
}
|
||||
c.invoker = invoker
|
||||
|
||||
defer func() {
|
||||
c.invoker = nil
|
||||
}()
|
||||
|
||||
// Start the opener, if set.
|
||||
if c.opener != nil {
|
||||
go func() {
|
||||
// TODO: handle error correctly here.
|
||||
if err := c.opener.OpenInbound(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var msg binding.Message
|
||||
var respFn protocol.ResponseFn
|
||||
// Start Polling.
|
||||
for {
|
||||
if c.responder != nil {
|
||||
msg, respFn, err = c.responder.Respond(ctx)
|
||||
} else if c.receiver != nil {
|
||||
msg, err = c.receiver.Receive(ctx)
|
||||
} else {
|
||||
return errors.New("responder nor receiver set")
|
||||
}
|
||||
|
||||
if err == io.EOF { // Normal close
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
cecontext.LoggerFrom(ctx).Warn("Error while receiving a message: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if err := c.invoker.Invoke(ctx, msg, respFn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"github.com/cloudevents/sdk-go/v2/protocol/http"
|
||||
)
|
||||
|
||||
// NewDefault provides the good defaults for the common case using an HTTP
|
||||
// Protocol client. The http transport has had WithBinaryEncoding http
|
||||
// transport option applied to it. The client will always send Binary
|
||||
// encoding but will inspect the outbound event context and match the version.
|
||||
// The WithTimeNow, and WithUUIDs client options are also applied to the
|
||||
// client, all outbound events will have a time and id set if not already
|
||||
// present.
|
||||
func NewDefault() (Client, error) {
|
||||
p, err := http.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := NewObserved(p, WithTimeNow(), WithUUIDs())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
"github.com/cloudevents/sdk-go/v2/extensions"
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
"github.com/cloudevents/sdk-go/v2/protocol"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// NewObserved produces a new client with the provided transport object and applied
|
||||
// client options.
|
||||
func NewObserved(protocol interface{}, opts ...Option) (Client, error) {
|
||||
client, err := New(protocol, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &obsClient{client: client}
|
||||
|
||||
if err := c.applyOptions(opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type obsClient struct {
|
||||
client Client
|
||||
|
||||
addTracing bool
|
||||
}
|
||||
|
||||
func (c *obsClient) applyOptions(opts ...Option) error {
|
||||
for _, fn := range opts {
|
||||
if err := fn(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send transmits the provided event on a preconfigured Protocol. Send returns
|
||||
// an error if there was an an issue validating the outbound event or the
|
||||
// transport returns an error.
|
||||
func (c *obsClient) Send(ctx context.Context, e event.Event) protocol.Result {
|
||||
ctx, r := observability.NewReporter(ctx, reportSend)
|
||||
ctx, span := trace.StartSpan(ctx, observability.ClientSpanName, trace.WithSpanKind(trace.SpanKindClient))
|
||||
defer span.End()
|
||||
if span.IsRecordingEvents() {
|
||||
span.AddAttributes(EventTraceAttributes(&e)...)
|
||||
}
|
||||
|
||||
if c.addTracing {
|
||||
e.Context = e.Context.Clone()
|
||||
extensions.FromSpanContext(span.SpanContext()).AddTracingAttributes(&e)
|
||||
}
|
||||
|
||||
result := c.client.Send(ctx, e)
|
||||
|
||||
if protocol.IsACK(result) {
|
||||
r.OK()
|
||||
} else {
|
||||
r.Error()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *obsClient) Request(ctx context.Context, e event.Event) (*event.Event, protocol.Result) {
|
||||
ctx, r := observability.NewReporter(ctx, reportRequest)
|
||||
ctx, span := trace.StartSpan(ctx, observability.ClientSpanName, trace.WithSpanKind(trace.SpanKindClient))
|
||||
defer span.End()
|
||||
if span.IsRecordingEvents() {
|
||||
span.AddAttributes(EventTraceAttributes(&e)...)
|
||||
}
|
||||
|
||||
resp, result := c.client.Request(ctx, e)
|
||||
|
||||
if protocol.IsACK(result) {
|
||||
r.OK()
|
||||
} else {
|
||||
r.Error()
|
||||
}
|
||||
|
||||
return resp, result
|
||||
}
|
||||
|
||||
// StartReceiver sets up the given fn to handle Receive.
|
||||
// See Client.StartReceiver for details. This is a blocking call.
|
||||
func (c *obsClient) StartReceiver(ctx context.Context, fn interface{}) error {
|
||||
ctx, r := observability.NewReporter(ctx, reportStartReceiver)
|
||||
|
||||
err := c.client.StartReceiver(ctx, fn)
|
||||
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// EventDefaulter is the function signature for extensions that are able
|
||||
// to perform event defaulting.
|
||||
type EventDefaulter func(ctx context.Context, event event.Event) event.Event
|
||||
|
||||
// DefaultIDToUUIDIfNotSet will inspect the provided event and assign a UUID to
|
||||
// context.ID if it is found to be empty.
|
||||
func DefaultIDToUUIDIfNotSet(ctx context.Context, event event.Event) event.Event {
|
||||
if event.Context != nil {
|
||||
if event.ID() == "" {
|
||||
event.Context = event.Context.Clone()
|
||||
event.SetID(uuid.New().String())
|
||||
}
|
||||
}
|
||||
return event
|
||||
}
|
||||
|
||||
// DefaultTimeToNowIfNotSet will inspect the provided event and assign a new
|
||||
// Timestamp to context.Time if it is found to be nil or zero.
|
||||
func DefaultTimeToNowIfNotSet(ctx context.Context, event event.Event) event.Event {
|
||||
if event.Context != nil {
|
||||
if event.Time().IsZero() {
|
||||
event.Context = event.Context.Clone()
|
||||
event.SetTime(time.Now())
|
||||
}
|
||||
}
|
||||
return event
|
||||
}
|
||||
|
||||
// NewDefaultDataContentTypeIfNotSet returns a defaulter that will inspect the
|
||||
// provided event and set the provided content type if content type is found
|
||||
// to be empty.
|
||||
func NewDefaultDataContentTypeIfNotSet(contentType string) EventDefaulter {
|
||||
return func(ctx context.Context, event event.Event) event.Event {
|
||||
if event.Context != nil {
|
||||
if event.DataContentType() == "" {
|
||||
event.SetDataContentType(contentType)
|
||||
}
|
||||
}
|
||||
return event
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
/*
|
||||
Package client holds the recommended entry points for interacting with the CloudEvents Golang SDK. The client wraps
|
||||
a selected transport. The client adds validation and defaulting for sending events, and flexible receiver method
|
||||
registration. For full details, read the `client.Client` documentation.
|
||||
*/
|
||||
package client
|
|
@ -1,39 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
thttp "github.com/cloudevents/sdk-go/v2/protocol/http"
|
||||
)
|
||||
|
||||
func NewHTTPReceiveHandler(ctx context.Context, p *thttp.Protocol, fn interface{}) (*EventReceiver, error) {
|
||||
invoker, err := newReceiveInvoker(fn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &EventReceiver{
|
||||
p: p,
|
||||
invoker: invoker,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type EventReceiver struct {
|
||||
p *thttp.Protocol
|
||||
invoker Invoker
|
||||
}
|
||||
|
||||
func (r *EventReceiver) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
go func() {
|
||||
r.p.ServeHTTP(rw, req)
|
||||
}()
|
||||
|
||||
ctx := context.Background()
|
||||
msg, respFn, err := r.p.Respond(ctx)
|
||||
if err != nil {
|
||||
//lint:ignore SA9003 TODO: Branch left empty
|
||||
} else if err := r.invoker.Invoke(ctx, msg, respFn); err != nil {
|
||||
// TODO
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding"
|
||||
cecontext "github.com/cloudevents/sdk-go/v2/context"
|
||||
"github.com/cloudevents/sdk-go/v2/protocol"
|
||||
)
|
||||
|
||||
type Invoker interface {
|
||||
Invoke(context.Context, binding.Message, protocol.ResponseFn) error
|
||||
IsReceiver() bool
|
||||
IsResponder() bool
|
||||
}
|
||||
|
||||
var _ Invoker = (*receiveInvoker)(nil)
|
||||
|
||||
func newReceiveInvoker(fn interface{}, fns ...EventDefaulter) (Invoker, error) {
|
||||
r := &receiveInvoker{
|
||||
eventDefaulterFns: fns,
|
||||
}
|
||||
|
||||
if fn, err := receiver(fn); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
r.fn = fn
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type receiveInvoker struct {
|
||||
fn *receiverFn
|
||||
eventDefaulterFns []EventDefaulter
|
||||
}
|
||||
|
||||
func (r *receiveInvoker) Invoke(ctx context.Context, m binding.Message, respFn protocol.ResponseFn) (err error) {
|
||||
var isFinished bool
|
||||
defer func() {
|
||||
if !isFinished {
|
||||
if err2 := m.Finish(err); err2 == nil {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
e, err := binding.ToEvent(ctx, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if e != nil && r.fn != nil {
|
||||
resp, result := r.fn.invoke(ctx, *e)
|
||||
|
||||
// Apply the defaulter chain to the outgoing event.
|
||||
if resp != nil && len(r.eventDefaulterFns) > 0 {
|
||||
for _, fn := range r.eventDefaulterFns {
|
||||
*resp = fn(ctx, *resp)
|
||||
}
|
||||
// Validate the event conforms to the CloudEvents Spec.
|
||||
if verr := resp.Validate(); verr != nil {
|
||||
cecontext.LoggerFrom(ctx).Error(fmt.Errorf("cloudevent validation failed on response event: %v, %w", verr, err))
|
||||
}
|
||||
}
|
||||
|
||||
// protocol can manual ack by the result
|
||||
if respFn == nil {
|
||||
if !protocol.IsACK(result) {
|
||||
err = m.Finish(result)
|
||||
isFinished = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var rm binding.Message
|
||||
if resp != nil {
|
||||
rm = (*binding.EventMessage)(resp)
|
||||
}
|
||||
|
||||
return respFn(ctx, rm, result)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *receiveInvoker) IsReceiver() bool {
|
||||
return !r.fn.hasEventOut
|
||||
}
|
||||
|
||||
func (r *receiveInvoker) IsResponder() bool {
|
||||
return r.fn.hasEventOut
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
"github.com/cloudevents/sdk-go/v2/extensions"
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
// LatencyMs measures the latency in milliseconds for the CloudEvents
|
||||
// client methods.
|
||||
LatencyMs = stats.Float64("cloudevents.io/sdk-go/client/latency", "The latency in milliseconds for the CloudEvents client methods.", "ms")
|
||||
)
|
||||
|
||||
var (
|
||||
// LatencyView is an OpenCensus view that shows client method latency.
|
||||
LatencyView = &view.View{
|
||||
Name: "client/latency",
|
||||
Measure: LatencyMs,
|
||||
Description: "The distribution of latency inside of client for CloudEvents.",
|
||||
Aggregation: view.Distribution(0, .01, .1, 1, 10, 100, 1000, 10000),
|
||||
TagKeys: observability.LatencyTags(),
|
||||
}
|
||||
)
|
||||
|
||||
type observed int32
|
||||
|
||||
// Adheres to Observable
|
||||
var _ observability.Observable = observed(0)
|
||||
|
||||
const (
|
||||
specversionAttr = "cloudevents.specversion"
|
||||
idAttr = "cloudevents.id"
|
||||
typeAttr = "cloudevents.type"
|
||||
sourceAttr = "cloudevents.source"
|
||||
subjectAttr = "cloudevents.subject"
|
||||
datacontenttypeAttr = "cloudevents.datacontenttype"
|
||||
|
||||
reportSend observed = iota
|
||||
reportRequest
|
||||
reportStartReceiver
|
||||
)
|
||||
|
||||
// MethodName implements Observable.MethodName
|
||||
func (o observed) MethodName() string {
|
||||
switch o {
|
||||
case reportSend:
|
||||
return "send"
|
||||
case reportRequest:
|
||||
return "request"
|
||||
case reportStartReceiver:
|
||||
return "start_receiver"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// LatencyMs implements Observable.LatencyMs
|
||||
func (o observed) LatencyMs() *stats.Float64Measure {
|
||||
return LatencyMs
|
||||
}
|
||||
|
||||
func EventTraceAttributes(e event.EventReader) []trace.Attribute {
|
||||
as := []trace.Attribute{
|
||||
trace.StringAttribute(specversionAttr, e.SpecVersion()),
|
||||
trace.StringAttribute(idAttr, e.ID()),
|
||||
trace.StringAttribute(typeAttr, e.Type()),
|
||||
trace.StringAttribute(sourceAttr, e.Source()),
|
||||
}
|
||||
if sub := e.Subject(); sub != "" {
|
||||
as = append(as, trace.StringAttribute(subjectAttr, sub))
|
||||
}
|
||||
if dct := e.DataContentType(); dct != "" {
|
||||
as = append(as, trace.StringAttribute(datacontenttypeAttr, dct))
|
||||
}
|
||||
return as
|
||||
}
|
||||
|
||||
// TraceSpan returns context and trace.Span based on event. Caller must call span.End()
|
||||
func TraceSpan(ctx context.Context, e event.Event) (context.Context, *trace.Span) {
|
||||
var span *trace.Span
|
||||
if ext, ok := extensions.GetDistributedTracingExtension(e); ok {
|
||||
ctx, span = ext.StartChildSpan(ctx, observability.ClientSpanName, trace.WithSpanKind(trace.SpanKindServer))
|
||||
}
|
||||
if span == nil {
|
||||
ctx, span = trace.StartSpan(ctx, observability.ClientSpanName, trace.WithSpanKind(trace.SpanKindServer))
|
||||
}
|
||||
if span.IsRecordingEvents() {
|
||||
span.AddAttributes(EventTraceAttributes(&e)...)
|
||||
}
|
||||
return ctx, span
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding"
|
||||
)
|
||||
|
||||
// Option is the function signature required to be considered an client.Option.
|
||||
type Option func(interface{}) error
|
||||
|
||||
// WithEventDefaulter adds an event defaulter to the end of the defaulter chain.
|
||||
func WithEventDefaulter(fn EventDefaulter) Option {
|
||||
return func(i interface{}) error {
|
||||
if c, ok := i.(*ceClient); ok {
|
||||
if fn == nil {
|
||||
return fmt.Errorf("client option was given an nil event defaulter")
|
||||
}
|
||||
c.eventDefaulterFns = append(c.eventDefaulterFns, fn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithForceBinary() Option {
|
||||
return func(i interface{}) error {
|
||||
if c, ok := i.(*ceClient); ok {
|
||||
c.outboundContextDecorators = append(c.outboundContextDecorators, binding.WithForceBinary)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithForceStructured() Option {
|
||||
return func(i interface{}) error {
|
||||
if c, ok := i.(*ceClient); ok {
|
||||
c.outboundContextDecorators = append(c.outboundContextDecorators, binding.WithForceStructured)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithUUIDs adds DefaultIDToUUIDIfNotSet event defaulter to the end of the
|
||||
// defaulter chain.
|
||||
func WithUUIDs() Option {
|
||||
return func(i interface{}) error {
|
||||
if c, ok := i.(*ceClient); ok {
|
||||
c.eventDefaulterFns = append(c.eventDefaulterFns, DefaultIDToUUIDIfNotSet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimeNow adds DefaultTimeToNowIfNotSet event defaulter to the end of the
|
||||
// defaulter chain.
|
||||
func WithTimeNow() Option {
|
||||
return func(i interface{}) error {
|
||||
if c, ok := i.(*ceClient); ok {
|
||||
c.eventDefaulterFns = append(c.eventDefaulterFns, DefaultTimeToNowIfNotSet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTracePropagation enables trace propagation via the distributed tracing
|
||||
// extension.
|
||||
func WithTracePropagation() Option {
|
||||
return func(i interface{}) error {
|
||||
if c, ok := i.(*obsClient); ok {
|
||||
c.addTracing = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -1,189 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
"github.com/cloudevents/sdk-go/v2/protocol"
|
||||
)
|
||||
|
||||
// ReceiveFull is the signature of a fn to be invoked for incoming cloudevents.
|
||||
type ReceiveFull func(context.Context, event.Event) protocol.Result
|
||||
|
||||
type receiverFn struct {
|
||||
numIn int
|
||||
numOut int
|
||||
fnValue reflect.Value
|
||||
|
||||
hasContextIn bool
|
||||
hasEventIn bool
|
||||
|
||||
hasEventOut bool
|
||||
hasResultOut bool
|
||||
}
|
||||
|
||||
const (
|
||||
inParamUsage = "expected a function taking either no parameters, one or more of (context.Context, event.Event) ordered"
|
||||
outParamUsage = "expected a function returning one or mode of (*event.Event, protocol.Result) ordered"
|
||||
)
|
||||
|
||||
var (
|
||||
contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
|
||||
eventType = reflect.TypeOf((*event.Event)(nil)).Elem()
|
||||
eventPtrType = reflect.TypeOf((*event.Event)(nil)) // want the ptr type
|
||||
resultType = reflect.TypeOf((*protocol.Result)(nil)).Elem()
|
||||
)
|
||||
|
||||
// receiver creates a receiverFn wrapper class that is used by the client to
|
||||
// validate and invoke the provided function.
|
||||
// Valid fn signatures are:
|
||||
// * func()
|
||||
// * func() error
|
||||
// * func(context.Context)
|
||||
// * func(context.Context) transport.Result
|
||||
// * func(event.Event)
|
||||
// * func(event.Event) transport.Result
|
||||
// * func(context.Context, event.Event)
|
||||
// * func(context.Context, event.Event) transport.Result
|
||||
// * func(event.Event) *event.Event
|
||||
// * func(event.Event) (*event.Event, transport.Result)
|
||||
// * func(context.Context, event.Event, *event.Event
|
||||
// * func(context.Context, event.Event) (*event.Event, transport.Result)
|
||||
//
|
||||
func receiver(fn interface{}) (*receiverFn, error) {
|
||||
fnType := reflect.TypeOf(fn)
|
||||
if fnType.Kind() != reflect.Func {
|
||||
return nil, errors.New("must pass a function to handle events")
|
||||
}
|
||||
|
||||
r := &receiverFn{
|
||||
fnValue: reflect.ValueOf(fn),
|
||||
numIn: fnType.NumIn(),
|
||||
numOut: fnType.NumOut(),
|
||||
}
|
||||
|
||||
if err := r.validate(fnType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *receiverFn) invoke(ctx context.Context, e event.Event) (*event.Event, protocol.Result) {
|
||||
args := make([]reflect.Value, 0, r.numIn)
|
||||
|
||||
if r.numIn > 0 {
|
||||
if r.hasContextIn {
|
||||
args = append(args, reflect.ValueOf(ctx))
|
||||
}
|
||||
if r.hasEventIn {
|
||||
args = append(args, reflect.ValueOf(e))
|
||||
}
|
||||
}
|
||||
v := r.fnValue.Call(args)
|
||||
var respOut protocol.Result
|
||||
var eOut *event.Event
|
||||
if r.numOut > 0 {
|
||||
i := 0
|
||||
if r.hasEventOut {
|
||||
if eo, ok := v[i].Interface().(*event.Event); ok {
|
||||
eOut = eo
|
||||
}
|
||||
i++ // <-- note, need to inc i.
|
||||
}
|
||||
if r.hasResultOut {
|
||||
if resp, ok := v[i].Interface().(protocol.Result); ok {
|
||||
respOut = resp
|
||||
}
|
||||
}
|
||||
}
|
||||
return eOut, respOut
|
||||
}
|
||||
|
||||
// Verifies that the inputs to a function have a valid signature
|
||||
// Valid input is to be [0, all] of
|
||||
// context.Context, event.Event in this order.
|
||||
func (r *receiverFn) validateInParamSignature(fnType reflect.Type) error {
|
||||
r.hasContextIn = false
|
||||
r.hasEventIn = false
|
||||
|
||||
switch fnType.NumIn() {
|
||||
case 2:
|
||||
// has to be (context.Context, event.Event)
|
||||
if !fnType.In(1).ConvertibleTo(eventType) {
|
||||
return fmt.Errorf("%s; cannot convert parameter 2 from %s to event.Event", inParamUsage, fnType.In(1))
|
||||
} else {
|
||||
r.hasEventIn = true
|
||||
}
|
||||
fallthrough
|
||||
case 1:
|
||||
if !fnType.In(0).ConvertibleTo(contextType) {
|
||||
if !fnType.In(0).ConvertibleTo(eventType) {
|
||||
return fmt.Errorf("%s; cannot convert parameter 1 from %s to context.Context or event.Event", inParamUsage, fnType.In(0))
|
||||
} else if r.hasEventIn {
|
||||
return fmt.Errorf("%s; duplicate parameter of type event.Event", inParamUsage)
|
||||
} else {
|
||||
r.hasEventIn = true
|
||||
}
|
||||
} else {
|
||||
r.hasContextIn = true
|
||||
}
|
||||
fallthrough
|
||||
case 0:
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("%s; function has too many parameters (%d)", inParamUsage, fnType.NumIn())
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the outputs of a function have a valid signature
|
||||
// Valid output signatures to be [0, all] of
|
||||
// *event.Event, transport.Result in this order
|
||||
func (r *receiverFn) validateOutParamSignature(fnType reflect.Type) error {
|
||||
r.hasEventOut = false
|
||||
r.hasResultOut = false
|
||||
|
||||
switch fnType.NumOut() {
|
||||
case 2:
|
||||
// has to be (*event.Event, transport.Result)
|
||||
if !fnType.Out(1).ConvertibleTo(resultType) {
|
||||
return fmt.Errorf("%s; cannot convert parameter 2 from %s to event.Response", outParamUsage, fnType.Out(1))
|
||||
} else {
|
||||
r.hasResultOut = true
|
||||
}
|
||||
fallthrough
|
||||
case 1:
|
||||
if !fnType.Out(0).ConvertibleTo(resultType) {
|
||||
if !fnType.Out(0).ConvertibleTo(eventPtrType) {
|
||||
return fmt.Errorf("%s; cannot convert parameter 1 from %s to *event.Event or transport.Result", outParamUsage, fnType.Out(0))
|
||||
} else {
|
||||
r.hasEventOut = true
|
||||
}
|
||||
} else if r.hasResultOut {
|
||||
return fmt.Errorf("%s; duplicate parameter of type event.Response", outParamUsage)
|
||||
} else {
|
||||
r.hasResultOut = true
|
||||
}
|
||||
fallthrough
|
||||
case 0:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("%s; function has too many return types (%d)", outParamUsage, fnType.NumOut())
|
||||
}
|
||||
}
|
||||
|
||||
// validateReceiverFn validates that a function has the right number of in and
|
||||
// out params and that they are of allowed types.
|
||||
func (r *receiverFn) validate(fnType reflect.Type) error {
|
||||
if err := r.validateInParamSignature(fnType); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.validateOutParamSignature(fnType); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Opaque key type used to store target
|
||||
type targetKeyType struct{}
|
||||
|
||||
var targetKey = targetKeyType{}
|
||||
|
||||
// WithTarget returns back a new context with the given target. Target is intended to be transport dependent.
|
||||
// For http transport, `target` should be a full URL and will be injected into the outbound http request.
|
||||
func WithTarget(ctx context.Context, target string) context.Context {
|
||||
return context.WithValue(ctx, targetKey, target)
|
||||
}
|
||||
|
||||
// TargetFrom looks in the given context and returns `target` as a parsed url if found and valid, otherwise nil.
|
||||
func TargetFrom(ctx context.Context) *url.URL {
|
||||
c := ctx.Value(targetKey)
|
||||
if c != nil {
|
||||
if s, ok := c.(string); ok && s != "" {
|
||||
if target, err := url.Parse(s); err == nil {
|
||||
return target
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Opaque key type used to store topic
|
||||
type topicKeyType struct{}
|
||||
|
||||
var topicKey = topicKeyType{}
|
||||
|
||||
// WithTopic returns back a new context with the given topic. Topic is intended to be transport dependent.
|
||||
// For pubsub transport, `topic` should be a Pub/Sub Topic ID.
|
||||
func WithTopic(ctx context.Context, topic string) context.Context {
|
||||
return context.WithValue(ctx, topicKey, topic)
|
||||
}
|
||||
|
||||
// TopicFrom looks in the given context and returns `topic` as a string if found and valid, otherwise "".
|
||||
func TopicFrom(ctx context.Context) string {
|
||||
c := ctx.Value(topicKey)
|
||||
if c != nil {
|
||||
if s, ok := c.(string); ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Opaque key type used to store retry parameters
|
||||
type retriesKeyType struct{}
|
||||
|
||||
var retriesKey = retriesKeyType{}
|
||||
|
||||
// WithRetriesConstantBackoff returns back a new context with retries parameters using constant backoff strategy.
|
||||
// MaxTries is the maximum number for retries and delay is the time interval between retries
|
||||
func WithRetriesConstantBackoff(ctx context.Context, delay time.Duration, maxTries int) context.Context {
|
||||
return WithRetryParams(ctx, &RetryParams{
|
||||
Strategy: BackoffStrategyConstant,
|
||||
Period: delay,
|
||||
MaxTries: maxTries,
|
||||
})
|
||||
}
|
||||
|
||||
// WithRetriesLinearBackoff returns back a new context with retries parameters using linear backoff strategy.
|
||||
// MaxTries is the maximum number for retries and delay*tries is the time interval between retries
|
||||
func WithRetriesLinearBackoff(ctx context.Context, delay time.Duration, maxTries int) context.Context {
|
||||
return WithRetryParams(ctx, &RetryParams{
|
||||
Strategy: BackoffStrategyLinear,
|
||||
Period: delay,
|
||||
MaxTries: maxTries,
|
||||
})
|
||||
}
|
||||
|
||||
// WithRetriesExponentialBackoff returns back a new context with retries parameters using exponential backoff strategy.
|
||||
// MaxTries is the maximum number for retries and period is the amount of time to wait, used as `period * 2^retries`.
|
||||
func WithRetriesExponentialBackoff(ctx context.Context, period time.Duration, maxTries int) context.Context {
|
||||
return WithRetryParams(ctx, &RetryParams{
|
||||
Strategy: BackoffStrategyExponential,
|
||||
Period: period,
|
||||
MaxTries: maxTries,
|
||||
})
|
||||
}
|
||||
|
||||
// WithRetryParams returns back a new context with retries parameters.
|
||||
func WithRetryParams(ctx context.Context, rp *RetryParams) context.Context {
|
||||
return context.WithValue(ctx, retriesKey, rp)
|
||||
}
|
||||
|
||||
// RetriesFrom looks in the given context and returns the retries parameters if found.
|
||||
// Otherwise returns the default retries configuration (ie. no retries).
|
||||
func RetriesFrom(ctx context.Context) *RetryParams {
|
||||
c := ctx.Value(retriesKey)
|
||||
if c != nil {
|
||||
if s, ok := c.(*RetryParams); ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return &DefaultRetryParams
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
/*
|
||||
Package context holds the last resort overrides and fyi objects that can be passed to clients and transports added to
|
||||
context.Context objects.
|
||||
*/
|
||||
package context
|
|
@ -1,43 +0,0 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Opaque key type used to store logger
|
||||
type loggerKeyType struct{}
|
||||
|
||||
var loggerKey = loggerKeyType{}
|
||||
|
||||
// fallbackLogger is the logger is used when there is no logger attached to the context.
|
||||
var fallbackLogger *zap.SugaredLogger
|
||||
|
||||
func init() {
|
||||
if logger, err := zap.NewProduction(); err != nil {
|
||||
// We failed to create a fallback logger.
|
||||
fallbackLogger = zap.NewNop().Sugar()
|
||||
} else {
|
||||
fallbackLogger = logger.Named("fallback").Sugar()
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger returns a new context with the logger injected into the given context.
|
||||
func WithLogger(ctx context.Context, logger *zap.SugaredLogger) context.Context {
|
||||
if logger == nil {
|
||||
return context.WithValue(ctx, loggerKey, fallbackLogger)
|
||||
}
|
||||
return context.WithValue(ctx, loggerKey, logger)
|
||||
}
|
||||
|
||||
// LoggerFrom returns the logger stored in context.
|
||||
func LoggerFrom(ctx context.Context) *zap.SugaredLogger {
|
||||
l := ctx.Value(loggerKey)
|
||||
if l != nil {
|
||||
if logger, ok := l.(*zap.SugaredLogger); ok {
|
||||
return logger
|
||||
}
|
||||
}
|
||||
return fallbackLogger
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BackoffStrategy string
|
||||
|
||||
const (
|
||||
BackoffStrategyNone = "none"
|
||||
BackoffStrategyConstant = "constant"
|
||||
BackoffStrategyLinear = "linear"
|
||||
BackoffStrategyExponential = "exponential"
|
||||
)
|
||||
|
||||
var DefaultRetryParams = RetryParams{Strategy: BackoffStrategyNone}
|
||||
|
||||
// RetryParams holds parameters applied to retries
|
||||
type RetryParams struct {
|
||||
// Strategy is the backoff strategy to applies between retries
|
||||
Strategy BackoffStrategy
|
||||
|
||||
// MaxTries is the maximum number of times to retry request before giving up
|
||||
MaxTries int
|
||||
|
||||
// Period is
|
||||
// - for none strategy: no delay
|
||||
// - for constant strategy: the delay interval between retries
|
||||
// - for linear strategy: interval between retries = Period * retries
|
||||
// - for exponential strategy: interval between retries = Period * retries^2
|
||||
Period time.Duration
|
||||
}
|
||||
|
||||
// BackoffFor tries will return the time duration that should be used for this
|
||||
// current try count.
|
||||
// `tries` is assumed to be the number of times the caller has already retried.
|
||||
func (r *RetryParams) BackoffFor(tries int) time.Duration {
|
||||
switch r.Strategy {
|
||||
case BackoffStrategyConstant:
|
||||
return r.Period
|
||||
case BackoffStrategyLinear:
|
||||
return r.Period * time.Duration(tries)
|
||||
case BackoffStrategyExponential:
|
||||
exp := math.Exp2(float64(tries))
|
||||
return r.Period * time.Duration(exp)
|
||||
case BackoffStrategyNone:
|
||||
fallthrough // default
|
||||
default:
|
||||
return r.Period
|
||||
}
|
||||
}
|
||||
|
||||
// Backoff is a blocking call to wait for the correct amount of time for the retry.
|
||||
// `tries` is assumed to be the number of times the caller has already retried.
|
||||
func (r *RetryParams) Backoff(ctx context.Context, tries int) error {
|
||||
if tries > r.MaxTries {
|
||||
return errors.New("too many retries")
|
||||
}
|
||||
ticker := time.NewTicker(r.BackoffFor(tries))
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
ticker.Stop()
|
||||
return errors.New("context has been cancelled")
|
||||
case <-ticker.C:
|
||||
ticker.Stop()
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package event
|
||||
|
||||
const (
|
||||
TextPlain = "text/plain"
|
||||
TextJSON = "text/json"
|
||||
ApplicationJSON = "application/json"
|
||||
ApplicationXML = "application/xml"
|
||||
ApplicationCloudEventsJSON = "application/cloudevents+json"
|
||||
ApplicationCloudEventsBatchJSON = "application/cloudevents-batch+json"
|
||||
)
|
||||
|
||||
// StringOfApplicationJSON returns a string pointer to "application/json"
|
||||
func StringOfApplicationJSON() *string {
|
||||
a := ApplicationJSON
|
||||
return &a
|
||||
}
|
||||
|
||||
// StringOfApplicationXML returns a string pointer to "application/xml"
|
||||
func StringOfApplicationXML() *string {
|
||||
a := ApplicationXML
|
||||
return &a
|
||||
}
|
||||
|
||||
// StringOfTextPlain returns a string pointer to "text/plain"
|
||||
func StringOfTextPlain() *string {
|
||||
a := TextPlain
|
||||
return &a
|
||||
}
|
||||
|
||||
// StringOfApplicationCloudEventsJSON returns a string pointer to
|
||||
// "application/cloudevents+json"
|
||||
func StringOfApplicationCloudEventsJSON() *string {
|
||||
a := ApplicationCloudEventsJSON
|
||||
return &a
|
||||
}
|
||||
|
||||
// StringOfApplicationCloudEventsBatchJSON returns a string pointer to
|
||||
// "application/cloudevents-batch+json"
|
||||
func StringOfApplicationCloudEventsBatchJSON() *string {
|
||||
a := ApplicationCloudEventsBatchJSON
|
||||
return &a
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package event
|
||||
|
||||
const (
|
||||
Base64 = "base64"
|
||||
)
|
||||
|
||||
// StringOfBase64 returns a string pointer to "Base64"
|
||||
func StringOfBase64() *string {
|
||||
a := Base64
|
||||
return &a
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
package datacodec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/event/datacodec/json"
|
||||
"github.com/cloudevents/sdk-go/v2/event/datacodec/text"
|
||||
"github.com/cloudevents/sdk-go/v2/event/datacodec/xml"
|
||||
)
|
||||
|
||||
// Decoder is the expected function signature for decoding `in` to `out`.
|
||||
// If Event sent the payload as base64, Decoder assumes that `in` is the
|
||||
// decoded base64 byte array.
|
||||
type Decoder func(ctx context.Context, in []byte, out interface{}) error
|
||||
|
||||
// Encoder is the expected function signature for encoding `in` to bytes.
|
||||
// Returns an error if the encoder has an issue encoding `in`.
|
||||
type Encoder func(ctx context.Context, in interface{}) ([]byte, error)
|
||||
|
||||
var decoder map[string]Decoder
|
||||
var encoder map[string]Encoder
|
||||
|
||||
func init() {
|
||||
decoder = make(map[string]Decoder, 10)
|
||||
encoder = make(map[string]Encoder, 10)
|
||||
|
||||
AddDecoder("", json.Decode)
|
||||
AddDecoder("application/json", json.Decode)
|
||||
AddDecoder("text/json", json.Decode)
|
||||
AddDecoder("application/xml", xml.Decode)
|
||||
AddDecoder("text/xml", xml.Decode)
|
||||
AddDecoder("text/plain", text.Decode)
|
||||
|
||||
AddEncoder("", json.Encode)
|
||||
AddEncoder("application/json", json.Encode)
|
||||
AddEncoder("text/json", json.Encode)
|
||||
AddEncoder("application/xml", xml.Encode)
|
||||
AddEncoder("text/xml", xml.Encode)
|
||||
AddEncoder("text/plain", text.Encode)
|
||||
}
|
||||
|
||||
// AddDecoder registers a decoder for a given content type. The codecs will use
|
||||
// these to decode the data payload from a cloudevent.Event object.
|
||||
func AddDecoder(contentType string, fn Decoder) {
|
||||
decoder[contentType] = fn
|
||||
}
|
||||
|
||||
// AddEncoder registers an encoder for a given content type. The codecs will
|
||||
// use these to encode the data payload for a cloudevent.Event object.
|
||||
func AddEncoder(contentType string, fn Encoder) {
|
||||
encoder[contentType] = fn
|
||||
}
|
||||
|
||||
// Decode looks up and invokes the decoder registered for the given content
|
||||
// type. An error is returned if no decoder is registered for the given
|
||||
// content type.
|
||||
func Decode(ctx context.Context, contentType string, in []byte, out interface{}) error {
|
||||
if fn, ok := decoder[contentType]; ok {
|
||||
return fn(ctx, in, out)
|
||||
}
|
||||
return fmt.Errorf("[decode] unsupported content type: %q", contentType)
|
||||
}
|
||||
|
||||
// Encode looks up and invokes the encoder registered for the given content
|
||||
// type. An error is returned if no encoder is registered for the given
|
||||
// content type.
|
||||
func Encode(ctx context.Context, contentType string, in interface{}) ([]byte, error) {
|
||||
if fn, ok := encoder[contentType]; ok {
|
||||
return fn(ctx, in)
|
||||
}
|
||||
return nil, fmt.Errorf("[encode] unsupported content type: %q", contentType)
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package datacodec
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/event/datacodec/json"
|
||||
"github.com/cloudevents/sdk-go/v2/event/datacodec/text"
|
||||
"github.com/cloudevents/sdk-go/v2/event/datacodec/xml"
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
)
|
||||
|
||||
func SetObservedCodecs() {
|
||||
AddDecoder("", json.DecodeObserved)
|
||||
AddDecoder("application/json", json.DecodeObserved)
|
||||
AddDecoder("text/json", json.DecodeObserved)
|
||||
AddDecoder("application/xml", xml.DecodeObserved)
|
||||
AddDecoder("text/xml", xml.DecodeObserved)
|
||||
AddDecoder("text/plain", text.DecodeObserved)
|
||||
|
||||
AddEncoder("", json.Encode)
|
||||
AddEncoder("application/json", json.EncodeObserved)
|
||||
AddEncoder("text/json", json.EncodeObserved)
|
||||
AddEncoder("application/xml", xml.EncodeObserved)
|
||||
AddEncoder("text/xml", xml.EncodeObserved)
|
||||
AddEncoder("text/plain", text.EncodeObserved)
|
||||
}
|
||||
|
||||
// DecodeObserved calls Decode and records the result.
|
||||
func DecodeObserved(ctx context.Context, contentType string, in []byte, out interface{}) error {
|
||||
_, r := observability.NewReporter(ctx, reportDecode)
|
||||
err := Decode(ctx, contentType, in, out)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// EncodeObserved calls Encode and records the result.
|
||||
func EncodeObserved(ctx context.Context, contentType string, in interface{}) ([]byte, error) {
|
||||
_, r := observability.NewReporter(ctx, reportEncode)
|
||||
b, err := Encode(ctx, contentType, in)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return b, err
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
/*
|
||||
Package datacodec holds the data codec registry and adds known encoders and decoders supporting media types such as
|
||||
`application/json` and `application/xml`.
|
||||
*/
|
||||
package datacodec
|
|
@ -1,51 +0,0 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Decode takes `in` as []byte.
|
||||
// If Event sent the payload as base64, Decoder assumes that `in` is the
|
||||
// decoded base64 byte array.
|
||||
func Decode(ctx context.Context, in []byte, out interface{}) error {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
if out == nil {
|
||||
return fmt.Errorf("out is nil")
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(in, out); err != nil {
|
||||
return fmt.Errorf("[json] found bytes \"%s\", but failed to unmarshal: %s", string(in), err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode attempts to json.Marshal `in` into bytes. Encode will inspect `in`
|
||||
// and returns `in` unmodified if it is detected that `in` is already a []byte;
|
||||
// Or json.Marshal errors.
|
||||
func Encode(ctx context.Context, in interface{}) ([]byte, error) {
|
||||
if in == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
it := reflect.TypeOf(in)
|
||||
switch it.Kind() {
|
||||
case reflect.Slice:
|
||||
if it.Elem().Kind() == reflect.Uint8 {
|
||||
|
||||
if b, ok := in.([]byte); ok && len(b) > 0 {
|
||||
// check to see if it is a pre-encoded byte string.
|
||||
if b[0] == byte('"') || b[0] == byte('{') || b[0] == byte('[') {
|
||||
return b, nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return json.Marshal(in)
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
)
|
||||
|
||||
// DecodeObserved calls Decode and records the results.
|
||||
func DecodeObserved(ctx context.Context, in []byte, out interface{}) error {
|
||||
_, r := observability.NewReporter(ctx, reportDecode)
|
||||
err := Decode(ctx, in, out)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// EncodeObserved calls Encode and records the results.
|
||||
func EncodeObserved(ctx context.Context, in interface{}) ([]byte, error) {
|
||||
_, r := observability.NewReporter(ctx, reportEncode)
|
||||
b, err := Encode(ctx, in)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return b, err
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
/*
|
||||
Package json holds the encoder/decoder implementation for `application/json`.
|
||||
*/
|
||||
package json
|
|
@ -1,51 +0,0 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
)
|
||||
|
||||
var (
|
||||
// LatencyMs measures the latency in milliseconds for the CloudEvents json
|
||||
// data codec methods.
|
||||
LatencyMs = stats.Float64("cloudevents.io/sdk-go/datacodec/json/latency", "The latency in milliseconds for the CloudEvents json data codec methods.", "ms")
|
||||
)
|
||||
|
||||
var (
|
||||
// LatencyView is an OpenCensus view that shows data codec json method latency.
|
||||
LatencyView = &view.View{
|
||||
Name: "datacodec/json/latency",
|
||||
Measure: LatencyMs,
|
||||
Description: "The distribution of latency inside of the json data codec for CloudEvents.",
|
||||
Aggregation: view.Distribution(0, .01, .1, 1, 10, 100, 1000, 10000),
|
||||
TagKeys: observability.LatencyTags(),
|
||||
}
|
||||
)
|
||||
|
||||
type observed int32
|
||||
|
||||
// Adheres to Observable
|
||||
var _ observability.Observable = observed(0)
|
||||
|
||||
const (
|
||||
reportEncode observed = iota
|
||||
reportDecode
|
||||
)
|
||||
|
||||
// MethodName implements Observable.MethodName
|
||||
func (o observed) MethodName() string {
|
||||
switch o {
|
||||
case reportEncode:
|
||||
return "encode"
|
||||
case reportDecode:
|
||||
return "decode"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// LatencyMs implements Observable.LatencyMs
|
||||
func (o observed) LatencyMs() *stats.Float64Measure {
|
||||
return LatencyMs
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package datacodec
|
||||
|
||||
import (
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
)
|
||||
|
||||
var (
|
||||
// LatencyMs measures the latency in milliseconds for the CloudEvents generic
|
||||
// codec data methods.
|
||||
LatencyMs = stats.Float64("cloudevents.io/sdk-go/datacodec/latency", "The latency in milliseconds for the CloudEvents generic data codec methods.", "ms")
|
||||
)
|
||||
|
||||
var (
|
||||
// LatencyView is an OpenCensus view that shows data codec method latency.
|
||||
LatencyView = &view.View{
|
||||
Name: "datacodec/latency",
|
||||
Measure: LatencyMs,
|
||||
Description: "The distribution of latency inside of the generic data codec for CloudEvents.",
|
||||
Aggregation: view.Distribution(0, .01, .1, 1, 10, 100, 1000, 10000),
|
||||
TagKeys: observability.LatencyTags(),
|
||||
}
|
||||
)
|
||||
|
||||
type observed int32
|
||||
|
||||
// Adheres to Observable
|
||||
var _ observability.Observable = observed(0)
|
||||
|
||||
const (
|
||||
reportEncode observed = iota
|
||||
reportDecode
|
||||
)
|
||||
|
||||
// MethodName implements Observable.MethodName
|
||||
func (o observed) MethodName() string {
|
||||
switch o {
|
||||
case reportEncode:
|
||||
return "encode"
|
||||
case reportDecode:
|
||||
return "decode"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// LatencyMs implements Observable.LatencyMs
|
||||
func (o observed) LatencyMs() *stats.Float64Measure {
|
||||
return LatencyMs
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Text codec converts []byte or string to string and vice-versa.
|
||||
|
||||
func Decode(_ context.Context, in []byte, out interface{}) error {
|
||||
p, _ := out.(*string)
|
||||
if p == nil {
|
||||
return fmt.Errorf("text.Decode out: want *string, got %T", out)
|
||||
}
|
||||
*p = string(in)
|
||||
return nil
|
||||
}
|
||||
|
||||
func Encode(_ context.Context, in interface{}) ([]byte, error) {
|
||||
s, ok := in.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("text.Encode in: want string, got %T", in)
|
||||
}
|
||||
return []byte(s), nil
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
)
|
||||
|
||||
// DecodeObserved calls Decode and records the results.
|
||||
func DecodeObserved(ctx context.Context, in []byte, out interface{}) error {
|
||||
_, r := observability.NewReporter(ctx, reportDecode)
|
||||
err := Decode(ctx, in, out)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// EncodeObserved calls Encode and records the results.
|
||||
func EncodeObserved(ctx context.Context, in interface{}) ([]byte, error) {
|
||||
_, r := observability.NewReporter(ctx, reportEncode)
|
||||
b, err := Encode(ctx, in)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return b, err
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
/*
|
||||
Package text holds the encoder/decoder implementation for `text/plain`.
|
||||
*/
|
||||
package text
|
|
@ -1,51 +0,0 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
)
|
||||
|
||||
var (
|
||||
// LatencyMs measures the latency in milliseconds for the CloudEvents xml data
|
||||
// codec methods.
|
||||
LatencyMs = stats.Float64("cloudevents.io/sdk-go/datacodec/text/latency", "The latency in milliseconds for the CloudEvents text data codec methods.", "ms")
|
||||
)
|
||||
|
||||
var (
|
||||
// LatencyView is an OpenCensus view that shows data codec xml method latency.
|
||||
LatencyView = &view.View{
|
||||
Name: "datacodec/text/latency",
|
||||
Measure: LatencyMs,
|
||||
Description: "The distribution of latency inside of the text data codec for CloudEvents.",
|
||||
Aggregation: view.Distribution(0, .01, .1, 1, 10, 100, 1000, 10000),
|
||||
TagKeys: observability.LatencyTags(),
|
||||
}
|
||||
)
|
||||
|
||||
type observed int32
|
||||
|
||||
// Adheres to Observable
|
||||
var _ observability.Observable = observed(0)
|
||||
|
||||
const (
|
||||
reportEncode observed = iota
|
||||
reportDecode
|
||||
)
|
||||
|
||||
// MethodName implements Observable.MethodName
|
||||
func (o observed) MethodName() string {
|
||||
switch o {
|
||||
case reportEncode:
|
||||
return "encode"
|
||||
case reportDecode:
|
||||
return "decode"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// LatencyMs implements Observable.LatencyMs
|
||||
func (o observed) LatencyMs() *stats.Float64Measure {
|
||||
return LatencyMs
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
package xml
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Decode takes `in` as []byte.
|
||||
// If Event sent the payload as base64, Decoder assumes that `in` is the
|
||||
// decoded base64 byte array.
|
||||
func Decode(ctx context.Context, in []byte, out interface{}) error {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := xml.Unmarshal(in, out); err != nil {
|
||||
return fmt.Errorf("[xml] found bytes, but failed to unmarshal: %s %s", err.Error(), string(in))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode attempts to xml.Marshal `in` into bytes. Encode will inspect `in`
|
||||
// and returns `in` unmodified if it is detected that `in` is already a []byte;
|
||||
// Or xml.Marshal errors.
|
||||
func Encode(ctx context.Context, in interface{}) ([]byte, error) {
|
||||
if b, ok := in.([]byte); ok {
|
||||
// check to see if it is a pre-encoded byte string.
|
||||
if len(b) > 0 && b[0] == byte('"') {
|
||||
return b, nil
|
||||
}
|
||||
}
|
||||
|
||||
return xml.Marshal(in)
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package xml
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
)
|
||||
|
||||
// DecodeObserved calls Decode and records the result.
|
||||
func DecodeObserved(ctx context.Context, in []byte, out interface{}) error {
|
||||
_, r := observability.NewReporter(ctx, reportDecode)
|
||||
err := Decode(ctx, in, out)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// EncodeObserved calls Encode and records the result.
|
||||
func EncodeObserved(ctx context.Context, in interface{}) ([]byte, error) {
|
||||
_, r := observability.NewReporter(ctx, reportEncode)
|
||||
b, err := Encode(ctx, in)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return b, err
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
/*
|
||||
Package xml holds the encoder/decoder implementation for `application/xml`.
|
||||
*/
|
||||
package xml
|
|
@ -1,51 +0,0 @@
|
|||
package xml
|
||||
|
||||
import (
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
)
|
||||
|
||||
var (
|
||||
// LatencyMs measures the latency in milliseconds for the CloudEvents xml data
|
||||
// codec methods.
|
||||
LatencyMs = stats.Float64("cloudevents.io/sdk-go/datacodec/xml/latency", "The latency in milliseconds for the CloudEvents xml data codec methods.", "ms")
|
||||
)
|
||||
|
||||
var (
|
||||
// LatencyView is an OpenCensus view that shows data codec xml method latency.
|
||||
LatencyView = &view.View{
|
||||
Name: "datacodec/xml/latency",
|
||||
Measure: LatencyMs,
|
||||
Description: "The distribution of latency inside of the xml data codec for CloudEvents.",
|
||||
Aggregation: view.Distribution(0, .01, .1, 1, 10, 100, 1000, 10000),
|
||||
TagKeys: observability.LatencyTags(),
|
||||
}
|
||||
)
|
||||
|
||||
type observed int32
|
||||
|
||||
// Adheres to Observable
|
||||
var _ observability.Observable = observed(0)
|
||||
|
||||
const (
|
||||
reportEncode observed = iota
|
||||
reportDecode
|
||||
)
|
||||
|
||||
// MethodName implements Observable.MethodName
|
||||
func (o observed) MethodName() string {
|
||||
switch o {
|
||||
case reportEncode:
|
||||
return "encode"
|
||||
case reportDecode:
|
||||
return "decode"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// LatencyMs implements Observable.LatencyMs
|
||||
func (o observed) LatencyMs() *stats.Float64Measure {
|
||||
return LatencyMs
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
/*
|
||||
Package event provides primitives to work with CloudEvents specification: https://github.com/cloudevents/spec.
|
||||
*/
|
||||
package event
|
|
@ -1,158 +0,0 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Event represents the canonical representation of a CloudEvent.
|
||||
type Event struct {
|
||||
Context EventContext
|
||||
DataEncoded []byte
|
||||
// DataBase64 indicates if the event, when serialized, represents
|
||||
// the data field using the base64 encoding.
|
||||
// In v0.3, this field is superseded by DataContentEncoding
|
||||
DataBase64 bool
|
||||
FieldErrors map[string]error
|
||||
}
|
||||
|
||||
const (
|
||||
defaultEventVersion = CloudEventsVersionV1
|
||||
)
|
||||
|
||||
func (e *Event) fieldError(field string, err error) {
|
||||
if e.FieldErrors == nil {
|
||||
e.FieldErrors = make(map[string]error)
|
||||
}
|
||||
e.FieldErrors[field] = err
|
||||
}
|
||||
|
||||
func (e *Event) fieldOK(field string) {
|
||||
if e.FieldErrors != nil {
|
||||
delete(e.FieldErrors, field)
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new Event, an optional version can be passed to change the
|
||||
// default spec version from 1.0 to the provided version.
|
||||
func New(version ...string) Event {
|
||||
specVersion := defaultEventVersion
|
||||
if len(version) >= 1 {
|
||||
specVersion = version[0]
|
||||
}
|
||||
e := &Event{}
|
||||
e.SetSpecVersion(specVersion)
|
||||
return *e
|
||||
}
|
||||
|
||||
// ExtensionAs is deprecated: access extensions directly via the e.Extensions() map.
|
||||
// Use functions in the types package to convert extension values.
|
||||
// For example replace this:
|
||||
//
|
||||
// var i int
|
||||
// err := e.ExtensionAs("foo", &i)
|
||||
//
|
||||
// With this:
|
||||
//
|
||||
// i, err := types.ToInteger(e.Extensions["foo"])
|
||||
//
|
||||
func (e Event) ExtensionAs(name string, obj interface{}) error {
|
||||
return e.Context.ExtensionAs(name, obj)
|
||||
}
|
||||
|
||||
// Validate performs a spec based validation on this event.
|
||||
// Validation is dependent on the spec version specified in the event context.
|
||||
func (e Event) Validate() error {
|
||||
if e.Context == nil {
|
||||
return fmt.Errorf("every event conforming to the CloudEvents specification MUST include a context")
|
||||
}
|
||||
|
||||
if e.FieldErrors != nil {
|
||||
errs := make([]string, 0)
|
||||
for f, e := range e.FieldErrors {
|
||||
errs = append(errs, fmt.Sprintf("%q: %s,", f, e))
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("previous field errors: [%s]", strings.Join(errs, "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.Context.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a pretty-printed representation of the Event.
|
||||
func (e Event) String() string {
|
||||
b := strings.Builder{}
|
||||
|
||||
b.WriteString("Validation: ")
|
||||
|
||||
valid := e.Validate()
|
||||
if valid == nil {
|
||||
b.WriteString("valid\n")
|
||||
} else {
|
||||
b.WriteString("invalid\n")
|
||||
}
|
||||
if valid != nil {
|
||||
b.WriteString(fmt.Sprintf("Validation Error: \n%s\n", valid.Error()))
|
||||
}
|
||||
|
||||
b.WriteString(e.Context.String())
|
||||
|
||||
if e.DataEncoded != nil {
|
||||
if e.DataBase64 {
|
||||
b.WriteString("Data (binary),\n ")
|
||||
} else {
|
||||
b.WriteString("Data,\n ")
|
||||
}
|
||||
switch e.DataMediaType() {
|
||||
case ApplicationJSON:
|
||||
var prettyJSON bytes.Buffer
|
||||
err := json.Indent(&prettyJSON, e.DataEncoded, " ", " ")
|
||||
if err != nil {
|
||||
b.Write(e.DataEncoded)
|
||||
} else {
|
||||
b.Write(prettyJSON.Bytes())
|
||||
}
|
||||
default:
|
||||
b.Write(e.DataEncoded)
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (e Event) Clone() Event {
|
||||
out := Event{}
|
||||
out.Context = e.Context.Clone()
|
||||
out.DataEncoded = cloneBytes(e.DataEncoded)
|
||||
out.DataBase64 = e.DataBase64
|
||||
out.FieldErrors = e.cloneFieldErrors()
|
||||
return out
|
||||
}
|
||||
|
||||
func cloneBytes(in []byte) []byte {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]byte, len(in))
|
||||
copy(out, in)
|
||||
return out
|
||||
}
|
||||
|
||||
func (e Event) cloneFieldErrors() map[string]error {
|
||||
if e.FieldErrors == nil {
|
||||
return nil
|
||||
}
|
||||
newFE := make(map[string]error, len(e.FieldErrors))
|
||||
for k, v := range e.FieldErrors {
|
||||
newFE[k] = v
|
||||
}
|
||||
return newFE
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/event/datacodec"
|
||||
)
|
||||
|
||||
// SetData encodes the given payload with the given content type.
|
||||
// If the provided payload is a byte array, when marshalled to json it will be encoded as base64.
|
||||
// If the provided payload is different from byte array, datacodec.Encode is invoked to attempt a
|
||||
// marshalling to byte array.
|
||||
func (e *Event) SetData(contentType string, obj interface{}) error {
|
||||
e.SetDataContentType(contentType)
|
||||
|
||||
if e.SpecVersion() != CloudEventsVersionV1 {
|
||||
return e.legacySetData(obj)
|
||||
}
|
||||
|
||||
// Version 1.0 and above.
|
||||
switch obj := obj.(type) {
|
||||
case []byte:
|
||||
e.DataEncoded = obj
|
||||
e.DataBase64 = true
|
||||
default:
|
||||
data, err := datacodec.Encode(context.Background(), e.DataMediaType(), obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.DataEncoded = data
|
||||
e.DataBase64 = false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: Delete when we do not have to support Spec v0.3.
|
||||
func (e *Event) legacySetData(obj interface{}) error {
|
||||
data, err := datacodec.Encode(context.Background(), e.DataMediaType(), obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.DeprecatedDataContentEncoding() == Base64 {
|
||||
buf := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
|
||||
base64.StdEncoding.Encode(buf, data)
|
||||
e.DataEncoded = buf
|
||||
e.DataBase64 = false
|
||||
} else {
|
||||
data, err := datacodec.Encode(context.Background(), e.DataMediaType(), obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.DataEncoded = data
|
||||
e.DataBase64 = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
quotes = `"'`
|
||||
)
|
||||
|
||||
func (e Event) Data() []byte {
|
||||
return e.DataEncoded
|
||||
}
|
||||
|
||||
// DataAs attempts to populate the provided data object with the event payload.
|
||||
// data should be a pointer type.
|
||||
func (e Event) DataAs(obj interface{}) error {
|
||||
data := e.Data()
|
||||
|
||||
if len(data) == 0 {
|
||||
// No data.
|
||||
return nil
|
||||
}
|
||||
|
||||
if e.SpecVersion() != CloudEventsVersionV1 {
|
||||
var err error
|
||||
if data, err = e.legacyConvertData(data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return datacodec.Decode(context.Background(), e.DataMediaType(), data, obj)
|
||||
}
|
||||
|
||||
func (e Event) legacyConvertData(data []byte) ([]byte, error) {
|
||||
if e.Context.DeprecatedGetDataContentEncoding() == Base64 {
|
||||
var bs []byte
|
||||
// test to see if we need to unquote the data.
|
||||
if data[0] == quotes[0] || data[0] == quotes[1] {
|
||||
str, err := strconv.Unquote(string(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bs = []byte(str)
|
||||
} else {
|
||||
bs = data
|
||||
}
|
||||
|
||||
buf := make([]byte, base64.StdEncoding.DecodedLen(len(bs)))
|
||||
n, err := base64.StdEncoding.Decode(buf, bs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode data from base64: %s", err.Error())
|
||||
}
|
||||
data = buf[:n]
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// EventReader is the interface for reading through an event from attributes.
|
||||
type EventReader interface {
|
||||
// SpecVersion returns event.Context.GetSpecVersion().
|
||||
SpecVersion() string
|
||||
// Type returns event.Context.GetType().
|
||||
Type() string
|
||||
// Source returns event.Context.GetSource().
|
||||
Source() string
|
||||
// Subject returns event.Context.GetSubject().
|
||||
Subject() string
|
||||
// ID returns event.Context.GetID().
|
||||
ID() string
|
||||
// Time returns event.Context.GetTime().
|
||||
Time() time.Time
|
||||
// DataSchema returns event.Context.GetDataSchema().
|
||||
DataSchema() string
|
||||
// DataContentType returns event.Context.GetDataContentType().
|
||||
DataContentType() string
|
||||
// DataMediaType returns event.Context.GetDataMediaType().
|
||||
DataMediaType() string
|
||||
// DeprecatedDataContentEncoding returns event.Context.DeprecatedGetDataContentEncoding().
|
||||
DeprecatedDataContentEncoding() string
|
||||
|
||||
// Extension Attributes
|
||||
|
||||
// Extensions returns the event.Context.GetExtensions().
|
||||
// Extensions use the CloudEvents type system, details in package cloudevents/types.
|
||||
Extensions() map[string]interface{}
|
||||
|
||||
// ExtensionAs returns event.Context.ExtensionAs(name, obj).
|
||||
//
|
||||
// DEPRECATED: Access extensions directly via the e.Extensions() map.
|
||||
// Use functions in the types package to convert extension values.
|
||||
// For example replace this:
|
||||
//
|
||||
// var i int
|
||||
// err := e.ExtensionAs("foo", &i)
|
||||
//
|
||||
// With this:
|
||||
//
|
||||
// i, err := types.ToInteger(e.Extensions["foo"])
|
||||
//
|
||||
ExtensionAs(string, interface{}) error
|
||||
|
||||
// Data Attribute
|
||||
|
||||
// Data returns the raw data buffer
|
||||
// If the event was encoded with base64 encoding, Data returns the already decoded
|
||||
// byte array
|
||||
Data() []byte
|
||||
|
||||
// DataAs attempts to populate the provided data object with the event payload.
|
||||
DataAs(interface{}) error
|
||||
}
|
||||
|
||||
// EventWriter is the interface for writing through an event onto attributes.
|
||||
// If an error is thrown by a sub-component, EventWriter caches the error
|
||||
// internally and exposes errors with a call to event.Validate().
|
||||
type EventWriter interface {
|
||||
// Context Attributes
|
||||
|
||||
// SetSpecVersion performs event.Context.SetSpecVersion.
|
||||
SetSpecVersion(string)
|
||||
// SetType performs event.Context.SetType.
|
||||
SetType(string)
|
||||
// SetSource performs event.Context.SetSource.
|
||||
SetSource(string)
|
||||
// SetSubject( performs event.Context.SetSubject.
|
||||
SetSubject(string)
|
||||
// SetID performs event.Context.SetID.
|
||||
SetID(string)
|
||||
// SetTime performs event.Context.SetTime.
|
||||
SetTime(time.Time)
|
||||
// SetDataSchema performs event.Context.SetDataSchema.
|
||||
SetDataSchema(string)
|
||||
// SetDataContentType performs event.Context.SetDataContentType.
|
||||
SetDataContentType(string)
|
||||
// DeprecatedSetDataContentEncoding performs event.Context.DeprecatedSetDataContentEncoding.
|
||||
SetDataContentEncoding(string)
|
||||
|
||||
// Extension Attributes
|
||||
|
||||
// SetExtension performs event.Context.SetExtension.
|
||||
SetExtension(string, interface{})
|
||||
|
||||
// SetData encodes the given payload with the given content type.
|
||||
// If the provided payload is a byte array, when marshalled to json it will be encoded as base64.
|
||||
// If the provided payload is different from byte array, datacodec.Encode is invoked to attempt a
|
||||
// marshalling to byte array.
|
||||
SetData(string, interface{}) error
|
||||
}
|
|
@ -1,327 +0,0 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
errors2 "github.com/pkg/errors"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
)
|
||||
|
||||
// MarshalJSON implements a custom json marshal method used when this type is
|
||||
// marshaled using json.Marshal.
|
||||
func (e Event) MarshalJSON() ([]byte, error) {
|
||||
_, r := observability.NewReporter(context.Background(), eventJSONObserved{o: reportMarshal, v: e.SpecVersion()})
|
||||
|
||||
if err := e.Validate(); err != nil {
|
||||
r.Error()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var b []byte
|
||||
var err error
|
||||
|
||||
switch e.SpecVersion() {
|
||||
case CloudEventsVersionV03:
|
||||
b, err = JsonEncodeLegacy(e)
|
||||
case CloudEventsVersionV1:
|
||||
b, err = JsonEncode(e)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown spec version: %q", e.SpecVersion())
|
||||
}
|
||||
|
||||
// Report the observable
|
||||
if err != nil {
|
||||
r.Error()
|
||||
return nil, err
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json unmarshal method used when this type is
|
||||
// unmarshaled using json.Unmarshal.
|
||||
func (e *Event) UnmarshalJSON(b []byte) error {
|
||||
raw := make(map[string]json.RawMessage)
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
version := versionFromRawMessage(raw)
|
||||
|
||||
_, r := observability.NewReporter(context.Background(), eventJSONObserved{o: reportUnmarshal, v: version})
|
||||
|
||||
var err error
|
||||
switch version {
|
||||
case CloudEventsVersionV03:
|
||||
err = e.JsonDecodeV03(b, raw)
|
||||
case CloudEventsVersionV1:
|
||||
err = e.JsonDecodeV1(b, raw)
|
||||
default:
|
||||
return fmt.Errorf("unknown spec version: %q", version)
|
||||
}
|
||||
|
||||
// Report the observable
|
||||
if err != nil {
|
||||
r.Error()
|
||||
return err
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func versionFromRawMessage(raw map[string]json.RawMessage) string {
|
||||
// v0.2 and after
|
||||
if v, ok := raw["specversion"]; ok {
|
||||
var version string
|
||||
if err := json.Unmarshal(v, &version); err != nil {
|
||||
return ""
|
||||
}
|
||||
return version
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// JsonEncode encodes an event to JSON
|
||||
func JsonEncode(e Event) ([]byte, error) {
|
||||
return jsonEncode(e.Context, e.DataEncoded, e.DataBase64)
|
||||
}
|
||||
|
||||
// JsonEncodeLegacy performs legacy JSON encoding
|
||||
func JsonEncodeLegacy(e Event) ([]byte, error) {
|
||||
isBase64 := e.Context.DeprecatedGetDataContentEncoding() == Base64
|
||||
return jsonEncode(e.Context, e.DataEncoded, isBase64)
|
||||
}
|
||||
|
||||
func jsonEncode(ctx EventContextReader, data []byte, shouldEncodeToBase64 bool) ([]byte, error) {
|
||||
var b map[string]json.RawMessage
|
||||
var err error
|
||||
|
||||
b, err = marshalEvent(ctx, ctx.GetExtensions())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
// data here is a serialized version of whatever payload.
|
||||
// If we need to write the payload as base64, shouldEncodeToBase64 is true.
|
||||
mediaType, err := ctx.GetDataMediaType()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isJson := mediaType == "" || mediaType == ApplicationJSON || mediaType == TextJSON
|
||||
// If isJson and no encoding to base64, we don't need to perform additional steps
|
||||
if isJson && !shouldEncodeToBase64 {
|
||||
b["data"] = data
|
||||
} else {
|
||||
var dataKey = "data"
|
||||
if ctx.GetSpecVersion() == CloudEventsVersionV1 && shouldEncodeToBase64 {
|
||||
dataKey = "data_base64"
|
||||
}
|
||||
var dataPointer []byte
|
||||
if shouldEncodeToBase64 {
|
||||
dataPointer, err = json.Marshal(data)
|
||||
} else {
|
||||
dataPointer, err = json.Marshal(string(data))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b[dataKey] = dataPointer
|
||||
}
|
||||
}
|
||||
|
||||
body, err := json.Marshal(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// JsonDecodeV03 takes in the byte representation of a version 0.3 structured json CloudEvent and returns a
|
||||
// cloudevent.Event or an error if there are parsing errors.
|
||||
func (e *Event) JsonDecodeV03(body []byte, raw map[string]json.RawMessage) error {
|
||||
ec := EventContextV03{}
|
||||
if err := json.Unmarshal(body, &ec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(raw, "specversion")
|
||||
delete(raw, "type")
|
||||
delete(raw, "source")
|
||||
delete(raw, "subject")
|
||||
delete(raw, "id")
|
||||
delete(raw, "time")
|
||||
delete(raw, "schemaurl")
|
||||
delete(raw, "datacontenttype")
|
||||
delete(raw, "datacontentencoding")
|
||||
|
||||
var data []byte
|
||||
if d, ok := raw["data"]; ok {
|
||||
data = d
|
||||
|
||||
// Decode the Base64 if we have a base64 payload
|
||||
if ec.DeprecatedGetDataContentEncoding() == Base64 {
|
||||
var tmp []byte
|
||||
if err := json.Unmarshal(d, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
e.DataBase64 = true
|
||||
e.DataEncoded = tmp
|
||||
} else {
|
||||
if ec.DataContentType != nil {
|
||||
ct := *ec.DataContentType
|
||||
if ct != ApplicationJSON && ct != TextJSON {
|
||||
var dataStr string
|
||||
err := json.Unmarshal(d, &dataStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data = []byte(dataStr)
|
||||
}
|
||||
}
|
||||
e.DataEncoded = data
|
||||
e.DataBase64 = false
|
||||
}
|
||||
}
|
||||
delete(raw, "data")
|
||||
|
||||
if len(raw) > 0 {
|
||||
extensions := make(map[string]interface{}, len(raw))
|
||||
ec.Extensions = extensions
|
||||
for k, v := range raw {
|
||||
k = strings.ToLower(k)
|
||||
var tmp interface{}
|
||||
if err := json.Unmarshal(v, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ec.SetExtension(k, tmp); err != nil {
|
||||
return errors2.Wrap(err, "Cannot set extension with key "+k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
e.Context = &ec
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// JsonDecodeV1 takes in the byte representation of a version 1.0 structured json CloudEvent and returns a
|
||||
// cloudevent.Event or an error if there are parsing errors.
|
||||
func (e *Event) JsonDecodeV1(body []byte, raw map[string]json.RawMessage) error {
|
||||
ec := EventContextV1{}
|
||||
if err := json.Unmarshal(body, &ec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(raw, "specversion")
|
||||
delete(raw, "type")
|
||||
delete(raw, "source")
|
||||
delete(raw, "subject")
|
||||
delete(raw, "id")
|
||||
delete(raw, "time")
|
||||
delete(raw, "dataschema")
|
||||
delete(raw, "datacontenttype")
|
||||
|
||||
var data []byte
|
||||
if d, ok := raw["data"]; ok {
|
||||
data = d
|
||||
if ec.DataContentType != nil {
|
||||
ct := *ec.DataContentType
|
||||
if ct != ApplicationJSON && ct != TextJSON {
|
||||
var dataStr string
|
||||
err := json.Unmarshal(d, &dataStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data = []byte(dataStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
delete(raw, "data")
|
||||
|
||||
var dataBase64 []byte
|
||||
if d, ok := raw["data_base64"]; ok {
|
||||
var tmp []byte
|
||||
if err := json.Unmarshal(d, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
dataBase64 = tmp
|
||||
|
||||
}
|
||||
delete(raw, "data_base64")
|
||||
|
||||
if data != nil && dataBase64 != nil {
|
||||
return errors.New("parsing error: JSON decoder found both 'data', and 'data_base64' in JSON payload")
|
||||
}
|
||||
if data != nil {
|
||||
e.DataEncoded = data
|
||||
e.DataBase64 = false
|
||||
} else if dataBase64 != nil {
|
||||
e.DataEncoded = dataBase64
|
||||
e.DataBase64 = true
|
||||
}
|
||||
|
||||
if len(raw) > 0 {
|
||||
extensions := make(map[string]interface{}, len(raw))
|
||||
ec.Extensions = extensions
|
||||
for k, v := range raw {
|
||||
k = strings.ToLower(k)
|
||||
var tmp interface{}
|
||||
if err := json.Unmarshal(v, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ec.SetExtension(k, tmp); err != nil {
|
||||
return errors2.Wrap(err, "Cannot set extension with key "+k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
e.Context = &ec
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshalEvent(eventCtx EventContextReader, extensions map[string]interface{}) (map[string]json.RawMessage, error) {
|
||||
b, err := json.Marshal(eventCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
brm := map[string]json.RawMessage{}
|
||||
if err := json.Unmarshal(b, &brm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sv, err := json.Marshal(eventCtx.GetSpecVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
brm["specversion"] = sv
|
||||
|
||||
for k, v := range extensions {
|
||||
k = strings.ToLower(k)
|
||||
vb, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Don't overwrite spec keys.
|
||||
if _, ok := brm[k]; !ok {
|
||||
brm[k] = vb
|
||||
}
|
||||
}
|
||||
|
||||
return brm, nil
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
)
|
||||
|
||||
var (
|
||||
// EventMarshalLatencyMs measures the latency in milliseconds for the
|
||||
// CloudEvents.Event marshal/unmarshalJSON methods.
|
||||
EventMarshalLatencyMs = stats.Float64(
|
||||
"cloudevents.io/sdk-go/event/json/latency",
|
||||
"The latency in milliseconds of (un)marshalJSON methods for CloudEvents.Event.",
|
||||
"ms")
|
||||
)
|
||||
|
||||
var (
|
||||
// LatencyView is an OpenCensus view that shows CloudEvents.Event (un)marshalJSON method latency.
|
||||
EventMarshalLatencyView = &view.View{
|
||||
Name: "event/json/latency",
|
||||
Measure: EventMarshalLatencyMs,
|
||||
Description: "The distribution of latency inside of (un)marshalJSON methods for CloudEvents.Event.",
|
||||
Aggregation: view.Distribution(0, .01, .1, 1, 10, 100, 1000, 10000),
|
||||
TagKeys: observability.LatencyTags(),
|
||||
}
|
||||
)
|
||||
|
||||
type observed int32
|
||||
|
||||
// Adheres to Observable
|
||||
var _ observability.Observable = observed(0)
|
||||
|
||||
const (
|
||||
reportMarshal observed = iota
|
||||
reportUnmarshal
|
||||
)
|
||||
|
||||
// MethodName implements Observable.MethodName
|
||||
func (o observed) MethodName() string {
|
||||
switch o {
|
||||
case reportMarshal:
|
||||
return "marshaljson"
|
||||
case reportUnmarshal:
|
||||
return "unmarshaljson"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// LatencyMs implements Observable.LatencyMs
|
||||
func (o observed) LatencyMs() *stats.Float64Measure {
|
||||
return EventMarshalLatencyMs
|
||||
}
|
||||
|
||||
// eventJSONObserved is a wrapper to append version to observed.
|
||||
type eventJSONObserved struct {
|
||||
// Method
|
||||
o observed
|
||||
// Version
|
||||
v string
|
||||
}
|
||||
|
||||
// Adheres to Observable
|
||||
var _ observability.Observable = (*eventJSONObserved)(nil)
|
||||
|
||||
// MethodName implements Observable.MethodName
|
||||
func (c eventJSONObserved) MethodName() string {
|
||||
return fmt.Sprintf("%s/%s", c.o.MethodName(), c.v)
|
||||
}
|
||||
|
||||
// LatencyMs implements Observable.LatencyMs
|
||||
func (c eventJSONObserved) LatencyMs() *stats.Float64Measure {
|
||||
return c.o.LatencyMs()
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ EventReader = (*Event)(nil)
|
||||
|
||||
// SpecVersion implements EventReader.SpecVersion
|
||||
func (e Event) SpecVersion() string {
|
||||
if e.Context != nil {
|
||||
return e.Context.GetSpecVersion()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Type implements EventReader.Type
|
||||
func (e Event) Type() string {
|
||||
if e.Context != nil {
|
||||
return e.Context.GetType()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Source implements EventReader.Source
|
||||
func (e Event) Source() string {
|
||||
if e.Context != nil {
|
||||
return e.Context.GetSource()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Subject implements EventReader.Subject
|
||||
func (e Event) Subject() string {
|
||||
if e.Context != nil {
|
||||
return e.Context.GetSubject()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ID implements EventReader.ID
|
||||
func (e Event) ID() string {
|
||||
if e.Context != nil {
|
||||
return e.Context.GetID()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Time implements EventReader.Time
|
||||
func (e Event) Time() time.Time {
|
||||
if e.Context != nil {
|
||||
return e.Context.GetTime()
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// DataSchema implements EventReader.DataSchema
|
||||
func (e Event) DataSchema() string {
|
||||
if e.Context != nil {
|
||||
return e.Context.GetDataSchema()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DataContentType implements EventReader.DataContentType
|
||||
func (e Event) DataContentType() string {
|
||||
if e.Context != nil {
|
||||
return e.Context.GetDataContentType()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DataMediaType returns the parsed DataMediaType of the event. If parsing
|
||||
// fails, the empty string is returned. To retrieve the parsing error, use
|
||||
// `Context.GetDataMediaType` instead.
|
||||
func (e Event) DataMediaType() string {
|
||||
if e.Context != nil {
|
||||
mediaType, _ := e.Context.GetDataMediaType()
|
||||
return mediaType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DeprecatedDataContentEncoding implements EventReader.DeprecatedDataContentEncoding
|
||||
func (e Event) DeprecatedDataContentEncoding() string {
|
||||
if e.Context != nil {
|
||||
return e.Context.DeprecatedGetDataContentEncoding()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Extensions implements EventReader.Extensions
|
||||
func (e Event) Extensions() map[string]interface{} {
|
||||
if e.Context != nil {
|
||||
return e.Context.GetExtensions()
|
||||
}
|
||||
return map[string]interface{}(nil)
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ EventWriter = (*Event)(nil)
|
||||
|
||||
// SetSpecVersion implements EventWriter.SetSpecVersion
|
||||
func (e *Event) SetSpecVersion(v string) {
|
||||
switch v {
|
||||
case CloudEventsVersionV03:
|
||||
if e.Context == nil {
|
||||
e.Context = &EventContextV03{}
|
||||
} else {
|
||||
e.Context = e.Context.AsV03()
|
||||
}
|
||||
case CloudEventsVersionV1:
|
||||
if e.Context == nil {
|
||||
e.Context = &EventContextV1{}
|
||||
} else {
|
||||
e.Context = e.Context.AsV1()
|
||||
}
|
||||
default:
|
||||
e.fieldError("specversion", fmt.Errorf("a valid spec version is required: [%s, %s]",
|
||||
CloudEventsVersionV03, CloudEventsVersionV1))
|
||||
return
|
||||
}
|
||||
e.fieldOK("specversion")
|
||||
}
|
||||
|
||||
// SetType implements EventWriter.SetType
|
||||
func (e *Event) SetType(t string) {
|
||||
if err := e.Context.SetType(t); err != nil {
|
||||
e.fieldError("type", err)
|
||||
} else {
|
||||
e.fieldOK("type")
|
||||
}
|
||||
}
|
||||
|
||||
// SetSource implements EventWriter.SetSource
|
||||
func (e *Event) SetSource(s string) {
|
||||
if err := e.Context.SetSource(s); err != nil {
|
||||
e.fieldError("source", err)
|
||||
} else {
|
||||
e.fieldOK("source")
|
||||
}
|
||||
}
|
||||
|
||||
// SetSubject implements EventWriter.SetSubject
|
||||
func (e *Event) SetSubject(s string) {
|
||||
if err := e.Context.SetSubject(s); err != nil {
|
||||
e.fieldError("subject", err)
|
||||
} else {
|
||||
e.fieldOK("subject")
|
||||
}
|
||||
}
|
||||
|
||||
// SetID implements EventWriter.SetID
|
||||
func (e *Event) SetID(id string) {
|
||||
if err := e.Context.SetID(id); err != nil {
|
||||
e.fieldError("id", err)
|
||||
} else {
|
||||
e.fieldOK("id")
|
||||
}
|
||||
}
|
||||
|
||||
// SetTime implements EventWriter.SetTime
|
||||
func (e *Event) SetTime(t time.Time) {
|
||||
if err := e.Context.SetTime(t); err != nil {
|
||||
e.fieldError("time", err)
|
||||
} else {
|
||||
e.fieldOK("time")
|
||||
}
|
||||
}
|
||||
|
||||
// SetDataSchema implements EventWriter.SetDataSchema
|
||||
func (e *Event) SetDataSchema(s string) {
|
||||
if err := e.Context.SetDataSchema(s); err != nil {
|
||||
e.fieldError("dataschema", err)
|
||||
} else {
|
||||
e.fieldOK("dataschema")
|
||||
}
|
||||
}
|
||||
|
||||
// SetDataContentType implements EventWriter.SetDataContentType
|
||||
func (e *Event) SetDataContentType(ct string) {
|
||||
if err := e.Context.SetDataContentType(ct); err != nil {
|
||||
e.fieldError("datacontenttype", err)
|
||||
} else {
|
||||
e.fieldOK("datacontenttype")
|
||||
}
|
||||
}
|
||||
|
||||
// SetDataContentEncoding is deprecated. Implements EventWriter.SetDataContentEncoding.
|
||||
func (e *Event) SetDataContentEncoding(enc string) {
|
||||
if err := e.Context.DeprecatedSetDataContentEncoding(enc); err != nil {
|
||||
e.fieldError("datacontentencoding", err)
|
||||
} else {
|
||||
e.fieldOK("datacontentencoding")
|
||||
}
|
||||
}
|
||||
|
||||
// SetExtension implements EventWriter.SetExtension
|
||||
func (e *Event) SetExtension(name string, obj interface{}) {
|
||||
if err := e.Context.SetExtension(name, obj); err != nil {
|
||||
e.fieldError("extension:"+name, err)
|
||||
} else {
|
||||
e.fieldOK("extension:" + name)
|
||||
}
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
package event
|
||||
|
||||
import "time"
|
||||
|
||||
// EventContextReader are the methods required to be a reader of context
|
||||
// attributes.
|
||||
type EventContextReader interface {
|
||||
// GetSpecVersion returns the native CloudEvents Spec version of the event
|
||||
// context.
|
||||
GetSpecVersion() string
|
||||
// GetType returns the CloudEvents type from the context.
|
||||
GetType() string
|
||||
// GetSource returns the CloudEvents source from the context.
|
||||
GetSource() string
|
||||
// GetSubject returns the CloudEvents subject from the context.
|
||||
GetSubject() string
|
||||
// GetID returns the CloudEvents ID from the context.
|
||||
GetID() string
|
||||
// GetTime returns the CloudEvents creation time from the context.
|
||||
GetTime() time.Time
|
||||
// GetDataSchema returns the CloudEvents schema URL (if any) from the
|
||||
// context.
|
||||
GetDataSchema() string
|
||||
// GetDataContentType returns content type on the context.
|
||||
GetDataContentType() string
|
||||
// DeprecatedGetDataContentEncoding returns content encoding on the context.
|
||||
DeprecatedGetDataContentEncoding() string
|
||||
|
||||
// GetDataMediaType returns the MIME media type for encoded data, which is
|
||||
// needed by both encoding and decoding. This is a processed form of
|
||||
// GetDataContentType and it may return an error.
|
||||
GetDataMediaType() (string, error)
|
||||
|
||||
// DEPRECATED: Access extensions directly via the GetExtensions()
|
||||
// For example replace this:
|
||||
//
|
||||
// var i int
|
||||
// err := ec.ExtensionAs("foo", &i)
|
||||
//
|
||||
// With this:
|
||||
//
|
||||
// i, err := types.ToInteger(ec.GetExtensions["foo"])
|
||||
//
|
||||
ExtensionAs(string, interface{}) error
|
||||
|
||||
// GetExtensions returns the full extensions map.
|
||||
//
|
||||
// Extensions use the CloudEvents type system, details in package cloudevents/types.
|
||||
GetExtensions() map[string]interface{}
|
||||
|
||||
// GetExtension returns the extension associated with with the given key.
|
||||
// The given key is case insensitive. If the extension can not be found,
|
||||
// an error will be returned.
|
||||
GetExtension(string) (interface{}, error)
|
||||
}
|
||||
|
||||
// EventContextWriter are the methods required to be a writer of context
|
||||
// attributes.
|
||||
type EventContextWriter interface {
|
||||
// SetType sets the type of the context.
|
||||
SetType(string) error
|
||||
// SetSource sets the source of the context.
|
||||
SetSource(string) error
|
||||
// SetSubject sets the subject of the context.
|
||||
SetSubject(string) error
|
||||
// SetID sets the ID of the context.
|
||||
SetID(string) error
|
||||
// SetTime sets the time of the context.
|
||||
SetTime(time time.Time) error
|
||||
// SetDataSchema sets the schema url of the context.
|
||||
SetDataSchema(string) error
|
||||
// SetDataContentType sets the data content type of the context.
|
||||
SetDataContentType(string) error
|
||||
// DeprecatedSetDataContentEncoding sets the data context encoding of the context.
|
||||
DeprecatedSetDataContentEncoding(string) error
|
||||
|
||||
// SetExtension sets the given interface onto the extension attributes
|
||||
// determined by the provided name.
|
||||
//
|
||||
// This function fails in V1 if the name doesn't respect the regex ^[a-zA-Z0-9]+$
|
||||
//
|
||||
// Package ./types documents the types that are allowed as extension values.
|
||||
SetExtension(string, interface{}) error
|
||||
}
|
||||
|
||||
// EventContextConverter are the methods that allow for event version
|
||||
// conversion.
|
||||
type EventContextConverter interface {
|
||||
// AsV03 provides a translation from whatever the "native" encoding of the
|
||||
// CloudEvent was to the equivalent in v0.3 field names, moving fields to or
|
||||
// from extensions as necessary.
|
||||
AsV03() *EventContextV03
|
||||
|
||||
// AsV1 provides a translation from whatever the "native" encoding of the
|
||||
// CloudEvent was to the equivalent in v1.0 field names, moving fields to or
|
||||
// from extensions as necessary.
|
||||
AsV1() *EventContextV1
|
||||
}
|
||||
|
||||
// EventContext is conical interface for a CloudEvents Context.
|
||||
type EventContext interface {
|
||||
// EventContextConverter allows for conversion between versions.
|
||||
EventContextConverter
|
||||
|
||||
// EventContextReader adds methods for reading context.
|
||||
EventContextReader
|
||||
|
||||
// EventContextWriter adds methods for writing to context.
|
||||
EventContextWriter
|
||||
|
||||
// Validate the event based on the specifics of the CloudEvents spec version
|
||||
// represented by this event context.
|
||||
Validate() error
|
||||
|
||||
// Clone clones the event context.
|
||||
Clone() EventContext
|
||||
|
||||
// String returns a pretty-printed representation of the EventContext.
|
||||
String() string
|
||||
}
|
|
@ -1,307 +0,0 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mime"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// CloudEventsVersionV03 represents the version 0.3 of the CloudEvents spec.
|
||||
CloudEventsVersionV03 = "0.3"
|
||||
)
|
||||
|
||||
// EventContextV03 represents the non-data attributes of a CloudEvents v0.3
|
||||
// event.
|
||||
type EventContextV03 struct {
|
||||
// Type - The type of the occurrence which has happened.
|
||||
Type string `json:"type"`
|
||||
// Source - A URI describing the event producer.
|
||||
Source types.URIRef `json:"source"`
|
||||
// Subject - The subject of the event in the context of the event producer
|
||||
// (identified by `source`).
|
||||
Subject *string `json:"subject,omitempty"`
|
||||
// ID of the event; must be non-empty and unique within the scope of the producer.
|
||||
ID string `json:"id"`
|
||||
// Time - A Timestamp when the event happened.
|
||||
Time *types.Timestamp `json:"time,omitempty"`
|
||||
// DataSchema - A link to the schema that the `data` attribute adheres to.
|
||||
SchemaURL *types.URIRef `json:"schemaurl,omitempty"`
|
||||
// GetDataMediaType - A MIME (RFC2046) string describing the media type of `data`.
|
||||
DataContentType *string `json:"datacontenttype,omitempty"`
|
||||
// DeprecatedDataContentEncoding describes the content encoding for the `data` attribute. Valid: nil, `Base64`.
|
||||
DataContentEncoding *string `json:"datacontentencoding,omitempty"`
|
||||
// Extensions - Additional extension metadata beyond the base spec.
|
||||
Extensions map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
// Adhere to EventContext
|
||||
var _ EventContext = (*EventContextV03)(nil)
|
||||
|
||||
// ExtensionAs implements EventContext.ExtensionAs
|
||||
func (ec EventContextV03) ExtensionAs(name string, obj interface{}) error {
|
||||
value, ok := ec.Extensions[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("extension %q does not exist", name)
|
||||
}
|
||||
|
||||
// Try to unmarshal extension if we find it as a RawMessage.
|
||||
switch v := value.(type) {
|
||||
case json.RawMessage:
|
||||
if err := json.Unmarshal(v, obj); err == nil {
|
||||
// if that worked, return with obj set.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// else try as a string ptr.
|
||||
|
||||
// Only support *string for now.
|
||||
switch v := obj.(type) {
|
||||
case *string:
|
||||
if valueAsString, ok := value.(string); ok {
|
||||
*v = valueAsString
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("invalid type for extension %q", name)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown extension type %T", obj)
|
||||
}
|
||||
}
|
||||
|
||||
// SetExtension adds the extension 'name' with value 'value' to the CloudEvents context.
|
||||
func (ec *EventContextV03) SetExtension(name string, value interface{}) error {
|
||||
if ec.Extensions == nil {
|
||||
ec.Extensions = make(map[string]interface{})
|
||||
}
|
||||
if value == nil {
|
||||
delete(ec.Extensions, name)
|
||||
if len(ec.Extensions) == 0 {
|
||||
ec.Extensions = nil
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
v, err := types.Validate(value)
|
||||
if err == nil {
|
||||
ec.Extensions[name] = v
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Clone implements EventContextConverter.Clone
|
||||
func (ec EventContextV03) Clone() EventContext {
|
||||
ec03 := ec.AsV03()
|
||||
ec03.Source = types.Clone(ec.Source).(types.URIRef)
|
||||
if ec.Time != nil {
|
||||
ec03.Time = types.Clone(ec.Time).(*types.Timestamp)
|
||||
}
|
||||
if ec.SchemaURL != nil {
|
||||
ec03.SchemaURL = types.Clone(ec.SchemaURL).(*types.URIRef)
|
||||
}
|
||||
ec03.Extensions = ec.cloneExtensions()
|
||||
return ec03
|
||||
}
|
||||
|
||||
func (ec *EventContextV03) cloneExtensions() map[string]interface{} {
|
||||
old := ec.Extensions
|
||||
if old == nil {
|
||||
return nil
|
||||
}
|
||||
new := make(map[string]interface{}, len(ec.Extensions))
|
||||
for k, v := range old {
|
||||
new[k] = types.Clone(v)
|
||||
}
|
||||
return new
|
||||
}
|
||||
|
||||
// AsV03 implements EventContextConverter.AsV03
|
||||
func (ec EventContextV03) AsV03() *EventContextV03 {
|
||||
return &ec
|
||||
}
|
||||
|
||||
// AsV1 implements EventContextConverter.AsV1
|
||||
func (ec EventContextV03) AsV1() *EventContextV1 {
|
||||
ret := EventContextV1{
|
||||
ID: ec.ID,
|
||||
Time: ec.Time,
|
||||
Type: ec.Type,
|
||||
DataContentType: ec.DataContentType,
|
||||
Source: types.URIRef{URL: ec.Source.URL},
|
||||
Subject: ec.Subject,
|
||||
Extensions: make(map[string]interface{}),
|
||||
}
|
||||
if ec.SchemaURL != nil {
|
||||
ret.DataSchema = &types.URI{URL: ec.SchemaURL.URL}
|
||||
}
|
||||
|
||||
// DataContentEncoding was removed in 1.0, so put it in an extension for 1.0.
|
||||
if ec.DataContentEncoding != nil {
|
||||
_ = ret.SetExtension(DataContentEncodingKey, *ec.DataContentEncoding)
|
||||
}
|
||||
|
||||
if ec.Extensions != nil {
|
||||
for k, v := range ec.Extensions {
|
||||
k = strings.ToLower(k)
|
||||
ret.Extensions[k] = v
|
||||
}
|
||||
}
|
||||
if len(ret.Extensions) == 0 {
|
||||
ret.Extensions = nil
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
// Validate returns errors based on requirements from the CloudEvents spec.
|
||||
// For more details, see https://github.com/cloudevents/spec/blob/master/spec.md
|
||||
// As of Feb 26, 2019, commit 17c32ea26baf7714ad027d9917d03d2fff79fc7e
|
||||
// + https://github.com/cloudevents/spec/pull/387 -> datacontentencoding
|
||||
// + https://github.com/cloudevents/spec/pull/406 -> subject
|
||||
func (ec EventContextV03) Validate() error {
|
||||
errors := []string(nil)
|
||||
|
||||
// type
|
||||
// Type: String
|
||||
// Constraints:
|
||||
// REQUIRED
|
||||
// MUST be a non-empty string
|
||||
// SHOULD be prefixed with a reverse-DNS name. The prefixed domain dictates the organization which defines the semantics of this event type.
|
||||
eventType := strings.TrimSpace(ec.Type)
|
||||
if eventType == "" {
|
||||
errors = append(errors, "type: MUST be a non-empty string")
|
||||
}
|
||||
|
||||
// source
|
||||
// Type: URI-reference
|
||||
// Constraints:
|
||||
// REQUIRED
|
||||
source := strings.TrimSpace(ec.Source.String())
|
||||
if source == "" {
|
||||
errors = append(errors, "source: REQUIRED")
|
||||
}
|
||||
|
||||
// subject
|
||||
// Type: String
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// MUST be a non-empty string
|
||||
if ec.Subject != nil {
|
||||
subject := strings.TrimSpace(*ec.Subject)
|
||||
if subject == "" {
|
||||
errors = append(errors, "subject: if present, MUST be a non-empty string")
|
||||
}
|
||||
}
|
||||
|
||||
// id
|
||||
// Type: String
|
||||
// Constraints:
|
||||
// REQUIRED
|
||||
// MUST be a non-empty string
|
||||
// MUST be unique within the scope of the producer
|
||||
id := strings.TrimSpace(ec.ID)
|
||||
if id == "" {
|
||||
errors = append(errors, "id: MUST be a non-empty string")
|
||||
|
||||
// no way to test "MUST be unique within the scope of the producer"
|
||||
}
|
||||
|
||||
// time
|
||||
// Type: Timestamp
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// If present, MUST adhere to the format specified in RFC 3339
|
||||
// --> no need to test this, no way to set the time without it being valid.
|
||||
|
||||
// schemaurl
|
||||
// Type: URI
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// If present, MUST adhere to the format specified in RFC 3986
|
||||
if ec.SchemaURL != nil {
|
||||
schemaURL := strings.TrimSpace(ec.SchemaURL.String())
|
||||
// empty string is not RFC 3986 compatible.
|
||||
if schemaURL == "" {
|
||||
errors = append(errors, "schemaurl: if present, MUST adhere to the format specified in RFC 3986")
|
||||
}
|
||||
}
|
||||
|
||||
// datacontenttype
|
||||
// Type: String per RFC 2046
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// If present, MUST adhere to the format specified in RFC 2046
|
||||
if ec.DataContentType != nil {
|
||||
dataContentType := strings.TrimSpace(*ec.DataContentType)
|
||||
if dataContentType == "" {
|
||||
errors = append(errors, "datacontenttype: if present, MUST adhere to the format specified in RFC 2046")
|
||||
} else {
|
||||
_, _, err := mime.ParseMediaType(dataContentType)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Sprintf("datacontenttype: failed to parse RFC 2046 media type, %s", err.Error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// datacontentencoding
|
||||
// Type: String per RFC 2045 Section 6.1
|
||||
// Constraints:
|
||||
// The attribute MUST be set if the data attribute contains string-encoded binary data.
|
||||
// Otherwise the attribute MUST NOT be set.
|
||||
// If present, MUST adhere to RFC 2045 Section 6.1
|
||||
if ec.DataContentEncoding != nil {
|
||||
dataContentEncoding := strings.ToLower(strings.TrimSpace(*ec.DataContentEncoding))
|
||||
if dataContentEncoding != Base64 {
|
||||
errors = append(errors, "datacontentencoding: if present, MUST adhere to RFC 2045 Section 6.1")
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf(strings.Join(errors, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a pretty-printed representation of the EventContext.
|
||||
func (ec EventContextV03) String() string {
|
||||
b := strings.Builder{}
|
||||
|
||||
b.WriteString("Context Attributes,\n")
|
||||
|
||||
b.WriteString(" specversion: " + CloudEventsVersionV03 + "\n")
|
||||
b.WriteString(" type: " + ec.Type + "\n")
|
||||
b.WriteString(" source: " + ec.Source.String() + "\n")
|
||||
if ec.Subject != nil {
|
||||
b.WriteString(" subject: " + *ec.Subject + "\n")
|
||||
}
|
||||
b.WriteString(" id: " + ec.ID + "\n")
|
||||
if ec.Time != nil {
|
||||
b.WriteString(" time: " + ec.Time.String() + "\n")
|
||||
}
|
||||
if ec.SchemaURL != nil {
|
||||
b.WriteString(" schemaurl: " + ec.SchemaURL.String() + "\n")
|
||||
}
|
||||
if ec.DataContentType != nil {
|
||||
b.WriteString(" datacontenttype: " + *ec.DataContentType + "\n")
|
||||
}
|
||||
if ec.DataContentEncoding != nil {
|
||||
b.WriteString(" datacontentencoding: " + *ec.DataContentEncoding + "\n")
|
||||
}
|
||||
|
||||
if ec.Extensions != nil && len(ec.Extensions) > 0 {
|
||||
b.WriteString("Extensions,\n")
|
||||
keys := make([]string, 0, len(ec.Extensions))
|
||||
for k := range ec.Extensions {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
b.WriteString(fmt.Sprintf(" %s: %v\n", key, ec.Extensions[key]))
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetSpecVersion implements EventContextReader.GetSpecVersion
|
||||
func (ec EventContextV03) GetSpecVersion() string {
|
||||
return CloudEventsVersionV03
|
||||
}
|
||||
|
||||
// GetDataContentType implements EventContextReader.GetDataContentType
|
||||
func (ec EventContextV03) GetDataContentType() string {
|
||||
if ec.DataContentType != nil {
|
||||
return *ec.DataContentType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetDataMediaType implements EventContextReader.GetDataMediaType
|
||||
func (ec EventContextV03) GetDataMediaType() (string, error) {
|
||||
if ec.DataContentType != nil {
|
||||
mediaType, _, err := mime.ParseMediaType(*ec.DataContentType)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return mediaType, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// GetType implements EventContextReader.GetType
|
||||
func (ec EventContextV03) GetType() string {
|
||||
return ec.Type
|
||||
}
|
||||
|
||||
// GetSource implements EventContextReader.GetSource
|
||||
func (ec EventContextV03) GetSource() string {
|
||||
return ec.Source.String()
|
||||
}
|
||||
|
||||
// GetSubject implements EventContextReader.GetSubject
|
||||
func (ec EventContextV03) GetSubject() string {
|
||||
if ec.Subject != nil {
|
||||
return *ec.Subject
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetTime implements EventContextReader.GetTime
|
||||
func (ec EventContextV03) GetTime() time.Time {
|
||||
if ec.Time != nil {
|
||||
return ec.Time.Time
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// GetID implements EventContextReader.GetID
|
||||
func (ec EventContextV03) GetID() string {
|
||||
return ec.ID
|
||||
}
|
||||
|
||||
// GetDataSchema implements EventContextReader.GetDataSchema
|
||||
func (ec EventContextV03) GetDataSchema() string {
|
||||
if ec.SchemaURL != nil {
|
||||
return ec.SchemaURL.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DeprecatedGetDataContentEncoding implements EventContextReader.DeprecatedGetDataContentEncoding
|
||||
func (ec EventContextV03) DeprecatedGetDataContentEncoding() string {
|
||||
if ec.DataContentEncoding != nil {
|
||||
return *ec.DataContentEncoding
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetExtensions implements EventContextReader.GetExtensions
|
||||
func (ec EventContextV03) GetExtensions() map[string]interface{} {
|
||||
return ec.Extensions
|
||||
}
|
||||
|
||||
// GetExtension implements EventContextReader.GetExtension
|
||||
func (ec EventContextV03) GetExtension(key string) (interface{}, error) {
|
||||
v, ok := caseInsensitiveSearch(key, ec.Extensions)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%q not found", key)
|
||||
}
|
||||
return v, nil
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/types"
|
||||
)
|
||||
|
||||
// Adhere to EventContextWriter
|
||||
var _ EventContextWriter = (*EventContextV03)(nil)
|
||||
|
||||
// SetDataContentType implements EventContextWriter.SetDataContentType
|
||||
func (ec *EventContextV03) SetDataContentType(ct string) error {
|
||||
ct = strings.TrimSpace(ct)
|
||||
if ct == "" {
|
||||
ec.DataContentType = nil
|
||||
} else {
|
||||
ec.DataContentType = &ct
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetType implements EventContextWriter.SetType
|
||||
func (ec *EventContextV03) SetType(t string) error {
|
||||
t = strings.TrimSpace(t)
|
||||
ec.Type = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSource implements EventContextWriter.SetSource
|
||||
func (ec *EventContextV03) SetSource(u string) error {
|
||||
pu, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ec.Source = types.URIRef{URL: *pu}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSubject implements EventContextWriter.SetSubject
|
||||
func (ec *EventContextV03) SetSubject(s string) error {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
ec.Subject = nil
|
||||
} else {
|
||||
ec.Subject = &s
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetID implements EventContextWriter.SetID
|
||||
func (ec *EventContextV03) SetID(id string) error {
|
||||
id = strings.TrimSpace(id)
|
||||
if id == "" {
|
||||
return errors.New("id is required to be a non-empty string")
|
||||
}
|
||||
ec.ID = id
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTime implements EventContextWriter.SetTime
|
||||
func (ec *EventContextV03) SetTime(t time.Time) error {
|
||||
if t.IsZero() {
|
||||
ec.Time = nil
|
||||
} else {
|
||||
ec.Time = &types.Timestamp{Time: t}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDataSchema implements EventContextWriter.SetDataSchema
|
||||
func (ec *EventContextV03) SetDataSchema(u string) error {
|
||||
u = strings.TrimSpace(u)
|
||||
if u == "" {
|
||||
ec.SchemaURL = nil
|
||||
return nil
|
||||
}
|
||||
pu, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ec.SchemaURL = &types.URIRef{URL: *pu}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeprecatedSetDataContentEncoding implements EventContextWriter.DeprecatedSetDataContentEncoding
|
||||
func (ec *EventContextV03) DeprecatedSetDataContentEncoding(e string) error {
|
||||
e = strings.ToLower(strings.TrimSpace(e))
|
||||
if e == "" {
|
||||
ec.DataContentEncoding = nil
|
||||
} else {
|
||||
ec.DataContentEncoding = &e
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,297 +0,0 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/types"
|
||||
)
|
||||
|
||||
// WIP: AS OF SEP 20, 2019
|
||||
|
||||
const (
|
||||
// CloudEventsVersionV1 represents the version 1.0 of the CloudEvents spec.
|
||||
CloudEventsVersionV1 = "1.0"
|
||||
)
|
||||
|
||||
// EventContextV1 represents the non-data attributes of a CloudEvents v1.0
|
||||
// event.
|
||||
type EventContextV1 struct {
|
||||
// ID of the event; must be non-empty and unique within the scope of the producer.
|
||||
// +required
|
||||
ID string `json:"id"`
|
||||
// Source - A URI describing the event producer.
|
||||
// +required
|
||||
Source types.URIRef `json:"source"`
|
||||
// Type - The type of the occurrence which has happened.
|
||||
// +required
|
||||
Type string `json:"type"`
|
||||
|
||||
// DataContentType - A MIME (RFC2046) string describing the media type of `data`.
|
||||
// +optional
|
||||
DataContentType *string `json:"datacontenttype,omitempty"`
|
||||
// Subject - The subject of the event in the context of the event producer
|
||||
// (identified by `source`).
|
||||
// +optional
|
||||
Subject *string `json:"subject,omitempty"`
|
||||
// Time - A Timestamp when the event happened.
|
||||
// +optional
|
||||
Time *types.Timestamp `json:"time,omitempty"`
|
||||
// DataSchema - A link to the schema that the `data` attribute adheres to.
|
||||
// +optional
|
||||
DataSchema *types.URI `json:"dataschema,omitempty"`
|
||||
|
||||
// Extensions - Additional extension metadata beyond the base spec.
|
||||
// +optional
|
||||
Extensions map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
// Adhere to EventContext
|
||||
var _ EventContext = (*EventContextV1)(nil)
|
||||
|
||||
// ExtensionAs implements EventContext.ExtensionAs
|
||||
func (ec EventContextV1) ExtensionAs(name string, obj interface{}) error {
|
||||
name = strings.ToLower(name)
|
||||
value, ok := ec.Extensions[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("extension %q does not exist", name)
|
||||
}
|
||||
|
||||
// Only support *string for now.
|
||||
if v, ok := obj.(*string); ok {
|
||||
if *v, ok = value.(string); ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("unknown extension type %T", obj)
|
||||
}
|
||||
|
||||
// SetExtension adds the extension 'name' with value 'value' to the CloudEvents context.
|
||||
// This function fails if the name doesn't respect the regex ^[a-zA-Z0-9]+$
|
||||
func (ec *EventContextV1) SetExtension(name string, value interface{}) error {
|
||||
if !IsAlphaNumeric(name) {
|
||||
return errors.New("bad key, CloudEvents attribute names MUST consist of lower-case letters ('a' to 'z') or digits ('0' to '9') from the ASCII character set")
|
||||
}
|
||||
|
||||
name = strings.ToLower(name)
|
||||
if ec.Extensions == nil {
|
||||
ec.Extensions = make(map[string]interface{})
|
||||
}
|
||||
if value == nil {
|
||||
delete(ec.Extensions, name)
|
||||
if len(ec.Extensions) == 0 {
|
||||
ec.Extensions = nil
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
v, err := types.Validate(value) // Ensure it's a legal CE attribute value
|
||||
if err == nil {
|
||||
ec.Extensions[name] = v
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Clone implements EventContextConverter.Clone
|
||||
func (ec EventContextV1) Clone() EventContext {
|
||||
ec1 := ec.AsV1()
|
||||
ec1.Source = types.Clone(ec.Source).(types.URIRef)
|
||||
if ec.Time != nil {
|
||||
ec1.Time = types.Clone(ec.Time).(*types.Timestamp)
|
||||
}
|
||||
if ec.DataSchema != nil {
|
||||
ec1.DataSchema = types.Clone(ec.DataSchema).(*types.URI)
|
||||
}
|
||||
ec1.Extensions = ec.cloneExtensions()
|
||||
return ec1
|
||||
}
|
||||
|
||||
func (ec *EventContextV1) cloneExtensions() map[string]interface{} {
|
||||
old := ec.Extensions
|
||||
if old == nil {
|
||||
return nil
|
||||
}
|
||||
new := make(map[string]interface{}, len(ec.Extensions))
|
||||
for k, v := range old {
|
||||
new[k] = types.Clone(v)
|
||||
}
|
||||
return new
|
||||
}
|
||||
|
||||
// AsV03 implements EventContextConverter.AsV03
|
||||
func (ec EventContextV1) AsV03() *EventContextV03 {
|
||||
ret := EventContextV03{
|
||||
ID: ec.ID,
|
||||
Time: ec.Time,
|
||||
Type: ec.Type,
|
||||
DataContentType: ec.DataContentType,
|
||||
Source: types.URIRef{URL: ec.Source.URL},
|
||||
Subject: ec.Subject,
|
||||
Extensions: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
if ec.DataSchema != nil {
|
||||
ret.SchemaURL = &types.URIRef{URL: ec.DataSchema.URL}
|
||||
}
|
||||
|
||||
if ec.Extensions != nil {
|
||||
for k, v := range ec.Extensions {
|
||||
k = strings.ToLower(k)
|
||||
// DeprecatedDataContentEncoding was introduced in 0.3, removed in 1.0
|
||||
if strings.EqualFold(k, DataContentEncodingKey) {
|
||||
etv, ok := v.(string)
|
||||
if ok && etv != "" {
|
||||
ret.DataContentEncoding = &etv
|
||||
}
|
||||
continue
|
||||
}
|
||||
ret.Extensions[k] = v
|
||||
}
|
||||
}
|
||||
if len(ret.Extensions) == 0 {
|
||||
ret.Extensions = nil
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
// AsV1 implements EventContextConverter.AsV1
|
||||
func (ec EventContextV1) AsV1() *EventContextV1 {
|
||||
return &ec
|
||||
}
|
||||
|
||||
// Validate returns errors based on requirements from the CloudEvents spec.
|
||||
// For more details, see https://github.com/cloudevents/spec/blob/v1.0-rc1/spec.md.
|
||||
func (ec EventContextV1) Validate() error {
|
||||
errors := []string(nil)
|
||||
|
||||
// id
|
||||
// Type: String
|
||||
// Constraints:
|
||||
// REQUIRED
|
||||
// MUST be a non-empty string
|
||||
// MUST be unique within the scope of the producer
|
||||
id := strings.TrimSpace(ec.ID)
|
||||
if id == "" {
|
||||
errors = append(errors, "id: MUST be a non-empty string")
|
||||
// no way to test "MUST be unique within the scope of the producer"
|
||||
}
|
||||
|
||||
// source
|
||||
// Type: URI-reference
|
||||
// Constraints:
|
||||
// REQUIRED
|
||||
// MUST be a non-empty URI-reference
|
||||
// An absolute URI is RECOMMENDED
|
||||
source := strings.TrimSpace(ec.Source.String())
|
||||
if source == "" {
|
||||
errors = append(errors, "source: REQUIRED")
|
||||
}
|
||||
|
||||
// type
|
||||
// Type: String
|
||||
// Constraints:
|
||||
// REQUIRED
|
||||
// MUST be a non-empty string
|
||||
// SHOULD be prefixed with a reverse-DNS name. The prefixed domain dictates the organization which defines the semantics of this event type.
|
||||
eventType := strings.TrimSpace(ec.Type)
|
||||
if eventType == "" {
|
||||
errors = append(errors, "type: MUST be a non-empty string")
|
||||
}
|
||||
|
||||
// The following attributes are optional but still have validation.
|
||||
|
||||
// datacontenttype
|
||||
// Type: String per RFC 2046
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// If present, MUST adhere to the format specified in RFC 2046
|
||||
if ec.DataContentType != nil {
|
||||
dataContentType := strings.TrimSpace(*ec.DataContentType)
|
||||
if dataContentType == "" {
|
||||
errors = append(errors, "datacontenttype: if present, MUST adhere to the format specified in RFC 2046")
|
||||
} else {
|
||||
_, _, err := mime.ParseMediaType(dataContentType)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Sprintf("datacontenttype: failed to parse RFC 2046 media type, %s", err.Error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dataschema
|
||||
// Type: URI
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// If present, MUST adhere to the format specified in RFC 3986
|
||||
if ec.DataSchema != nil {
|
||||
dataSchema := strings.TrimSpace(ec.DataSchema.String())
|
||||
// empty string is not RFC 3986 compatible.
|
||||
if dataSchema == "" {
|
||||
errors = append(errors, "dataschema: if present, MUST adhere to the format specified in RFC 3986")
|
||||
}
|
||||
}
|
||||
|
||||
// subject
|
||||
// Type: String
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// MUST be a non-empty string
|
||||
if ec.Subject != nil {
|
||||
subject := strings.TrimSpace(*ec.Subject)
|
||||
if subject == "" {
|
||||
errors = append(errors, "subject: if present, MUST be a non-empty string")
|
||||
}
|
||||
}
|
||||
|
||||
// time
|
||||
// Type: Timestamp
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// If present, MUST adhere to the format specified in RFC 3339
|
||||
// --> no need to test this, no way to set the time without it being valid.
|
||||
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf(strings.Join(errors, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a pretty-printed representation of the EventContext.
|
||||
func (ec EventContextV1) String() string {
|
||||
b := strings.Builder{}
|
||||
|
||||
b.WriteString("Context Attributes,\n")
|
||||
|
||||
b.WriteString(" specversion: " + CloudEventsVersionV1 + "\n")
|
||||
b.WriteString(" type: " + ec.Type + "\n")
|
||||
b.WriteString(" source: " + ec.Source.String() + "\n")
|
||||
if ec.Subject != nil {
|
||||
b.WriteString(" subject: " + *ec.Subject + "\n")
|
||||
}
|
||||
b.WriteString(" id: " + ec.ID + "\n")
|
||||
if ec.Time != nil {
|
||||
b.WriteString(" time: " + ec.Time.String() + "\n")
|
||||
}
|
||||
if ec.DataSchema != nil {
|
||||
b.WriteString(" dataschema: " + ec.DataSchema.String() + "\n")
|
||||
}
|
||||
if ec.DataContentType != nil {
|
||||
b.WriteString(" datacontenttype: " + *ec.DataContentType + "\n")
|
||||
}
|
||||
|
||||
if ec.Extensions != nil && len(ec.Extensions) > 0 {
|
||||
b.WriteString("Extensions,\n")
|
||||
keys := make([]string, 0, len(ec.Extensions))
|
||||
for k := range ec.Extensions {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
b.WriteString(fmt.Sprintf(" %s: %v\n", key, ec.Extensions[key]))
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetSpecVersion implements EventContextReader.GetSpecVersion
|
||||
func (ec EventContextV1) GetSpecVersion() string {
|
||||
return CloudEventsVersionV1
|
||||
}
|
||||
|
||||
// GetDataContentType implements EventContextReader.GetDataContentType
|
||||
func (ec EventContextV1) GetDataContentType() string {
|
||||
if ec.DataContentType != nil {
|
||||
return *ec.DataContentType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetDataMediaType implements EventContextReader.GetDataMediaType
|
||||
func (ec EventContextV1) GetDataMediaType() (string, error) {
|
||||
if ec.DataContentType != nil {
|
||||
mediaType, _, err := mime.ParseMediaType(*ec.DataContentType)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return mediaType, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// GetType implements EventContextReader.GetType
|
||||
func (ec EventContextV1) GetType() string {
|
||||
return ec.Type
|
||||
}
|
||||
|
||||
// GetSource implements EventContextReader.GetSource
|
||||
func (ec EventContextV1) GetSource() string {
|
||||
return ec.Source.String()
|
||||
}
|
||||
|
||||
// GetSubject implements EventContextReader.GetSubject
|
||||
func (ec EventContextV1) GetSubject() string {
|
||||
if ec.Subject != nil {
|
||||
return *ec.Subject
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetTime implements EventContextReader.GetTime
|
||||
func (ec EventContextV1) GetTime() time.Time {
|
||||
if ec.Time != nil {
|
||||
return ec.Time.Time
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// GetID implements EventContextReader.GetID
|
||||
func (ec EventContextV1) GetID() string {
|
||||
return ec.ID
|
||||
}
|
||||
|
||||
// GetDataSchema implements EventContextReader.GetDataSchema
|
||||
func (ec EventContextV1) GetDataSchema() string {
|
||||
if ec.DataSchema != nil {
|
||||
return ec.DataSchema.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DeprecatedGetDataContentEncoding implements EventContextReader.DeprecatedGetDataContentEncoding
|
||||
func (ec EventContextV1) DeprecatedGetDataContentEncoding() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetExtensions implements EventContextReader.GetExtensions
|
||||
func (ec EventContextV1) GetExtensions() map[string]interface{} {
|
||||
if len(ec.Extensions) == 0 {
|
||||
return nil
|
||||
}
|
||||
// For now, convert the extensions of v1.0 to the pre-v1.0 style.
|
||||
ext := make(map[string]interface{}, len(ec.Extensions))
|
||||
for k, v := range ec.Extensions {
|
||||
ext[k] = v
|
||||
}
|
||||
return ext
|
||||
}
|
||||
|
||||
// GetExtension implements EventContextReader.GetExtension
|
||||
func (ec EventContextV1) GetExtension(key string) (interface{}, error) {
|
||||
v, ok := caseInsensitiveSearch(key, ec.Extensions)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%q not found", key)
|
||||
}
|
||||
return v, nil
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/types"
|
||||
)
|
||||
|
||||
// Adhere to EventContextWriter
|
||||
var _ EventContextWriter = (*EventContextV1)(nil)
|
||||
|
||||
// SetDataContentType implements EventContextWriter.SetDataContentType
|
||||
func (ec *EventContextV1) SetDataContentType(ct string) error {
|
||||
ct = strings.TrimSpace(ct)
|
||||
if ct == "" {
|
||||
ec.DataContentType = nil
|
||||
} else {
|
||||
ec.DataContentType = &ct
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetType implements EventContextWriter.SetType
|
||||
func (ec *EventContextV1) SetType(t string) error {
|
||||
t = strings.TrimSpace(t)
|
||||
ec.Type = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSource implements EventContextWriter.SetSource
|
||||
func (ec *EventContextV1) SetSource(u string) error {
|
||||
pu, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ec.Source = types.URIRef{URL: *pu}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSubject implements EventContextWriter.SetSubject
|
||||
func (ec *EventContextV1) SetSubject(s string) error {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
ec.Subject = nil
|
||||
} else {
|
||||
ec.Subject = &s
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetID implements EventContextWriter.SetID
|
||||
func (ec *EventContextV1) SetID(id string) error {
|
||||
id = strings.TrimSpace(id)
|
||||
if id == "" {
|
||||
return errors.New("id is required to be a non-empty string")
|
||||
}
|
||||
ec.ID = id
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTime implements EventContextWriter.SetTime
|
||||
func (ec *EventContextV1) SetTime(t time.Time) error {
|
||||
if t.IsZero() {
|
||||
ec.Time = nil
|
||||
} else {
|
||||
ec.Time = &types.Timestamp{Time: t}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDataSchema implements EventContextWriter.SetDataSchema
|
||||
func (ec *EventContextV1) SetDataSchema(u string) error {
|
||||
u = strings.TrimSpace(u)
|
||||
if u == "" {
|
||||
ec.DataSchema = nil
|
||||
return nil
|
||||
}
|
||||
pu, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ec.DataSchema = &types.URI{URL: *pu}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeprecatedSetDataContentEncoding implements EventContextWriter.DeprecatedSetDataContentEncoding
|
||||
func (ec *EventContextV1) DeprecatedSetDataContentEncoding(e string) error {
|
||||
return errors.New("deprecated: SetDataContentEncoding is not supported in v1.0 of CloudEvents")
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// DataContentEncodingKey is the key to DeprecatedDataContentEncoding for versions that do not support data content encoding
|
||||
// directly.
|
||||
DataContentEncodingKey = "datacontentencoding"
|
||||
)
|
||||
|
||||
func caseInsensitiveSearch(key string, space map[string]interface{}) (interface{}, bool) {
|
||||
lkey := strings.ToLower(key)
|
||||
for k, v := range space {
|
||||
if strings.EqualFold(lkey, strings.ToLower(k)) {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var IsAlphaNumeric = regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString
|
|
@ -1,161 +0,0 @@
|
|||
package extensions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding"
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/types"
|
||||
"github.com/lightstep/tracecontext.go/traceparent"
|
||||
"github.com/lightstep/tracecontext.go/tracestate"
|
||||
"go.opencensus.io/trace"
|
||||
octs "go.opencensus.io/trace/tracestate"
|
||||
)
|
||||
|
||||
const (
|
||||
TraceParentExtension = "traceparent"
|
||||
TraceStateExtension = "tracestate"
|
||||
)
|
||||
|
||||
// DistributedTracingExtension represents the extension for cloudevents context
|
||||
type DistributedTracingExtension struct {
|
||||
TraceParent string `json:"traceparent"`
|
||||
TraceState string `json:"tracestate"`
|
||||
}
|
||||
|
||||
// AddTracingAttributes adds the tracing attributes traceparent and tracestate to the cloudevents context
|
||||
func (d DistributedTracingExtension) AddTracingAttributes(e event.EventWriter) {
|
||||
if d.TraceParent != "" {
|
||||
value := reflect.ValueOf(d)
|
||||
typeOf := value.Type()
|
||||
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
k := strings.ToLower(typeOf.Field(i).Name)
|
||||
v := value.Field(i).Interface()
|
||||
if k == TraceStateExtension && v == "" {
|
||||
continue
|
||||
}
|
||||
e.SetExtension(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetDistributedTracingExtension(event event.Event) (DistributedTracingExtension, bool) {
|
||||
if tp, ok := event.Extensions()[TraceParentExtension]; ok {
|
||||
if tpStr, err := types.ToString(tp); err == nil {
|
||||
var tsStr string
|
||||
if ts, ok := event.Extensions()[TraceStateExtension]; ok {
|
||||
tsStr, _ = types.ToString(ts)
|
||||
}
|
||||
return DistributedTracingExtension{TraceParent: tpStr, TraceState: tsStr}, true
|
||||
}
|
||||
}
|
||||
return DistributedTracingExtension{}, false
|
||||
}
|
||||
|
||||
func (d *DistributedTracingExtension) ReadTransformer() binding.TransformerFunc {
|
||||
return func(reader binding.MessageMetadataReader, writer binding.MessageMetadataWriter) error {
|
||||
tp := reader.GetExtension(TraceParentExtension)
|
||||
if tp != nil {
|
||||
tpFormatted, err := types.Format(tp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.TraceParent = tpFormatted
|
||||
}
|
||||
ts := reader.GetExtension(TraceStateExtension)
|
||||
if ts != nil {
|
||||
tsFormatted, err := types.Format(ts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.TraceState = tsFormatted
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DistributedTracingExtension) WriteTransformer() binding.TransformerFunc {
|
||||
return func(reader binding.MessageMetadataReader, writer binding.MessageMetadataWriter) error {
|
||||
err := writer.SetExtension(TraceParentExtension, d.TraceParent)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return writer.SetExtension(TraceStateExtension, d.TraceState)
|
||||
}
|
||||
}
|
||||
|
||||
// FromSpanContext populates DistributedTracingExtension from a SpanContext.
|
||||
func FromSpanContext(sc trace.SpanContext) DistributedTracingExtension {
|
||||
tp := traceparent.TraceParent{
|
||||
TraceID: sc.TraceID,
|
||||
SpanID: sc.SpanID,
|
||||
Flags: traceparent.Flags{
|
||||
Recorded: sc.IsSampled(),
|
||||
},
|
||||
}
|
||||
|
||||
entries := make([]string, 0, len(sc.Tracestate.Entries()))
|
||||
for _, entry := range sc.Tracestate.Entries() {
|
||||
entries = append(entries, strings.Join([]string{entry.Key, entry.Value}, "="))
|
||||
}
|
||||
|
||||
return DistributedTracingExtension{
|
||||
TraceParent: tp.String(),
|
||||
TraceState: strings.Join(entries, ","),
|
||||
}
|
||||
}
|
||||
|
||||
// ToSpanContext creates a SpanContext from a DistributedTracingExtension instance.
|
||||
func (d DistributedTracingExtension) ToSpanContext() (trace.SpanContext, error) {
|
||||
tp, err := traceparent.ParseString(d.TraceParent)
|
||||
if err != nil {
|
||||
return trace.SpanContext{}, err
|
||||
}
|
||||
sc := trace.SpanContext{
|
||||
TraceID: tp.TraceID,
|
||||
SpanID: tp.SpanID,
|
||||
}
|
||||
if tp.Flags.Recorded {
|
||||
sc.TraceOptions |= 1
|
||||
}
|
||||
|
||||
if ts, err := tracestate.ParseString(d.TraceState); err == nil {
|
||||
entries := make([]octs.Entry, 0, len(ts))
|
||||
for _, member := range ts {
|
||||
var key string
|
||||
if member.Tenant != "" {
|
||||
// Due to github.com/lightstep/tracecontext.go/issues/6,
|
||||
// the meaning of Vendor and Tenant are swapped here.
|
||||
key = member.Vendor + "@" + member.Tenant
|
||||
} else {
|
||||
key = member.Vendor
|
||||
}
|
||||
entries = append(entries, octs.Entry{Key: key, Value: member.Value})
|
||||
}
|
||||
sc.Tracestate, _ = octs.New(nil, entries...)
|
||||
}
|
||||
|
||||
return sc, nil
|
||||
}
|
||||
|
||||
func (d DistributedTracingExtension) StartChildSpan(ctx context.Context, name string, opts ...trace.StartOption) (context.Context, *trace.Span) {
|
||||
if sc, err := d.ToSpanContext(); err == nil {
|
||||
tSpan := trace.FromContext(ctx)
|
||||
ctx, span := trace.StartSpanWithRemoteParent(ctx, name, sc, opts...)
|
||||
if tSpan != nil {
|
||||
// Add link to the previous in-process trace.
|
||||
tsc := tSpan.SpanContext()
|
||||
span.AddLink(trace.Link{
|
||||
TraceID: tsc.TraceID,
|
||||
SpanID: tsc.SpanID,
|
||||
Type: trace.LinkTypeParent,
|
||||
})
|
||||
}
|
||||
return ctx, span
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
// Package extensions provides implementations of common event extensions.
|
||||
package extensions
|
|
@ -1,29 +0,0 @@
|
|||
module github.com/cloudevents/sdk-go/v2
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.40.0
|
||||
contrib.go.opencensus.io/exporter/prometheus v0.1.0
|
||||
github.com/Azure/go-amqp v0.12.7
|
||||
github.com/Shopify/sarama v1.19.0
|
||||
github.com/cloudevents/sdk-go v1.1.2
|
||||
github.com/cucumber/godog v0.9.0
|
||||
github.com/cucumber/messages-go/v10 v10.0.3
|
||||
github.com/google/go-cmp v0.4.0
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gorilla/mux v1.6.2
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac
|
||||
github.com/nats-io/nats-streaming-server v0.17.0 // indirect
|
||||
github.com/nats-io/nats.go v1.9.1
|
||||
github.com/nats-io/stan.go v0.6.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/valyala/bytebufferpool v1.0.0
|
||||
go.opencensus.io v0.22.0
|
||||
go.uber.org/zap v1.10.0
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
||||
google.golang.org/api v0.15.0
|
||||
google.golang.org/grpc v1.26.0
|
||||
)
|
||||
|
||||
go 1.13
|
|
@ -1,385 +0,0 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.40.0 h1:FjSY7bOj+WzJe6TZRVtXI2b9kAYvtNg4lMbcH2+MUkk=
|
||||
cloud.google.com/go v0.40.0/go.mod h1:Tk58MuI9rbLMKlAjeO/bDnteAx7tX2gJIXw4T5Jwlro=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.4.12 h1:jGFvw3l57ViIVEPKKEUXPcLYIXJmQxLUh6ey1eJhwyc=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
|
||||
contrib.go.opencensus.io/exporter/prometheus v0.1.0 h1:SByaIoWwNgMdPSgl5sMqM2KDE5H/ukPWBRo314xiDvg=
|
||||
contrib.go.opencensus.io/exporter/prometheus v0.1.0/go.mod h1:cGFniUXGZlKRjzOyuZJ6mgB+PgBcCIa79kEKR8YCW+A=
|
||||
github.com/Azure/azure-sdk-for-go v30.1.0+incompatible h1:HyYPft8wXpxMd0kfLtXo6etWcO+XuPbLkcgx9g2cqxU=
|
||||
github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-amqp v0.12.7 h1:/Uyqh30J5JrDFAOERQtEqP0qPWkrNXxr94vRnSa54Ac=
|
||||
github.com/Azure/go-amqp v0.12.7/go.mod h1:qApuH6OFTSKZFmCOxccvAv5rLizBQf4v8pRmG138DPo=
|
||||
github.com/Azure/go-autorest/autorest v0.2.0 h1:zBtSTOQTtjzHVRe+mhkiHvHwRTKHhjBEyo1m6DfI3So=
|
||||
github.com/Azure/go-autorest/autorest v0.2.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.1.0 h1:RSw/7EAullliqwkZvgIGDYZWQm1PGKXI8c4aY/87yuU=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0 h1:Kx+AUU2Te+A3JIyYn6Dfs+cFgx5XorQKuIXrZGoq/SI=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/to v0.2.0 h1:nQOZzFCudTh+TvquAtCRjM01VEYx85e9qbwt5ncW4L8=
|
||||
github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.1.0 h1:ISSNzGUh+ZSzizJWOWzs8bwpXIePbGLW4z/AmUFGH5A=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
|
||||
github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.1.0 h1:TRBxC5Pj/fIuh4Qob0ZpkggbfT8RC0SubHbpV3p4/Vc=
|
||||
github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM=
|
||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=
|
||||
github.com/aslakhellesoy/gox v1.0.100/go.mod h1:AJl542QsKKG96COVsv0N74HHzVQgDIQPceVUh1aeU2M=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudevents/sdk-go v1.1.2 h1:mg/7d+BzubBPrPpH1bdeF85BQZYV85j7Ljqat3+m+qE=
|
||||
github.com/cloudevents/sdk-go v1.1.2/go.mod h1:ss+jWJ88wypiewnPEzChSBzTYXGpdcILoN9YHk8uhTQ=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cucumber/gherkin-go/v11 v11.0.0 h1:cwVwN1Qn2VRSfHZNLEh5x00tPBmZcjATBWDpxsR5Xug=
|
||||
github.com/cucumber/gherkin-go/v11 v11.0.0/go.mod h1:CX33k2XU2qog4e+TFjOValoq6mIUq0DmVccZs238R9w=
|
||||
github.com/cucumber/godog v0.9.0 h1:QOb8wyC7f+FVFXzY3RdgowwJUb4WeJfqbnQqaH4jp+A=
|
||||
github.com/cucumber/godog v0.9.0/go.mod h1:roWCHkpeK6UTOyIRRl7IR+fgfBeZ4vZR7OSq2J/NbM4=
|
||||
github.com/cucumber/messages-go/v10 v10.0.1/go.mod h1:kA5T38CBlBbYLU12TIrJ4fk4wSkVVOgyh7Enyy8WnSg=
|
||||
github.com/cucumber/messages-go/v10 v10.0.3 h1:m/9SD/K/A15WP7i1aemIv7cwvUw+viS51Ui5HBw1cdE=
|
||||
github.com/cucumber/messages-go/v10 v10.0.3/go.mod h1:9jMZ2Y8ZxjLY6TG2+x344nt5rXstVVDYSdS5ySfI1WY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJlb8Kqsd41CTE=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v0.9.1 h1:9PZfAcVEvez4yhLH2TBU64/h/z4xlFI80cWXRrxuKuM=
|
||||
github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
|
||||
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/raft v1.1.1 h1:HJr7UE1x/JrJSc9Oy6aDBHtNHUUBHjcQjTgvUVihoZs=
|
||||
github.com/hashicorp/raft v1.1.1/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8=
|
||||
github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
||||
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac h1:+2b6iGRJe3hvV/yVXrd41yVEjxuFHxasJqDhkIjS4gk=
|
||||
github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac/go.mod h1:Frd2bnT3w5FB5q49ENTfVlztJES+1k/7lyWX2+9gq/M=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/jwt v0.3.2 h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI=
|
||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||
github.com/nats-io/nats-server/v2 v2.1.2 h1:i2Ly0B+1+rzNZHHWtD4ZwKi+OU5l+uQo1iDHZ2PmiIc=
|
||||
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
||||
github.com/nats-io/nats-server/v2 v2.1.4 h1:BILRnsJ2Yb/fefiFbBWADpViGF69uh4sxe8poVDQ06g=
|
||||
github.com/nats-io/nats-server/v2 v2.1.4/go.mod h1:Jw1Z28soD/QasIA2uWjXyM9El1jly3YwyFOuR8tH1rg=
|
||||
github.com/nats-io/nats-streaming-server v0.17.0 h1:eYhSmjRmRsCYNsoUshmZ+RgKbhq6B+7FvMHXo3M5yMs=
|
||||
github.com/nats-io/nats-streaming-server v0.17.0/go.mod h1:ewPBEsmp62Znl3dcRsYtlcfwudxHEdYMtYqUQSt4fE0=
|
||||
github.com/nats-io/nats.go v1.9.1 h1:ik3HbLhZ0YABLto7iX80pZLPw/6dx3T+++MZJwLnMrQ=
|
||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nkeys v0.1.3 h1:6JrEfig+HzTH85yxzhSVbjHRJv9cn0p6n3IngIcM5/k=
|
||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/nats-io/stan.go v0.6.0 h1:26IJPeykh88d8KVLT4jJCIxCyUBOC5/IQup8oWD/QYY=
|
||||
github.com/nats-io/stan.go v0.6.0/go.mod h1:eIcD5bi3pqbHT/xIIvXMwvzXYElgouBvaVRftaE+eac=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 h1:D+CiwcpGTW6pL6bv6KI3KbyEyCKyS+1JWS2h8PNDnGA=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f h1:BVwpUVJDADN2ufcGik7W992pyps0wZ888b/y9GXcLTU=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1 h1:/K3IL0Z1quvmJ7X0A1AwNEK7CRkVK3YwfOU/QAL4WGg=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 h1:+ELyKg6m8UBf0nPFSqD0mi7zUfwPyXo23HNjMnXPz7w=
|
||||
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200206161412-a0c6ece9d31a h1:aczoJ0HPNE92XKa7DrIzkNN6esOKO2TBwiiYoKcINhA=
|
||||
golang.org/x/crypto v0.0.0-20200206161412-a0c6ece9d31a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
|
||||
google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
pack.ag/amqp v0.11.0 h1:ot/IA0enDkt4/c8xfbCO7AZzjM4bHys/UffnFmnHUnU=
|
||||
pack.ag/amqp v0.11.0/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
|
@ -1,4 +0,0 @@
|
|||
/*
|
||||
Package observability holds metrics and tracing recording implementations.
|
||||
*/
|
||||
package observability
|
|
@ -1,22 +0,0 @@
|
|||
package observability
|
||||
|
||||
import (
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
var (
|
||||
// KeyMethod is the tag used for marking method on a metric.
|
||||
KeyMethod, _ = tag.NewKey("method")
|
||||
// KeyResult is the tag used for marking result on a metric.
|
||||
KeyResult, _ = tag.NewKey("result")
|
||||
)
|
||||
|
||||
const (
|
||||
// ClientSpanName is the key used to start spans from the client.
|
||||
ClientSpanName = "cloudevents.client"
|
||||
|
||||
// ResultError is a shared result tag value for error.
|
||||
ResultError = "error"
|
||||
// ResultOK is a shared result tag value for success.
|
||||
ResultOK = "success"
|
||||
)
|
|
@ -1,87 +0,0 @@
|
|||
package observability
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
// Observable represents the the customization used by the Reporter for a given
|
||||
// measurement and trace for a single method.
|
||||
type Observable interface {
|
||||
MethodName() string
|
||||
LatencyMs() *stats.Float64Measure
|
||||
}
|
||||
|
||||
// Reporter represents a running latency counter. When Error or OK are
|
||||
// called, the latency is calculated. Error or OK are only allowed to
|
||||
// be called once.
|
||||
type Reporter interface {
|
||||
Error()
|
||||
OK()
|
||||
}
|
||||
|
||||
type reporter struct {
|
||||
ctx context.Context
|
||||
on Observable
|
||||
start time.Time
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// LatencyTags returns all tags used for Latency measurements.
|
||||
func LatencyTags() []tag.Key {
|
||||
return []tag.Key{KeyMethod, KeyResult}
|
||||
}
|
||||
|
||||
// EnableTracing is deprecated. Tracing is always enabled.
|
||||
func EnableTracing(enabled bool) {}
|
||||
|
||||
// NewReporter creates and returns a reporter wrapping the provided Observable.
|
||||
func NewReporter(ctx context.Context, on Observable) (context.Context, Reporter) {
|
||||
r := &reporter{
|
||||
ctx: ctx,
|
||||
on: on,
|
||||
start: time.Now(),
|
||||
}
|
||||
r.tagMethod()
|
||||
return ctx, r
|
||||
}
|
||||
|
||||
func (r *reporter) tagMethod() {
|
||||
var err error
|
||||
r.ctx, err = tag.New(r.ctx, tag.Insert(KeyMethod, r.on.MethodName()))
|
||||
if err != nil {
|
||||
panic(err) // or ignore?
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reporter) record() {
|
||||
ms := float64(time.Since(r.start) / time.Millisecond)
|
||||
stats.Record(r.ctx, r.on.LatencyMs().M(ms))
|
||||
}
|
||||
|
||||
// Error records the result as an error.
|
||||
func (r *reporter) Error() {
|
||||
r.once.Do(func() {
|
||||
r.result(ResultError)
|
||||
})
|
||||
}
|
||||
|
||||
// OK records the result as a success.
|
||||
func (r *reporter) OK() {
|
||||
r.once.Do(func() {
|
||||
r.result(ResultOK)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *reporter) result(v string) {
|
||||
var err error
|
||||
r.ctx, err = tag.New(r.ctx, tag.Insert(KeyResult, v))
|
||||
if err != nil {
|
||||
panic(err) // or ignore?
|
||||
}
|
||||
r.record()
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
Package protocol defines interfaces to decouple the client package
|
||||
from protocol implementations.
|
||||
|
||||
Most event sender and receiver applications should not use this
|
||||
package, they should use the client package. This package is for
|
||||
infrastructure developers implementing new transports, or intermediary
|
||||
components like importers, channels or brokers.
|
||||
|
||||
Available protocols:
|
||||
|
||||
* HTTP (using net/http)
|
||||
* Kafka (using github.com/Shopify/sarama)
|
||||
* AMQP (using pack.ag/amqp)
|
||||
* Go Channels
|
||||
* Nats
|
||||
* Nats Streaming (stan)
|
||||
* Google PubSub
|
||||
|
||||
*/
|
||||
package protocol
|
|
@ -1,37 +0,0 @@
|
|||
package protocol
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ErrTransportMessageConversion is an error produced when the transport
|
||||
// message can not be converted.
|
||||
type ErrTransportMessageConversion struct {
|
||||
fatal bool
|
||||
handled bool
|
||||
transport string
|
||||
message string
|
||||
}
|
||||
|
||||
// NewErrTransportMessageConversion makes a new ErrTransportMessageConversion.
|
||||
func NewErrTransportMessageConversion(transport, message string, handled, fatal bool) *ErrTransportMessageConversion {
|
||||
return &ErrTransportMessageConversion{
|
||||
transport: transport,
|
||||
message: message,
|
||||
handled: handled,
|
||||
fatal: fatal,
|
||||
}
|
||||
}
|
||||
|
||||
// IsFatal reports if this error should be considered fatal.
|
||||
func (e *ErrTransportMessageConversion) IsFatal() bool {
|
||||
return e.fatal
|
||||
}
|
||||
|
||||
// Handled reports if this error should be considered accepted and no further action.
|
||||
func (e *ErrTransportMessageConversion) Handled() bool {
|
||||
return e.handled
|
||||
}
|
||||
|
||||
// Error implements error.Error
|
||||
func (e *ErrTransportMessageConversion) Error() string {
|
||||
return fmt.Sprintf("transport %s failed to convert message: %s", e.transport, e.message)
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
/*
|
||||
Package http implements an HTTP binding using net/http module
|
||||
*/
|
||||
package http
|
|
@ -1,33 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding/spec"
|
||||
)
|
||||
|
||||
var attributeHeadersMapping map[string]string
|
||||
|
||||
func init() {
|
||||
attributeHeadersMapping = make(map[string]string)
|
||||
for _, v := range specs.Versions() {
|
||||
for _, a := range v.Attributes() {
|
||||
if a.Kind() == spec.DataContentType {
|
||||
attributeHeadersMapping[a.Name()] = ContentType
|
||||
} else {
|
||||
attributeHeadersMapping[a.Name()] = textproto.CanonicalMIMEHeaderKey(prefix + a.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func extNameToHeaderName(name string) string {
|
||||
var b strings.Builder
|
||||
b.Grow(len(name) + len(prefix))
|
||||
b.WriteString(prefix)
|
||||
b.WriteRune(unicode.ToUpper(rune(name[0])))
|
||||
b.WriteString(name[1:])
|
||||
return b.String()
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
nethttp "net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding"
|
||||
"github.com/cloudevents/sdk-go/v2/binding/format"
|
||||
"github.com/cloudevents/sdk-go/v2/binding/spec"
|
||||
)
|
||||
|
||||
const prefix = "Ce-"
|
||||
|
||||
var specs = spec.WithPrefixMatchExact(
|
||||
func(s string) string {
|
||||
if s == "datacontenttype" {
|
||||
return "Content-Type"
|
||||
} else {
|
||||
return textproto.CanonicalMIMEHeaderKey("Ce-" + s)
|
||||
}
|
||||
},
|
||||
"Ce-",
|
||||
)
|
||||
|
||||
const ContentType = "Content-Type"
|
||||
const ContentLength = "Content-Length"
|
||||
|
||||
// Message holds the Header and Body of a HTTP Request or Response.
|
||||
// The Message instance *must* be constructed from NewMessage function.
|
||||
// This message *cannot* be read several times. In order to read it more times, buffer it using binding/buffering methods
|
||||
type Message struct {
|
||||
Header nethttp.Header
|
||||
BodyReader io.ReadCloser
|
||||
OnFinish func(error) error
|
||||
|
||||
format format.Format
|
||||
version spec.Version
|
||||
}
|
||||
|
||||
// Check if http.Message implements binding.Message
|
||||
var _ binding.Message = (*Message)(nil)
|
||||
var _ binding.MessageMetadataReader = (*Message)(nil)
|
||||
|
||||
// NewMessage returns a binding.Message with header and data.
|
||||
// The returned binding.Message *cannot* be read several times. In order to read it more times, buffer it using binding/buffering methods
|
||||
func NewMessage(header nethttp.Header, body io.ReadCloser) *Message {
|
||||
m := Message{Header: header}
|
||||
if body != nil {
|
||||
m.BodyReader = body
|
||||
}
|
||||
if m.format = format.Lookup(header.Get(ContentType)); m.format == nil {
|
||||
m.version = specs.Version(m.Header.Get(specs.PrefixedSpecVersionName()))
|
||||
}
|
||||
return &m
|
||||
}
|
||||
|
||||
// NewMessageFromHttpRequest returns a binding.Message with header and data.
|
||||
// The returned binding.Message *cannot* be read several times. In order to read it more times, buffer it using binding/buffering methods
|
||||
func NewMessageFromHttpRequest(req *nethttp.Request) *Message {
|
||||
if req == nil {
|
||||
return nil
|
||||
}
|
||||
return NewMessage(req.Header, req.Body)
|
||||
}
|
||||
|
||||
// NewMessageFromHttpResponse returns a binding.Message with header and data.
|
||||
// The returned binding.Message *cannot* be read several times. In order to read it more times, buffer it using binding/buffering methods
|
||||
func NewMessageFromHttpResponse(resp *nethttp.Response) *Message {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
msg := NewMessage(resp.Header, resp.Body)
|
||||
return msg
|
||||
}
|
||||
|
||||
func (m *Message) ReadEncoding() binding.Encoding {
|
||||
if m.version != nil {
|
||||
return binding.EncodingBinary
|
||||
}
|
||||
if m.format != nil {
|
||||
return binding.EncodingStructured
|
||||
}
|
||||
return binding.EncodingUnknown
|
||||
}
|
||||
|
||||
func (m *Message) ReadStructured(ctx context.Context, encoder binding.StructuredWriter) error {
|
||||
if m.format == nil {
|
||||
return binding.ErrNotStructured
|
||||
} else {
|
||||
return encoder.SetStructuredEvent(ctx, m.format, m.BodyReader)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Message) ReadBinary(ctx context.Context, encoder binding.BinaryWriter) (err error) {
|
||||
if m.version == nil {
|
||||
return binding.ErrNotBinary
|
||||
}
|
||||
|
||||
for k, v := range m.Header {
|
||||
attr := m.version.Attribute(k)
|
||||
if attr != nil {
|
||||
err = encoder.SetAttribute(attr, v[0])
|
||||
} else if strings.HasPrefix(k, prefix) {
|
||||
// Trim Prefix + To lower
|
||||
var b strings.Builder
|
||||
b.Grow(len(k) - len(prefix))
|
||||
b.WriteRune(unicode.ToLower(rune(k[len(prefix)])))
|
||||
b.WriteString(k[len(prefix)+1:])
|
||||
err = encoder.SetExtension(b.String(), v[0])
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if m.BodyReader != nil {
|
||||
err = encoder.SetData(m.BodyReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Message) GetAttribute(k spec.Kind) (spec.Attribute, interface{}) {
|
||||
attr := m.version.AttributeFromKind(k)
|
||||
if attr != nil {
|
||||
h := m.Header[attributeHeadersMapping[attr.Name()]]
|
||||
if h != nil {
|
||||
return attr, h[0]
|
||||
}
|
||||
return attr, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *Message) GetExtension(name string) interface{} {
|
||||
h := m.Header[extNameToHeaderName(name)]
|
||||
if h != nil {
|
||||
return h[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Message) Finish(err error) error {
|
||||
if m.BodyReader != nil {
|
||||
_ = m.BodyReader.Close()
|
||||
}
|
||||
if m.OnFinish != nil {
|
||||
return m.OnFinish(err)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,197 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
nethttp "net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Option is the function signature required to be considered an http.Option.
|
||||
type Option func(*Protocol) error
|
||||
|
||||
// WithTarget sets the outbound recipient of cloudevents when using an HTTP
|
||||
// request.
|
||||
func WithTarget(targetUrl string) Option {
|
||||
return func(p *Protocol) error {
|
||||
if p == nil {
|
||||
return fmt.Errorf("http target option can not set nil protocol")
|
||||
}
|
||||
targetUrl = strings.TrimSpace(targetUrl)
|
||||
if targetUrl != "" {
|
||||
var err error
|
||||
var target *url.URL
|
||||
target, err = url.Parse(targetUrl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("http target option failed to parse target url: %s", err.Error())
|
||||
}
|
||||
|
||||
p.Target = target
|
||||
|
||||
if p.RequestTemplate == nil {
|
||||
p.RequestTemplate = &nethttp.Request{
|
||||
Method: nethttp.MethodPost,
|
||||
}
|
||||
}
|
||||
p.RequestTemplate.URL = target
|
||||
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("http target option was empty string")
|
||||
}
|
||||
}
|
||||
|
||||
// WithHeader sets an additional default outbound header for all cloudevents
|
||||
// when using an HTTP request.
|
||||
func WithHeader(key, value string) Option {
|
||||
return func(p *Protocol) error {
|
||||
if p == nil {
|
||||
return fmt.Errorf("http header option can not set nil protocol")
|
||||
}
|
||||
key = strings.TrimSpace(key)
|
||||
if key != "" {
|
||||
if p.RequestTemplate == nil {
|
||||
p.RequestTemplate = &nethttp.Request{
|
||||
Method: nethttp.MethodPost,
|
||||
}
|
||||
}
|
||||
if p.RequestTemplate.Header == nil {
|
||||
p.RequestTemplate.Header = nethttp.Header{}
|
||||
}
|
||||
p.RequestTemplate.Header.Add(key, value)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("http header option was empty string")
|
||||
}
|
||||
}
|
||||
|
||||
// WithShutdownTimeout sets the shutdown timeout when the http server is being shutdown.
|
||||
func WithShutdownTimeout(timeout time.Duration) Option {
|
||||
return func(t *Protocol) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http shutdown timeout option can not set nil protocol")
|
||||
}
|
||||
t.ShutdownTimeout = timeout
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func checkListen(t *Protocol, prefix string) error {
|
||||
switch {
|
||||
case t.Port != nil:
|
||||
return fmt.Errorf("%v port already set", prefix)
|
||||
case t.listener != nil:
|
||||
return fmt.Errorf("%v listener already set", prefix)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithPort sets the listening port for StartReceiver.
|
||||
// Only one of WithListener or WithPort is allowed.
|
||||
func WithPort(port int) Option {
|
||||
return func(t *Protocol) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http port option can not set nil protocol")
|
||||
}
|
||||
if port < 0 || port > 65535 {
|
||||
return fmt.Errorf("http port option was given an invalid port: %d", port)
|
||||
}
|
||||
if err := checkListen(t, "http port option"); err != nil {
|
||||
return err
|
||||
}
|
||||
t.setPort(port)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithListener sets the listener for StartReceiver.
|
||||
// Only one of WithListener or WithPort is allowed.
|
||||
func WithListener(l net.Listener) Option {
|
||||
return func(t *Protocol) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http listener option can not set nil protocol")
|
||||
}
|
||||
if err := checkListen(t, "http port option"); err != nil {
|
||||
return err
|
||||
}
|
||||
t.listener = l
|
||||
_, err := t.listen()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithPath sets the path to receive cloudevents on for HTTP transports.
|
||||
func WithPath(path string) Option {
|
||||
return func(t *Protocol) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http path option can not set nil protocol")
|
||||
}
|
||||
path = strings.TrimSpace(path)
|
||||
if len(path) == 0 {
|
||||
return fmt.Errorf("http path option was given an invalid path: %q", path)
|
||||
}
|
||||
t.Path = path
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithMethod sets the HTTP verb (GET, POST, PUT, etc.) to use
|
||||
// when using an HTTP request.
|
||||
func WithMethod(method string) Option {
|
||||
return func(p *Protocol) error {
|
||||
if p == nil {
|
||||
return fmt.Errorf("http method option can not set nil protocol")
|
||||
}
|
||||
method = strings.TrimSpace(method)
|
||||
if method != "" {
|
||||
if p.RequestTemplate == nil {
|
||||
p.RequestTemplate = &nethttp.Request{}
|
||||
}
|
||||
p.RequestTemplate.Method = method
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("http method option was empty string")
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Middleware is a function that takes an existing http.Handler and wraps it in middleware,
|
||||
// returning the wrapped http.Handler.
|
||||
type Middleware func(next nethttp.Handler) nethttp.Handler
|
||||
|
||||
// WithMiddleware adds an HTTP middleware to the transport. It may be specified multiple times.
|
||||
// Middleware is applied to everything before it. For example
|
||||
// `NewClient(WithMiddleware(foo), WithMiddleware(bar))` would result in `bar(foo(original))`.
|
||||
func WithMiddleware(middleware Middleware) Option {
|
||||
return func(t *Protocol) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http middleware option can not set nil protocol")
|
||||
}
|
||||
t.middleware = append(t.middleware, middleware)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithRoundTripper sets the HTTP RoundTripper.
|
||||
func WithRoundTripper(roundTripper nethttp.RoundTripper) Option {
|
||||
return func(t *Protocol) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http round tripper option can not set nil protocol")
|
||||
}
|
||||
t.roundTripper = roundTripper
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithClient sets the protocol client
|
||||
func WithClient(client nethttp.Client) Option {
|
||||
return func(p *Protocol) error {
|
||||
if p == nil {
|
||||
return fmt.Errorf("client option can not set nil protocol")
|
||||
}
|
||||
p.Client = &client
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -1,261 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding"
|
||||
cecontext "github.com/cloudevents/sdk-go/v2/context"
|
||||
"github.com/cloudevents/sdk-go/v2/protocol"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultShutdownTimeout defines the default timeout given to the http.Server when calling Shutdown.
|
||||
DefaultShutdownTimeout = time.Minute * 1
|
||||
)
|
||||
|
||||
// Protocol acts as both a http client and a http handler.
|
||||
type Protocol struct {
|
||||
Target *url.URL
|
||||
RequestTemplate *http.Request
|
||||
Client *http.Client
|
||||
incoming chan msgErr
|
||||
|
||||
// To support Opener:
|
||||
|
||||
// ShutdownTimeout defines the timeout given to the http.Server when calling Shutdown.
|
||||
// If nil, DefaultShutdownTimeout is used.
|
||||
ShutdownTimeout time.Duration
|
||||
|
||||
// Port is the port to bind the receiver to. Defaults to 8080.
|
||||
Port *int
|
||||
// Path is the path to bind the receiver to. Defaults to "/".
|
||||
Path string
|
||||
|
||||
// Receive Mutex
|
||||
reMu sync.Mutex
|
||||
// Handler is the handler the http Server will use. Use this to reuse the
|
||||
// http server. If nil, the Protocol will create a one.
|
||||
Handler *http.ServeMux
|
||||
listener net.Listener
|
||||
roundTripper http.RoundTripper
|
||||
server *http.Server
|
||||
handlerRegistered bool
|
||||
middleware []Middleware
|
||||
}
|
||||
|
||||
func New(opts ...Option) (*Protocol, error) {
|
||||
p := &Protocol{
|
||||
incoming: make(chan msgErr),
|
||||
}
|
||||
if err := p.applyOptions(opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.Client == nil {
|
||||
p.Client = http.DefaultClient
|
||||
}
|
||||
|
||||
if p.roundTripper != nil {
|
||||
p.Client.Transport = p.roundTripper
|
||||
}
|
||||
|
||||
if p.ShutdownTimeout == 0 {
|
||||
p.ShutdownTimeout = DefaultShutdownTimeout
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *Protocol) applyOptions(opts ...Option) error {
|
||||
for _, fn := range opts {
|
||||
if err := fn(p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send implements binding.Sender
|
||||
func (p *Protocol) Send(ctx context.Context, m binding.Message, transformers ...binding.Transformer) error {
|
||||
if ctx == nil {
|
||||
return fmt.Errorf("nil Context")
|
||||
} else if m == nil {
|
||||
return fmt.Errorf("nil Message")
|
||||
}
|
||||
|
||||
_, err := p.Request(ctx, m, transformers...)
|
||||
return err
|
||||
}
|
||||
|
||||
// Request implements binding.Requester
|
||||
func (p *Protocol) Request(ctx context.Context, m binding.Message, transformers ...binding.Transformer) (binding.Message, error) {
|
||||
if ctx == nil {
|
||||
return nil, fmt.Errorf("nil Context")
|
||||
} else if m == nil {
|
||||
return nil, fmt.Errorf("nil Message")
|
||||
}
|
||||
|
||||
var err error
|
||||
defer func() { _ = m.Finish(err) }()
|
||||
|
||||
req := p.makeRequest(ctx)
|
||||
|
||||
if p.Client == nil || req == nil || req.URL == nil {
|
||||
return nil, fmt.Errorf("not initialized: %#v", p)
|
||||
}
|
||||
|
||||
if err = WriteRequest(ctx, m, req, transformers...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.do(ctx, req)
|
||||
}
|
||||
|
||||
func (p *Protocol) makeRequest(ctx context.Context) *http.Request {
|
||||
// TODO: support custom headers from context?
|
||||
req := &http.Request{
|
||||
Method: http.MethodPost,
|
||||
Header: make(http.Header),
|
||||
// TODO: HeaderFrom(ctx),
|
||||
}
|
||||
|
||||
if p.RequestTemplate != nil {
|
||||
req.Method = p.RequestTemplate.Method
|
||||
req.URL = p.RequestTemplate.URL
|
||||
req.Close = p.RequestTemplate.Close
|
||||
req.Host = p.RequestTemplate.Host
|
||||
copyHeadersEnsure(p.RequestTemplate.Header, &req.Header)
|
||||
}
|
||||
|
||||
if p.Target != nil {
|
||||
req.URL = p.Target
|
||||
}
|
||||
|
||||
// Override the default request with target from context.
|
||||
if target := cecontext.TargetFrom(ctx); target != nil {
|
||||
req.URL = target
|
||||
}
|
||||
return req.WithContext(ctx)
|
||||
}
|
||||
|
||||
// Ensure to is a non-nil map before copying
|
||||
func copyHeadersEnsure(from http.Header, to *http.Header) {
|
||||
if len(from) > 0 {
|
||||
if *to == nil {
|
||||
*to = http.Header{}
|
||||
}
|
||||
copyHeaders(from, *to)
|
||||
}
|
||||
}
|
||||
|
||||
func copyHeaders(from, to http.Header) {
|
||||
if from == nil || to == nil {
|
||||
return
|
||||
}
|
||||
for header, values := range from {
|
||||
for _, value := range values {
|
||||
to.Add(header, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Receive the next incoming HTTP request as a CloudEvent.
|
||||
// Returns non-nil error if the incoming HTTP request fails to parse as a CloudEvent
|
||||
// Returns io.EOF if the receiver is closed.
|
||||
func (p *Protocol) Receive(ctx context.Context) (binding.Message, error) {
|
||||
if ctx == nil {
|
||||
return nil, fmt.Errorf("nil Context")
|
||||
}
|
||||
|
||||
msg, fn, err := p.Respond(ctx)
|
||||
// No-op the response when finish is invoked.
|
||||
if msg != nil {
|
||||
return binding.WithFinish(msg, func(err error) {
|
||||
if fn != nil {
|
||||
_ = fn(ctx, nil, nil)
|
||||
}
|
||||
}), err
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Respond receives the next incoming HTTP request as a CloudEvent and waits
|
||||
// for the response callback to invoked before continuing.
|
||||
// Returns non-nil error if the incoming HTTP request fails to parse as a CloudEvent
|
||||
// Returns io.EOF if the receiver is closed.
|
||||
func (p *Protocol) Respond(ctx context.Context) (binding.Message, protocol.ResponseFn, error) {
|
||||
if ctx == nil {
|
||||
return nil, nil, fmt.Errorf("nil Context")
|
||||
}
|
||||
|
||||
select {
|
||||
case in, ok := <-p.incoming:
|
||||
if !ok {
|
||||
return nil, nil, io.EOF
|
||||
}
|
||||
return in.msg, in.respFn, in.err
|
||||
case <-ctx.Done():
|
||||
return nil, nil, io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
type msgErr struct {
|
||||
msg *Message
|
||||
respFn protocol.ResponseFn
|
||||
err error
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler.
|
||||
// Blocks until ResponseFn is invoked.
|
||||
func (p *Protocol) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
m := NewMessageFromHttpRequest(req)
|
||||
if m == nil || m.ReadEncoding() == binding.EncodingUnknown {
|
||||
p.incoming <- msgErr{msg: nil, err: binding.ErrUnknownEncoding}
|
||||
return // if there was no message, return.
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
var finishErr error
|
||||
|
||||
m.OnFinish = func(err error) error {
|
||||
finishErr = err
|
||||
return nil
|
||||
}
|
||||
|
||||
var fn protocol.ResponseFn = func(ctx context.Context, resp binding.Message, er protocol.Result, transformers ...binding.Transformer) error {
|
||||
// Unblock the ServeHTTP after the reply is written
|
||||
defer func() {
|
||||
done <- struct{}{}
|
||||
}()
|
||||
status := http.StatusOK
|
||||
if finishErr != nil {
|
||||
http.Error(rw, fmt.Sprintf("cannot forward CloudEvent: %v", finishErr), http.StatusInternalServerError)
|
||||
}
|
||||
if er != nil {
|
||||
var result *Result
|
||||
if protocol.ResultAs(er, &result) {
|
||||
if result.StatusCode > 100 && result.StatusCode < 600 {
|
||||
status = result.StatusCode
|
||||
}
|
||||
}
|
||||
}
|
||||
if resp != nil {
|
||||
err := WriteResponseWriter(ctx, resp, status, rw, transformers...)
|
||||
return resp.Finish(err)
|
||||
}
|
||||
rw.WriteHeader(status)
|
||||
return nil
|
||||
}
|
||||
|
||||
p.incoming <- msgErr{msg: m, respFn: fn} // Send to Request
|
||||
// Block until ResponseFn is invoked
|
||||
<-done
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/protocol"
|
||||
"go.opencensus.io/plugin/ochttp"
|
||||
"go.opencensus.io/plugin/ochttp/propagation/tracecontext"
|
||||
)
|
||||
|
||||
var _ protocol.Opener = (*Protocol)(nil)
|
||||
|
||||
func (p *Protocol) OpenInbound(ctx context.Context) error {
|
||||
p.reMu.Lock()
|
||||
defer p.reMu.Unlock()
|
||||
|
||||
if p.Handler == nil {
|
||||
p.Handler = http.NewServeMux()
|
||||
}
|
||||
|
||||
if !p.handlerRegistered {
|
||||
// handler.Handle might panic if the user tries to use the same path as the sdk.
|
||||
p.Handler.Handle(p.GetPath(), p)
|
||||
p.handlerRegistered = true
|
||||
}
|
||||
|
||||
addr, err := p.listen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.server = &http.Server{
|
||||
Addr: addr.String(),
|
||||
Handler: &ochttp.Handler{
|
||||
Propagation: &tracecontext.HTTPFormat{},
|
||||
Handler: attachMiddleware(p.Handler, p.middleware),
|
||||
FormatSpanName: formatSpanName,
|
||||
},
|
||||
}
|
||||
|
||||
// Shutdown
|
||||
defer func() {
|
||||
_ = p.server.Close()
|
||||
p.server = nil
|
||||
}()
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
errChan <- p.server.Serve(p.listener)
|
||||
}()
|
||||
|
||||
// wait for the server to return or ctx.Done().
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Try a gracefully shutdown.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), p.ShutdownTimeout)
|
||||
defer cancel()
|
||||
err := p.server.Shutdown(ctx)
|
||||
<-errChan // Wait for server goroutine to exit
|
||||
return err
|
||||
case err := <-errChan:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// GetPort returns the listening port.
|
||||
// Returns -1 if there is a listening error.
|
||||
// Note this will call net.Listen() if the listener is not already started.
|
||||
func (p *Protocol) GetPort() int {
|
||||
// Ensure we have a listener and therefore a port.
|
||||
if _, err := p.listen(); err == nil || p.Port != nil {
|
||||
return *p.Port
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func formatSpanName(r *http.Request) string {
|
||||
return "cloudevents.http." + r.URL.Path
|
||||
}
|
||||
|
||||
func (p *Protocol) setPort(port int) {
|
||||
if p.Port == nil {
|
||||
p.Port = new(int)
|
||||
}
|
||||
*p.Port = port
|
||||
}
|
||||
|
||||
// listen if not already listening, update t.Port
|
||||
func (p *Protocol) listen() (net.Addr, error) {
|
||||
if p.listener == nil {
|
||||
port := 8080
|
||||
if p.Port != nil {
|
||||
port = *p.Port
|
||||
if port < 0 || port > 65535 {
|
||||
return nil, fmt.Errorf("invalid port %d", port)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
if p.listener, err = net.Listen("tcp", fmt.Sprintf(":%d", port)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
addr := p.listener.Addr()
|
||||
if tcpAddr, ok := addr.(*net.TCPAddr); ok {
|
||||
p.setPort(tcpAddr.Port)
|
||||
}
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// GetPath returns the path the transport is hosted on. If the path is '/',
|
||||
// the transport will handle requests on any URI. To discover the true path
|
||||
// a request was received on, inspect the context from Receive(cxt, ...) with
|
||||
// TransportContextFrom(ctx).
|
||||
func (p *Protocol) GetPath() string {
|
||||
path := strings.TrimSpace(p.Path)
|
||||
if len(path) > 0 {
|
||||
return path
|
||||
}
|
||||
return "/" // default
|
||||
}
|
||||
|
||||
// attachMiddleware attaches the HTTP middleware to the specified handler.
|
||||
func attachMiddleware(h http.Handler, middleware []Middleware) http.Handler {
|
||||
for _, m := range middleware {
|
||||
h = m(h)
|
||||
}
|
||||
return h
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding"
|
||||
cecontext "github.com/cloudevents/sdk-go/v2/context"
|
||||
"github.com/cloudevents/sdk-go/v2/protocol"
|
||||
)
|
||||
|
||||
func (p *Protocol) do(ctx context.Context, req *http.Request) (binding.Message, error) {
|
||||
params := cecontext.RetriesFrom(ctx)
|
||||
|
||||
switch params.Strategy {
|
||||
case cecontext.BackoffStrategyConstant, cecontext.BackoffStrategyLinear, cecontext.BackoffStrategyExponential:
|
||||
return p.doWithRetry(ctx, params, req)
|
||||
case cecontext.BackoffStrategyNone:
|
||||
fallthrough
|
||||
default:
|
||||
return p.doOnce(req)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Protocol) doOnce(req *http.Request) (binding.Message, protocol.Result) {
|
||||
resp, err := p.Client.Do(req)
|
||||
if err != nil {
|
||||
return nil, protocol.NewReceipt(false, "%w", err)
|
||||
}
|
||||
|
||||
var result protocol.Result
|
||||
if resp.StatusCode/100 == 2 {
|
||||
result = protocol.ResultACK
|
||||
} else {
|
||||
result = protocol.ResultNACK
|
||||
}
|
||||
|
||||
return NewMessage(resp.Header, resp.Body), NewResult(resp.StatusCode, "%w", result)
|
||||
}
|
||||
|
||||
func (p *Protocol) doWithRetry(ctx context.Context, params *cecontext.RetryParams, req *http.Request) (binding.Message, error) {
|
||||
then := time.Now()
|
||||
retry := 0
|
||||
results := make([]protocol.Result, 0)
|
||||
|
||||
for {
|
||||
msg, result := p.doOnce(req)
|
||||
|
||||
// Fast track common case.
|
||||
if protocol.IsACK(result) {
|
||||
return msg, NewRetriesResult(result, retry, then, results)
|
||||
}
|
||||
|
||||
// Try again?
|
||||
//
|
||||
// Make sure the error was something we should retry.
|
||||
|
||||
{
|
||||
var uErr *url.Error
|
||||
if errors.As(result, &uErr) {
|
||||
goto DoBackoff
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var httpResult *Result
|
||||
if errors.As(result, &httpResult) {
|
||||
// Potentially retry when:
|
||||
// - 404 Not Found
|
||||
// - 413 Payload Too Large with Retry-After (NOT SUPPORTED)
|
||||
// - 425 Too Early
|
||||
// - 429 Too Many Requests
|
||||
// - 503 Service Unavailable (with or without Retry-After) (IGNORE Retry-After)
|
||||
// - 504 Gateway Timeout
|
||||
|
||||
sc := httpResult.StatusCode
|
||||
if sc == 404 || sc == 425 || sc == 429 || sc == 503 || sc == 504 {
|
||||
// retry!
|
||||
goto DoBackoff
|
||||
} else {
|
||||
// Permanent error
|
||||
cecontext.LoggerFrom(ctx).Debugw("status code not retryable, will not try again",
|
||||
zap.Error(httpResult),
|
||||
zap.Int("statusCode", sc))
|
||||
return msg, NewRetriesResult(result, retry, then, results)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DoBackoff:
|
||||
// Wait for the correct amount of backoff time.
|
||||
|
||||
// total tries = retry + 1
|
||||
if err := params.Backoff(ctx, retry+1); err != nil {
|
||||
// do not try again.
|
||||
cecontext.LoggerFrom(ctx).Debugw("backoff error, will not try again", zap.Error(err))
|
||||
return msg, NewRetriesResult(result, retry, then, results)
|
||||
}
|
||||
|
||||
retry++
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/protocol"
|
||||
)
|
||||
|
||||
// NewResult returns a fully populated http Result that should be used as
|
||||
// a transport.Result.
|
||||
func NewResult(statusCode int, messageFmt string, args ...interface{}) protocol.Result {
|
||||
return &Result{
|
||||
StatusCode: statusCode,
|
||||
Format: messageFmt,
|
||||
Args: args,
|
||||
}
|
||||
}
|
||||
|
||||
// Result wraps the fields required to make adjustments for http Responses.
|
||||
type Result struct {
|
||||
StatusCode int
|
||||
Format string
|
||||
Args []interface{}
|
||||
}
|
||||
|
||||
// make sure Result implements error.
|
||||
var _ error = (*Result)(nil)
|
||||
|
||||
// Is returns if the target error is a Result type checking target.
|
||||
func (e *Result) Is(target error) bool {
|
||||
if o, ok := target.(*Result); ok {
|
||||
return e.StatusCode == o.StatusCode
|
||||
}
|
||||
// Allow for wrapped errors.
|
||||
err := fmt.Errorf(e.Format, e.Args...)
|
||||
return errors.Is(err, target)
|
||||
}
|
||||
|
||||
// Error returns the string that is formed by using the format string with the
|
||||
// provided args.
|
||||
func (e *Result) Error() string {
|
||||
return fmt.Sprintf("%d: %s", e.StatusCode, fmt.Sprintf(e.Format, e.Args...))
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/protocol"
|
||||
)
|
||||
|
||||
// NewRetriesResult returns a http RetriesResult that should be used as
|
||||
// a transport.Result without retries
|
||||
func NewRetriesResult(result protocol.Result, retries int, startTime time.Time, attempts []protocol.Result) protocol.Result {
|
||||
rr := &RetriesResult{
|
||||
Result: result,
|
||||
Retries: retries,
|
||||
Duration: time.Since(startTime),
|
||||
}
|
||||
if len(attempts) > 0 {
|
||||
rr.Attempts = attempts
|
||||
}
|
||||
return rr
|
||||
}
|
||||
|
||||
// RetriesResult wraps the fields required to make adjustments for http Responses.
|
||||
type RetriesResult struct {
|
||||
// The last result
|
||||
protocol.Result
|
||||
|
||||
// Retries is the number of times the request was tried
|
||||
Retries int
|
||||
|
||||
// Duration records the time spent retrying. Exclude the successful request (if any)
|
||||
Duration time.Duration
|
||||
|
||||
// Attempts of all failed requests. Exclude last result.
|
||||
Attempts []protocol.Result
|
||||
}
|
||||
|
||||
// make sure RetriesResult implements error.
|
||||
var _ error = (*RetriesResult)(nil)
|
||||
|
||||
// Is returns if the target error is a RetriesResult type checking target.
|
||||
func (e *RetriesResult) Is(target error) bool {
|
||||
return protocol.ResultIs(e.Result, target)
|
||||
}
|
||||
|
||||
// Error returns the string that is formed by using the format string with the
|
||||
// provided args.
|
||||
func (e *RetriesResult) Error() string {
|
||||
if e.Retries == 0 {
|
||||
return e.Result.Error()
|
||||
}
|
||||
return fmt.Sprintf("%s (%dx)", e.Result.Error(), e.Retries)
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding"
|
||||
"github.com/cloudevents/sdk-go/v2/binding/format"
|
||||
"github.com/cloudevents/sdk-go/v2/binding/spec"
|
||||
"github.com/cloudevents/sdk-go/v2/types"
|
||||
)
|
||||
|
||||
// WriteRequest fills the provided httpRequest with the message m.
|
||||
// Using context you can tweak the encoding processing (more details on binding.Write documentation).
|
||||
func WriteRequest(ctx context.Context, m binding.Message, httpRequest *http.Request, transformers ...binding.Transformer) error {
|
||||
structuredWriter := (*httpRequestWriter)(httpRequest)
|
||||
binaryWriter := (*httpRequestWriter)(httpRequest)
|
||||
|
||||
_, err := binding.Write(
|
||||
ctx,
|
||||
m,
|
||||
structuredWriter,
|
||||
binaryWriter,
|
||||
transformers...,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
type httpRequestWriter http.Request
|
||||
|
||||
func (b *httpRequestWriter) SetStructuredEvent(ctx context.Context, format format.Format, event io.Reader) error {
|
||||
b.Header.Set(ContentType, format.MediaType())
|
||||
return b.setBody(event)
|
||||
}
|
||||
|
||||
func (b *httpRequestWriter) Start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *httpRequestWriter) End(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *httpRequestWriter) SetData(data io.Reader) error {
|
||||
return b.setBody(data)
|
||||
}
|
||||
|
||||
// setBody is a cherry-pick of the implementation in http.NewRequestWithContext
|
||||
func (b *httpRequestWriter) setBody(body io.Reader) error {
|
||||
rc, ok := body.(io.ReadCloser)
|
||||
if !ok && body != nil {
|
||||
rc = ioutil.NopCloser(body)
|
||||
}
|
||||
b.Body = rc
|
||||
if body != nil {
|
||||
switch v := body.(type) {
|
||||
case *bytes.Buffer:
|
||||
b.ContentLength = int64(v.Len())
|
||||
buf := v.Bytes()
|
||||
b.GetBody = func() (io.ReadCloser, error) {
|
||||
r := bytes.NewReader(buf)
|
||||
return ioutil.NopCloser(r), nil
|
||||
}
|
||||
case *bytes.Reader:
|
||||
b.ContentLength = int64(v.Len())
|
||||
snapshot := *v
|
||||
b.GetBody = func() (io.ReadCloser, error) {
|
||||
r := snapshot
|
||||
return ioutil.NopCloser(&r), nil
|
||||
}
|
||||
case *strings.Reader:
|
||||
b.ContentLength = int64(v.Len())
|
||||
snapshot := *v
|
||||
b.GetBody = func() (io.ReadCloser, error) {
|
||||
r := snapshot
|
||||
return ioutil.NopCloser(&r), nil
|
||||
}
|
||||
default:
|
||||
// This is where we'd set it to -1 (at least
|
||||
// if body != NoBody) to mean unknown, but
|
||||
// that broke people during the Go 1.8 testing
|
||||
// period. People depend on it being 0 I
|
||||
// guess. Maybe retry later. See Issue 18117.
|
||||
}
|
||||
// For client requests, Request.ContentLength of 0
|
||||
// means either actually 0, or unknown. The only way
|
||||
// to explicitly say that the ContentLength is zero is
|
||||
// to set the Body to nil. But turns out too much code
|
||||
// depends on NewRequest returning a non-nil Body,
|
||||
// so we use a well-known ReadCloser variable instead
|
||||
// and have the http package also treat that sentinel
|
||||
// variable to mean explicitly zero.
|
||||
if b.GetBody != nil && b.ContentLength == 0 {
|
||||
b.Body = http.NoBody
|
||||
b.GetBody = func() (io.ReadCloser, error) { return http.NoBody, nil }
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *httpRequestWriter) SetAttribute(attribute spec.Attribute, value interface{}) error {
|
||||
mapping := attributeHeadersMapping[attribute.Name()]
|
||||
if value == nil {
|
||||
delete(b.Header, mapping)
|
||||
}
|
||||
|
||||
// Http headers, everything is a string!
|
||||
s, err := types.Format(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Header[mapping] = append(b.Header[mapping], s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *httpRequestWriter) SetExtension(name string, value interface{}) error {
|
||||
if value == nil {
|
||||
delete(b.Header, extNameToHeaderName(name))
|
||||
}
|
||||
// Http headers, everything is a string!
|
||||
s, err := types.Format(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Header[extNameToHeaderName(name)] = []string{s}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ binding.StructuredWriter = (*httpRequestWriter)(nil) // Test it conforms to the interface
|
||||
var _ binding.BinaryWriter = (*httpRequestWriter)(nil) // Test it conforms to the interface
|
|
@ -1,121 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding"
|
||||
"github.com/cloudevents/sdk-go/v2/binding/format"
|
||||
"github.com/cloudevents/sdk-go/v2/binding/spec"
|
||||
"github.com/cloudevents/sdk-go/v2/types"
|
||||
)
|
||||
|
||||
// WriteResponseWriter writes out to the the provided httpResponseWriter with the message m.
|
||||
// Using context you can tweak the encoding processing (more details on binding.Write documentation).
|
||||
func WriteResponseWriter(ctx context.Context, m binding.Message, status int, rw http.ResponseWriter, transformers ...binding.Transformer) error {
|
||||
if status < 200 || status >= 600 {
|
||||
status = http.StatusOK
|
||||
}
|
||||
writer := &httpResponseWriter{rw: rw, status: status}
|
||||
|
||||
_, err := binding.Write(
|
||||
ctx,
|
||||
m,
|
||||
writer,
|
||||
writer,
|
||||
transformers...,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
type httpResponseWriter struct {
|
||||
rw http.ResponseWriter
|
||||
status int
|
||||
body io.Reader
|
||||
}
|
||||
|
||||
func (b *httpResponseWriter) SetStructuredEvent(ctx context.Context, format format.Format, event io.Reader) error {
|
||||
b.rw.Header().Set(ContentType, format.MediaType())
|
||||
b.body = event
|
||||
return b.finalizeWriter()
|
||||
}
|
||||
|
||||
func (b *httpResponseWriter) Start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *httpResponseWriter) SetAttribute(attribute spec.Attribute, value interface{}) error {
|
||||
mapping := attributeHeadersMapping[attribute.Name()]
|
||||
if value == nil {
|
||||
delete(b.rw.Header(), mapping)
|
||||
}
|
||||
|
||||
// Http headers, everything is a string!
|
||||
s, err := types.Format(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.rw.Header()[mapping] = append(b.rw.Header()[mapping], s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *httpResponseWriter) SetExtension(name string, value interface{}) error {
|
||||
if value == nil {
|
||||
delete(b.rw.Header(), extNameToHeaderName(name))
|
||||
}
|
||||
// Http headers, everything is a string!
|
||||
s, err := types.Format(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.rw.Header()[extNameToHeaderName(name)] = []string{s}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *httpResponseWriter) SetData(reader io.Reader) error {
|
||||
b.body = reader
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *httpResponseWriter) finalizeWriter() error {
|
||||
if b.body != nil {
|
||||
// Try to figure it out if we have a content-length
|
||||
contentLength := -1
|
||||
switch v := b.body.(type) {
|
||||
case *bytes.Buffer:
|
||||
contentLength = v.Len()
|
||||
case *bytes.Reader:
|
||||
contentLength = v.Len()
|
||||
case *strings.Reader:
|
||||
contentLength = v.Len()
|
||||
}
|
||||
|
||||
if contentLength != -1 {
|
||||
b.rw.Header().Add("Content-length", strconv.Itoa(contentLength))
|
||||
}
|
||||
|
||||
// Finalize the headers.
|
||||
b.rw.WriteHeader(b.status)
|
||||
|
||||
// Write body.
|
||||
_, err := io.Copy(b.rw, b.body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Finalize the headers.
|
||||
b.rw.WriteHeader(b.status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *httpResponseWriter) End(ctx context.Context) error {
|
||||
return b.finalizeWriter()
|
||||
}
|
||||
|
||||
var _ binding.StructuredWriter = (*httpResponseWriter)(nil) // Test it conforms to the interface
|
||||
var _ binding.BinaryWriter = (*httpResponseWriter)(nil) // Test it conforms to the interface
|
|
@ -1,47 +0,0 @@
|
|||
package protocol
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding"
|
||||
)
|
||||
|
||||
// Receiver receives messages.
|
||||
type Receiver interface {
|
||||
// Receive blocks till a message is received or ctx expires.
|
||||
//
|
||||
// A non-nil error means the receiver is closed.
|
||||
// io.EOF means it closed cleanly, any other value indicates an error.
|
||||
// The caller is responsible for `Finish()` the returned message
|
||||
Receive(ctx context.Context) (binding.Message, error)
|
||||
}
|
||||
|
||||
// ReceiveCloser is a Receiver that can be closed.
|
||||
type ReceiveCloser interface {
|
||||
Receiver
|
||||
Closer
|
||||
}
|
||||
|
||||
// ResponseFn is the function callback provided from Responder.Respond to allow
|
||||
// for a receiver to "reply" to a message it receives.
|
||||
// transformers are applied when the message is written on the wire.
|
||||
type ResponseFn func(ctx context.Context, m binding.Message, r Result, transformers ...binding.Transformer) error
|
||||
|
||||
// Responder receives messages and is given a callback to respond.
|
||||
type Responder interface {
|
||||
// Receive blocks till a message is received or ctx expires.
|
||||
//
|
||||
// A non-nil error means the receiver is closed.
|
||||
// io.EOF means it closed cleanly, any other value indicates an error.
|
||||
// The caller is responsible for `Finish()` the returned message,
|
||||
// while the protocol implementation is responsible for `Finish()` the response message.
|
||||
// The caller MUST invoke ResponseFn, in order to avoid leaks.
|
||||
// The correct flow for the caller is to finish the received message and then invoke the ResponseFn
|
||||
Respond(ctx context.Context) (binding.Message, ResponseFn, error)
|
||||
}
|
||||
|
||||
// ResponderCloser is a Responder that can be closed.
|
||||
type ResponderCloser interface {
|
||||
Responder
|
||||
Closer
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package protocol
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// Opener is the common interface for things that need to be opened.
|
||||
type Opener interface {
|
||||
// OpenInbound is a blocking call and ctx is used to stop the Inbound message Receiver/Responder.
|
||||
// Closing the context won't close the Receiver/Responder, aka it won't invoke Close(ctx).
|
||||
OpenInbound(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Closer is the common interface for things that can be closed.
|
||||
// After invoking Close(ctx), you cannot reuse the object you closed.
|
||||
type Closer interface {
|
||||
Close(ctx context.Context) error
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package protocol
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding"
|
||||
)
|
||||
|
||||
// Sender sends messages.
|
||||
type Sender interface {
|
||||
// Send a message.
|
||||
//
|
||||
// Send returns when the "outbound" message has been sent. The Sender may
|
||||
// still be expecting acknowledgment or holding other state for the message.
|
||||
//
|
||||
// m.Finish() is called when sending is finished (both succeeded or failed):
|
||||
// expected acknowledgments (or errors) have been received, the Sender is
|
||||
// no longer holding any state for the message.
|
||||
// m.Finish() may be called during or after Send().
|
||||
//
|
||||
// transformers are applied when the message is written on the wire.
|
||||
Send(ctx context.Context, m binding.Message, transformers ...binding.Transformer) error
|
||||
}
|
||||
|
||||
// SendCloser is a Sender that can be closed.
|
||||
type SendCloser interface {
|
||||
Sender
|
||||
Closer
|
||||
}
|
||||
|
||||
// Requester sends a message and receives a response
|
||||
//
|
||||
// Optional interface that may be implemented by protocols that support
|
||||
// request/response correlation.
|
||||
type Requester interface {
|
||||
// Request sends m like Sender.Send() but also arranges to receive a response.
|
||||
Request(ctx context.Context, m binding.Message, transformers ...binding.Transformer) (binding.Message, error)
|
||||
}
|
||||
|
||||
// RequesterCloser is a Requester that can be closed.
|
||||
type RequesterCloser interface {
|
||||
Requester
|
||||
Closer
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
package protocol
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Result leverages go's 1.13 error wrapping.
|
||||
type Result error
|
||||
|
||||
// ResultIs reports whether any error in err's chain matches target.
|
||||
//
|
||||
// The chain consists of err itself followed by the sequence of errors obtained by
|
||||
// repeatedly calling Unwrap.
|
||||
//
|
||||
// An error is considered to match a target if it is equal to that target or if
|
||||
// it implements a method Is(error) bool such that Is(target) returns true.
|
||||
// (text from errors/wrap.go)
|
||||
var ResultIs = errors.Is
|
||||
|
||||
// ResultAs finds the first error in err's chain that matches target, and if so, sets
|
||||
// target to that error value and returns true.
|
||||
//
|
||||
// The chain consists of err itself followed by the sequence of errors obtained by
|
||||
// repeatedly calling Unwrap.
|
||||
//
|
||||
// An error matches target if the error's concrete value is assignable to the value
|
||||
// pointed to by target, or if the error has a method As(interface{}) bool such that
|
||||
// As(target) returns true. In the latter case, the As method is responsible for
|
||||
// setting target.
|
||||
//
|
||||
// As will panic if target is not a non-nil pointer to either a type that implements
|
||||
// error, or to any interface type. As returns false if err is nil.
|
||||
// (text from errors/wrap.go)
|
||||
var ResultAs = errors.As
|
||||
|
||||
func NewResult(messageFmt string, args ...interface{}) Result {
|
||||
return fmt.Errorf(messageFmt, args...) // TODO: look at adding ACK/Nak support.
|
||||
}
|
||||
|
||||
func IsACK(target Result) bool {
|
||||
// special case, nil target also means ACK.
|
||||
if target == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return ResultIs(target, ResultACK)
|
||||
}
|
||||
|
||||
func IsNACK(target Result) bool {
|
||||
return ResultIs(target, ResultNACK)
|
||||
}
|
||||
|
||||
var (
|
||||
ResultACK = NewReceipt(true, "")
|
||||
ResultNACK = NewReceipt(false, "")
|
||||
)
|
||||
|
||||
// NewReceipt returns a fully populated protocol Receipt that should be used as
|
||||
// a transport.Result. This type holds the base ACK/NACK results.
|
||||
func NewReceipt(ack bool, messageFmt string, args ...interface{}) Result {
|
||||
return &Receipt{
|
||||
ACK: ack,
|
||||
Format: messageFmt,
|
||||
Args: args,
|
||||
}
|
||||
}
|
||||
|
||||
// Receipt wraps the fields required to understand if a protocol event is acknowledged.
|
||||
type Receipt struct {
|
||||
ACK bool
|
||||
Format string
|
||||
Args []interface{}
|
||||
}
|
||||
|
||||
// make sure Result implements error.
|
||||
var _ error = (*Receipt)(nil)
|
||||
|
||||
// Is returns if the target error is a Result type checking target.
|
||||
func (e *Receipt) Is(target error) bool {
|
||||
if o, ok := target.(*Receipt); ok {
|
||||
return e.ACK == o.ACK
|
||||
}
|
||||
// Allow for wrapped errors.
|
||||
err := fmt.Errorf(e.Format, e.Args...)
|
||||
return errors.Is(err, target)
|
||||
}
|
||||
|
||||
// Error returns the string that is formed by using the format string with the
|
||||
// provided args.
|
||||
func (e *Receipt) Error() string {
|
||||
return fmt.Sprintf(e.Format, e.Args...)
|
||||
}
|
||||
|
||||
// Unwrap returns the wrapped error if exist or nil
|
||||
func (e *Receipt) Unwrap() error {
|
||||
err := fmt.Errorf(e.Format, e.Args...)
|
||||
return errors.Unwrap(err)
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
checks = [
|
||||
"all", "-ST1003",
|
||||
]
|
|
@ -1,36 +0,0 @@
|
|||
package types
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Allocate allocates a new instance of type t and returns:
|
||||
// asPtr is of type t if t is a pointer type and of type &t otherwise
|
||||
// asValue is a Value of type t pointing to the same data as asPtr
|
||||
func Allocate(obj interface{}) (asPtr interface{}, asValue reflect.Value) {
|
||||
if obj == nil {
|
||||
return nil, reflect.Value{}
|
||||
}
|
||||
|
||||
switch t := reflect.TypeOf(obj); t.Kind() {
|
||||
case reflect.Ptr:
|
||||
reflectPtr := reflect.New(t.Elem())
|
||||
asPtr = reflectPtr.Interface()
|
||||
asValue = reflectPtr
|
||||
case reflect.Map:
|
||||
reflectPtr := reflect.MakeMap(t)
|
||||
asPtr = reflectPtr.Interface()
|
||||
asValue = reflectPtr
|
||||
case reflect.String:
|
||||
reflectPtr := reflect.New(t)
|
||||
asPtr = ""
|
||||
asValue = reflectPtr.Elem()
|
||||
case reflect.Slice:
|
||||
reflectPtr := reflect.MakeSlice(t, 0, 0)
|
||||
asPtr = reflectPtr.Interface()
|
||||
asValue = reflectPtr
|
||||
default:
|
||||
reflectPtr := reflect.New(t)
|
||||
asPtr = reflectPtr.Interface()
|
||||
asValue = reflectPtr.Elem()
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
Package types implements the CloudEvents type system.
|
||||
|
||||
CloudEvents defines a set of abstract types for event context attributes. Each
|
||||
type has a corresponding native Go type and a canonical string encoding. The
|
||||
native Go types used to represent the CloudEvents types are:
|
||||
bool, int32, string, []byte, *url.URL, time.Time
|
||||
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|CloudEvents Type|Native Type |Convertible From |
|
||||
+================+================+===================================+
|
||||
|Bool |bool |bool |
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|Integer |int32 |Any numeric type with value in |
|
||||
| | |range of int32 |
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|String |string |string |
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|Binary |[]byte |[]byte |
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|URI-Reference |*url.URL |url.URL, types.URIRef, types.URI |
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|URI |*url.URL |url.URL, types.URIRef, types.URI |
|
||||
| | |Must be an absolute URI. |
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|Timestamp |time.Time |time.Time, types.Timestamp |
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|
||||
Extension attributes may be stored as a native type or a canonical string. The
|
||||
To<Type> functions will convert to the desired <Type> from any convertible type
|
||||
or from the canonical string form.
|
||||
|
||||
The Parse<Type> and Format<Type> functions convert native types to/from
|
||||
canonical strings.
|
||||
|
||||
Note are no Parse or Format functions for URL or string. For URL use the
|
||||
standard url.Parse() and url.URL.String(). The canonical string format of a
|
||||
string is the string itself.
|
||||
|
||||
*/
|
||||
package types
|
|
@ -1,70 +0,0 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Timestamp wraps time.Time to normalize the time layout to RFC3339. It is
|
||||
// intended to enforce compliance with the CloudEvents spec for their
|
||||
// definition of Timestamp. Custom marshal methods are implemented to ensure
|
||||
// the outbound Timestamp is a string in the RFC3339 layout.
|
||||
type Timestamp struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
// ParseTimestamp attempts to parse the given time assuming RFC3339 layout
|
||||
func ParseTimestamp(s string) (*Timestamp, error) {
|
||||
if s == "" {
|
||||
return nil, nil
|
||||
}
|
||||
tt, err := ParseTime(s)
|
||||
return &Timestamp{Time: tt}, err
|
||||
}
|
||||
|
||||
// MarshalJSON implements a custom json marshal method used when this type is
|
||||
// marshaled using json.Marshal.
|
||||
func (t *Timestamp) MarshalJSON() ([]byte, error) {
|
||||
if t == nil || t.IsZero() {
|
||||
return []byte(`""`), nil
|
||||
}
|
||||
return []byte(fmt.Sprintf("%q", t)), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json unmarshal method used when this type is
|
||||
// unmarshaled using json.Unmarshal.
|
||||
func (t *Timestamp) UnmarshalJSON(b []byte) error {
|
||||
var timestamp string
|
||||
if err := json.Unmarshal(b, ×tamp); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
t.Time, err = ParseTime(timestamp)
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalXML implements a custom xml marshal method used when this type is
|
||||
// marshaled using xml.Marshal.
|
||||
func (t *Timestamp) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
if t == nil || t.IsZero() {
|
||||
return e.EncodeElement(nil, start)
|
||||
}
|
||||
return e.EncodeElement(t.String(), start)
|
||||
}
|
||||
|
||||
// UnmarshalXML implements the xml unmarshal method used when this type is
|
||||
// unmarshaled using xml.Unmarshal.
|
||||
func (t *Timestamp) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
var timestamp string
|
||||
if err := d.DecodeElement(×tamp, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
t.Time, err = ParseTime(timestamp)
|
||||
return err
|
||||
}
|
||||
|
||||
// String outputs the time using RFC3339 format.
|
||||
func (t Timestamp) String() string { return FormatTime(t.Time) }
|
|
@ -1,77 +0,0 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// URI is a wrapper to url.URL. It is intended to enforce compliance with
|
||||
// the CloudEvents spec for their definition of URI. Custom
|
||||
// marshal methods are implemented to ensure the outbound URI object
|
||||
// is a flat string.
|
||||
type URI struct {
|
||||
url.URL
|
||||
}
|
||||
|
||||
// ParseURI attempts to parse the given string as a URI.
|
||||
func ParseURI(u string) *URI {
|
||||
if u == "" {
|
||||
return nil
|
||||
}
|
||||
pu, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &URI{URL: *pu}
|
||||
}
|
||||
|
||||
// MarshalJSON implements a custom json marshal method used when this type is
|
||||
// marshaled using json.Marshal.
|
||||
func (u URI) MarshalJSON() ([]byte, error) {
|
||||
b := fmt.Sprintf("%q", u.String())
|
||||
return []byte(b), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json unmarshal method used when this type is
|
||||
// unmarshaled using json.Unmarshal.
|
||||
func (u *URI) UnmarshalJSON(b []byte) error {
|
||||
var ref string
|
||||
if err := json.Unmarshal(b, &ref); err != nil {
|
||||
return err
|
||||
}
|
||||
r := ParseURI(ref)
|
||||
if r != nil {
|
||||
*u = *r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalXML implements a custom xml marshal method used when this type is
|
||||
// marshaled using xml.Marshal.
|
||||
func (u URI) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
return e.EncodeElement(u.String(), start)
|
||||
}
|
||||
|
||||
// UnmarshalXML implements the xml unmarshal method used when this type is
|
||||
// unmarshaled using xml.Unmarshal.
|
||||
func (u *URI) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
var ref string
|
||||
if err := d.DecodeElement(&ref, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
r := ParseURI(ref)
|
||||
if r != nil {
|
||||
*u = *r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the full string representation of the URI-Reference.
|
||||
func (u *URI) String() string {
|
||||
if u == nil {
|
||||
return ""
|
||||
}
|
||||
return u.URL.String()
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// URIRef is a wrapper to url.URL. It is intended to enforce compliance with
|
||||
// the CloudEvents spec for their definition of URI-Reference. Custom
|
||||
// marshal methods are implemented to ensure the outbound URIRef object is
|
||||
// is a flat string.
|
||||
type URIRef struct {
|
||||
url.URL
|
||||
}
|
||||
|
||||
// ParseURIRef attempts to parse the given string as a URI-Reference.
|
||||
func ParseURIRef(u string) *URIRef {
|
||||
if u == "" {
|
||||
return nil
|
||||
}
|
||||
pu, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &URIRef{URL: *pu}
|
||||
}
|
||||
|
||||
// MarshalJSON implements a custom json marshal method used when this type is
|
||||
// marshaled using json.Marshal.
|
||||
func (u URIRef) MarshalJSON() ([]byte, error) {
|
||||
b := fmt.Sprintf("%q", u.String())
|
||||
return []byte(b), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json unmarshal method used when this type is
|
||||
// unmarshaled using json.Unmarshal.
|
||||
func (u *URIRef) UnmarshalJSON(b []byte) error {
|
||||
var ref string
|
||||
if err := json.Unmarshal(b, &ref); err != nil {
|
||||
return err
|
||||
}
|
||||
r := ParseURIRef(ref)
|
||||
if r != nil {
|
||||
*u = *r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalXML implements a custom xml marshal method used when this type is
|
||||
// marshaled using xml.Marshal.
|
||||
func (u URIRef) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
return e.EncodeElement(u.String(), start)
|
||||
}
|
||||
|
||||
// UnmarshalXML implements the xml unmarshal method used when this type is
|
||||
// unmarshaled using xml.Unmarshal.
|
||||
func (u *URIRef) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
var ref string
|
||||
if err := d.DecodeElement(&ref, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
r := ParseURIRef(ref)
|
||||
if r != nil {
|
||||
*u = *r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the full string representation of the URI-Reference.
|
||||
func (u *URIRef) String() string {
|
||||
if u == nil {
|
||||
return ""
|
||||
}
|
||||
return u.URL.String()
|
||||
}
|
|
@ -1,330 +0,0 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FormatBool returns canonical string format: "true" or "false"
|
||||
func FormatBool(v bool) string { return strconv.FormatBool(v) }
|
||||
|
||||
// FormatInteger returns canonical string format: decimal notation.
|
||||
func FormatInteger(v int32) string { return strconv.Itoa(int(v)) }
|
||||
|
||||
// FormatBinary returns canonical string format: standard base64 encoding
|
||||
func FormatBinary(v []byte) string { return base64.StdEncoding.EncodeToString(v) }
|
||||
|
||||
// FormatTime returns canonical string format: RFC3339 with nanoseconds
|
||||
func FormatTime(v time.Time) string { return v.UTC().Format(time.RFC3339Nano) }
|
||||
|
||||
// ParseBool parse canonical string format: "true" or "false"
|
||||
func ParseBool(v string) (bool, error) { return strconv.ParseBool(v) }
|
||||
|
||||
// ParseInteger parse canonical string format: decimal notation.
|
||||
func ParseInteger(v string) (int32, error) {
|
||||
// Accept floating-point but truncate to int32 as per CE spec.
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if f > math.MaxInt32 || f < math.MinInt32 {
|
||||
return 0, rangeErr(v)
|
||||
}
|
||||
return int32(f), nil
|
||||
}
|
||||
|
||||
// ParseBinary parse canonical string format: standard base64 encoding
|
||||
func ParseBinary(v string) ([]byte, error) { return base64.StdEncoding.DecodeString(v) }
|
||||
|
||||
// ParseTime parse canonical string format: RFC3339 with nanoseconds
|
||||
func ParseTime(v string) (time.Time, error) {
|
||||
t, err := time.Parse(time.RFC3339Nano, v)
|
||||
if err != nil {
|
||||
err := convertErr(time.Time{}, v)
|
||||
err.extra = ": not in RFC3339 format"
|
||||
return time.Time{}, err
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Format returns the canonical string format of v, where v can be
|
||||
// any type that is convertible to a CloudEvents type.
|
||||
func Format(v interface{}) (string, error) {
|
||||
v, err := Validate(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case bool:
|
||||
return FormatBool(v), nil
|
||||
case int32:
|
||||
return FormatInteger(v), nil
|
||||
case string:
|
||||
return v, nil
|
||||
case []byte:
|
||||
return FormatBinary(v), nil
|
||||
case URI:
|
||||
return v.String(), nil
|
||||
case URIRef:
|
||||
// url.URL is often passed by pointer so allow both
|
||||
return v.String(), nil
|
||||
case Timestamp:
|
||||
return FormatTime(v.Time), nil
|
||||
default:
|
||||
return "", fmt.Errorf("%T is not a CloudEvents type", v)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate v is a valid CloudEvents attribute value, convert it to one of:
|
||||
// bool, int32, string, []byte, types.URI, types.URIRef, types.Timestamp
|
||||
func Validate(v interface{}) (interface{}, error) {
|
||||
switch v := v.(type) {
|
||||
case bool, int32, string, []byte:
|
||||
return v, nil // Already a CloudEvents type, no validation needed.
|
||||
|
||||
case uint, uintptr, uint8, uint16, uint32, uint64:
|
||||
u := reflect.ValueOf(v).Uint()
|
||||
if u > math.MaxInt32 {
|
||||
return nil, rangeErr(v)
|
||||
}
|
||||
return int32(u), nil
|
||||
case int, int8, int16, int64:
|
||||
i := reflect.ValueOf(v).Int()
|
||||
if i > math.MaxInt32 || i < math.MinInt32 {
|
||||
return nil, rangeErr(v)
|
||||
}
|
||||
return int32(i), nil
|
||||
case float32, float64:
|
||||
f := reflect.ValueOf(v).Float()
|
||||
if f > math.MaxInt32 || f < math.MinInt32 {
|
||||
return nil, rangeErr(v)
|
||||
}
|
||||
return int32(f), nil
|
||||
|
||||
case *url.URL:
|
||||
if v == nil {
|
||||
break
|
||||
}
|
||||
return URI{URL: *v}, nil
|
||||
case url.URL:
|
||||
return URI{URL: v}, nil
|
||||
case *URIRef:
|
||||
if v != nil {
|
||||
return *v, nil
|
||||
}
|
||||
return nil, nil
|
||||
case URIRef:
|
||||
return v, nil
|
||||
case *URI:
|
||||
if v != nil {
|
||||
return *v, nil
|
||||
}
|
||||
return nil, nil
|
||||
case URI:
|
||||
return v, nil
|
||||
case time.Time:
|
||||
return Timestamp{Time: v}, nil
|
||||
case *time.Time:
|
||||
if v == nil {
|
||||
break
|
||||
}
|
||||
return Timestamp{Time: *v}, nil
|
||||
case Timestamp:
|
||||
return v, nil
|
||||
}
|
||||
rx := reflect.ValueOf(v)
|
||||
if rx.Kind() == reflect.Ptr && !rx.IsNil() {
|
||||
// Allow pointers-to convertible types
|
||||
return Validate(rx.Elem().Interface())
|
||||
}
|
||||
return nil, fmt.Errorf("invalid CloudEvents value: %#v", v)
|
||||
}
|
||||
|
||||
// Clone v clones a CloudEvents attribute value, which is one of the valid types:
|
||||
// bool, int32, string, []byte, types.URI, types.URIRef, types.Timestamp
|
||||
// Returns the same type
|
||||
// Panics if the type is not valid
|
||||
func Clone(v interface{}) interface{} {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case bool, int32, string, nil:
|
||||
return v // Already a CloudEvents type, no validation needed.
|
||||
case []byte:
|
||||
clone := make([]byte, len(v))
|
||||
copy(clone, v)
|
||||
return v
|
||||
case url.URL:
|
||||
return URI{v}
|
||||
case *url.URL:
|
||||
return &URI{*v}
|
||||
case URIRef:
|
||||
return v
|
||||
case *URIRef:
|
||||
return &URIRef{v.URL}
|
||||
case URI:
|
||||
return v
|
||||
case *URI:
|
||||
return &URI{v.URL}
|
||||
case time.Time:
|
||||
return Timestamp{v}
|
||||
case *time.Time:
|
||||
return &Timestamp{*v}
|
||||
case Timestamp:
|
||||
return v
|
||||
case *Timestamp:
|
||||
return &Timestamp{v.Time}
|
||||
}
|
||||
panic(fmt.Errorf("invalid CloudEvents value: %#v", v))
|
||||
}
|
||||
|
||||
// ToBool accepts a bool value or canonical "true"/"false" string.
|
||||
func ToBool(v interface{}) (bool, error) {
|
||||
v, err := Validate(v)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case bool:
|
||||
return v, nil
|
||||
case string:
|
||||
return ParseBool(v)
|
||||
default:
|
||||
return false, convertErr(true, v)
|
||||
}
|
||||
}
|
||||
|
||||
// ToInteger accepts any numeric value in int32 range, or canonical string.
|
||||
func ToInteger(v interface{}) (int32, error) {
|
||||
v, err := Validate(v)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case int32:
|
||||
return v, nil
|
||||
case string:
|
||||
return ParseInteger(v)
|
||||
default:
|
||||
return 0, convertErr(int32(0), v)
|
||||
}
|
||||
}
|
||||
|
||||
// ToString returns a string value unaltered.
|
||||
//
|
||||
// This function does not perform canonical string encoding, use one of the
|
||||
// Format functions for that.
|
||||
func ToString(v interface{}) (string, error) {
|
||||
v, err := Validate(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
return v, nil
|
||||
default:
|
||||
return "", convertErr("", v)
|
||||
}
|
||||
}
|
||||
|
||||
// ToBinary returns a []byte value, decoding from base64 string if necessary.
|
||||
func ToBinary(v interface{}) ([]byte, error) {
|
||||
v, err := Validate(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case []byte:
|
||||
return v, nil
|
||||
case string:
|
||||
return base64.StdEncoding.DecodeString(v)
|
||||
default:
|
||||
return nil, convertErr([]byte(nil), v)
|
||||
}
|
||||
}
|
||||
|
||||
// ToURL returns a *url.URL value, parsing from string if necessary.
|
||||
func ToURL(v interface{}) (*url.URL, error) {
|
||||
v, err := Validate(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case *URI:
|
||||
return &v.URL, nil
|
||||
case URI:
|
||||
return &v.URL, nil
|
||||
case *URIRef:
|
||||
return &v.URL, nil
|
||||
case URIRef:
|
||||
return &v.URL, nil
|
||||
case string:
|
||||
u, err := url.Parse(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return u, nil
|
||||
default:
|
||||
return nil, convertErr((*url.URL)(nil), v)
|
||||
}
|
||||
}
|
||||
|
||||
// ToTime returns a time.Time value, parsing from RFC3339 string if necessary.
|
||||
func ToTime(v interface{}) (time.Time, error) {
|
||||
v, err := Validate(v)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case Timestamp:
|
||||
return v.Time, nil
|
||||
case string:
|
||||
ts, err := time.Parse(time.RFC3339Nano, v)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return ts, nil
|
||||
default:
|
||||
return time.Time{}, convertErr(time.Time{}, v)
|
||||
}
|
||||
}
|
||||
|
||||
func IsZero(v interface{}) bool {
|
||||
// Fast path
|
||||
if v == nil {
|
||||
return true
|
||||
}
|
||||
if s, ok := v.(string); ok && s == "" {
|
||||
return true
|
||||
}
|
||||
return reflect.ValueOf(v).IsZero()
|
||||
}
|
||||
|
||||
type ConvertErr struct {
|
||||
// Value being converted
|
||||
Value interface{}
|
||||
// Type of attempted conversion
|
||||
Type reflect.Type
|
||||
|
||||
extra string
|
||||
}
|
||||
|
||||
func (e *ConvertErr) Error() string {
|
||||
return fmt.Sprintf("cannot convert %#v to %s%s", e.Value, e.Type, e.extra)
|
||||
}
|
||||
|
||||
func convertErr(target, v interface{}) *ConvertErr {
|
||||
return &ConvertErr{Value: v, Type: reflect.TypeOf(target)}
|
||||
}
|
||||
|
||||
func rangeErr(v interface{}) error {
|
||||
e := convertErr(int32(0), v)
|
||||
e.extra = ": out of range"
|
||||
return e
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.4.3
|
||||
- 1.5.3
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
|
@ -1,10 +0,0 @@
|
|||
# How to contribute
|
||||
|
||||
We definitely welcome patches and contribution to this project!
|
||||
|
||||
### Legal requirements
|
||||
|
||||
In order to protect both you and ourselves, you will need to sign the
|
||||
[Contributor License Agreement](https://cla.developers.google.com/clas).
|
||||
|
||||
You may have already signed it for other Google projects.
|
|
@ -1,9 +0,0 @@
|
|||
Paul Borman <borman@google.com>
|
||||
bmatsuo
|
||||
shawnps
|
||||
theory
|
||||
jboverfelt
|
||||
dsymonds
|
||||
cd1
|
||||
wallclockbuilder
|
||||
dansouza
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue